devworkspace 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +4 -3
  3. package/dist/agents/claude/content.js +3 -2
  4. package/dist/agents/claude/content.js.map +1 -1
  5. package/dist/cli/args.d.ts +16 -0
  6. package/dist/cli/args.js +5 -0
  7. package/dist/cli/args.js.map +1 -1
  8. package/dist/cli/help.js +30 -7
  9. package/dist/cli/help.js.map +1 -1
  10. package/dist/cli/render.d.ts +7 -1
  11. package/dist/cli/render.js +70 -13
  12. package/dist/cli/render.js.map +1 -1
  13. package/dist/cli/run.js +47 -8
  14. package/dist/cli/run.js.map +1 -1
  15. package/dist/config/schemas.d.ts +12 -1
  16. package/dist/config/schemas.js +12 -0
  17. package/dist/config/schemas.js.map +1 -1
  18. package/dist/discovery/commands.d.ts +34 -3
  19. package/dist/discovery/commands.js +109 -33
  20. package/dist/discovery/commands.js.map +1 -1
  21. package/dist/discovery/detect.d.ts +23 -0
  22. package/dist/discovery/detect.js +113 -48
  23. package/dist/discovery/detect.js.map +1 -1
  24. package/dist/discovery/docs.d.ts +10 -0
  25. package/dist/discovery/docs.js +52 -0
  26. package/dist/discovery/docs.js.map +1 -0
  27. package/dist/discovery/entrypoints.d.ts +20 -0
  28. package/dist/discovery/entrypoints.js +67 -0
  29. package/dist/discovery/entrypoints.js.map +1 -0
  30. package/dist/integrations/adapters/graphify.js +46 -8
  31. package/dist/integrations/adapters/graphify.js.map +1 -1
  32. package/dist/integrations/manager.js +14 -1
  33. package/dist/integrations/manager.js.map +1 -1
  34. package/dist/integrations/persistentPath.d.ts +20 -0
  35. package/dist/integrations/persistentPath.js +47 -0
  36. package/dist/integrations/persistentPath.js.map +1 -0
  37. package/dist/integrations/types.d.ts +6 -0
  38. package/dist/integrations/types.js.map +1 -1
  39. package/dist/knowledge/facts.d.ts +59 -2
  40. package/dist/knowledge/facts.js +115 -17
  41. package/dist/knowledge/facts.js.map +1 -1
  42. package/dist/prompts/onboarding.js +9 -6
  43. package/dist/prompts/onboarding.js.map +1 -1
  44. package/dist/services/analysis.d.ts +13 -1
  45. package/dist/services/analysis.js +23 -18
  46. package/dist/services/analysis.js.map +1 -1
  47. package/dist/services/doctor.js +92 -7
  48. package/dist/services/doctor.js.map +1 -1
  49. package/dist/services/knowledge.d.ts +17 -0
  50. package/dist/services/knowledge.js +28 -2
  51. package/dist/services/knowledge.js.map +1 -1
  52. package/dist/services/refresh.js +6 -6
  53. package/dist/services/refresh.js.map +1 -1
  54. package/dist/services/status.d.ts +12 -2
  55. package/dist/services/status.js +11 -9
  56. package/dist/services/status.js.map +1 -1
  57. package/dist/services/tasks.d.ts +2 -0
  58. package/dist/services/tasks.js +20 -12
  59. package/dist/services/tasks.js.map +1 -1
  60. package/dist/services/verify.d.ts +47 -0
  61. package/dist/services/verify.js +172 -0
  62. package/dist/services/verify.js.map +1 -0
  63. package/dist/tasks/routing.js +8 -3
  64. package/dist/tasks/routing.js.map +1 -1
  65. package/dist/tasks/task.d.ts +49 -5
  66. package/dist/tasks/task.js +68 -11
  67. package/dist/tasks/task.js.map +1 -1
  68. package/dist/verification/safety.d.ts +38 -0
  69. package/dist/verification/safety.js +120 -0
  70. package/dist/verification/safety.js.map +1 -0
  71. package/dist/verification/scope.d.ts +19 -0
  72. package/dist/verification/scope.js +119 -0
  73. package/dist/verification/scope.js.map +1 -0
  74. package/docs/integrations.md +1 -1
  75. package/docs/knowledge.md +2 -0
  76. package/docs/testing.md +29 -2
  77. package/docs/troubleshooting.md +4 -1
  78. package/package.json +1 -1
