opencode-swarm 7.135.0 → 7.135.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 (86) hide show
  1. package/.opencode/skills/swarm-pr-feedback/SKILL.md +24 -21
  2. package/.opencode/skills/swarm-pr-review/SKILL.md +55 -42
  3. package/dist/agents/explorer.d.ts +1 -1
  4. package/dist/background/candidate-contract.d.ts +113 -0
  5. package/dist/background/candidate-parser.d.ts +8 -29
  6. package/dist/background/pr-review-trigger-contract.d.ts +197 -0
  7. package/dist/background/workspace-snapshot.d.ts +26 -0
  8. package/dist/cli/{config-doctor-j1x63x87.js → config-doctor-pj3yvxdb.js} +2 -2
  9. package/dist/cli/{core-fvqp2gnn.js → core-4va9z2ng.js} +2 -2
  10. package/dist/cli/{curation-policy-xg9zw0ep.js → curation-policy-b2caf3v3.js} +5 -5
  11. package/dist/cli/{curator-drift-9q6f85bm.js → curator-drift-2ta83hss.js} +2 -2
  12. package/dist/cli/{curator-llm-factory-4mat6cq5.js → curator-llm-factory-9983528d.js} +26 -26
  13. package/dist/cli/{curator-yc06cmnz.js → curator-r1s2mwf2.js} +26 -26
  14. package/dist/cli/{dispatch-gxbeb2ps.js → dispatch-wtk6dxky.js} +2 -2
  15. package/dist/cli/{evidence-summary-service-0cczdmjv.js → evidence-summary-service-feby8ny0.js} +5 -5
  16. package/dist/cli/{explorer-jp0nen03.js → explorer-15gqzxh8.js} +1 -1
  17. package/dist/cli/{gate-evidence-8zrwdj69.js → gate-evidence-84b03mb4.js} +3 -3
  18. package/dist/cli/guardrail-explain-pwxvg5n0.js +57 -0
  19. package/dist/cli/{guardrail-log-7egxrxap.js → guardrail-log-yzpz8b7t.js} +4 -4
  20. package/dist/cli/{hive-promoter-7s3gzajy.js → hive-promoter-586dhndj.js} +26 -26
  21. package/dist/cli/{index-nrvs08pr.js → index-02cqjna3.js} +6 -6
  22. package/dist/cli/{index-grxkbbc5.js → index-2pxk6qg4.js} +1 -1
  23. package/dist/cli/{index-crw4n55v.js → index-340chr1e.js} +1612 -166
  24. package/dist/cli/{index-g0raqavt.js → index-3m7kpnqf.js} +2 -2
  25. package/dist/cli/{index-z085b4ka.js → index-3xbx305x.js} +1 -1
  26. package/dist/cli/{index-b4z7s917.js → index-73dfyfhq.js} +1 -1
  27. package/dist/cli/{index-ens0gt38.js → index-7pnvf04s.js} +8 -8
  28. package/dist/cli/{index-x88w0cp5.js → index-8gw8p7yh.js} +3 -3
  29. package/dist/cli/{index-e8w62fgs.js → index-9c3gf2wd.js} +1 -1
  30. package/dist/cli/{index-vf6e8ame.js → index-b0eva00y.js} +2 -2
  31. package/dist/cli/{index-2ghkk9ve.js → index-bfd4xfpp.js} +3 -3
  32. package/dist/cli/{index-6kmmdtdp.js → index-bs4m85v1.js} +2 -2
  33. package/dist/cli/{index-86wdrskb.js → index-ckdva4rs.js} +5 -5
  34. package/dist/cli/{index-96b78s7p.js → index-cwdy5vw9.js} +28 -28
  35. package/dist/cli/{index-44zgqvqh.js → index-d5psymdk.js} +4 -4
  36. package/dist/cli/{index-vxv732ex.js → index-dg1njnms.js} +7 -4
  37. package/dist/cli/{index-yhba8swx.js → index-dzyjb33e.js} +1 -1
  38. package/dist/cli/{index-xg9repnx.js → index-ey29aap6.js} +1 -1
  39. package/dist/cli/{index-q9mq5tv3.js → index-g3meqrsg.js} +2 -2
  40. package/dist/cli/{index-7an2qn0s.js → index-hdy2nd5h.js} +4 -4
  41. package/dist/cli/{index-4yv2rn8q.js → index-hvrrq3f3.js} +1 -1
  42. package/dist/cli/{index-337r9pkr.js → index-j0kte6gd.js} +1 -1
  43. package/dist/cli/{index-ctvnb76s.js → index-j92ser8n.js} +1 -1
  44. package/dist/cli/{index-997d6p42.js → index-jgkjxpdh.js} +4 -4
  45. package/dist/cli/{index-13xxjfhn.js → index-m0ynxkaa.js} +3 -3
  46. package/dist/cli/{index-a4972t0f.js → index-qqkw794p.js} +3 -3
  47. package/dist/cli/{index-b7p0mp2q.js → index-ryqnn3nv.js} +1 -1
  48. package/dist/cli/{index-ny93srnd.js → index-vg3yx648.js} +1 -1
  49. package/dist/cli/{index-qe3v54nb.js → index-y111zefa.js} +64 -23
  50. package/dist/cli/{index-3nasn371.js → index-y6a7gjtj.js} +126 -42
  51. package/dist/cli/index.js +26 -26
  52. package/dist/cli/{knowledge-escalator-d76a090n.js → knowledge-escalator-w7by1m8m.js} +6 -6
  53. package/dist/cli/{knowledge-events-3mcg1a0e.js → knowledge-events-n8xxsay3.js} +4 -4
  54. package/dist/cli/{knowledge-link-etrfntkd.js → knowledge-link-mm1w967j.js} +3 -3
  55. package/dist/cli/{knowledge-store-xtt4sede.js → knowledge-store-xzmvfnx3.js} +4 -4
  56. package/dist/cli/{knowledge-validator-h1k7ymtd.js → knowledge-validator-5q1amsq6.js} +7 -7
  57. package/dist/cli/{pending-delegations-4z3tkkct.js → pending-delegations-mt5gs1bt.js} +3 -3
  58. package/dist/cli/{pr-subscriptions-29j794kd.js → pr-subscriptions-v5xd3j6d.js} +3 -3
  59. package/dist/cli/{scan-cursor-stztfv7d.js → scan-cursor-1q1zmt4b.js} +5 -5
  60. package/dist/cli/{schema-tbq2srrh.js → schema-8mtsfspm.js} +1 -1
  61. package/dist/cli/{scope-persistence-7wydjzx8.js → scope-persistence-h2fpgxww.js} +2 -2
  62. package/dist/cli/{skill-generator-02q1qdf0.js → skill-generator-54xbjgak.js} +8 -8
  63. package/dist/cli/{workspace-snapshot-9h5za0s6.js → workspace-snapshot-jmyamqnv.js} +6 -1
  64. package/dist/cli/{worktree-collision-ownership-dtwr0xj4.js → worktree-collision-ownership-15hymf1y.js} +5 -5
  65. package/dist/commands/registry.d.ts +1 -1
  66. package/dist/hooks/pr-workflow-gate.d.ts +30 -4
  67. package/dist/hooks/repo-graph-builder.d.ts +18 -0
  68. package/dist/hooks/scope-guard.d.ts +11 -4
  69. package/dist/index.js +395 -391
  70. package/dist/scope/scope-binding.d.ts +68 -0
  71. package/dist/session/snapshot-reader.d.ts +6 -0
  72. package/dist/state.d.ts +118 -0
  73. package/dist/tools/convene-council.d.ts +3 -3
  74. package/dist/tools/dispatch-lanes.d.ts +65 -7
  75. package/dist/tools/repo-graph/builder.d.ts +20 -2
  76. package/dist/tools/repo-graph/incremental.d.ts +18 -1
  77. package/dist/tools/repo-graph/ontology.d.ts +8 -0
  78. package/dist/tools/repo-graph/types.d.ts +62 -1
  79. package/dist/tools/repo-graph.d.ts +1 -1
  80. package/dist/tools/submit-phase-council-verdicts.d.ts +3 -3
  81. package/dist/tools/write-final-council-evidence.d.ts +3 -3
  82. package/dist/tools/write-pr-review-trigger-eval.d.ts +34 -63
  83. package/dist/turbo/lean/lane-scope.d.ts +120 -0
  84. package/dist/turbo/lean/runner.d.ts +66 -4
  85. package/package.json +1 -1
  86. package/dist/cli/guardrail-explain-zmbxwh3s.js +0 -57
