Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"name": "taskbound",
"version": "0.2.3",
Expand All @@ -11,7 +11,7 @@
"test": "node --test test/*.test.mjs"
},
"dependencies": {
"agent-gov-core": "github:Conalh/agent-gov-core#v0.1.2"
"agent-gov-core": "github:Conalh/agent-gov-core#v0.2.0"

Check warning on line 14 in package.json

View workflow job for this annotation

GitHub Actions / scope-review

TaskBound low scope creep

Changed dependency agent-gov-core from github:Conalh/agent-gov-core#v0.1.2 to github:Conalh/agent-gov-core#v0.2.0. Recommendation: Review whether the version change is in scope for the task.
},
"devDependencies": {
"@types/node": "^24.0.0",
Expand Down
8 changes: 4 additions & 4 deletions src/detectors/capability-signals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { isCommentLine, isPackageJsonFile, isTestFixturePath } from '../paths.js';
import { isRecord, lineOfJsonKey, lineOfJsonStringValue } from '../discovery.js';
import { readFileFromSide } from '../git-diff.js';
Expand Down Expand Up @@ -58,7 +58,7 @@

return [
{
kind: 'external_fetch_added',
kind: 'task_bound.external_fetch_added',
category: 'capability',
severity: 'medium',
file: added.file,
Expand All @@ -77,7 +77,7 @@

return [
{
kind: 'subprocess_spawn_added',
kind: 'task_bound.subprocess_spawn_added',
category: 'capability',
severity: 'high',
file: added.file,
Expand All @@ -102,7 +102,7 @@

const line = lineOfJsonKey(newText, key) ?? lineOfJsonStringValue(newText, newValue);
findings.push({
kind: 'lifecycle_script_changed',
kind: 'task_bound.lifecycle_script_changed',
category: 'lifecycle',
severity: 'high',
file,
Expand All @@ -114,7 +114,7 @@

if (/(?:curl[^\n|]*\|\s*(?:ba)?sh|wget[^\n|]*\|\s*sh|Invoke-Expression|iex\s*\()/i.test(newValue)) {
findings.push({
kind: 'script_pipe_to_shell',
kind: 'task_bound.script_pipe_to_shell',
category: 'lifecycle',
severity: 'critical',
file,
Expand Down
4 changes: 2 additions & 2 deletions src/detectors/dependency-drift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function compareDependencies(file: string, oldText: string, newText: string): Fi
for (const [name, version] of Object.entries(newDeps)) {
if (!(name in oldDeps)) {
findings.push({
kind: 'dependency_added',
kind: 'task_bound.dependency_added',
category: 'dependency',
severity: 'medium',
file,
Expand All @@ -48,7 +48,7 @@ function compareDependencies(file: string, oldText: string, newText: string): Fi

if (oldDeps[name] !== version) {
findings.push({
kind: 'dependency_changed',
kind: 'task_bound.dependency_changed',
category: 'dependency',
severity: 'low',
file,
Expand Down
2 changes: 1 addition & 1 deletion src/detectors/env-drift.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { isEnvFile } from '../paths.js';
import type { ChangedFile, Finding } from '../types.js';

Expand All @@ -10,7 +10,7 @@
}

findings.push({
kind: 'env_file_changed',
kind: 'task_bound.env_file_changed',
category: 'env',
severity: 'high',
file: changed.file,
Expand Down
4 changes: 2 additions & 2 deletions src/detectors/file-scope.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { inferScope, isFileInScope } from '../scope-infer.js';
import { getSensitiveSurfaceLabel, isSensitiveSurface } from '../paths.js';
import type { DiffContext, Finding, InferredScope } from '../types.js';
Expand All @@ -14,7 +14,7 @@
const sensitiveLabel = getSensitiveSurfaceLabel(changed.file);
if (sensitiveLabel && !scope.mentionsSensitiveSurfaces) {
findings.push({
kind: 'sensitive_surface_touched',
kind: 'task_bound.sensitive_surface_touched',
category: 'sensitive_surface',
severity: 'high',
file: changed.file,
Expand All @@ -34,7 +34,7 @@
}

findings.push({
kind: 'out_of_scope_file',
kind: 'task_bound.out_of_scope_file',
category: 'scope',
severity: isSensitiveSurface(changed.file) ? 'high' : 'medium',
file: changed.file,
Expand Down
22 changes: 11 additions & 11 deletions test/cli-output.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ test('CLI flags scope creep for CSS task with CI, MCP, and package changes', asy
assert.equal(report.rating, 'critical');
assert.ok(report.findingCount >= 5);
assert.ok(report.taskScopeSummary.some((entry) => entry.includes('css')));
assert.ok(report.findings.some((finding) => finding.kind === 'sensitive_surface_touched'));
assert.ok(report.findings.some((finding) => finding.kind === 'dependency_added'));
assert.ok(report.findings.some((finding) => finding.kind === 'script_pipe_to_shell'));
assert.ok(report.findings.some((finding) => finding.kind === 'task_bound.sensitive_surface_touched'));
assert.ok(report.findings.some((finding) => finding.kind === 'task_bound.dependency_added'));
assert.ok(report.findings.some((finding) => finding.kind === 'task_bound.script_pipe_to_shell'));
});

test('CLI emits Markdown task scope summary', async () => {
Expand Down Expand Up @@ -128,7 +128,7 @@ test('CLI writes supplemental report files from one review', async () => {
assert.match(stdout, /::warning file=\.mcp\.json/);
assert.match(markdown, /# TaskBound scope review: CRITICAL/);
assert.equal(json.rating, 'critical');
assert.ok(json.findings.some((finding) => finding.kind === 'script_pipe_to_shell'));
assert.ok(json.findings.some((finding) => finding.kind === 'task_bound.script_pipe_to_shell'));
} finally {
await rm(outputDir, { recursive: true, force: true });
}
Expand Down Expand Up @@ -186,7 +186,7 @@ test('additional scope context affects out-of-scope file findings', async () =>
);
const report = JSON.parse(stdout);
const apiFileScopeFinding = report.findings.find(
(finding) => finding.kind === 'out_of_scope_file' && finding.file === 'src/api/client.ts'
(finding) => finding.kind === 'task_bound.out_of_scope_file' && finding.file === 'src/api/client.ts'
);

assert.equal(report.scopeMatchCount, 2);
Expand Down Expand Up @@ -255,7 +255,7 @@ test('CLI applies .taskbound.yml per-rule severity overrides', async () => {
const report = JSON.parse(stdout);

assert.equal(report.rating, 'high');
assert.equal(report.findings.find((finding) => finding.kind === 'script_pipe_to_shell')?.severity, 'high');
assert.equal(report.findings.find((finding) => finding.kind === 'task_bound.script_pipe_to_shell')?.severity, 'high');
});

test('CLI does not treat repository test fixture package files as install surfaces', async () => {
Expand All @@ -269,9 +269,9 @@ test('CLI does not treat repository test fixture package files as install surfac
);
const report = JSON.parse(stdout);

assert.equal(report.findings.some((finding) => finding.kind === 'script_pipe_to_shell'), false);
assert.equal(report.findings.some((finding) => finding.kind === 'lifecycle_script_changed'), false);
assert.equal(report.findings.some((finding) => finding.kind === 'dependency_added'), false);
assert.equal(report.findings.some((finding) => finding.kind === 'task_bound.script_pipe_to_shell'), false);
assert.equal(report.findings.some((finding) => finding.kind === 'task_bound.lifecycle_script_changed'), false);
assert.equal(report.findings.some((finding) => finding.kind === 'task_bound.dependency_added'), false);
});


Expand All @@ -285,8 +285,8 @@ test('capability signal findings expose stable kind and category fields', async
{ cwd: packageRoot }
);
const report = JSON.parse(stdout);
const capabilityFinding = report.findings.find((finding) => finding.kind === 'external_fetch_added');
const capabilityFinding = report.findings.find((finding) => finding.kind === 'task_bound.external_fetch_added');

assert.equal(capabilityFinding?.kind, 'external_fetch_added');
assert.equal(capabilityFinding?.kind, 'task_bound.external_fetch_added');
assert.equal(capabilityFinding?.category, 'capability');
});
2 changes: 1 addition & 1 deletion test/fixtures/configured-severity/new/.taskbound.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
severity:
script_pipe_to_shell: high
task_bound.script_pipe_to_shell: high
2 changes: 1 addition & 1 deletion test/fixtures/configured-severity/old/.taskbound.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
severity:
script_pipe_to_shell: high
task_bound.script_pipe_to_shell: high
4 changes: 2 additions & 2 deletions test/git-review.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ test('CLI reviews scope creep between git refs', async () => {
const report = JSON.parse(stdout);

assert.equal(report.rating, 'critical');
assert.ok(report.findings.some((finding) => finding.kind === 'sensitive_surface_touched'));
assert.ok(report.findings.some((finding) => finding.kind === 'dependency_added'));
assert.ok(report.findings.some((finding) => finding.kind === 'task_bound.sensitive_surface_touched'));
assert.ok(report.findings.some((finding) => finding.kind === 'task_bound.dependency_added'));
} finally {
await rm(repo, { recursive: true, force: true });
}
Expand Down
4 changes: 2 additions & 2 deletions test/scope-infer.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ test('file scope detector flags sensitive surfaces for CSS task', () => {
addedLines: []
});

assert.ok(findings.some((finding) => finding.kind === 'sensitive_surface_touched' && finding.file === '.mcp.json'));
assert.ok(findings.some((finding) => finding.kind === 'sensitive_surface_touched' && finding.file === 'package.json'));
assert.ok(findings.some((finding) => finding.kind === 'task_bound.sensitive_surface_touched' && finding.file === '.mcp.json'));
assert.ok(findings.some((finding) => finding.kind === 'task_bound.sensitive_surface_touched' && finding.file === 'package.json'));
assert.equal(findings.some((finding) => finding.file === 'styles/header.css'), false);
});