@@ -0,0 +1,172 @@
1
+ import path from 'node:path';
2
+ import { SCHEMA_VERSION, validateCommands } from '../config/schemas.js';
3
+ import { requireWorkspace } from '../core/workspace.js';
4
+ import { readVersioned } from '../core/versioned.js';
5
+ import { DevWorkspaceError } from '../errors/index.js';
6
+ import { createRunner } from '../integrations/exec.js';
7
+ import { scrub } from '../integrations/manager.js';
8
+ import { loadTask, saveTask, assertNoSecrets } from '../tasks/task.js';
9
+ import { pathExists } from '../utils/fs.js';
10
+ import { classifyCommand, isDeclared, loadTestEnv, readManifestEvidence, assertSafeTarget, removeDeclaration, saveDeclaration } from '../verification/safety.js';
11
+ import { changedSince, scopeByPackage } from '../verification/scope.js';
12
+ import { isoNow, mutate } from './common.js';
13
+ export async function loadContract(ws) {
14
+ if (!(await pathExists(ws.commands)))
15
+ return [];
16
+ return (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data.checks;
17
+ }
18
+ /** Contract plus what the task has changed per package: everything the completion gate needs to judge it. */
19
+ export async function gateContext(ws, task, contract) {
20
+ const checks = contract ?? (await loadContract(ws));
21
+ const scope = task.scope ? scopeByPackage(await changedSince(ws.root, task.scope), checks) : null;
22
+ return { contract: checks, scope };
23
+ }
24
+ /** Anything a shell would interpret. Contract commands are run as an argument vector, never through a shell. */
25
+ const SHELL_SYNTAX = /[&|;<>$`(){}"'\\*?!%^\r\n]/;
26
+ export function splitCommand(command) {
27
+ if (SHELL_SYNTAX.test(command))
28
+ return null;
29
+ const parts = command.trim().split(/\s+/);
30
+ return parts[0] ? parts : null;
31
+ }
32
+ const DEFAULT_TIMEOUT_MS = 10 * 60_000;
33
+ /**
34
+ * PATH lookup on Windows finds the extensionless Unix shell script (`npm`) before `npm.cmd`, and that cannot be
35
+ * spawned. Prefer the Windows launcher next to it. Elsewhere the lookup result is used as is.
36
+ */
37
+ export async function resolveExecutable(found, name, platform, exists) {
38
+ if (!found)
39
+ return name;
40
+ if (platform !== 'win32' || /\.(?:exe|cmd|bat|com)$/i.test(found))
41
+ return found;
42
+ for (const ext of ['.cmd', '.exe', '.bat'])
43
+ if (await exists(found + ext))
44
+ return found + ext;
45
+ return found;
46
+ }
47
+ /**
48
+ * Run one contract check for the packages the task changed, and record what really happened. DevWorkspace only runs
49
+ * read-only, non-destructive commands; anything that may touch a database runs only after the user declared a test
50
+ * environment for that package and check. Refusals are recorded as "could not run", never as a pass.
51
+ */
52
+ export async function runTaskCheck(o, id, check, opts = {}) {
53
+ const ws = await requireWorkspace(o.cwd);
54
+ const task = await loadTask(ws, id);
55
+ const contract = await loadContract(ws);
56
+ const wanted = opts.package ? (opts.package === '.' || opts.package === '' ? '.' : opts.package.replace(/\/+$/, '')) : undefined;
57
+ const entries = contract.filter((c) => c.id === check && c.available && (wanted === undefined || (c.cwd ?? '.') === wanted));
58
+ if (entries.length === 0) {
59
+ throw new DevWorkspaceError('NOT_FOUND', `The testing contract has no "${check}" command${wanted ? ` for package "${wanted}"` : ''}.`, {
60
+ hint: 'See the contract with "devworkspace context", or add the command to .devworkspace/context/commands.json.',
61
+ });
62
+ }
63
+ const scope = (await gateContext(ws, task, contract)).scope;
64
+ const decls = await loadTestEnv(ws);
65
+ const runner = o.runner ?? createRunner(o.env ?? process.env, o.platform);
66
+ const runs = [];
67
+ const evidence = [];
68
+ for (const entry of entries) {
69
+ const pkg = entry.cwd ?? '.';
70
+ const base = { package: pkg, command: entry.command };
71
+ const fingerprint = scope === null ? 'unknown' : scope[pkg];
72
+ if (wanted === undefined && fingerprint === undefined) {
73
+ runs.push({ ...base, outcome: 'not-affected', safety: 'safe', reason: 'the task changed nothing in this package' });
74
+ continue;
75
+ }
76
+ const manifest = await readManifestEvidence(ws.root, entry);
77
+ const cls = classifyCommand(entry, manifest);
78
+ if (cls.safety === 'blocked') {
79
+ runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: `refused: ${cls.reasons.join('; ')}` });
80
+ continue;
81
+ }
82
+ if (cls.safety === 'needs-test-environment' && !isDeclared(decls, pkg, check)) {
83
+ runs.push({
84
+ ...base,
85
+ outcome: 'refused',
86
+ safety: cls.safety,
87
+ reason: `refused: ${cls.reasons.join('; ')}. Run it yourself against a test environment you control, then declare it with: devworkspace testenv declare --package ${pkg} --check ${check} --target "<description>" --yes`,
88
+ });
89
+ continue;
90
+ }
91
+ const argv = splitCommand(entry.command ?? '');
92
+ if (!argv) {
93
+ runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: 'refused: the command uses shell syntax, and DevWorkspace never runs a shell' });
94
+ continue;
95
+ }
96
+ const exe = await resolveExecutable(await runner.which(argv[0]), argv[0], o.platform ?? process.platform, pathExists);
97
+ const started = Date.now();
98
+ const r = await runner.run({ command: exe, args: argv.slice(1), cwd: pkg === '.' ? ws.root : path.join(ws.root, ...pkg.split('/')), timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS });
99
+ const durationMs = Date.now() - started;
100
+ if (r.code === null && !r.timedOut && r.error) {
101
+ // The command never started (missing executable, bad cwd): that is "could not run", not a test failure.
102
+ runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: `could not start: ${scrub(r.error)}` });
103
+ continue;
104
+ }
105
+ const passed = r.ok && r.code === 0;
106
+ const tail = passed ? undefined : scrub((r.error ?? '') || (r.timedOut ? 'timed out' : '') || r.stderr.slice(-300) || r.stdout.slice(-300) || `exit ${r.code}`);
107
+ runs.push({ ...base, outcome: passed ? 'passed' : 'failed', safety: cls.safety, exitCode: r.code, durationMs, ...(tail ? { reason: tail } : {}) });
108
+ evidence.push({ kind: 'executed', at: isoNow(o), package: pkg, command: entry.command ?? undefined, exitCode: r.code, durationMs, scope: fingerprint ?? 'unknown', ...(tail ? { note: tail } : {}) });
109
+ }
110
+ const updated = await mutate(o, async (w) => {
111
+ const t = await loadTask(w, id);
112
+ let item = t.verification.find((v) => v.check === check);
113
+ if (!item) {
114
+ item = { check, required: false, status: 'PENDING' };
115
+ t.verification.push(item);
116
+ }
117
+ for (const e of evidence)
118
+ item.evidence = [...(item.evidence ?? []).filter((x) => x.package !== e.package), e];
119
+ item.status = summarize(item, runs);
120
+ const refused = runs.filter((r) => r.outcome === 'refused');
121
+ if (item.status === 'COULD_NOT_RUN')
122
+ item.detail = refused.map((r) => `${r.package}: ${r.reason}`).join(' | ').slice(0, 500);
123
+ else
124
+ delete item.detail;
125
+ delete item.reason;
126
+ item.updatedAt = isoNow(o);
127
+ t.updatedAt = item.updatedAt;
128
+ assertNoSecrets(t);
129
+ await saveTask(w, t);
130
+ return t;
131
+ });
132
+ return { task: updated, check, runs };
133
+ }
134
+ function summarize(item, runs) {
135
+ if (runs.some((r) => r.outcome === 'failed'))
136
+ return 'FAIL';
137
+ if (runs.some((r) => r.outcome === 'refused'))
138
+ return 'COULD_NOT_RUN';
139
+ if (runs.some((r) => r.outcome === 'passed'))
140
+ return 'PASS';
141
+ return item.status;
142
+ }
143
+ // ---------- declared test environments ----------
144
+ export async function declareTestEnv(o, input) {
145
+ if (!input.yes) {
146
+ throw new DevWorkspaceError('USAGE', 'Declaring a test environment is a statement that running these checks there is safe; confirm with --yes.', {
147
+ hint: 'DevWorkspace cannot verify a database target (it never reads connection settings). This records your assertion, locally, and nothing else.',
148
+ });
149
+ }
150
+ assertSafeTarget(input.target);
151
+ const ws = await requireWorkspace(o.cwd);
152
+ const pkg = input.package === '' ? '.' : input.package.replace(/\/+$/, '');
153
+ const contract = await loadContract(ws);
154
+ if (!contract.some((c) => c.available && (c.cwd ?? '.') === pkg)) {
155
+ throw new DevWorkspaceError('NOT_FOUND', `The testing contract has no commands for package "${pkg}".`, { hint: 'Use a package directory shown by "devworkspace context".' });
156
+ }
157
+ const allowed = new Set(['unit', 'integration', 'e2e', 'security']);
158
+ const bad = input.checks.filter((c) => !allowed.has(c));
159
+ if (input.checks.length === 0 || bad.length > 0) {
160
+ throw new DevWorkspaceError('USAGE', `--check must be one or more of: ${[...allowed].join(', ')}.`, { hint: 'Migrations, seeds and resets are never run by DevWorkspace.' });
161
+ }
162
+ const decl = { package: pkg, checks: [...new Set(input.checks)].sort(), target: input.target.trim(), declaredAt: isoNow(o) };
163
+ await mutate(o, async (w) => saveDeclaration(w, decl));
164
+ return decl;
165
+ }
166
+ export async function listTestEnv(o) {
167
+ return loadTestEnv(await requireWorkspace(o.cwd));
168
+ }
169
+ export async function revokeTestEnv(o, pkg) {
170
+ return mutate(o, async (w) => removeDeclaration(w, pkg === '' ? '.' : pkg.replace(/\/+$/, '')));
171
+ }
172
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/services/verify.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport { SCHEMA_VERSION, validateCommands, type CommandEntry } from '../config/schemas.js';\nimport { requireWorkspace, type Ws } from '../core/workspace.js';\nimport { readVersioned } from '../core/versioned.js';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { createRunner } from '../integrations/exec.js';\nimport { scrub } from '../integrations/manager.js';\nimport { loadTask, saveTask, type Evidence, type GateContext, type Task, type VerificationItem, type VerifyStatus, assertNoSecrets } from '../tasks/task.js';\nimport { pathExists } from '../utils/fs.js';\nimport { classifyCommand, isDeclared, loadTestEnv, readManifestEvidence, assertSafeTarget, removeDeclaration, saveDeclaration, type CommandSafety, type TestEnvDeclaration } from '../verification/safety.js';\nimport { changedSince, scopeByPackage } from '../verification/scope.js';\nimport { isoNow, mutate, type ServiceOptions } from './common.js';\n\nexport async function loadContract(ws: Ws): Promise<CommandEntry[]> {\n if (!(await pathExists(ws.commands))) return [];\n return (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data.checks;\n}\n\n/** Contract plus what the task has changed per package: everything the completion gate needs to judge it. */\nexport async function gateContext(ws: Ws, task: Task, contract?: CommandEntry[]): Promise<GateContext> {\n const checks = contract ?? (await loadContract(ws));\n const scope = task.scope ? scopeByPackage(await changedSince(ws.root, task.scope), checks) : null;\n return { contract: checks, scope };\n}\n\nexport type RunOutcome = 'passed' | 'failed' | 'refused' | 'not-affected';\n\nexport interface PackageRun {\n package: string;\n command: string | null;\n outcome: RunOutcome;\n exitCode?: number | null;\n durationMs?: number;\n /** Why DevWorkspace did not run it, or the scrubbed tail of a failure. */\n reason?: string;\n safety: CommandSafety['safety'];\n}\n\nexport interface RunCheckResult {\n task: Task;\n check: string;\n runs: PackageRun[];\n}\n\n/** Anything a shell would interpret. Contract commands are run as an argument vector, never through a shell. */\nconst SHELL_SYNTAX = /[&|;<>$`(){}\"'\\\\*?!%^\\r\\n]/;\n\nexport function splitCommand(command: string): string[] | null {\n if (SHELL_SYNTAX.test(command)) return null;\n const parts = command.trim().split(/\\s+/);\n return parts[0] ? parts : null;\n}\n\nconst DEFAULT_TIMEOUT_MS = 10 * 60_000;\n\n/**\n * PATH lookup on Windows finds the extensionless Unix shell script (`npm`) before `npm.cmd`, and that cannot be\n * spawned. Prefer the Windows launcher next to it. Elsewhere the lookup result is used as is.\n */\nexport async function resolveExecutable(found: string | null, name: string, platform: NodeJS.Platform, exists: (p: string) => Promise<boolean>): Promise<string> {\n if (!found) return name;\n if (platform !== 'win32' || /\\.(?:exe|cmd|bat|com)$/i.test(found)) return found;\n for (const ext of ['.cmd', '.exe', '.bat']) if (await exists(found + ext)) return found + ext;\n return found;\n}\n\n/**\n * Run one contract check for the packages the task changed, and record what really happened. DevWorkspace only runs\n * read-only, non-destructive commands; anything that may touch a database runs only after the user declared a test\n * environment for that package and check. Refusals are recorded as \"could not run\", never as a pass.\n */\nexport async function runTaskCheck(o: ServiceOptions, id: string, check: string, opts: { package?: string; timeoutMs?: number } = {}): Promise<RunCheckResult> {\n const ws = await requireWorkspace(o.cwd);\n const task = await loadTask(ws, id);\n const contract = await loadContract(ws);\n const wanted = opts.package ? (opts.package === '.' || opts.package === '' ? '.' : opts.package.replace(/\\/+$/, '')) : undefined;\n const entries = contract.filter((c) => c.id === check && c.available && (wanted === undefined || (c.cwd ?? '.') === wanted));\n if (entries.length === 0) {\n throw new DevWorkspaceError('NOT_FOUND', `The testing contract has no \"${check}\" command${wanted ? ` for package \"${wanted}\"` : ''}.`, {\n hint: 'See the contract with \"devworkspace context\", or add the command to .devworkspace/context/commands.json.',\n });\n }\n const scope = (await gateContext(ws, task, contract)).scope;\n const decls = await loadTestEnv(ws);\n const runner = o.runner ?? createRunner(o.env ?? process.env, o.platform);\n const runs: PackageRun[] = [];\n const evidence: Evidence[] = [];\n\n for (const entry of entries) {\n const pkg = entry.cwd ?? '.';\n const base = { package: pkg, command: entry.command };\n const fingerprint = scope === null ? 'unknown' : scope[pkg];\n if (wanted === undefined && fingerprint === undefined) {\n runs.push({ ...base, outcome: 'not-affected', safety: 'safe', reason: 'the task changed nothing in this package' });\n continue;\n }\n const manifest = await readManifestEvidence(ws.root, entry);\n const cls = classifyCommand(entry, manifest);\n if (cls.safety === 'blocked') {\n runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: `refused: ${cls.reasons.join('; ')}` });\n continue;\n }\n if (cls.safety === 'needs-test-environment' && !isDeclared(decls, pkg, check)) {\n runs.push({\n ...base,\n outcome: 'refused',\n safety: cls.safety,\n reason: `refused: ${cls.reasons.join('; ')}. Run it yourself against a test environment you control, then declare it with: devworkspace testenv declare --package ${pkg} --check ${check} --target \"<description>\" --yes`,\n });\n continue;\n }\n const argv = splitCommand(entry.command ?? '');\n if (!argv) {\n runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: 'refused: the command uses shell syntax, and DevWorkspace never runs a shell' });\n continue;\n }\n const exe = await resolveExecutable(await runner.which(argv[0]!), argv[0]!, o.platform ?? process.platform, pathExists);\n const started = Date.now();\n const r = await runner.run({ command: exe, args: argv.slice(1), cwd: pkg === '.' ? ws.root : path.join(ws.root, ...pkg.split('/')), timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS });\n const durationMs = Date.now() - started;\n if (r.code === null && !r.timedOut && r.error) {\n // The command never started (missing executable, bad cwd): that is \"could not run\", not a test failure.\n runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: `could not start: ${scrub(r.error)}` });\n continue;\n }\n const passed = r.ok && r.code === 0;\n const tail = passed ? undefined : scrub((r.error ?? '') || (r.timedOut ? 'timed out' : '') || r.stderr.slice(-300) || r.stdout.slice(-300) || `exit ${r.code}`);\n runs.push({ ...base, outcome: passed ? 'passed' : 'failed', safety: cls.safety, exitCode: r.code, durationMs, ...(tail ? { reason: tail } : {}) });\n evidence.push({ kind: 'executed', at: isoNow(o), package: pkg, command: entry.command ?? undefined, exitCode: r.code, durationMs, scope: fingerprint ?? 'unknown', ...(tail ? { note: tail } : {}) });\n }\n\n const updated = await mutate(o, async (w) => {\n const t = await loadTask(w, id);\n let item = t.verification.find((v) => v.check === check);\n if (!item) {\n item = { check, required: false, status: 'PENDING' };\n t.verification.push(item);\n }\n for (const e of evidence) item.evidence = [...(item.evidence ?? []).filter((x) => x.package !== e.package), e];\n item.status = summarize(item, runs);\n const refused = runs.filter((r) => r.outcome === 'refused');\n if (item.status === 'COULD_NOT_RUN') item.detail = refused.map((r) => `${r.package}: ${r.reason}`).join(' | ').slice(0, 500);\n else delete item.detail;\n delete item.reason;\n item.updatedAt = isoNow(o);\n t.updatedAt = item.updatedAt;\n assertNoSecrets(t);\n await saveTask(w, t);\n return t;\n });\n return { task: updated, check, runs };\n}\n\nfunction summarize(item: VerificationItem, runs: PackageRun[]): VerifyStatus {\n if (runs.some((r) => r.outcome === 'failed')) return 'FAIL';\n if (runs.some((r) => r.outcome === 'refused')) return 'COULD_NOT_RUN';\n if (runs.some((r) => r.outcome === 'passed')) return 'PASS';\n return item.status;\n}\n\n// ---------- declared test environments ----------\n\nexport async function declareTestEnv(o: ServiceOptions, input: { package: string; checks: string[]; target: string; yes: boolean }): Promise<TestEnvDeclaration> {\n if (!input.yes) {\n throw new DevWorkspaceError('USAGE', 'Declaring a test environment is a statement that running these checks there is safe; confirm with --yes.', {\n hint: 'DevWorkspace cannot verify a database target (it never reads connection settings). This records your assertion, locally, and nothing else.',\n });\n }\n assertSafeTarget(input.target);\n const ws = await requireWorkspace(o.cwd);\n const pkg = input.package === '' ? '.' : input.package.replace(/\\/+$/, '');\n const contract = await loadContract(ws);\n if (!contract.some((c) => c.available && (c.cwd ?? '.') === pkg)) {\n throw new DevWorkspaceError('NOT_FOUND', `The testing contract has no commands for package \"${pkg}\".`, { hint: 'Use a package directory shown by \"devworkspace context\".' });\n }\n const allowed = new Set(['unit', 'integration', 'e2e', 'security']);\n const bad = input.checks.filter((c) => !allowed.has(c));\n if (input.checks.length === 0 || bad.length > 0) {\n throw new DevWorkspaceError('USAGE', `--check must be one or more of: ${[...allowed].join(', ')}.`, { hint: 'Migrations, seeds and resets are never run by DevWorkspace.' });\n }\n const decl: TestEnvDeclaration = { package: pkg, checks: [...new Set(input.checks)].sort(), target: input.target.trim(), declaredAt: isoNow(o) };\n await mutate(o, async (w) => saveDeclaration(w, decl));\n return decl;\n}\n\nexport async function listTestEnv(o: ServiceOptions): Promise<TestEnvDeclaration[]> {\n return loadTestEnv(await requireWorkspace(o.cwd));\n}\n\nexport async function revokeTestEnv(o: ServiceOptions, pkg: string): Promise<boolean> {\n return mutate(o, async (w) => removeDeclaration(w, pkg === '' ? '.' : pkg.replace(/\\/+$/, '')));\n}\n"]}
@@ -158,7 +158,9 @@ export function detectConstraints(text) {
158
158
  return OVERRIDE_PATTERNS.filter(([, re]) => re.test(text)).map(([c]) => c);
159
159
  }