@@ -83,6 +83,66 @@ export declare function getAuthorizedScopeBindingByPlanIdentity(input: {
83
83
  taskId: string;
84
84
  activeSessionId: string;
85
85
  }): ScopeBinding | null;
86
+ /**
87
+ * Issue #2002 recurrence guardrail (detection half).
88
+ *
89
+ * The defect class is: *a hook constructed once with the plugin-root
90
+ * `ctx.directory` applies that root to sessions that actually execute in a
91
+ * different root.* Its signature failure is silent — a binding for this exact
92
+ * session exists and is otherwise valid, but `workspaceIdentity` (filter
93
+ * condition #1 in `getAuthorizedScopeBindingByPlanIdentity`) never matches, so
94
+ * the lookup returns null and the caller reports a generic
95
+ * `SCOPE_NOT_DECLARED` that names neither root.
96
+ *
97
+ * This turns that silence into a precise, self-describing diagnostic at the one
98
+ * user-visible failure site (`src/hooks/scope-guard.ts`). It catches the class
99
+ * at ANY call site that resolves a binding with the wrong root — present or
100
+ * future — rather than only at the two gates fixed for this issue.
101
+ *
102
+ * Diagnostic-only: never grants or denies authorization. Callers still fall
103
+ * back to the ordinary `SCOPE_NOT_DECLARED` denial whenever this returns null.
104
+ *
105
+ * The candidate filter mirrors every non-workspace correlation clause
106
+ * `getAuthorizedScopeBindingByPlanIdentity` requires for an otherwise-valid
107
+ * Task-scoped authorization — dispatch correlation (`dispatchCallId` present
108
+ * and equal to `ownerMessageId`/`parentCallId`) plus a genuine parent/child
109
+ * session split — so a stale or malformed "active" leftover that never went
110
+ * through the real claim/derive path cannot be mislabeled as a workspace
111
+ * mismatch just because it happens to share session + task + `activation`.
112
+ * `pr_feedback` bindings are excluded outright: they resolve through an
113
+ * entirely separate authorization path
114
+ * (`getAuthorizedPrFeedbackScopeBinding` / `resolveAuthorizedPrFeedbackScopeBindingFromDisk`)
115
+ * that the caller already attempted before falling into this diagnostic, and
116
+ * a live `pr_feedback` binding otherwise satisfies every correlation clause
117
+ * above.
118
+ *
119
+ * `planId`/`planStructureHash` are deliberately NOT compared here: a
120
+ * plan-drifted binding at the SAME root is already excluded by the
121
+ * `workspaceIdentity` clause below (it would never have reached this
122
+ * diagnostic branch in the first place — the ordinary lookup would have
123
+ * rejected it on plan identity, not returned null for lack of a match at
124
+ * all). Reading the plan at `input.directory` to compare identities would
125
+ * also be unreliable here: `input.directory` is, by construction, the root
126
+ * this diagnostic suspects is wrong, so its `.swarm/plan.json` may not exist
127
+ * or may belong to an unrelated project — silently disabling the guardrail
128
+ * exactly where it matters most.
129
+ *
130
+ * Returns null (no mismatch claim) whenever `input.directory` itself cannot
131
+ * be resolved to a canonical identity (e.g. a deleted lane directory) —
132
+ * otherwise every active binding for the session would trivially satisfy
133
+ * `workspaceIdentity !== null`, misreporting a missing-directory failure as a
134
+ * wrong-root failure.
135
+ *
136
+ * @param input.directory - The root the caller resolved against
137
+ * @param input.activeSessionId - The session attempting the write
138
+ * @param input.taskId - Optional task filter
139
+ * @returns A human-readable mismatch description, or null
140
+ */
141
+ export declare function describeScopeWorkspaceMismatch(input: {
142
+ directory: string;
143
+ activeSessionId: string;
144
+ taskId?: string | null;
145
+ }): string | null;
86
146
  export declare function clearScopeBindings(predicate?: (binding: ScopeBinding) => boolean): ScopeBinding[];
