opencode-swarm 7.113.1 → 7.113.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 (27) hide show
  1. package/.opencode/skills/engineering-conventions/SKILL.md +5 -0
  2. package/.opencode/skills/gate-attribution/SKILL.md +2 -0
  3. package/dist/background/pending-delegations.d.ts +8 -0
  4. package/dist/background/workspace-snapshot.d.ts +7 -0
  5. package/dist/cli/{curator-nm2c9y1p.js → curator-9s04amtf.js} +4 -3
  6. package/dist/cli/{curator-llm-factory-nmhzc774.js → curator-llm-factory-dwvk7rh0.js} +4 -3
  7. package/dist/cli/{evidence-summary-service-j6fnsfeq.js → evidence-summary-service-w1er1ea2.js} +2 -2
  8. package/dist/cli/{gate-evidence-mk0ss1re.js → gate-evidence-9hdwj6tt.js} +1 -1
  9. package/dist/cli/{guardrail-explain-q1np1xaw.js → guardrail-explain-2k271yh4.js} +5 -4
  10. package/dist/cli/{hive-promoter-z4dezw62.js → hive-promoter-0y4pnft4.js} +4 -3
  11. package/dist/cli/{index-npd9rypp.js → index-54dgk0bv.js} +10 -10
  12. package/dist/cli/{index-d0w40jm3.js → index-afgh75zg.js} +1 -1
  13. package/dist/cli/{workspace-snapshot-w58jr2ga.js → index-dqh3zhhc.js} +53 -10
  14. package/dist/cli/{index-ky8eb4q6.js → index-n7rd2rkj.js} +1 -1
  15. package/dist/cli/{index-rqpm9cwe.js → index-w51xz4dr.js} +5 -4
  16. package/dist/cli/{index-pm992z24.js → index-x5g7et24.js} +10 -7
  17. package/dist/cli/index.js +4 -3
  18. package/dist/cli/{pending-delegations-gc3a2hfx.js → pending-delegations-t047f4a7.js} +7 -0
  19. package/dist/cli/workspace-snapshot-eyf6gd0d.js +22 -0
  20. package/dist/config/skill-mirrors.d.ts +3 -1
  21. package/dist/gate-evidence-classification.d.ts +5 -0
  22. package/dist/gate-evidence.d.ts +14 -3
  23. package/dist/index.js +272 -272
  24. package/dist/memory/schema.d.ts +4 -4
  25. package/dist/tools/phase-complete.d.ts +8 -0
  26. package/dist/tools/sast-scan.d.ts +2 -0
  27. package/package.json +1 -1
@@ -149,6 +149,11 @@ A baseline captured post-edit silently encodes the very bugs the scan is meant
149
149
  to catch as "pre-existing," suppressing them indefinitely. This turns the SAST
150
150
  gate into theater.
151
151
 
152
+ Baseline capture also requires at least one supported, existing file to be
153
+ successfully scanned. Omitted, empty, or entirely unscannable `changed_files`
154
+ returns `capture_baseline requires changed_files to produce a non-empty baseline`
155
+ instead of reporting a successful no-op capture.
156
+
152
157
  ### How to use it
153
158
 
154
159
  1. Identify the files to scan. In a phase, use the union of declared task-scope
@@ -35,6 +35,8 @@ no reviewed rows are parseable, attribution falls back to the single-task rule.
35
35
  4. **Collect + attribute:** Single-task lanes auto-attribute to their taskId; set-dispatch rows auto-attribute per parsed row.
36
36
  5. **Do NOT rely on prose summaries:** A batched dispatch without parseable rows is ambiguous and does not count per-task.
37
37
 
38
+ Gate evidence is persisted independently as `.swarm/evidence/{taskId}.json` for each task. Passing set-dispatch rows cause the hook to write one task-scoped file per task; a single multi-task evidence file cannot satisfy any task.
39
+
38
40
  ## Optimization for trivial tasks
39
41
  For pure ceremony gates (1-line doc fix):