160
160
  const QUESTION_START = /^\s*(?:where|what|which|who|when|how\s+(?:does|do|is|are|can|should)|why|explain|describe|show\s+me|walk\s+me|is\s+there|are\s+there|can\s+you\s+(?:explain|tell|show)|tell\s+me|list|find)\b/i;
161
- const IMPERATIVE_START = /^\s*(?:fix|add|implement|create|build|refactor|remove|delete|update|change|rename|write|make|migrate|upgrade|optimi[sz]e|release|publish)\b/i;
161
+ const IMPERATIVE_START = /^\s*(?:fix|add|implement|create|build|refactor|remove|delete|update|change|rename|write|make|migrate|upgrade|optimi[sz]e|release|publish|validate|ensure|prevent|enforce|guard|reject|restrict|handle|resolve|patch|repair|address|correct|replace|move|extract|rewrite|convert|improve|enable|disable|support|introduce|apply|configure|install|harden|secure|scope|isolate)\b/i;
162
+ /** A direct yes/no or open question: it ends in a question mark and does not start like an instruction. */
163
+ const DIRECT_QUESTION = /\?\s*$/;
162
164
  /** Heuristic classifier used to propose a task type. The agent may override it; ambiguous input defaults to the safest (read-only) type. */
163
165
  export function classifyRequest(text) {
164
166
  const constraints = detectConstraints(text);
@@ -170,7 +172,7 @@ export function classifyRequest(text) {
170
172
  constraints,
171
173
  readOnly: WORKFLOWS[type].readOnly || constraints.includes('no-modify') || constraints.includes('plan-only'),
172
174
  });