87
147
  /**
88
148
  * Atomically claim the pending scope for the exact Task call whose upstream
@@ -103,6 +163,14 @@ export declare function deriveChildScopeBinding(parent: ScopeBinding, input: {
103
163
  childSessionId: string;
104
164
  parentCallId: string;
105
165
  }): ScopeBinding;
166
+ /**
167
+ * Containment predicate shared by every scope consumer: a candidate path is
168
+ * covered by a scope entry when it is that entry or lives beneath it. Exported
169
+ * so authority-minting callers (e.g. the Lean Turbo lane publisher) narrow a
170
+ * candidate file set against plan authority using the *same* semantics the
171
+ * write gates later enforce, instead of re-implementing containment.
172
+ */
173
+ export declare function scopeContains(scope: readonly string[], candidate: string): boolean;
106
174
  export declare function resolveCoderScopeSources(input: {
107
175
  explicitFiles: readonly string[] | null | undefined;
108
176
  planFiles: readonly string[] | null | undefined;
@@ -7,6 +7,12 @@ import type { SerializedAgentSession, SnapshotData } from './snapshot-writer';
7
7
  /**
8
8
  * Transient session fields that must be reset on rehydration.
9
9
  * Centralised here to keep the reset logic DRY and auditable.
10
+ *
11
+ * `workspaceDirectory` (issue #2002) deliberately does NOT belong in this
12
+ * list: it is never part of `SerializedAgentSession` in the first place (see
13
+ * the TRUST BOUNDARY / DELIBERATELY NOT SNAPSHOTTED comment at its field
14
+ * declaration in `src/state.ts`), so `deserializeAgentSession` never restores
15
+ * it and there is nothing here to reset.
10
16
  */
11
17
  export declare const TRANSIENT_SESSION_FIELDS: ReadonlyArray<{
12
18
  name: string;
package/dist/state.d.ts CHANGED
@@ -226,6 +226,72 @@ export interface AgentSessionState {
226
226
  } | null;
227
227
  /** Declared file scope for current coder task (null = no scope declared) */
228
228
  declaredCoderScope: string[] | null;
229
+ /**
230
+ * Issue #2002: the root this session actually EXECUTES in, when it differs
231
+ * from the plugin-root `ctx.directory`.
232
+ *
233
+ * Worktree-isolated coder children are created with
234
+ * `session.create({ query: { directory: <lane> } })`, and their scope binding
235
+ * is derived and published against that lane. The write gates are constructed
236
+ * once at plugin init with `ctx.directory` and would otherwise resolve every
237
+ * session's binding, containment, and `.swarm/` reads against the project
238
+ * root — which can never match a lane-rooted binding.
239
+ *
240
+ * TRUST BOUNDARY: written ONLY by `recordSessionWorkspaceRoot`, whose single
241
+ * closed allowlist of production callers each pass `provisionWorktree`'s own
242
+ * output. `ensureAgentSession`'s `directory` argument (which can carry an
243
+ * agent-supplied `working_directory`, e.g. via `declare_scope`) must NEVER
244
+ * write this field.
245
+ *
246
+ * DELIBERATELY NOT SNAPSHOTTED (issue #2002 follow-up). `serializeAgentSession`
247
+ * (`src/session/snapshot-writer.ts`) intentionally omits this field, and it
248
+ * MUST stay omitted. The reason is trust, not oversight: `.swarm/session/state.json`
249
+ * lives under the project-root `.swarm/` directory, which the architect agent
250
+ * can write, so a restored string in that file is not a trusted resolution
251
+ * root — a tampered or stale snapshot could install an arbitrary root, or
252
+ * re-point a session at a *different* lane it never owned (cross-lane
253
+ * containment re-rooting). Persisting it would only be safe if rehydrate could
254
+ * revalidate the restored value against a durable, plugin-owned record of
255
+ * provisioned lanes, and no such record exists:
256
+ * - `worktree-provisioning-owner.ts`'s durable marker
257
+ * (`recordWorktreeProvisioningOwner`) stores no `worktreePath` at all, and
258
+ * its `worktreeSessionId` field is always the *parent* session id
259
+ * (`worktree-isolation.ts` passes `args.parentSessionID`, confirmed by
260
+ * every production and test call site) — it cannot answer "does child
261
+ * session X own lane path Y".
262
+ * - `background-delegations.jsonl` (`src/background/pending-delegations.ts`)
263
+ * does carry a `worktree.worktreePath` keyed by the child session id, but
264
+ * it is written only inside the `isBackgroundTrue(...)` branch of
265
+ * `delegation-gate.ts` (~line 3157) — i.e. only for `background: true` Task
266
+ * dispatches. It is never written for the ordinary synchronous worktree
267
+ * dispatch in `worktree-isolation.ts`, nor for the Lean Turbo dispatch in
268
+ * `src/turbo/lean/lane-scope.ts`, which are the two actual
269
+ * `recordSessionWorkspaceRoot` callers. It does not cover the field it
270
+ * would need to validate.
271
+ * - Re-deriving the expected path from `provisionWorktree`'s own naming
272
+ * scheme (`resolveWorktreeBaseDir(directory, worktreeDir)/parentSessionId/taskId`,
273
+ * or the `os.tmpdir()/swwt/...` Windows-shortened form) does not close the
274
+ * gap either: it requires the child's *parent* session id, which is only
275
+ * available from `delegationChains` in the very same untrusted snapshot —
276
+ * validating one untrusted field with another untrusted field is circular,
277
+ * not a validation. It would also require spawning `git worktree list` per
278
+ * restored session inside `loadSnapshot`, which runs on the plugin-init
279
+ * path under a bounded timeout (AGENTS.md invariant 1 / repro-704).
280
+ * Net effect: a plugin restart mid-lane loses the recorded root, and
281
+ * `resolveSessionWorkspaceDirectory` falls back to the plugin-root directory —
282
+ * fail-closed, byte-identical to pre-#2002 behaviour, never a widened
283
+ * authority (see its own doc comment). The lane coder is blocked with
284
+ * `SCOPE_NOT_DECLARED` (`src/hooks/scope-guard.ts`,
285
+ * `src/hooks/guardrails/tool-before.ts`) until a human/architect re-dispatches
286
+ * it, exactly like the pre-fix bug — not worse. `rehydrateState` (which would
287
+ * apply a restored value) only runs from `loadSnapshot`, itself only called at
288
+ * plugin init (`src/index.ts`), so this window is a real process restart, not
289
+ * something that can strip a live in-session lane coder of its root.
290
+ * Do NOT add this field to `TRANSIENT_SESSION_FIELDS`
291
+ * (`src/session/snapshot-reader.ts`) either — it is never restored in the
292
+ * first place, so there is nothing to reset.
293
+ */
294
+ workspaceDirectory?: string;
229
295
  /** Last scope violation message (null = no violation) */
230
296
  lastScopeViolation: string | null;
231
297
  /** Flag for one-shot scope violation warning injection in messagesTransform */
@@ -720,6 +786,58 @@ export declare function getAgentSession(sessionId: string): AgentSessionState |
720
786
  * @returns The AgentSessionState
721
787
  */
722
788
  export declare function ensureAgentSession(sessionId: string, agentName?: string, directory?: string): AgentSessionState;
789
+ /**
790
+ * Issue #2002 — record the root a session actually EXECUTES in, when it
791
+ * differs from the plugin-root `ctx.directory`.
792
+ *
793
+ * TRUST BOUNDARY (security-critical): this function performs NO path
794
+ * validation. `laneRoot` is trusted purely because of WHO calls it, not
795
+ * because of its shape — `provisionWorktree` can legitimately relocate a lane
796
+ * outside the swarm worktree base (e.g. a `os.tmpdir()`-shortened path on
797
+ * Windows when the path-budget check trips), so a shape predicate would
798
+ * refuse a legitimately provisioned lane and silently reintroduce the bug
799
+ * this closes.
800
+ *
801
+ * The only thing standing between this setter and a privilege-escalation bug
802
+ * is a closed allowlist of production callers, each of which passes
803
+ * `provisionWorktree`'s own return value — never a tool argument, never
804
+ * `ensureAgentSession`'s `directory` parameter (which CAN carry an
805
+ * agent-supplied `working_directory`, e.g. via `declare_scope`). That
806
+ * allowlist is mechanically enforced by
807
+ * `tests/unit/state/session-workspace-root-trusted-callers.test.ts`. Adding a
808
+ * new call site is a privilege-escalation review, not a formality: the
809
+ * reviewer must confirm the new caller passes `provisionWorktree` output
810
+ * before adding it to that test's allowlist.
811
+ *
812
+ * A blank or whitespace-only `laneRoot` is ignored (treated as unset) rather
813
+ * than being recorded and later resolving to `path.resolve('')` (cwd).
814
+ *
815
+ * ORDERING IS PART OF THE CONTRACT. This never creates the session — callers
816
+ * MUST register it with its real agent name (`ensureAgentSession(sessionId,
817
+ * 'coder', laneRoot)`) FIRST. Calling this on an unregistered session is a
818
+ * deliberate no-op, so resolution falls back to the plugin root and the write
819
+ * is blocked. Creating the session here instead would register
820
+ * `swarmState.activeAgent` as 'unknown', which fails OPEN rather than closed —
821
+ * see the inline comment in the body for the full chain.
822
+ *
823
+ * @param sessionId - The session identifier
824
+ * @param laneRoot - `provisionWorktree`'s own output; never agent-supplied
825
+ */
826
+ export declare function recordSessionWorkspaceRoot(sessionId: string, laneRoot: string): void;
827
+ /**
828
+ * Issue #2002 — resolve the root a write gate should evaluate path
829
+ * containment and scope-binding lookups against for a given session.
830
+ *
831
+ * Fail-closed: returns `fallbackDirectory` (the plugin-root `ctx.directory`)
832
+ * whenever no root was recorded for this session, or the recorded value is
833
+ * blank/unset — i.e. byte-identical to pre-#2002 behaviour for every session
834
+ * that was never lane-rooted.
835
+ *
836
+ * @param sessionId - The session identifier
837
+ * @param fallbackDirectory - The plugin-root directory to fall back to
838
+ * @returns the session's recorded workspace root, or `fallbackDirectory`
839
+ */
840
+ export declare function resolveSessionWorkspaceDirectory(sessionId: string, fallbackDirectory: string): string;
723
841
  /**
724
842
  * Update only the agent event timestamp (for stale detection).
725
843
  * Does NOT change agent name or reset guardrail state.
@@ -36,10 +36,10 @@ export declare const ArgsSchema: z.ZodObject<{
36
36
  confidence: z.ZodNumber;
37
37
  findings: z.ZodArray<z.ZodObject<{
38
38
  severity: z.ZodEnum<{
39
- CRITICAL: "CRITICAL";
40
- HIGH: "HIGH";
41
- MEDIUM: "MEDIUM";
42
39
  LOW: "LOW";
40
+ MEDIUM: "MEDIUM";
41
+ HIGH: "HIGH";
42
+ CRITICAL: "CRITICAL";
43
43
  }>;
44
44
  category: z.ZodString;
45
45
  location: z.ZodString;
@@ -48,11 +48,39 @@ declare const DispatchLanesAsyncArgsSchema: z.ZodObject<{
48
48
  base_sha: z.ZodOptional<z.ZodString>;
49
49
  base_ref: z.ZodOptional<z.ZodString>;
50
50
  scope: z.ZodOptional<z.ZodString>;
51
- trigger_evaluation: z.ZodOptional<z.ZodArray<z.ZodObject<{
52
- trigger_id: z.ZodString;
51
+ trigger_evaluation: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
52
+ trigger_id: z.ZodEnum<{
53
+ "auth-identity-secrets": "auth-identity-secrets";
54
+ "untrusted-input-boundaries": "untrusted-input-boundaries";
55
+ "subprocess-platform": "subprocess-platform";
56
+ "concurrency-state": "concurrency-state";
57
+ "dependencies-build-release": "dependencies-build-release";
58
+ "api-schema-migrations": "api-schema-migrations";
59
+ "test-infrastructure": "test-infrastructure";
60
+ "ui-accessibility-i18n": "ui-accessibility-i18n";
61
+ "privacy-observability": "privacy-observability";
62
+ "generated-provenance": "generated-provenance";
63
+ "unclassified-risk": "unclassified-risk";
64
+ }>;
53
65
  result: z.ZodLiteral<"MATCHED">;
54
66
  evidence: z.ZodString;
55
- }, z.core.$strict>>>;
67
+ }, z.core.$strict>, z.ZodObject<{
68
+ trigger_id: z.ZodEnum<{
69
+ "auth-identity-secrets": "auth-identity-secrets";
70
+ "untrusted-input-boundaries": "untrusted-input-boundaries";
71
+ "subprocess-platform": "subprocess-platform";
72
+ "concurrency-state": "concurrency-state";
73
+ "dependencies-build-release": "dependencies-build-release";
74
+ "api-schema-migrations": "api-schema-migrations";
75
+ "test-infrastructure": "test-infrastructure";
76
+ "ui-accessibility-i18n": "ui-accessibility-i18n";
77
+ "privacy-observability": "privacy-observability";
78
+ "generated-provenance": "generated-provenance";
79
+ "unclassified-risk": "unclassified-risk";
80
+ }>;
81
+ result: z.ZodLiteral<"NOT_TRIGGERED">;
82
+ evidence: z.ZodString;
83
+ }, z.core.$strict>], "result">>>;
56
84
  feedback_inventory: z.ZodOptional<z.ZodArray<z.ZodString>>;
57
85
  }, z.core.$strip>;
58
86
  declare const CollectLaneResultsArgsSchema: z.ZodObject<{
@@ -297,11 +325,39 @@ export declare const _test_exports: {
297
325
  base_sha: z.ZodOptional<z.ZodString>;
298
326
  base_ref: z.ZodOptional<z.ZodString>;
299
327
  scope: z.ZodOptional<z.ZodString>;
300
- trigger_evaluation: z.ZodOptional<z.ZodArray<z.ZodObject<{
301
- trigger_id: z.ZodString;
328
+ trigger_evaluation: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
329
+ trigger_id: z.ZodEnum<{
330
+ "auth-identity-secrets": "auth-identity-secrets";
331
+ "untrusted-input-boundaries": "untrusted-input-boundaries";
332
+ "subprocess-platform": "subprocess-platform";
333
+ "concurrency-state": "concurrency-state";
334
+ "dependencies-build-release": "dependencies-build-release";
335
+ "api-schema-migrations": "api-schema-migrations";
336
+ "test-infrastructure": "test-infrastructure";
337
+ "ui-accessibility-i18n": "ui-accessibility-i18n";
338
+ "privacy-observability": "privacy-observability";
339
+ "generated-provenance": "generated-provenance";
340
+ "unclassified-risk": "unclassified-risk";
341
+ }>;
302
342
  result: z.ZodLiteral<"MATCHED">;
303
343
  evidence: z.ZodString;
304
- }, z.core.$strict>>>;
344
+ }, z.core.$strict>, z.ZodObject<{
345
+ trigger_id: z.ZodEnum<{
346
+ "auth-identity-secrets": "auth-identity-secrets";
347
+ "untrusted-input-boundaries": "untrusted-input-boundaries";
348
+ "subprocess-platform": "subprocess-platform";
349
+ "concurrency-state": "concurrency-state";
350
+ "dependencies-build-release": "dependencies-build-release";
351
+ "api-schema-migrations": "api-schema-migrations";
352
+ "test-infrastructure": "test-infrastructure";
353
+ "ui-accessibility-i18n": "ui-accessibility-i18n";
354
+ "privacy-observability": "privacy-observability";
355
+ "generated-provenance": "generated-provenance";
356
+ "unclassified-risk": "unclassified-risk";
357
+ }>;
358
+ result: z.ZodLiteral<"NOT_TRIGGERED">;
359
+ evidence: z.ZodString;
360
+ }, z.core.$strict>], "result">>>;
305
361
  feedback_inventory: z.ZodOptional<z.ZodArray<z.ZodString>>;
306
362
  }, z.core.$strip>;
307
363
  DEFAULT_TIMEOUT_MS: number;
@@ -363,7 +419,9 @@ type ApplyCommonPromptResult = {
363
419
  * prompts when it is. Callers may treat the returned array as their own.
364
420
  */
365
421
  declare function applyCommonPrompt(lanes: DispatchLaneSpec[], commonPrompt: string | undefined): ApplyCommonPromptResult;
366
- declare function applyExplorerFormatSuffix(lanes: DispatchLaneSpec[]): DispatchLaneSpec[];
422
+ declare function applyExplorerFormatSuffix(lanes: DispatchLaneSpec[], options?: {
423
+ failClosed?: boolean;
424
+ }): ApplyCommonPromptResult;
367
425
  declare function applyPrWorkflowPromptContract(lanes: DispatchLaneSpec[], options: {
368
426
  mode?: string;
369
427
  prHeadSha?: string;
@@ -34,6 +34,24 @@ export declare const _internals: {
34
34
  computeUsedSymbols: typeof computeUsedSymbols;
35
35
  extractFileSymbols: typeof extractFileSymbols;
36
36
  };
37
+ /**
38
+ * Whether `p` is a scannable source file — i.e. one of the extensions the
39
+ * walker turns into a graph node. This is the SINGLE source of truth for "is
40
+ * this a graph-node-able file" (issue #1985, defect A2): the write hook and
41
+ * the incremental updater both route through it so the allowlist can never
42
+ * drift from the builder's `LANGUAGE_REGISTRY`-derived set.
43
+ */
44
+ export declare function isScannableSourcePath(p: string): boolean;
45
+ /**
46
+ * Whether an edge's resolved target is an asset (a real file that never
47
+ * becomes a graph node, e.g. JSON/CSS) rather than a node. Uses the explicit
48
+ * `targetKind` tag on schema >= 1.3.0 graphs and falls back to an extension
49
+ * check on older graphs whose edges are untagged (issue #1985, defect A1).
50
+ * Asset edges only require their source node to exist during incremental
51
+ * validation and are excluded from in-degree ranking / importer / dependent
52
+ * queries.
53
+ */
54
+ export declare function isAssetEdge(edge: GraphEdge): boolean;
37
55
  /**
38
56
  * Add or update a node in the graph.
39
57
  * @param graph - The graph to modify
@@ -156,7 +174,7 @@ export interface AsyncScanResult {
156
174
  * @param maxFileSize - Maximum file size in bytes
157
175
  * @returns ScanResult with node and edges
158
176
  */
159
- export declare function scanFile(filePath: string, absoluteRoot: string, maxFileSize: number): ScanResult;
177
+ export declare function scanFile(filePath: string, absoluteRoot: string, maxFileSize: number, hasManifest?: (relDir: string) => boolean): ScanResult;
160
178
  /**
161
179
  * Async variant of scanFile that uses tree-sitter symbol extraction
162
180
  * (extractFileSymbols) to populate exportRanges and symbolEdges.
@@ -173,7 +191,7 @@ export declare function scanFile(filePath: string, absoluteRoot: string, maxFile
173
191
  * @param maxFileSize - Maximum file size in bytes
174
192
  * @returns AsyncScanResult with node, edges, and symbolEdges
175
193
  */
176
- export declare function scanFileAsync(filePath: string, absoluteRoot: string, maxFileSize: number): Promise<AsyncScanResult>;
194
+ export declare function scanFileAsync(filePath: string, absoluteRoot: string, maxFileSize: number, hasManifest?: (relDir: string) => boolean): Promise<AsyncScanResult>;
177
195
  /**
178
196
  * Build a complete dependency graph for a workspace by scanning all source files.
179
197
  *
@@ -5,8 +5,25 @@
5
5
  * their nodes and edges in the existing graph, and saves the result. It
6
6
  * includes an optimistic concurrency check (mtime comparison) so that
7
7
  * concurrent sessions do not overwrite each other's updates — when a race
8
- * is detected the function falls back to a full rebuild.
8
+ * is detected the function reloads the freshest on-disk graph and replays
9
+ * the update once before falling back to a full rebuild.
10
+ *
11
+ * Asset edges (issue #1985, defect A1): an edge whose target is a real file
12
+ * that never becomes a graph node (e.g. `import data from './data.json'`) is
13
+ * tagged `targetKind: 'asset'`. Such edges only require their SOURCE node to
14
+ * exist during validation, so a single asset import no longer forces a full
15
+ * rebuild on every incremental update.
16
+ */
17
+ import { type Stats } from 'node:fs';
18
+ /**
19
+ * DI seam for the optimistic-concurrency stat check (defect A4). Tests override
20
+ * `stat` to inject deterministic mtime shifts between load and the pre-save
21
+ * re-check, exercising the reload-and-replay branch without races. Defaults to
22
+ * the real `fsPromises.stat`. Restore in afterEach.
9
23
  */
24
+ export declare const _internals: {
25
+ stat: (path: string) => Promise<Stats>;
26
+ };
10
27
  import type { RepoGraph } from './types';
11
28
  /**
12
29
  * Incrementally update the graph for a set of changed files.
@@ -6,5 +6,13 @@ export interface ExtractFileOntologyInput {
6
6
  language: string;
7
7
  exports: string[];
8
8
  imports: string[];
9
+ /**
10
+ * Optional callback returning true when a workspace-relative directory
11
+ * contains a package manifest (`package.json`, `Cargo.toml`,
12
+ * `pyproject.toml`, `go.mod`). When provided, the package-boundary rule
13
+ * becomes manifest-driven; otherwise it falls back to the static segment
14
+ * rules (issue #1985, defect A8). The extractor stays pure — no fs I/O.
15
+ */
16
+ hasManifest?: (relDir: string) => boolean;
9
17
  }
10
18
  export declare function extractFileOntology(input: ExtractFileOntologyInput): FileOntology;
@@ -23,11 +23,20 @@ export declare const REPO_GRAPH_FILENAME = "repo-graph.json";
23
23
  * still load without corruption. New queries may use these fields to provide
24
24
  * more precise context-packing and symbol-level navigation.
25
25
  *
26
+ * 1.3.0 adds the optional per-edge `targetKind` field (`'node' | 'asset'`)
27
+ * that distinguishes edges whose resolved target is a scannable source file
28
+ * (a graph node) from edges whose target is an asset (JSON/CSS/etc. — a real
29
+ * file that never becomes a node). The field is optional, so 1.0.0–1.2.0
30
+ * graphs still load; `storage.ts` only checks that a version string is
31
+ * present, and feature gating is per-query via {@link isSchemaVersionAtLeast}.
32
+ * For pre-1.3.0 graphs the loader/queries fall back to an extension check
33
+ * (`isScannableSourcePath`) to classify an untagged edge's target kind.
34
+ *
26
35
  * Diagnostics are additive and optional on all schema versions. Old graphs
27
36
  * without diagnostics remain readable; graph-health queries surface empty
28
37
  * diagnostics with an explicit rebuild note.
29
38
  */
30
- export declare const GRAPH_SCHEMA_VERSION = "1.2.0";
39
+ export declare const GRAPH_SCHEMA_VERSION = "1.3.0";
31
40
  /**
32
41
  * Compare dotted numeric version strings (e.g. '1.1.0' >= '1.1.0').
33
42
  * Missing/non-numeric segments are treated as 0. Returns true when `version`
@@ -150,6 +159,16 @@ export interface GraphEdge {
150
159
  * imports, where individual symbol usage is not statically resolvable.
151
160
  */
152
161
  usedSymbols?: string[];
162
+ /**
163
+ * Whether the resolved target is a graph node or an asset. `'node'` targets
164
+ * are scannable source files that become graph nodes; `'asset'` targets are
165
+ * real files (JSON/CSS/etc.) that never become nodes (schema >= 1.3.0).
166
+ * Absent on older graphs — callers fall back to `isScannableSourcePath` on
167
+ * the target path. Asset edges only require their source node to exist
168
+ * during incremental validation, and are excluded from in-degree ranking /
169
+ * importer / dependent queries.
170
+ */
171
+ targetKind?: 'node' | 'asset';
153
172
  }
154
173
  export interface FileReference {
155
174
  file: string;
@@ -269,6 +288,20 @@ export interface RepoGraphDiagnostics {
269
288
  binaryFiles?: string[];
270
289
  unreadableFiles?: string[];
271
290
  lowConfidenceEdgeCount?: number;
291
+ /**
292
+ * True when the last workspace walk was truncated by the file cap or wall-
293
+ * clock budget (issue #1985, defect A7). Additive/optional on all schema
294
+ * versions — old graphs without it read as `false`.
295
+ */
296
+ walkTruncated?: boolean;
297
+ /** Why the walk truncated: hit the wall-clock `'budget'` or the `'cap'`. */
298
+ walkTruncationReason?: 'budget' | 'cap';
299
+ /**
300
+ * Count of incremental-update passes that fell back to a full rebuild
301
+ * (issue #1985, defect A1). Accumulated across incremental runs; reset on
302
+ * a fresh full build.
303
+ */
304
+ incrementalFallbacks?: number;
272
305
  }
273
306
  export interface GraphHealthResult {
274
307
  schemaVersion: string | null;
@@ -281,6 +314,9 @@ export interface GraphHealthResult {
281
314
  binaryFiles: string[];
282
315
  unreadableFiles: string[];
283
316
  lowConfidenceEdgeCount: number;
317
+ walkTruncated: boolean;
318
+ walkTruncationReason: 'budget' | 'cap' | null;
319
+ incrementalFallbacks: number;
284
320
  notes: string[];
285
321
  }
286
322
  export interface BlastRadiusResult {
@@ -359,6 +395,31 @@ export interface BuildWorkspaceGraphOptions {
359
395
  * This ensures the same file produces the same key on Windows, macOS, and Linux.
360
396
  */
361
397
  export declare function normalizeGraphPath(filePath: string): string;
398
+ /**
399
+ * Infer the package boundary for a module name using the generic rule shared
400
+ * by ontology extraction and the query-side no-ontology fallback (issue #1985,
401
+ * defect A8). Pure / dependency-free / no fs I/O so it can live in this
402
+ * foundation module and stay callable from both `ontology.ts` and `query.ts`.
403
+ *
404
+ * Rules (in order):
405
+ * - empty → `'.'`
406
+ * - segment 0 ∈ {packages, crates, apps, libs, services} with ≥2 segments → `<seg0>/<seg1>`
407
+ * - segment 0 === `src` with ≥2 segments → `src/<seg1>`
408
+ * - segment 0 === `tests` with ≥2 segments → `tests/<seg1>`
409
+ * - when `hasManifest` is provided and either `<seg0>` or `<seg0>/<seg1>` is a
410
+ * manifest-bearing directory (with ≥2 segments) → `<seg0>/<seg1>`
411
+ * - otherwise → segment 0 (or `'.'` when there are no segments)
412
+ *
413
+ * The previous `src/tools/repo-graph` special case is intentionally removed:
414
+ * user repos should not inherit this project's internal layout.
415
+ *
416
+ * @param moduleName - Workspace-relative module name (forward slashes).
417
+ * @param hasManifest - Optional callback returning true when a workspace-
418
+ * relative directory contains a package manifest (`package.json`,
419
+ * `Cargo.toml`, `pyproject.toml`, `go.mod`). When omitted, only the static
420
+ * segment rules apply.
421
+ */
422
+ export declare function inferPackageBoundary(moduleName: string, hasManifest?: (relDir: string) => boolean): string;
362
423
  /**
363
424
  * Create an empty graph for a workspace.
364
425
  * @param workspaceRoot - The workspace root directory
@@ -15,7 +15,7 @@
15
15
  * All existing imports of this module continue to work unchanged.
16
16
  */
17
17
  export type { ScanResult } from './repo-graph/builder';
18
- export { addEdge, buildWorkspaceGraph, buildWorkspaceGraphAsync, resolveModuleSpecifier, upsertNode, } from './repo-graph/builder';
18
+ export { addEdge, buildWorkspaceGraph, buildWorkspaceGraphAsync, isScannableSourcePath, resolveModuleSpecifier, upsertNode, } from './repo-graph/builder';
19
19
  export { clearCache, getCachedGraph, getCachedMtime, isDirty, markDirty, setCachedGraph, } from './repo-graph/cache';
20
20
  export { updateGraphForFiles } from './repo-graph/incremental';
21
21
  export type { ExtractFileOntologyInput } from './repo-graph/ontology';
@@ -22,10 +22,10 @@ export declare const ArgsSchema: z.ZodObject<{
22
22
  confidence: z.ZodNumber;
23
23
  findings: z.ZodArray<z.ZodObject<{
24
24
  severity: z.ZodEnum<{
25
- CRITICAL: "CRITICAL";
26
- HIGH: "HIGH";
27
- MEDIUM: "MEDIUM";
28
25
  LOW: "LOW";
26
+ MEDIUM: "MEDIUM";
27
+ HIGH: "HIGH";
28
+ CRITICAL: "CRITICAL";
29
29
  }>;
30
30
  category: z.ZodString;
31
31
  location: z.ZodString;
@@ -28,10 +28,10 @@ export declare const ArgsSchema: z.ZodObject<{
28
28
  confidence: z.ZodNumber;
29
29
  findings: z.ZodArray<z.ZodObject<{
30
30
  severity: z.ZodEnum<{
31
- CRITICAL: "CRITICAL";
32
- HIGH: "HIGH";
33
- MEDIUM: "MEDIUM";
34
31
  LOW: "LOW";
32
+ MEDIUM: "MEDIUM";
33
+ HIGH: "HIGH";
34
+ CRITICAL: "CRITICAL";
35
35
  }>;
36
36
  category: z.ZodString;
37
37
  location: z.ZodString;