40
42
  ```
@@ -56,6 +56,8 @@ export interface BackgroundDelegationRecord {
56
56
  promptHash?: string;
57
57
  /** Project/root provenance captured at dispatch time. */
58
58
  workspace?: BackgroundWorkspaceSnapshot;
59
+ /** Immutable pre-coder provenance for doc-only gate classification. */
60
+ taskChangeContext?: BackgroundTaskChangeContext;
59
61
  prompt?: BackgroundPromptSnapshot;
60
62
  generation?: number;
61
63
  result?: BackgroundDelegationResult;
@@ -65,9 +67,14 @@ export interface BackgroundWorkspaceSnapshot {
65
67
  directory: string;
66
68
  gitHead: string | null;
67
69
  dirtyHash: string | null;
70
+ changedFiles?: string[] | null;
68
71
  prHeadSha: string | null;
69
72
  scope: string | null;
70
73
  }
74
+ export interface BackgroundTaskChangeContext {
75
+ declaredFiles: string[] | null;
76
+ baseline: BackgroundWorkspaceSnapshot;
77
+ }
71
78
  export interface BackgroundPromptSnapshot {
72
79
  text: string;
73
80
  chars: number;
@@ -114,6 +121,7 @@ export interface RecordPendingInput {
114
121
  mode?: string;
115
122
  promptHash?: string;
116
123
  workspace?: BackgroundWorkspaceSnapshot;
124
+ taskChangeContext?: BackgroundTaskChangeContext;
117
125
  prompt?: BackgroundPromptSnapshot;
118
126
  generation?: number;
119
127
  }
@@ -11,7 +11,14 @@ interface CaptureWorkspaceSnapshotOptions {
11
11
  */
12
12
  resolveCurrentPrHeadSha?: boolean;
13
13
  }
14
+ /** Parse `git status --porcelain=v1 -z`, including both sides of renames. */
15
+ export declare function parsePorcelainPaths(output: string): string[] | null;
14
16
  export declare function captureWorkspaceSnapshot(directory: string, optionsOrScope?: string | null | CaptureWorkspaceSnapshotOptions, prHeadShaArg?: string | null): BackgroundWorkspaceSnapshot;
17
+ /**
18
+ * Conservatively derive final paths changed since a pre-task snapshot.
19
+ * Git or parsing failures return null so gate classification fails closed.
20
+ */
21
+ export declare function changedFilesSinceSnapshot(directory: string, baseline: BackgroundWorkspaceSnapshot | undefined): string[] | null;
15
22
  export declare function workspaceSnapshotMatches(expected: BackgroundWorkspaceSnapshot | undefined, current: BackgroundWorkspaceSnapshot): {
16
23
  ok: true;
17
24
  } | {
@@ -12,12 +12,12 @@ import {
12
12
  runCuratorInit,
13
13
  runCuratorPhase,
14
14
  writeCuratorSummary
15
- } from "./index-npd9rypp.js";
15
+ } from "./index-54dgk0bv.js";
16
16
  import"./index-8f256d7t.js";
17
17
  import"./index-c8s9a3zh.js";
18
18
  import"./index-0gf6de8b.js";
19
19
  import"./index-1ey3dxq7.js";
20
- import"./index-d0w40jm3.js";
20
+ import"./index-afgh75zg.js";
21
21
  import"./index-scww5b77.js";
22
22
  import"./index-9fxs0rm1.js";
23
23
  import"./index-q1jaeynn.js";
@@ -38,7 +38,8 @@ import"./index-jtqkh8jf.js";
38
38
  import"./index-5e4e2hvv.js";
39
39
  import"./index-p0arc26j.js";
40
40
  import"./index-zgwm4ryv.js";
41
- import"./index-pm992z24.js";
41
+ import"./index-dqh3zhhc.js";
42
+ import"./index-x5g7et24.js";
42
43
  import"./index-0dab9w37.js";
43
44
  import"./index-293f68mj.js";
44
45
  import"./index-5mkc2f9z.js";
@@ -1,12 +1,12 @@
1
1
  // @bun
2
2
  import {
3
3
  createCuratorLLMDelegate
4
- } from "./index-npd9rypp.js";
4
+ } from "./index-54dgk0bv.js";
5
5
  import"./index-8f256d7t.js";
6
6
  import"./index-c8s9a3zh.js";
7
7
  import"./index-0gf6de8b.js";
8
8
  import"./index-1ey3dxq7.js";
9
- import"./index-d0w40jm3.js";
9
+ import"./index-afgh75zg.js";
10
10
  import"./index-scww5b77.js";
11
11
  import"./index-9fxs0rm1.js";
12
12
  import"./index-q1jaeynn.js";
@@ -27,7 +27,8 @@ import"./index-jtqkh8jf.js";
27
27
  import"./index-5e4e2hvv.js";
28
28
  import"./index-p0arc26j.js";
29
29
  import"./index-zgwm4ryv.js";
30
- import"./index-pm992z24.js";
30
+ import"./index-dqh3zhhc.js";
31
+ import"./index-x5g7et24.js";
31
32
  import"./index-0dab9w37.js";
32
33
  import"./index-293f68mj.js";
33
34
  import"./index-5mkc2f9z.js";
@@ -6,7 +6,7 @@ import {
6
6
  loadPlanJsonOnly,
7
7
  mergeDurableGateEntriesFromEvidence,
8
8
  readDurableGateEvidence
9
- } from "./index-d0w40jm3.js";
9
+ } from "./index-afgh75zg.js";
10
10
  import"./index-scww5b77.js";
11
11
  import"./index-q1exe2b3.js";
12
12
  import"./index-1rga1vvb.js";
@@ -16,7 +16,7 @@ import"./index-p0arc26j.js";
16
16
  import {
17
17
  log
18
18
  } from "./index-zgwm4ryv.js";
19
- import"./index-pm992z24.js";
19
+ import"./index-x5g7et24.js";
20
20
  import"./index-0dab9w37.js";
21
21
  import"./index-293f68mj.js";
22
22
  import"./index-5mkc2f9z.js";
@@ -9,7 +9,7 @@ import {
9
9
  readTaskEvidenceRaw,
10
10
  recordAgentDispatch,
11
11
  recordGateEvidence
12
- } from "./index-pm992z24.js";
12
+ } from "./index-x5g7et24.js";
13
13
  import"./index-0dab9w37.js";
14
14
  import"./index-293f68mj.js";
15
15
  import"./index-5mkc2f9z.js";
@@ -1,13 +1,13 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-ky8eb4q6.js";
5
- import"./index-npd9rypp.js";
4
+ } from "./index-n7rd2rkj.js";
5
+ import"./index-54dgk0bv.js";
6
6
  import"./index-8f256d7t.js";
7
7
  import"./index-c8s9a3zh.js";
8
8
  import"./index-0gf6de8b.js";
9
9
  import"./index-1ey3dxq7.js";
10
- import"./index-d0w40jm3.js";
10
+ import"./index-afgh75zg.js";
11
11
  import"./index-scww5b77.js";
12
12
  import"./index-9fxs0rm1.js";
13
13
  import"./index-q1jaeynn.js";
@@ -28,7 +28,8 @@ import"./index-jtqkh8jf.js";
28
28
  import"./index-5e4e2hvv.js";
29
29
  import"./index-p0arc26j.js";
30
30
  import"./index-zgwm4ryv.js";
31
- import"./index-pm992z24.js";
31
+ import"./index-dqh3zhhc.js";
32
+ import"./index-x5g7et24.js";
32
33
  import"./index-0dab9w37.js";
33
34
  import"./index-293f68mj.js";
34
35
  import"./index-5mkc2f9z.js";
@@ -5,12 +5,12 @@ import {
5
5
  isHiveEligible,
6
6
  promoteFromSwarm,
7
7
  promoteToHive
8
- } from "./index-npd9rypp.js";
8
+ } from "./index-54dgk0bv.js";
9
9
  import"./index-8f256d7t.js";
10
10
  import"./index-c8s9a3zh.js";
11
11
  import"./index-0gf6de8b.js";
12
12
  import"./index-1ey3dxq7.js";
13
- import"./index-d0w40jm3.js";
13
+ import"./index-afgh75zg.js";
14
14
  import"./index-scww5b77.js";
15
15
  import"./index-9fxs0rm1.js";
16
16
  import"./index-q1jaeynn.js";
@@ -31,7 +31,8 @@ import"./index-jtqkh8jf.js";
31
31
  import"./index-5e4e2hvv.js";
32
32
  import"./index-p0arc26j.js";
33
33
  import"./index-zgwm4ryv.js";
34
- import"./index-pm992z24.js";
34
+ import"./index-dqh3zhhc.js";
35
+ import"./index-x5g7et24.js";
35
36
  import"./index-0dab9w37.js";
36
37
  import"./index-293f68mj.js";
37
38
  import"./index-5mkc2f9z.js";
@@ -68,7 +68,7 @@ import {
68
68
  savePlan,
69
69
  transientBackoff,
70
70
  validateProjectRoot
71
- } from "./index-d0w40jm3.js";
71
+ } from "./index-afgh75zg.js";
72
72
  import {
73
73
  buildOpenSpecProjectionSync,
74
74
  detectSpeckit,
@@ -10218,11 +10218,11 @@ var _internals12 = {
10218
10218
  return KnowledgeConfigSchema2.parse({});
10219
10219
  },
10220
10220
  applyCuratorKnowledgeUpdates: async (directory, recommendations, knowledgeConfig) => {
10221
- const { applyCuratorKnowledgeUpdates: applyCuratorKnowledgeUpdates2 } = await import("./curator-nm2c9y1p.js");
10221
+ const { applyCuratorKnowledgeUpdates: applyCuratorKnowledgeUpdates2 } = await import("./curator-9s04amtf.js");
10222
10222
  return applyCuratorKnowledgeUpdates2(directory, recommendations, knowledgeConfig);
10223
10223
  },
10224
10224
  checkHivePromotions: async (entries, knowledgeConfig) => {
10225
- const { checkHivePromotions } = await import("./hive-promoter-z4dezw62.js");
10225
+ const { checkHivePromotions } = await import("./hive-promoter-0y4pnft4.js");
10226
10226
  return checkHivePromotions(entries, knowledgeConfig);
10227
10227
  },
10228
10228
  applyProposalTriage: async (directory, triage) => {
@@ -17280,8 +17280,8 @@ var _internals23 = {
17280
17280
  loadCuratorDeps: async () => {
17281
17281
  const [{ CuratorConfigSchema }, curator, { createCuratorLLMDelegate: createCuratorLLMDelegate2 }] = await Promise.all([
17282
17282
  import("./schema-qb313fyy.js"),
17283
- import("./curator-nm2c9y1p.js"),
17284
- import("./curator-llm-factory-nmhzc774.js")
17283
+ import("./curator-9s04amtf.js"),
17284
+ import("./curator-llm-factory-dwvk7rh0.js")
17285
17285
  ]);
17286
17286
  return { CuratorConfigSchema, curator, createCuratorLLMDelegate: createCuratorLLMDelegate2 };
17287
17287
  }
@@ -17777,7 +17777,7 @@ import { fileURLToPath } from "url";
17777
17777
  // package.json
17778
17778
  var package_default = {
17779
17779
  name: "opencode-swarm",
17780
- version: "7.113.1",
17780
+ version: "7.113.2",
17781
17781
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
17782
17782
  main: "dist/index.js",
17783
17783
  types: "dist/index.d.ts",
@@ -20050,7 +20050,7 @@ async function handleEvidenceCommand(directory, args) {
20050
20050
  return formatTaskEvidenceMarkdown(evidenceData);
20051
20051
  }
20052
20052
  async function handleEvidenceSummaryCommand(directory) {
20053
- const { buildEvidenceSummary } = await import("./evidence-summary-service-j6fnsfeq.js");
20053
+ const { buildEvidenceSummary } = await import("./evidence-summary-service-w1er1ea2.js");
20054
20054
  const artifact = await buildEvidenceSummary(directory);
20055
20055
  if (!artifact) {
20056
20056
  return "No plan found. Run `/swarm plan` to check plan status.";
@@ -32618,7 +32618,7 @@ function buildDetailedHelp(commandName, entry) {
32618
32618
  async function handleHelpCommand(ctx) {
32619
32619
  const targetCommand = ctx.args.join(" ");
32620
32620
  if (!targetCommand) {
32621
- const { buildHelpText } = await import("./index-rqpm9cwe.js");
32621
+ const { buildHelpText } = await import("./index-w51xz4dr.js");
32622
32622
  return buildHelpText();
32623
32623
  }
32624
32624
  const tokens = targetCommand.split(/\s+/);
@@ -32627,7 +32627,7 @@ async function handleHelpCommand(ctx) {
32627
32627
  return _internals48.buildDetailedHelp(resolved.key, resolved.entry);
32628
32628
  }
32629
32629
  const similar = _internals48.findSimilarCommands(targetCommand);
32630
- const { buildHelpText: fullHelp } = await import("./index-rqpm9cwe.js");
32630
+ const { buildHelpText: fullHelp } = await import("./index-w51xz4dr.js");
32631
32631
  if (similar.length > 0) {
32632
32632
  return `Command '/swarm ${targetCommand}' not found.