173
- if (QUESTION_START.test(t) && !IMPERATIVE_START.test(t))
175
+ if ((QUESTION_START.test(t) || DIRECT_QUESTION.test(t)) && !IMPERATIVE_START.test(t))
174
176
  return make('QUESTION', 'high');
175
177
  if (/\b(?:add|write|improve|increase)\s+(?:\w+\s+)?(?:unit\s+|e2e\s+|integration\s+)?tests?\b|\btest coverage\b|^\s*run\s+(?:the\s+|all\s+)?(?:\w+\s+)?tests?\b/i.test(t))
176
178
  return make('TESTING', 'high');
@@ -180,7 +182,7 @@ export function classifyRequest(text) {
180
182
  return make('BUG', 'high');
181
183
  if (/\b(?:refactor|restructure|reorgani[sz]e|clean\s?up|simplify|de-?duplicate)\b/i.test(t))
182
184
  return make('REFACTOR', 'high');
183
- if (/\b(?:vulnerab\w*|security|csrf|xss|sql injection|sanitiz\w*|harden\w*|cve-\d+)\b/i.test(t))
185
+ if (/\b(?:vulnerab\w*|security|csrf|xss|sql injection|sanitiz\w*|harden\w*|cve-\d+|cross-?tenant|tenant isolation|ownership|authori[sz]ation|privilege escalation|idor)\b/i.test(t))
184
186
  return make('SECURITY', 'high');
185
187
  if (/\b(?:performance|slow|latency|speed up|optimi[sz]e|throughput)\b/i.test(t))
186
188
  return make('PERFORMANCE', 'high');
@@ -194,6 +196,9 @@ export function classifyRequest(text) {
194
196
  return make('ARCHITECTURE', 'high');
195
197
  if (/\b(?:add|implement|create|build|support|introduce|enable|new)\b/i.test(t))
196
198
  return make('FEATURE', 'high');
199
+ // An instruction we cannot place is still a request to change something. Never default it to a read-only question.
200
+ if (IMPERATIVE_START.test(t))
201
+ return make('FEATURE', 'low');
197
202
  return make('QUESTION', 'low');
198
203
  }
199
204
  export function renderRoutingTable() {
@@ -1 +1 @@
1
- {"version":3,"file":"routing.js","sourceRoot":"","sources":["../../src/tasks/routing.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TaskType } from './task.js';\n\nexport type Constraint = 'no-modify' | 'plan-only' | 'no-commit' | 'no-e2e' | 'no-tests' | 'no-fix';\n\nexport interface WorkflowSpec {\n type: TaskType;\n /** Highest context level normally needed (1 instructions … 6 tests/implementation). */\n contextDepth: 1 | 2 | 3 | 4 | 5 | 6;\n readOnly: boolean;\n trackTask: boolean;\n steps: string[];\n verification: string;\n agentSkills: string[];\n ponytail: string;\n knowledgeUpdate: string;\n}\n\n/** Single source of truth for routing. The generated Claude skill and docs/routing.md are rendered from this table. */\nexport const WORKFLOWS: Record<TaskType, WorkflowSpec> = {\n QUESTION: {\n type: 'QUESTION',\n contextDepth: 5,\n readOnly: true,\n trackTask: false,\n steps: [\n 'Read .devworkspace/context facts relevant to the topic (devworkspace context --topic <area>).',\n 'Use Graphify (if ready) or targeted search to locate the code; read only the files needed.',\n 'Answer with file references. State uncertainty explicitly.',\n ],\n verification: 'None. Do not modify files or run tests.',\n agentSkills: [],\n ponytail: 'Not used.',\n knowledgeUpdate: 'Only if you discovered a durable fact that is missing; record it with \"devworkspace fact add\".',\n },\n BUG: {\n type: 'BUG',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: [\n 'Check git status; protect uncommitted user changes.',\n 'Investigate and reproduce where practical.',\n 'Write a regression test and confirm it fails.',\n 'Fix the cause, confirm the test passes.',\n 'Run the applicable regression suite, then typecheck/lint/build as required by the testing contract.',\n 'Review the diff; record results with \"devworkspace task verify\".',\n ],\n verification: 'Regression test, applicable suite, typecheck, lint, build (whatever the testing contract marks required).',\n agentSkills: ['/test', '/review'],\n ponytail: 'Light review of the diff when review level is not \"off\".',\n knowledgeUpdate: 'Only if the bug revealed a persistent constraint or convention.',\n },\n FEATURE: {\n type: 'FEATURE',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: [\n 'Check git status. Read relevant facts and decisions.',\n 'Write acceptance criteria and a short plan (task update).',\n 'Write tests for the criteria, then implement.',\n 'Run targeted tests, then regression, then typecheck/lint/build.',\n 'Review the diff.',\n 'Update project knowledge for architecture, conventions or decisions that changed.',\n ],\n verification: 'Targeted tests, regression, typecheck, lint, build, review.',\n agentSkills: ['/spec', '/plan', '/build', '/test', '/review'],\n ponytail: 'Review for unnecessary complexity before completion.',\n knowledgeUpdate: 'Yes when the feature adds a module, integration, business rule or decision.',\n },\n REFACTOR: {\n type: 'REFACTOR',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: [\n 'Check git status. Analyse architecture, decisions and dependents (Graphify).',\n 'Record a baseline test result (devworkspace task tests --phase baseline).',\n 'Plan, then refactor in small verified steps.',\n 'Run targeted verification and the full regression suite; compare against the baseline.',\n 'Review the diff.',\n ],\n verification: 'Baseline vs after (no regressions), typecheck, lint, build, review.',\n agentSkills: ['/plan', '/code-simplify', '/review'],\n ponytail: 'Use for simplification review.',\n knowledgeUpdate: 'Yes if structure or conventions changed.',\n },\n DOCUMENTATION: {\n type: 'DOCUMENTATION',\n contextDepth: 4,\n readOnly: false,\n trackTask: false,\n steps: ['Read the code or facts being documented.', 'Edit documentation only.', 'Run documentation checks if the project has them (link or lint checks).'],\n verification: 'Only checks that apply to documentation.',\n agentSkills: [],\n ponytail: 'Not used.',\n knowledgeUpdate: 'Rarely.',\n },\n TESTING: {\n type: 'TESTING',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: ['Check git status.', 'Find existing test conventions.', 'Add tests; confirm they fail for the right reason where applicable.', 'Run the affected suite plus typecheck and lint.'],\n verification: 'Affected tests, typecheck, lint.',\n agentSkills: ['/test'],\n ponytail: 'Optional.',\n knowledgeUpdate: 'If the testing strategy or commands changed.',\n },\n PERFORMANCE: {\n type: 'PERFORMANCE',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: ['Measure first; record the baseline.', 'Change one thing at a time.', 'Re-measure and run the regression suite and build.', 'Review.'],\n verification: 'Before/after measurement, regression suite, build.',\n agentSkills: ['/webperf', '/review'],\n ponytail: 'Review for added complexity.',\n knowledgeUpdate: 'If a persistent constraint or decision emerged.',\n },\n SECURITY: {\n type: 'SECURITY',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: ['Check git status.', 'Understand the threat and the affected paths.', 'Test that demonstrates the issue, then fix.', 'Regression suite, typecheck, lint, build.', 'Security-focused review. Never print secret values.'],\n verification: 'Regression and exploit test, typecheck, lint, build, review.',\n agentSkills: ['/test', '/review'],\n ponytail: 'Use for bug and security review of the diff.',\n knowledgeUpdate: 'Yes for new security conventions or decisions.',\n },\n ARCHITECTURE: {\n type: 'ARCHITECTURE',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: ['Read facts, decisions and architecture notes; map structure with Graphify.', 'Present options and record the chosen one with \"devworkspace decision add\".', 'Plan and implement in verified steps.', 'Full verification and review.'],\n verification: 'Baseline vs after, typecheck, lint, build, review.',\n agentSkills: ['/spec', '/plan', '/build', '/review'],\n ponytail: 'Review for unnecessary abstraction.',\n knowledgeUpdate: 'Always: architecture notes and the decision record.',\n },\n RELEASE: {\n type: 'RELEASE',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: ['Check git status and branch.', 'Run the full testing contract including integration/e2e where available.', 'Update changelog and version per project convention.', 'Do not publish or push without explicit user authorisation.'],\n verification: 'Everything in the testing contract.',\n agentSkills: ['/ship'],\n ponytail: 'Optional final review.',\n knowledgeUpdate: 'If release process facts changed.',\n },\n MAINTENANCE: {\n type: 'MAINTENANCE',\n contextDepth: 4,\n readOnly: false,\n trackTask: true,\n steps: ['Check git status.', 'Make the change.', 'Run lint and build; run tests when behaviour could change.'],\n verification: 'Lint, build, tests if behaviour could change.',\n agentSkills: [],\n ponytail: 'Optional.',\n knowledgeUpdate: 'Only for changed commands or tooling.',\n },\n};\n\nconst OVERRIDE_PATTERNS: [Constraint, RegExp][] = [\n ['no-modify', /\\b(?:only|just)\\s+(?:explain|describe|investigate|look|read|review)\\b|\\b(?:do\\s*n[o']t|don'?t|never)\\s+(?:modify|change|edit|touch)\\b|\\bread[- ]only\\b|\\bno\\s+changes\\b/i],\n ['plan-only', /\\b(?:only|just)\\s+(?:make\\s+|write\\s+|give\\s+me\\s+)?a\\s+plan\\b|\\bplan\\s+only\\b/i],\n ['no-commit', /\\b(?:do\\s*n[o']t|don'?t|never)\\s+commit\\b|\\bno\\s+commits?\\b/i],\n ['no-e2e', /\\b(?:do\\s*n[o']t|don'?t|skip|no)\\s+(?:run\\s+)?(?:the\\s+)?(?:e2e|end[- ]to[- ]end)\\b/i],\n ['no-tests', /\\b(?:do\\s*n[o']t|don'?t|skip)\\s+(?:run\\s+)?(?:any\\s+|the\\s+)?tests\\b(?!\\s+but)/i],\n ['no-fix', /\\b(?:do\\s*n[o']t|don'?t)\\s+fix\\b|\\bwithout\\s+fixing\\b/i],\n];\n\nexport function detectConstraints(text: string): Constraint[] {\n return OVERRIDE_PATTERNS.filter(([, re]) => re.test(text)).map(([c]) => c);\n}\n\nexport interface Classification {\n type: TaskType;\n confidence: 'high' | 'low';\n constraints: Constraint[];\n /** True when the request must not change files (question type, or an explicit user override). */\n readOnly: boolean;\n}\n\nconst QUESTION_START = /^\\s*(?:where|what|which|who|when|how\\s+(?:does|do|is|are|can|should)|why|explain|describe|show\\s+me|walk\\s+me|is\\s+there|are\\s+there|can\\s+you\\s+(?:explain|tell|show)|tell\\s+me|list|find)\\b/i;\nconst IMPERATIVE_START = /^\\s*(?:fix|add|implement|create|build|refactor|remove|delete|update|change|rename|write|make|migrate|upgrade|optimi[sz]e|release|publish)\\b/i;\n\n/** Heuristic classifier used to propose a task type. The agent may override it; ambiguous input defaults to the safest (read-only) type. */\nexport function classifyRequest(text: string): Classification {\n const constraints = detectConstraints(text);\n // Negated phrases (\"don't fix anything\") describe limits, not the task itself.\n const t = OVERRIDE_PATTERNS.reduce((acc, [, re]) => acc.replace(new RegExp(re.source, re.flags + 'g'), ' '), text).trim();\n const make = (type: TaskType, confidence: 'high' | 'low'): Classification => ({\n type,\n confidence,\n constraints,\n readOnly: WORKFLOWS[type].readOnly || constraints.includes('no-modify') || constraints.includes('plan-only'),\n });\n if (QUESTION_START.test(t) && !IMPERATIVE_START.test(t)) return make('QUESTION', 'high');\n if (/\\b(?:add|write|improve|increase)\\s+(?:\\w+\\s+)?(?:unit\\s+|e2e\\s+|integration\\s+)?tests?\\b|\\btest coverage\\b|^\\s*run\\s+(?:the\\s+|all\\s+)?(?:\\w+\\s+)?tests?\\b/i.test(t)) return make('TESTING', 'high');\n if (/\\b(?:upgrade|bump|update\\s+dependenc\\w*)\\b/i.test(t)) return make('MAINTENANCE', 'high');\n if (/\\b(?:fix|bug|broken|crash(?:es|ing)?|regression|not working|fails?|failing|exception|error)\\b/i.test(t)) return make('BUG', 'high');\n if (/\\b(?:refactor|restructure|reorgani[sz]e|clean\\s?up|simplify|de-?duplicate)\\b/i.test(t)) return make('REFACTOR', 'high');\n if (/\\b(?:vulnerab\\w*|security|csrf|xss|sql injection|sanitiz\\w*|harden\\w*|cve-\\d+)\\b/i.test(t)) return make('SECURITY', 'high');\n if (/\\b(?:performance|slow|latency|speed up|optimi[sz]e|throughput)\\b/i.test(t)) return make('PERFORMANCE', 'high');\n if (/\\b(?:document|readme|docs|changelog|jsdoc|docstring)\\b/i.test(t)) return make('DOCUMENTATION', 'high');\n if (/\\b(?:release|publish|deploy|ship|version bump)\\b/i.test(t)) return make('RELEASE', 'high');\n if (/\\b(?:upgrade|bump|update\\s+dependenc\\w*|chore|lint(?:ing)?|format(?:ting)?)\\b/i.test(t)) return make('MAINTENANCE', 'high');\n if (/\\b(?:redesign|re-?architect\\w*|migrate\\s+to|architecture)\\b/i.test(t)) return make('ARCHITECTURE', 'high');\n if (/\\b(?:add|implement|create|build|support|introduce|enable|new)\\b/i.test(t)) return make('FEATURE', 'high');\n return make('QUESTION', 'low');\n}\n\nexport function renderRoutingTable(): string {\n const rows = (Object.values(WORKFLOWS) as WorkflowSpec[]).map((w) => {\n const flags = [w.readOnly ? 'read-only' : 'modifies code', w.trackTask ? 'tracked task' : 'no task file'].join(', ');\n return `| ${w.type} | ${flags} | L${w.contextDepth} | ${w.verification} |`;\n });\n return ['| Type | Nature | Max context | Verification |', '| --- | --- | --- | --- |', ...rows].join('\\n');\n}\n"]}
1
+ {"version":3,"file":"routing.js","sourceRoot":"","sources":["../../src/tasks/routing.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TaskType } from './task.js';\n\nexport type Constraint = 'no-modify' | 'plan-only' | 'no-commit' | 'no-e2e' | 'no-tests' | 'no-fix';\n\nexport interface WorkflowSpec {\n type: TaskType;\n /** Highest context level normally needed (1 instructions … 6 tests/implementation). */\n contextDepth: 1 | 2 | 3 | 4 | 5 | 6;\n readOnly: boolean;\n trackTask: boolean;\n steps: string[];\n verification: string;\n agentSkills: string[];\n ponytail: string;\n knowledgeUpdate: string;\n}\n\n/** Single source of truth for routing. The generated Claude skill and docs/routing.md are rendered from this table. */\nexport const WORKFLOWS: Record<TaskType, WorkflowSpec> = {\n QUESTION: {\n type: 'QUESTION',\n contextDepth: 5,\n readOnly: true,\n trackTask: false,\n steps: [\n 'Read .devworkspace/context facts relevant to the topic (devworkspace context --topic <area>).',\n 'Use Graphify (if ready) or targeted search to locate the code; read only the files needed.',\n 'Answer with file references. State uncertainty explicitly.',\n ],\n verification: 'None. Do not modify files or run tests.',\n agentSkills: [],\n ponytail: 'Not used.',\n knowledgeUpdate: 'Only if you discovered a durable fact that is missing; record it with \"devworkspace fact add\".',\n },\n BUG: {\n type: 'BUG',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: [\n 'Check git status; protect uncommitted user changes.',\n 'Investigate and reproduce where practical.',\n 'Write a regression test and confirm it fails.',\n 'Fix the cause, confirm the test passes.',\n 'Run the applicable regression suite, then typecheck/lint/build as required by the testing contract.',\n 'Review the diff; record results with \"devworkspace task verify\".',\n ],\n verification: 'Regression test, applicable suite, typecheck, lint, build (whatever the testing contract marks required).',\n agentSkills: ['/test', '/review'],\n ponytail: 'Light review of the diff when review level is not \"off\".',\n knowledgeUpdate: 'Only if the bug revealed a persistent constraint or convention.',\n },\n FEATURE: {\n type: 'FEATURE',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: [\n 'Check git status. Read relevant facts and decisions.',\n 'Write acceptance criteria and a short plan (task update).',\n 'Write tests for the criteria, then implement.',\n 'Run targeted tests, then regression, then typecheck/lint/build.',\n 'Review the diff.',\n 'Update project knowledge for architecture, conventions or decisions that changed.',\n ],\n verification: 'Targeted tests, regression, typecheck, lint, build, review.',\n agentSkills: ['/spec', '/plan', '/build', '/test', '/review'],\n ponytail: 'Review for unnecessary complexity before completion.',\n knowledgeUpdate: 'Yes when the feature adds a module, integration, business rule or decision.',\n },\n REFACTOR: {\n type: 'REFACTOR',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: [\n 'Check git status. Analyse architecture, decisions and dependents (Graphify).',\n 'Record a baseline test result (devworkspace task tests --phase baseline).',\n 'Plan, then refactor in small verified steps.',\n 'Run targeted verification and the full regression suite; compare against the baseline.',\n 'Review the diff.',\n ],\n verification: 'Baseline vs after (no regressions), typecheck, lint, build, review.',\n agentSkills: ['/plan', '/code-simplify', '/review'],\n ponytail: 'Use for simplification review.',\n knowledgeUpdate: 'Yes if structure or conventions changed.',\n },\n DOCUMENTATION: {\n type: 'DOCUMENTATION',\n contextDepth: 4,\n readOnly: false,\n trackTask: false,\n steps: ['Read the code or facts being documented.', 'Edit documentation only.', 'Run documentation checks if the project has them (link or lint checks).'],\n verification: 'Only checks that apply to documentation.',\n agentSkills: [],\n ponytail: 'Not used.',\n knowledgeUpdate: 'Rarely.',\n },\n TESTING: {\n type: 'TESTING',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: ['Check git status.', 'Find existing test conventions.', 'Add tests; confirm they fail for the right reason where applicable.', 'Run the affected suite plus typecheck and lint.'],\n verification: 'Affected tests, typecheck, lint.',\n agentSkills: ['/test'],\n ponytail: 'Optional.',\n knowledgeUpdate: 'If the testing strategy or commands changed.',\n },\n PERFORMANCE: {\n type: 'PERFORMANCE',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: ['Measure first; record the baseline.', 'Change one thing at a time.', 'Re-measure and run the regression suite and build.', 'Review.'],\n verification: 'Before/after measurement, regression suite, build.',\n agentSkills: ['/webperf', '/review'],\n ponytail: 'Review for added complexity.',\n knowledgeUpdate: 'If a persistent constraint or decision emerged.',\n },\n SECURITY: {\n type: 'SECURITY',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: ['Check git status.', 'Understand the threat and the affected paths.', 'Test that demonstrates the issue, then fix.', 'Regression suite, typecheck, lint, build.', 'Security-focused review. Never print secret values.'],\n verification: 'Regression and exploit test, typecheck, lint, build, review.',\n agentSkills: ['/test', '/review'],\n ponytail: 'Use for bug and security review of the diff.',\n knowledgeUpdate: 'Yes for new security conventions or decisions.',\n },\n ARCHITECTURE: {\n type: 'ARCHITECTURE',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: ['Read facts, decisions and architecture notes; map structure with Graphify.', 'Present options and record the chosen one with \"devworkspace decision add\".', 'Plan and implement in verified steps.', 'Full verification and review.'],\n verification: 'Baseline vs after, typecheck, lint, build, review.',\n agentSkills: ['/spec', '/plan', '/build', '/review'],\n ponytail: 'Review for unnecessary abstraction.',\n knowledgeUpdate: 'Always: architecture notes and the decision record.',\n },\n RELEASE: {\n type: 'RELEASE',\n contextDepth: 6,\n readOnly: false,\n trackTask: true,\n steps: ['Check git status and branch.', 'Run the full testing contract including integration/e2e where available.', 'Update changelog and version per project convention.', 'Do not publish or push without explicit user authorisation.'],\n verification: 'Everything in the testing contract.',\n agentSkills: ['/ship'],\n ponytail: 'Optional final review.',\n knowledgeUpdate: 'If release process facts changed.',\n },\n MAINTENANCE: {\n type: 'MAINTENANCE',\n contextDepth: 4,\n readOnly: false,\n trackTask: true,\n steps: ['Check git status.', 'Make the change.', 'Run lint and build; run tests when behaviour could change.'],\n verification: 'Lint, build, tests if behaviour could change.',\n agentSkills: [],\n ponytail: 'Optional.',\n knowledgeUpdate: 'Only for changed commands or tooling.',\n },\n};\n\nconst OVERRIDE_PATTERNS: [Constraint, RegExp][] = [\n ['no-modify', /\\b(?:only|just)\\s+(?:explain|describe|investigate|look|read|review)\\b|\\b(?:do\\s*n[o']t|don'?t|never)\\s+(?:modify|change|edit|touch)\\b|\\bread[- ]only\\b|\\bno\\s+changes\\b/i],\n ['plan-only', /\\b(?:only|just)\\s+(?:make\\s+|write\\s+|give\\s+me\\s+)?a\\s+plan\\b|\\bplan\\s+only\\b/i],\n ['no-commit', /\\b(?:do\\s*n[o']t|don'?t|never)\\s+commit\\b|\\bno\\s+commits?\\b/i],\n ['no-e2e', /\\b(?:do\\s*n[o']t|don'?t|skip|no)\\s+(?:run\\s+)?(?:the\\s+)?(?:e2e|end[- ]to[- ]end)\\b/i],\n ['no-tests', /\\b(?:do\\s*n[o']t|don'?t|skip)\\s+(?:run\\s+)?(?:any\\s+|the\\s+)?tests\\b(?!\\s+but)/i],\n ['no-fix', /\\b(?:do\\s*n[o']t|don'?t)\\s+fix\\b|\\bwithout\\s+fixing\\b/i],\n];\n\nexport function detectConstraints(text: string): Constraint[] {\n return OVERRIDE_PATTERNS.filter(([, re]) => re.test(text)).map(([c]) => c);\n}\n\nexport interface Classification {\n type: TaskType;\n confidence: 'high' | 'low';\n constraints: Constraint[];\n /** True when the request must not change files (question type, or an explicit user override). */\n readOnly: boolean;\n}\n\nconst QUESTION_START = /^\\s*(?:where|what|which|who|when|how\\s+(?:does|do|is|are|can|should)|why|explain|describe|show\\s+me|walk\\s+me|is\\s+there|are\\s+there|can\\s+you\\s+(?:explain|tell|show)|tell\\s+me|list|find)\\b/i;\nconst IMPERATIVE_START =\n /^\\s*(?:fix|add|implement|create|build|refactor|remove|delete|update|change|rename|write|make|migrate|upgrade|optimi[sz]e|release|publish|validate|ensure|prevent|enforce|guard|reject|restrict|handle|resolve|patch|repair|address|correct|replace|move|extract|rewrite|convert|improve|enable|disable|support|introduce|apply|configure|install|harden|secure|scope|isolate)\\b/i;\n/** A direct yes/no or open question: it ends in a question mark and does not start like an instruction. */\nconst DIRECT_QUESTION = /\\?\\s*$/;\n\n/** Heuristic classifier used to propose a task type. The agent may override it; ambiguous input defaults to the safest (read-only) type. */\nexport function classifyRequest(text: string): Classification {\n const constraints = detectConstraints(text);\n // Negated phrases (\"don't fix anything\") describe limits, not the task itself.\n const t = OVERRIDE_PATTERNS.reduce((acc, [, re]) => acc.replace(new RegExp(re.source, re.flags + 'g'), ' '), text).trim();\n const make = (type: TaskType, confidence: 'high' | 'low'): Classification => ({\n type,\n confidence,\n constraints,\n readOnly: WORKFLOWS[type].readOnly || constraints.includes('no-modify') || constraints.includes('plan-only'),\n });\n if ((QUESTION_START.test(t) || DIRECT_QUESTION.test(t)) && !IMPERATIVE_START.test(t)) return make('QUESTION', 'high');\n if (/\\b(?:add|write|improve|increase)\\s+(?:\\w+\\s+)?(?:unit\\s+|e2e\\s+|integration\\s+)?tests?\\b|\\btest coverage\\b|^\\s*run\\s+(?:the\\s+|all\\s+)?(?:\\w+\\s+)?tests?\\b/i.test(t)) return make('TESTING', 'high');\n if (/\\b(?:upgrade|bump|update\\s+dependenc\\w*)\\b/i.test(t)) return make('MAINTENANCE', 'high');\n if (/\\b(?:fix|bug|broken|crash(?:es|ing)?|regression|not working|fails?|failing|exception|error)\\b/i.test(t)) return make('BUG', 'high');\n if (/\\b(?:refactor|restructure|reorgani[sz]e|clean\\s?up|simplify|de-?duplicate)\\b/i.test(t)) return make('REFACTOR', 'high');\n if (/\\b(?:vulnerab\\w*|security|csrf|xss|sql injection|sanitiz\\w*|harden\\w*|cve-\\d+|cross-?tenant|tenant isolation|ownership|authori[sz]ation|privilege escalation|idor)\\b/i.test(t)) return make('SECURITY', 'high');\n if (/\\b(?:performance|slow|latency|speed up|optimi[sz]e|throughput)\\b/i.test(t)) return make('PERFORMANCE', 'high');\n if (/\\b(?:document|readme|docs|changelog|jsdoc|docstring)\\b/i.test(t)) return make('DOCUMENTATION', 'high');\n if (/\\b(?:release|publish|deploy|ship|version bump)\\b/i.test(t)) return make('RELEASE', 'high');\n if (/\\b(?:upgrade|bump|update\\s+dependenc\\w*|chore|lint(?:ing)?|format(?:ting)?)\\b/i.test(t)) return make('MAINTENANCE', 'high');\n if (/\\b(?:redesign|re-?architect\\w*|migrate\\s+to|architecture)\\b/i.test(t)) return make('ARCHITECTURE', 'high');\n if (/\\b(?:add|implement|create|build|support|introduce|enable|new)\\b/i.test(t)) return make('FEATURE', 'high');\n // An instruction we cannot place is still a request to change something. Never default it to a read-only question.\n if (IMPERATIVE_START.test(t)) return make('FEATURE', 'low');\n return make('QUESTION', 'low');\n}\n\nexport function renderRoutingTable(): string {\n const rows = (Object.values(WORKFLOWS) as WorkflowSpec[]).map((w) => {\n const flags = [w.readOnly ? 'read-only' : 'modifies code', w.trackTask ? 'tracked task' : 'no task file'].join(', ');\n return `| ${w.type} | ${flags} | L${w.contextDepth} | ${w.verification} |`;\n });\n return ['| Type | Nature | Max context | Verification |', '| --- | --- | --- | --- |', ...rows].join('\\n');\n}\n"]}
@@ -4,8 +4,27 @@ export declare const TASK_TYPES: readonly ["QUESTION", "BUG", "FEATURE", "REFACT
4
4
  export type TaskType = (typeof TASK_TYPES)[number];
5
5
  export declare const TASK_STATUSES: readonly ["IDEA", "SPECIFIED", "PLANNED", "IN_PROGRESS", "TESTING", "REVIEW", "BLOCKED", "FAILED", "COMPLETED"];
6
6
  export type TaskStatus = (typeof TASK_STATUSES)[number];
7
- export declare const VERIFY_STATUSES: readonly ["PENDING", "PASS", "FAIL", "SKIPPED"];
7
+ export declare const VERIFY_STATUSES: readonly ["PENDING", "PASS", "FAIL", "SKIPPED", "COULD_NOT_RUN"];
8
8
  export type VerifyStatus = (typeof VERIFY_STATUSES)[number];
9
+ /**
10
+ * One recorded result for one package. `executed` means DevWorkspace ran the contract command itself and saw the exit
11
+ * code; `claimed` is somebody's report (agent or person) that DevWorkspace could not observe.
12
+ */
13
+ export interface Evidence {
14
+ kind: 'executed' | 'claimed';
15
+ at: string;
16
+ /** Package directory relative to the project root; "." for the root. */
17
+ package: string;
18
+ command?: string;
19
+ exitCode?: number | null;
20
+ durationMs?: number;
21
+ /** Fingerprint of the task's changes inside this package when the result was recorded. */
22
+ scope?: string;
23
+ /** A claim the user explicitly vouched for (--reason user-override). */
24
+ userOverride?: boolean;
25
+ /** One-line, scrubbed explanation (failure tail or refusal reason). Never a connection string or credential. */
26
+ note?: string;
27
+ }
9
28
  export interface VerificationItem {
10
29
  check: string;
11
30
  required: boolean;
@@ -13,6 +32,16 @@ export interface VerificationItem {
13
32
  reason?: string;
14
33
  detail?: string;
15
34
  updatedAt?: string;
35
+ /** Latest result per package. Absent on tasks recorded by older versions. */
36
+ evidence?: Evidence[];
37
+ }
38
+ /** Files that were already modified when the task was created, so they are not blamed on the task. */
39
+ export interface TaskScope {
40
+ capturedAt: string;
41
+ /** Git HEAD when the task was created; null outside a repository. */
42
+ head: string | null;
43
+ /** Project-relative path -> content hash of files already modified at creation ("deleted" for removed files). */
44
+ baseline: Record<string, string>;
16
45
  }
17
46
  export interface TestCounts {
18
47
  passed: number;
@@ -41,6 +70,8 @@ export interface Task {
41
70
  after?: TestCounts;
42
71
  };
43
72
  attestations: Attestations;
73
+ /** Present on tasks created by 0.2.2+: enables package-aware, evidence-based completion. */
74
+ scope?: TaskScope;
44
75
  decisions: string[];
45
76
  blockers: string[];
46
77
  constraints: string[];
@@ -72,10 +103,23 @@ export interface GateResult {
72
103
  failures: string[];
73
104
  warnings: string[];
74
105
  }
75
- /** The completion gate. A task is only COMPLETED when every applicable requirement holds. */
76
- export declare function evaluateGate(task: Task): GateResult;
77
- export declare function transitionTask(task: Task, to: TaskStatus, now: string, note?: string): GateResult | null;
78
- export declare function nextRequiredStep(task: Task): string;
106
+ /** What the gate needs to know beyond the task itself: the contract and what the task changed, per package. */
107
+ export interface GateContext {
108
+ contract: CommandEntry[];
109
+ /**
110
+ * Fingerprint of the task's changes inside each package ("." = everything). `null` means the changes could not be
111
+ * determined (not a Git repository, or nothing changed since the task started), so every package counts as affected.
112
+ */
113
+ scope: Record<string, string> | null;
114
+ }
115
+ /**
116
+ * The completion gate. A task is only COMPLETED when every applicable requirement holds.
117
+ * Tasks that carry a `scope` (created by 0.2.2+) are checked per changed package against executed evidence; older
118
+ * tasks keep the original rules.
119
+ */
120
+ export declare function evaluateGate(task: Task, ctx?: GateContext): GateResult;
121
+ export declare function transitionTask(task: Task, to: TaskStatus, now: string, note?: string, ctx?: GateContext): GateResult | null;
122
+ export declare function nextRequiredStep(task: Task, ctx?: GateContext): string;
79
123
  export declare function saveTask(ws: Ws, task: Task): Promise<void>;
80
124
  export declare function loadTask(ws: Ws, idOrPrefix: string): Promise<Task>;
81
125
  export interface TaskListing {
@@ -21,7 +21,7 @@ export const TASK_TYPES = [
21
21
  'MAINTENANCE',
22
22
  ];
23
23
  export const TASK_STATUSES = ['IDEA', 'SPECIFIED', 'PLANNED', 'IN_PROGRESS', 'TESTING', 'REVIEW', 'BLOCKED', 'FAILED', 'COMPLETED'];
24
- export const VERIFY_STATUSES = ['PENDING', 'PASS', 'FAIL', 'SKIPPED'];
24
+ export const VERIFY_STATUSES = ['PENDING', 'PASS', 'FAIL', 'SKIPPED', 'COULD_NOT_RUN'];
25
25
  const TRANSITIONS = {
26
26
  IDEA: ['SPECIFIED', 'PLANNED', 'IN_PROGRESS', 'BLOCKED', 'FAILED'],
27
27
  SPECIFIED: ['PLANNED', 'IN_PROGRESS', 'BLOCKED', 'IDEA'],
@@ -126,7 +126,22 @@ export function validateTask(v, file = 'task') {
126
126
  ic.at_('check').str(io.check, { max: 50 });
127
127
  ic.at_('required').bool(io.required);
128
128
  ic.at_('status').oneOf(io.status, VERIFY_STATUSES);
129
+ if (io.evidence !== undefined) {
130
+ ic.at_('evidence')
131
+ .arr(io.evidence, 100)
132
+ .forEach((e, j) => {
133
+ const ec = c.at_(`verification.${i}.evidence.${j}`);
134
+ const eo = ec.obj(e);
135
+ ec.at_('kind').oneOf(eo.kind, ['executed', 'claimed']);
136
+ ec.at_('package').str(eo.package, { max: 240 });
137
+ });
138
+ }
129
139
  });
140
+ if (o.scope !== undefined) {
141
+ const sc = c.at_('scope').obj(o.scope);
142
+ c.at_('scope.capturedAt').iso(sc.capturedAt);
143
+ c.at_('scope.baseline').obj(sc.baseline);
144
+ }
130
145
  const a = c.at_('attestations').obj(o.attestations);
131
146
  c.at_('attestations.userWorkIntact').bool(a.userWorkIntact);
132
147
  c.at_('attestations.knowledgeReviewed').bool(a.knowledgeReviewed);
@@ -135,23 +150,65 @@ export function validateTask(v, file = 'task') {
135
150
  c.at_('history').arr(o.history, 500);
136
151
  return o;
137
152
  }
138
- /** The completion gate. A task is only COMPLETED when every applicable requirement holds. */
139
- export function evaluateGate(task) {
153
+ const UNKNOWN_SCOPE = 'unknown';
154
+ /** Per-package requirements of one check. Returns false when the contract has no applicable package entry. */
155
+ function packageGate(v, ctx, failures, warnings) {
156
+ const entries = ctx.contract.filter((c) => c.id === v.check && c.available && !c.mutates);
157
+ if (entries.length === 0)
158
+ return false;
159
+ const known = ctx.scope !== null;
160
+ const unknown = !known;
161
+ let applicable = 0;
162
+ for (const entry of entries) {
163
+ const pkg = entry.cwd ?? '.';
164
+ const fp = known ? ctx.scope[pkg] : UNKNOWN_SCOPE;
165
+ if (fp === undefined)
166
+ continue; // this package was not changed, so this check does not apply to it
167
+ applicable++;
168
+ const label = `${v.check} [${pkg}]`;
169
+ const ev = v.evidence?.find((e) => e.package === pkg);
170
+ if (!ev)
171
+ failures.push(`${label}: not run for the current changes`);
172
+ else if (ev.kind === 'claimed' && !ev.userOverride)
173
+ failures.push(`${label}: only claimed, not executed (run: devworkspace task run <id> ${v.check} --package ${pkg})`);
174
+ else if (ev.kind === 'claimed')
175
+ warnings.push(`${label}: accepted on the user's word; DevWorkspace did not observe it run`);
176
+ else if (ev.exitCode !== 0)
177
+ failures.push(`${label}: FAIL (exit ${ev.exitCode ?? 'none'})`);
178
+ else if (!unknown && ev.scope !== fp)
179
+ failures.push(`${label}: passed, but files changed afterwards; run it again`);
180
+ }
181
+ if (known && applicable === 0)
182
+ warnings.push(`${v.check}: no package with this check was changed`);
183
+ return true;
184
+ }
185
+ /**
186
+ * The completion gate. A task is only COMPLETED when every applicable requirement holds.
187
+ * Tasks that carry a `scope` (created by 0.2.2+) are checked per changed package against executed evidence; older
188
+ * tasks keep the original rules.
189
+ */
190
+ export function evaluateGate(task, ctx) {
140
191
  const failures = [];
141
192
  const warnings = [];
193
+ const scoped = !!ctx && !!task.scope;
142
194
  for (const v of task.verification) {
143
195
  if (!v.required)
144
196
  continue;
145
197
  if (v.status === 'FAIL')
146
198
  failures.push(`${v.check}: FAIL`);
147
- else if (v.status === 'PENDING')
148
- failures.push(`${v.check}: not run yet`);
199
+ else if (v.status === 'COULD_NOT_RUN')
200
+ failures.push(`${v.check}: could not run${v.detail ? ` (${v.detail})` : ''}`);
149
201
  else if (v.status === 'SKIPPED') {
150
202
  if (v.reason === 'user-override')
151
203
  warnings.push(`${v.check}: skipped at the user's request; completion is less certain`);
152
204
  else
153
205
  failures.push(`${v.check}: skipped without a user override`);
154
206
  }
207
+ else if (scoped && packageGate(v, ctx, failures, warnings)) {
208
+ // handled per package
209
+ }
210
+ else if (v.status === 'PENDING')
211
+ failures.push(`${v.check}: not run yet`);
155
212
  }
156
213
  const { baseline, after } = task.tests;
157
214
  if (baseline && !after)
@@ -175,7 +232,7 @@ export function evaluateGate(task) {
175
232
  }
176
233
  return { ok: failures.length === 0, failures, warnings };
177
234
  }
178
- export function transitionTask(task, to, now, note) {
235
+ export function transitionTask(task, to, now, note, ctx) {
179
236
  if (task.status === to)
180
237
  return null;
181
238
  if (!TRANSITIONS[task.status].includes(to)) {
@@ -185,7 +242,7 @@ export function transitionTask(task, to, now, note) {
185
242
  }
186
243
  let gate = null;
187
244
  if (to === 'COMPLETED') {
188
- gate = evaluateGate(task);
245
+ gate = evaluateGate(task, ctx);
189
246
  if (!gate.ok) {
190
247
  throw new DevWorkspaceError('GATE', `Task ${task.id} cannot be completed:\n - ${gate.failures.join('\n - ')}`, {
191
248
  hint: 'Resolve the items above (or mark the task BLOCKED/FAILED). A task is never complete while verification is failing.',
@@ -197,7 +254,7 @@ export function transitionTask(task, to, now, note) {
197
254
  task.updatedAt = now;
198
255
  return gate;
199
256
  }
200
- export function nextRequiredStep(task) {
257
+ export function nextRequiredStep(task, ctx) {
201
258
  switch (task.status) {
202
259
  case 'COMPLETED':
203
260
  return 'None. The task is complete.';
@@ -212,10 +269,10 @@ export function nextRequiredStep(task) {
212
269
  case 'PLANNED':
213
270
  return 'Start implementation (baseline verification first for meaningful changes).';
214
271
  default: {
215
- const pending = task.verification.filter((v) => v.required && (v.status === 'PENDING' || v.status === 'FAIL'));
272
+ const pending = task.verification.filter((v) => v.required && (v.status === 'PENDING' || v.status === 'FAIL' || v.status === 'COULD_NOT_RUN'));
216
273
  if (pending.length > 0)
217
- return `Run/fix verification: ${pending.map((p) => `${p.check} (${p.status.toLowerCase()})`).join(', ')}.`;
218
- const g = evaluateGate(task);
274
+ return `Run/fix verification: ${pending.map((p) => `${p.check} (${p.status.toLowerCase().replace(/_/g, ' ')})`).join(', ')}.`;
275
+ const g = evaluateGate(task, ctx);
219
276
  return g.ok ? 'All gates pass. Mark the task COMPLETED.' : `Satisfy the completion gate: ${g.failures[0]}`;
220
277
  }
221
278
  }