32633
32633
 
@@ -32760,7 +32760,7 @@ var COMMAND_REGISTRY = {
32760
32760
  },
32761
32761
  "guardrail explain": {
32762
32762
  handler: async (ctx) => {
32763
- const { handleGuardrailExplain } = await import("./guardrail-explain-q1np1xaw.js");
32763
+ const { handleGuardrailExplain } = await import("./guardrail-explain-2k271yh4.js");
32764
32764
  return handleGuardrailExplain(ctx.directory, ctx.args);
32765
32765
  },
32766
32766
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
@@ -27,7 +27,7 @@ import {
27
27
  readTaskEvidence,
28
28
  readTaskEvidenceRaw,
29
29
  sanitizeTaskId
30
- } from "./index-pm992z24.js";
30
+ } from "./index-x5g7et24.js";
31
31
  import {
32
32
  ZodError,
33
33
  exports_external
@@ -1,6 +1,4 @@
1
1
  // @bun
2
- import"./index-a76rekgs.js";
3
-
4
2
  // src/background/workspace-snapshot.ts
5
3
  import * as child_process from "child_process";
6
4
  import { createHash } from "crypto";
@@ -19,6 +17,30 @@ function runGit(directory, args) {
19
17
  return null;
20
18
  return typeof result.stdout === "string" ? result.stdout.trimEnd() : null;
21
19
  }
20
+ function parseNulPaths(output) {
21
+ return output.split("\x00").filter((entry) => entry.length > 0);
22
+ }
23
+ function parsePorcelainPaths(output) {
24
+ const entries = parseNulPaths(output);
25
+ const paths = [];
26
+ for (let index = 0;index < entries.length; index++) {
27
+ const entry = entries[index];
28
+ if (entry.length < 4 || entry[2] !== " ")
29
+ return null;
30
+ const status = entry.slice(0, 2);
31
+ const changedPath = entry.slice(3);
32
+ if (!changedPath)
33
+ return null;
34
+ paths.push(changedPath);
35
+ if (status.includes("R") || status.includes("C")) {
36
+ const originalPath = entries[++index];
37
+ if (!originalPath)
38
+ return null;
39
+ paths.push(originalPath);
40
+ }
41
+ }
42
+ return [...new Set(paths)];
43
+ }
22
44
  function captureWorkspaceSnapshot(directory, optionsOrScope = null, prHeadShaArg = null) {
23
45
  const scope = typeof optionsOrScope === "object" && optionsOrScope !== null ? optionsOrScope.scope ?? null : optionsOrScope;
24
46
  const prHeadSha = (() => {
@@ -34,16 +56,43 @@ function captureWorkspaceSnapshot(directory, optionsOrScope = null, prHeadShaArg
34
56
  const porcelain = runGit(directory, [
35
57
  "status",
36
58
  "--porcelain=v1",
59
+ "-z",
37
60
  "--untracked-files=all"
38
61
  ]);
62
+ const changedFiles = porcelain === null ? null : parsePorcelainPaths(porcelain);
39
63
  return {
40
64
  directory: path.resolve(directory),
41
65
  gitHead,
42
66
  dirtyHash: porcelain === null ? null : digest(porcelain),
67
+ changedFiles,
43
68
  prHeadSha,
44
69
  scope
45
70
  };
46
71
  }
72
+ function changedFilesSinceSnapshot(directory, baseline) {
73
+ if (!baseline?.gitHead || baseline.changedFiles == null)
74
+ return null;
75
+ if (baseline.changedFiles.length > 0)
76
+ return null;
77
+ const current = captureWorkspaceSnapshot(directory);
78
+ if (!current.gitHead || current.changedFiles == null)
79
+ return null;
80
+ const changed = new Set(current.changedFiles);
81
+ if (baseline.gitHead !== current.gitHead) {
82
+ const committed = runGit(directory, [
83
+ "diff",
84
+ "--name-only",
85
+ "-z",
86
+ baseline.gitHead,
87
+ current.gitHead
88
+ ]);
89
+ if (committed === null)
90
+ return null;
91
+ for (const changedPath of parseNulPaths(committed))
92
+ changed.add(changedPath);
93
+ }
94
+ return [...changed];
95
+ }
47
96
  function workspaceSnapshotMatches(expected, current) {
48
97
  if (!expected)
49
98
  return { ok: true };
@@ -80,11 +129,5 @@ function digest(text) {
80
129
  var _internals = {
81
130
  spawnSync: child_process.spawnSync
82
131
  };
83
- export {
84
- workspaceSnapshotMatches,
85
- digest,
86
- compareWorkspaceSnapshots,
87
- compareWorkspaceSnapshot,
88
- captureWorkspaceSnapshot,
89
- _internals
90
- };
132
+
133
+ export { parsePorcelainPaths, captureWorkspaceSnapshot, changedFilesSinceSnapshot, workspaceSnapshotMatches, compareWorkspaceSnapshot, compareWorkspaceSnapshots, digest, _internals };
@@ -12,7 +12,7 @@ import {
12
12
  detectPosixWrites,
13
13
  detectWindowsWrites,
14
14
  resolveWriteTargets
15
- } from "./index-npd9rypp.js";
15
+ } from "./index-54dgk0bv.js";
16
16
  import {
17
17
  checkFileAuthority,
18
18
  classifyFile,
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-ky8eb4q6.js";
4
+ } from "./index-n7rd2rkj.js";
5
5
  import {
6
6
  handleGuardrailLog
7
7
  } from "./index-r9fjzr3k.js";
@@ -81,7 +81,7 @@ import {
81
81
  handleWriteRetroCommand,
82
82
  normalizeSwarmCommandInput,
83
83
  resolveCommand
84
- } from "./index-npd9rypp.js";
84
+ } from "./index-54dgk0bv.js";
85
85
  import"./index-8f256d7t.js";
86
86
  import"./index-c8s9a3zh.js";
87
87
  import"./index-0gf6de8b.js";
@@ -90,7 +90,7 @@ import {
90
90
  ORCHESTRATOR_NAME,
91
91
  stripKnownSwarmPrefix
92
92
  } from "./index-1ey3dxq7.js";
93
- import"./index-d0w40jm3.js";
93
+ import"./index-afgh75zg.js";
94
94
  import"./index-scww5b77.js";
95
95
  import"./index-9fxs0rm1.js";
96
96
  import"./index-q1jaeynn.js";
@@ -111,7 +111,8 @@ import"./index-jtqkh8jf.js";
111
111
  import"./index-5e4e2hvv.js";
112
112
  import"./index-p0arc26j.js";
113
113
  import"./index-zgwm4ryv.js";
114
- import"./index-pm992z24.js";
114
+ import"./index-dqh3zhhc.js";
115
+ import"./index-x5g7et24.js";
115
116
  import"./index-0dab9w37.js";
116
117
  import"./index-293f68mj.js";
117
118
  import"./index-5mkc2f9z.js";
@@ -71,7 +71,8 @@ var TaskEvidenceSchema = exports_external.object({
71
71
  taskId: exports_external.string(),
72
72
  required_gates: exports_external.array(exports_external.string()).default([]),
73
73
  gates: exports_external.record(exports_external.string(), GateEvidenceSchema),
74
- turbo: exports_external.boolean().optional()
74
+ turbo: exports_external.boolean().optional(),
75
+ test_engineer_exempt: exports_external.boolean().optional()
75
76
  });
76
77
  var DEFAULT_REQUIRED_GATES = ["reviewer", "test_engineer"];
77
78
  function isValidTaskId(taskId) {
@@ -80,10 +81,10 @@ function isValidTaskId(taskId) {
80
81
  function assertValidTaskId(taskId) {
81
82
  assertStrictTaskId(taskId);
82
83
  }
83
- function deriveRequiredGates(agentType) {
84
+ function deriveRequiredGates(agentType, context = {}) {
84
85
  switch (agentType) {
85
86
  case "coder":
86
- return ["reviewer", "test_engineer"];
87
+ return context.testEngineerExempt === true ? ["reviewer"] : ["reviewer", "test_engineer"];
87
88
  case "docs":
88
89
  return ["docs"];
89
90
  case "designer":
@@ -110,8 +111,8 @@ function deriveRequiredGates(agentType) {
110
111
  return ["reviewer", "test_engineer"];
111
112
  }
112
113
  }
113
- function expandRequiredGates(existingGates, newAgentType) {
114
- const newGates = deriveRequiredGates(newAgentType);
114
+ function expandRequiredGates(existingGates, newAgentType, context = {}) {
115
+ const newGates = deriveRequiredGates(newAgentType, context);
115
116
  const combined = [...new Set([...existingGates ?? [], ...newGates])];
116
117
  return combined.sort();
117
118
  }
@@ -166,6 +167,7 @@ async function recordGateEvidence(directory, taskId, gate, sessionId, turbo) {
166
167
  taskId,
167
168
  required_gates: requiredGates,
168
169
  turbo: turbo === true ? true : existing?.turbo,
170
+ test_engineer_exempt: existing?.test_engineer_exempt,
169
171
  gates: {
170
172
  ...existing?.gates ?? {},
171
173
  [gate]: {
@@ -179,7 +181,7 @@ async function recordGateEvidence(directory, taskId, gate, sessionId, turbo) {
179
181
  });
180
182
  telemetry.gatePassed(sessionId, gate, taskId);
181
183
  }
182
- async function recordAgentDispatch(directory, taskId, agentType, turbo) {
184
+ async function recordAgentDispatch(directory, taskId, agentType, turbo, context = {}) {
183
185
  assertValidTaskId(taskId);
184
186
  await withTaskEvidenceLock(directory, taskId, agentType, async () => {
185
187
  const resolvedEvidenceDir = getEvidenceDir(directory);
@@ -191,11 +193,12 @@ async function recordAgentDispatch(directory, taskId, agentType, turbo) {
191
193
  telemetry.gateParseError(taskId, error);
192
194
  throw error;
193
195
  }
194
- const requiredGates = existing ? expandRequiredGates(existing.required_gates, agentType) : deriveRequiredGates(agentType);
196
+ const requiredGates = existing ? expandRequiredGates(existing.required_gates, agentType, context) : deriveRequiredGates(agentType, context);
195
197
  const updated = {
196
198
  taskId,
197
199
  required_gates: requiredGates,
198
200
  turbo: turbo === true ? true : existing?.turbo,
201
+ test_engineer_exempt: agentType === "coder" ? context.testEngineerExempt === true && !requiredGates.includes("test_engineer") : existing?.test_engineer_exempt,
199
202
  gates: existing?.gates ?? {}
200
203
  };
201
204
  await atomicWriteFile(evidencePath, JSON.stringify(updated, null, 2));
package/dist/cli/index.js CHANGED
@@ -7,14 +7,14 @@ import {
7
7
  getPluginLockFilePaths,
8
8
  package_default,
9
9
  resolveCommand
10
- } from "./index-npd9rypp.js";
10
+ } from "./index-54dgk0bv.js";
11
11
  import"./index-8f256d7t.js";
12
12
  import"./index-c8s9a3zh.js";
13
13
  import"./index-0gf6de8b.js";
14
14
  import {
15
15
  DEFAULT_AGENT_CONFIGS
16
16
  } from "./index-1ey3dxq7.js";
17
- import"./index-d0w40jm3.js";
17
+ import"./index-afgh75zg.js";
18
18
  import"./index-scww5b77.js";
19
19
  import"./index-9fxs0rm1.js";
20
20
  import"./index-q1jaeynn.js";
@@ -35,7 +35,8 @@ import"./index-jtqkh8jf.js";
35
35
  import"./index-5e4e2hvv.js";
36
36
  import"./index-p0arc26j.js";
37
37
  import"./index-zgwm4ryv.js";
38
- import"./index-pm992z24.js";
38
+ import"./index-dqh3zhhc.js";
39
+ import"./index-x5g7et24.js";
39
40
  import"./index-0dab9w37.js";
40
41
  import"./index-293f68mj.js";
41
42
  import"./index-5mkc2f9z.js";
@@ -45,9 +45,14 @@ var WorkspaceSchema = exports_external.object({
45
45
  directory: exports_external.string(),
46
46
  gitHead: exports_external.string().nullable(),
47
47
  dirtyHash: exports_external.string().nullable(),
48
+ changedFiles: exports_external.array(exports_external.string()).nullable().optional(),
48
49
  prHeadSha: exports_external.string().nullable(),
49
50
  scope: exports_external.string().nullable()
50
51
  }).strict();
52
+ var TaskChangeContextSchema = exports_external.object({
53
+ declaredFiles: exports_external.array(exports_external.string()).nullable(),
54
+ baseline: WorkspaceSchema
55
+ }).strict();
51
56
  var PromptSchema = exports_external.object({
52
57
  text: exports_external.string(),
53
58
  chars: exports_external.number(),
@@ -82,6 +87,7 @@ var RecordSchema = exports_external.object({
82
87
  mode: exports_external.string().optional(),
83
88
  promptHash: exports_external.string().optional(),
84
89
  workspace: WorkspaceSchema.optional(),
90
+ taskChangeContext: TaskChangeContextSchema.optional(),
85
91
  prompt: PromptSchema.optional(),
86
92
  generation: exports_external.number().optional(),
87
93
  result: ResultSchema.optional(),
@@ -157,6 +163,7 @@ async function recordPendingDelegation(directory, input, options = {}) {
157
163
  ...input.mode ? { mode: input.mode } : {},
158
164
  ...input.promptHash ? { promptHash: input.promptHash } : {},
159
165
  ...input.workspace ? { workspace: input.workspace } : {},
166
+ ...input.taskChangeContext ? { taskChangeContext: input.taskChangeContext } : {},
160
167
  ...input.prompt ? { prompt: input.prompt } : {},
161
168
  ...input.generation !== undefined ? { generation: input.generation } : {}
162
169
  };
@@ -0,0 +1,22 @@
1
+ // @bun
2
+ import {
3
+ _internals,
4
+ captureWorkspaceSnapshot,
5
+ changedFilesSinceSnapshot,
6
+ compareWorkspaceSnapshot,
7
+ compareWorkspaceSnapshots,
8
+ digest,
9
+ parsePorcelainPaths,
10
+ workspaceSnapshotMatches
11
+ } from "./index-dqh3zhhc.js";
12
+ import"./index-a76rekgs.js";
13
+ export {
14
+ workspaceSnapshotMatches,
15
+ parsePorcelainPaths,
16
+ digest,
17
+ compareWorkspaceSnapshots,
18
+ compareWorkspaceSnapshot,
19
+ changedFilesSinceSnapshot,
20
+ captureWorkspaceSnapshot,
21
+ _internals
22
+ };
@@ -68,7 +68,8 @@ export declare const OPENCODE_ONLY_ARCHITECT_MODE_SKILLS: Array<{
68
68
  * `kind`:
69
69
  * - `identical`: `.opencode` and `.claude` SKILL.md must be byte-identical.
70
70
  * `canonical` records which side wins when they drift (fix direction only;
71
- * detection is symmetric).
71
+ * detection is symmetric). `extraIdenticalPaths` narrowly extends the same
72
+ * byte-identity contract to additional runtime mirrors when present.
72
73
  * - `divergent`: both must exist; content intentionally differs per runtime.
73
74
  * - `opencode-only`: `.opencode` exists; no `.claude` mirror expected.
74
75
  */
@@ -76,6 +77,7 @@ export declare const ADDITIONAL_SKILL_MIRROR_CONTRACTS: Array<{
76
77
  slug: string;
77
78
  kind: 'identical' | 'divergent' | 'opencode-only';
78
79
  canonical?: '.claude' | '.opencode';
80
+ extraIdenticalPaths?: string[];
79
81
  reason: string;
80
82
  }>;
81
83
  /**
@@ -0,0 +1,5 @@
1
+ export declare function isExactMarkdownPath(value: unknown): boolean;
2
+ /** Cheap pre-classification used to avoid Git snapshots for ineligible tasks. */
3
+ export declare function isMarkdownOnlyDeclaredScope(value: unknown): value is string[];
4
+ /** Require independent non-empty declared and observed exact-.md proof. */
5
+ export declare function isMarkdownOnlyTaskChange(declaredFiles: unknown, observedFiles: unknown): boolean;
@@ -9,6 +9,11 @@
9
9
  * Evidence files survive session restarts (unlike in-memory state).
10
10
  * Agents never write these files directly — only the hook does.
11
11
  * Gates are append-only: required_gates can only grow, never shrink.
12
+ *
13
+ * Threat boundary: this store provides atomic, path-contained durability and
14
+ * auditability for cooperative same-user agents. It is not tamper-proof
15
+ * authorization against a process with arbitrary same-user workspace access;
16
+ * that requires a protected trust root outside the project workspace.
12
17
  */
13
18
  export interface GateEvidence {
14
19
  sessionId: string;
@@ -20,8 +25,14 @@ export interface TaskEvidence {
20
25
  required_gates: string[];
21
26
  gates: Record<string, GateEvidence>;
22
27
  turbo?: boolean;
28
+ /** Durable proof that the coder dispatch was classified as exact Markdown-only. */
29
+ test_engineer_exempt?: boolean;
23
30
  }
24
31
  export declare const DEFAULT_REQUIRED_GATES: string[];
32
+ export interface GateDerivationContext {
33
+ /** Trusted pre/post workspace classification; false/absent fails closed. */
34
+ testEngineerExempt?: boolean;
35
+ }
25
36
  /**
26
37
  * Canonical task-id validation helper.
27
38
  * Delegates to the shared strict validator (#452 item 2).
@@ -32,12 +43,12 @@ export declare function isValidTaskId(taskId: string): boolean;
32
43
  * Maps the first-dispatched agent type to the initial required_gates array.
33
44
  * Unknown agent types fall back to the safe default ["reviewer", "test_engineer"].
34
45
  */
35
- export declare function deriveRequiredGates(agentType: string): string[];
46
+ export declare function deriveRequiredGates(agentType: string, context?: GateDerivationContext): string[];
36
47
  /**
37
48
  * Returns the union of existingGates and deriveRequiredGates(newAgentType).
38
49
  * Sorted, deduplicated. Gates can only grow, never shrink.
39
50
  */
40
- export declare function expandRequiredGates(existingGates: string[], newAgentType: string): string[];
51
+ export declare function expandRequiredGates(existingGates: string[], newAgentType: string, context?: GateDerivationContext): string[];
41
52
  /**
42
53
  * Creates or updates .swarm/evidence/{taskId}.json with a gate pass entry.
43
54
  * If file doesn't exist: creates with required_gates from deriveRequiredGates(gate).
@@ -50,7 +61,7 @@ export declare function recordGateEvidence(directory: string, taskId: string, ga
50
61
  * Used when non-gate agents are dispatched (coder, explorer, sme, etc.).
51
62
  * Creates evidence file if it doesn't exist yet.
52
63
  */
53
- export declare function recordAgentDispatch(directory: string, taskId: string, agentType: string, turbo?: boolean): Promise<void>;
64
+ export declare function recordAgentDispatch(directory: string, taskId: string, agentType: string, turbo?: boolean, context?: GateDerivationContext): Promise<void>;
54
65
  /**
55
66
  * Returns the TaskEvidence for a task, or null if file missing or parse error.
56
67
  * Never throws.