opencode-swarm 7.108.0 → 7.109.1

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 (35) hide show
  1. package/.opencode/skills/clarify-spec/SKILL.md +15 -9
  2. package/.opencode/skills/critic-gate/SKILL.md +12 -4
  3. package/.opencode/skills/execute/SKILL.md +1 -0
  4. package/.opencode/skills/phase-wrap/SKILL.md +2 -2
  5. package/.opencode/skills/plan/SKILL.md +23 -11
  6. package/.opencode/skills/specify/SKILL.md +27 -9
  7. package/dist/cli/{core-vev13ej2.js → core-mbd2g302.js} +5 -1
  8. package/dist/cli/{curator-drift-fdfgj9qf.js → curator-drift-169wgaxn.js} +1 -1
  9. package/dist/cli/{curator-llm-factory-n70bxf40.js → curator-llm-factory-gtfhwnbr.js} +4 -4
  10. package/dist/cli/{curator-js92gc8a.js → curator-q4febs18.js} +4 -4
  11. package/dist/cli/{evidence-summary-service-bjhr75v0.js → evidence-summary-service-8tasw2tt.js} +2 -2
  12. package/dist/cli/{guardrail-explain-sn2a4pb7.js → guardrail-explain-r2d8ftkv.js} +5 -5
  13. package/dist/cli/{hive-promoter-3hh8ph4a.js → hive-promoter-mfd5p9f5.js} +4 -4
  14. package/dist/cli/{index-x06d2qqm.js → index-1capawwy.js} +5 -5
  15. package/dist/cli/{index-f1qyj61w.js → index-3yk9196e.js} +40 -3
  16. package/dist/cli/{index-48mc4d48.js → index-j2v3w1ds.js} +8 -2
  17. package/dist/cli/{index-1emhz3zb.js → index-nq9h2t3x.js} +137 -70
  18. package/dist/cli/{index-91j1sqzm.js → index-scww5b77.js} +47 -0
  19. package/dist/cli/{index-7jyndvsy.js → index-wsg3vkss.js} +1 -1
  20. package/dist/cli/index.js +4 -4
  21. package/dist/commands/registry.d.ts +2 -2
  22. package/dist/commands/sdd.d.ts +10 -0
  23. package/dist/config/index.d.ts +1 -0
  24. package/dist/config/worktree-isolation-config.d.ts +10 -0
  25. package/dist/hooks/delegation-gate/worktree-isolation.d.ts +4 -2
  26. package/dist/hooks/delegation-gate.d.ts +1 -0
  27. package/dist/hooks/guardrails/index.d.ts +1 -1
  28. package/dist/hooks/guardrails/tool-before.d.ts +5 -0
  29. package/dist/index.js +130 -121
  30. package/dist/plan/ledger.d.ts +60 -0
  31. package/dist/sdd/effective-spec.d.ts +21 -0
  32. package/dist/tools/save-plan.d.ts +24 -0
  33. package/dist/worktree/core.d.ts +58 -7
  34. package/dist/worktree/index.d.ts +1 -1
  35. package/package.json +1 -1
@@ -92,6 +92,29 @@ declare function getPlanJsonPath(directory: string): string;
92
92
  * @returns Hex-encoded SHA-256 hash
93
93
  */
94
94
  export declare function computePlanHash(plan: Plan): string;
95
+ /**
96
+ * Compute a SHA-256 hash of the plan's STRUCTURE, excluding transient
97
+ * execution progress fields (`phase.status` and `task.status`).
98
+ *
99
+ * This mirrors {@link computePlanHash}'s normalization byte-for-byte EXCEPT it
100
+ * omits the two status fields from the hashed payload. It exists solely for the
101
+ * plan-critic execution gate (`assertPlanCriticApprovedForExecution`), which
102
+ * must recognize a plan as "the same plan the critic approved" even after the
103
+ * architect flips a task to `in_progress` before delegating its coder. Including
104
+ * status (as `computePlanHash` does) would make the gate fire on the very first
105
+ * conforming coder dispatch, because `update_task_status(taskId,'in_progress')`
106
+ * runs before it and mutates the status-inclusive hash.
107
+ *
108
+ * IMPORTANT: This is intentionally a SEPARATE function, not a refactor of
109
+ * `computePlanHash`. `computePlanHash` is load-bearing for ledger replay and
110
+ * staleness/integrity detection (its output is persisted on-disk as
111
+ * `plan_hash_after`), so its byte output must never change. Do NOT collapse
112
+ * these two into a shared normalizer.
113
+ *
114
+ * @param plan - The plan to hash
115
+ * @returns Hex-encoded SHA-256 hash of the status-excluded structure
116
+ */
117
+ export declare function computePlanStructureHash(plan: Plan): string;
95
118
  /**
96
119
  * Read the current plan.json and compute its hash.
97
120
  *
@@ -206,12 +229,21 @@ export declare function takeSnapshotWithRetry(directory: string, plan: Plan, opt
206
229
  * - approvalMetadata: optional free-form metadata embedded into the
207
230
  * snapshot payload (e.g. phase number, verdict, summary) so that
208
231
  * downstream readers can filter without decoding prompts.
232
+ * - payloadHashOverride: when supplied, stored as the snapshot payload's
233
+ * `payload_hash` INSTEAD of the default `computePlanHash(plan)`. Used by the
234
+ * plan-critic gate to persist a status-excluded structural hash
235
+ * (`computePlanStructureHash`) so the gate can match the plan across the
236
+ * architect's pre-delegation `in_progress` status flip. Note this only
237
+ * changes the embedded snapshot `payload_hash`; the ledger event's
238
+ * hash-chain field `plan_hash_after` is unaffected (still governed by
239
+ * `planHashAfter` / on-disk plan.json), preserving replay integrity.
209
240
  * @returns The LedgerEvent that was written
210
241
  */
211
242
  export declare function takeSnapshotEvent(directory: string, plan: Plan, options?: {
212
243
  planHashAfter?: string;
213
244
  source?: string;
214
245
  approvalMetadata?: Record<string, unknown>;
246
+ payloadHashOverride?: string;
215
247
  }): Promise<LedgerEvent>;
216
248
  /**
217
249
  * Options for replayFromLedger
@@ -320,8 +352,35 @@ export interface ApprovedSnapshotInfo {
320
352
  * @returns The most recent approved snapshot info, or null if none exists
321
353
  */
322
354
  export declare function loadLastApprovedPlan(directory: string, expectedPlanId?: string): Promise<ApprovedSnapshotInfo | null>;
355
+ /**
356
+ * Find the most recent PLAN-CRITIC-approved snapshot in the ledger.
357
+ *
358
+ * Like {@link loadLastApprovedPlan}, but additionally requires the snapshot's
359
+ * embedded `approval.source === 'plan_critic_gate'`. This distinguishes the
360
+ * plan-critic execution-gate approval (recorded by
361
+ * `recordPlanCriticApprovalSnapshotIfApplicable` in the delegation gate) from
362
+ * the UNRELATED per-phase drift-verification snapshots that
363
+ * `src/tools/write-drift-evidence.ts` also writes with
364
+ * `source: 'critic_approved'` (but `approval: {phase, verdict, summary}` and no
365
+ * `plan_critic_gate` marker).
366
+ *
367
+ * Without this filter, a drift-verification snapshot landing AFTER a legitimate
368
+ * plan-critic approval would shadow it (being more recent), causing the gate to
369
+ * spuriously reject execution. This loader skips non-matching `critic_approved`
370
+ * snapshots and keeps scanning backward to find the plan-critic approval.
371
+ *
372
+ * SAFETY: `loadLastApprovedPlan`'s default behavior is intentionally left
373
+ * unchanged — other callers (`get-approved-plan`, restore/recovery paths) want
374
+ * ANY `critic_approved` snapshot as a restore point regardless of approval shape.
375
+ *
376
+ * @param directory - Working directory containing `.swarm/plan-ledger.jsonl`
377
+ * @param expectedPlanId - Optional plan identity filter (see loadLastApprovedPlan)
378
+ * @returns The most recent plan-critic-approved snapshot info, or null
379
+ */
380
+ export declare function loadLastPlanCriticApprovedSnapshot(directory: string, expectedPlanId?: string): Promise<ApprovedSnapshotInfo | null>;
323
381
  export declare const _internals: {
324
382
  computePlanHash: typeof computePlanHash;
383
+ computePlanStructureHash: typeof computePlanStructureHash;
325
384
  computeCurrentPlanHash: typeof computeCurrentPlanHash;
326
385
  ledgerExists: typeof ledgerExists;
327
386
  getLatestLedgerSeq: typeof getLatestLedgerSeq;
@@ -336,6 +395,7 @@ export declare const _internals: {
336
395
  quarantineLedgerSuffix: typeof quarantineLedgerSuffix;
337
396
  replayWithIntegrity: typeof replayWithIntegrity;
338
397
  loadLastApprovedPlan: typeof loadLastApprovedPlan;
398
+ loadLastPlanCriticApprovedSnapshot: typeof loadLastPlanCriticApprovedSnapshot;
339
399
  getLedgerPath: typeof getLedgerPath;
340
400
  getPlanJsonPath: typeof getPlanJsonPath;
341
401
  };
@@ -203,6 +203,21 @@ export interface ReadEffectiveSpecOpts {
203
203
  * `.specify/` a non-competing source, matching both citations.
204
204
  */
205
205
  export declare function readEffectiveSpecSync(directory: string, opts?: ReadEffectiveSpecOpts): EffectiveSpec | null;
206
+ /**
207
+ * Write an SDD projection to `.swarm/spec.md`.
208
+ *
209
+ * **Overwrite enforcement (FR-004, TOCTOU-safe):**
210
+ * When `overwrite` is `false` (default), the write uses `O_EXCL` exclusive
211
+ * creation (`fs.writeFileSync(target, data, { flag: 'wx' })`). If the
212
+ * target already exists the kernel returns `EEXIST` and the function
213
+ * returns `written: false` with an `error` field — no archive, no replace.
214
+ * This closes the TOCTOU race at the write boundary: a native spec that
215
+ * appears between a pre-check and this call cannot be silently replaced.
216
+ *
217
+ * When `overwrite` is `true`, the existing archive-then-replace path runs.
218
+ *
219
+ * The `--dry-run` early-return remains untouched (before any write).
220
+ */
206
221
  export declare function writeProjectedSpecSync(directory: string, options?: {
207
222
  changeId?: string;
208
223
  dryRun?: boolean;
@@ -210,11 +225,17 @@ export declare function writeProjectedSpecSync(directory: string, options?: {
210
225
  source?: 'openspec' | 'speckit';
211
226
  /** Feature selector forwarded to buildSpeckitProjectionSync when source is speckit. */
212
227
  feature?: string;
228
+ /**
229
+ * When true, archive the existing spec and overwrite.
230
+ * When false (default), use O_EXCL (wx flag) — refuse if target exists.
231
+ */
232
+ overwrite?: boolean;
213
233
  }): {
214
234
  written: boolean;
215
235
  projection: EffectiveSpec | null;
216
236
  archivePath?: string;
217
237
  path: string;
238
+ error?: string;
218
239
  };
219
240
  /**
220
241
  * Validate Spec-Kit artifacts READ-ONLY (FR-007, task 2.3).
@@ -64,6 +64,12 @@ export interface SavePlanArgs {
64
64
  * from the existing plan's identity.
65
65
  */
66
66
  confirm_identity_change?: boolean;
67
+ /**
68
+ * When true, allows saving a plan even when required FR-### MUST/SHALL
69
+ * requirements from the effective spec are not explicitly mapped in task
70
+ * descriptions or acceptance criteria.
71
+ */
72
+ confirm_requirement_coverage_gaps?: boolean;
67
73
  /**
68
74
  * Architect-facing concurrency controls for this plan.
69
75
  * When execution_profile.locked is true the profile is immutable — subsequent
@@ -90,6 +96,7 @@ export interface SavePlanResult {
90
96
  errors?: string[];
91
97
  warnings?: string[];
92
98
  recovery_guidance?: string;
99
+ requirement_coverage?: RequirementCoverageResult;
93
100
  /** The resolved execution_profile that was persisted, if any. */
94
101
  execution_profile?: {
95
102
  parallelization_enabled: boolean;
@@ -99,6 +106,22 @@ export interface SavePlanResult {
99
106
  auto_proceed?: boolean;
100
107
  };
101
108
  }
109
+ interface RequirementCoverageEntry {
110
+ id: string;
111
+ obligation: 'MUST' | 'SHOULD' | 'SHALL' | null;
112
+ text: string;
113
+ mapped_task_ids: string[];
114
+ }
115
+ interface RequirementCoverageResult {
116
+ status: 'passed' | 'failed' | 'override';
117
+ total_requirements: number;
118
+ covered_count: number;
119
+ missing_count: number;
120
+ blocking_missing_count: number;
121
+ covered: RequirementCoverageEntry[];
122
+ missing: RequirementCoverageEntry[];
123
+ blocking_missing: RequirementCoverageEntry[];
124
+ }
102
125
  /**
103
126
  * Detect template placeholder content (e.g., [task], [Project], [description], [N]).
104
127
  * These patterns indicate the LLM reproduced template examples literally rather than
@@ -126,3 +149,4 @@ export declare function executeSavePlan(args: SavePlanArgs, fallbackDir?: string
126
149
  * Tool definition for save_plan
127
150
  */
128
151
  export declare const save_plan: ToolDefinition;
152
+ export {};
@@ -180,23 +180,74 @@ export declare function makeWorktreeBranchName(sessionId: string, id: string, op
180
180
  * @param options - Worktree purpose, branch naming, and path options.
181
181
  * @returns A worktree handle on success, or `{ error: string }` on failure.
182
182
  */
183
+ /**
184
+ * Resolves the swarm-managed worktree base directory: `directory/worktreeDir`
185
+ * when an override is configured, otherwise the DD-6 default
186
+ * `<project-parent>/.swarm-worktrees`. Shared by `provisionWorktree` (path
187
+ * construction) and the destructive-command guard / removeWorktree force
188
+ * fallback (containment checks) so both sides agree on what counts as
189
+ * "swarm-managed".
190
+ *
191
+ * @param directory - Project root (an absolute path to the git working tree).
192
+ * @param worktreeDir - Optional worktree-dir override (relative to `directory`
193
+ * or absolute). When absent, the DD-6 default base is used.
194
+ * @returns The absolute worktree base directory.
195
+ */
196
+ export declare function resolveWorktreeBaseDir(directory: string, worktreeDir?: string): string;
197
+ /**
198
+ * Checks whether `targetPath` is contained within the swarm-managed worktree
199
+ * base directory (default base, plus any `worktreeDirOverrides`), using
200
+ * `fs.realpathSync` on both sides to resolve symlinks/junctions before the
201
+ * containment comparison — this both establishes containment AND defeats a
202
+ * symlink/junction escape in one step. Fails closed (returns false) if the
203
+ * target cannot be resolved (e.g. does not exist, or a permission error).
204
+ * Case-insensitive comparison on Windows, matching `isInDeclaredScope` in
205
+ * `src/hooks/guardrails/helpers.ts`.
206
+ *
207
+ * @param targetPath - Path to check (relative to `directory` or absolute).
208
+ * @param directory - Project root, used to resolve relative targets
209
+ * and the default base.
210
+ * @param worktreeDirOverrides - Additional configured worktree-dir overrides
211
+ * whose resolved bases are also treated as trusted.
212
+ * @returns `true` when the resolved target is inside a trusted base, else `false`.
213
+ */
214
+ export declare function isPathUnderSwarmWorktreeBase(targetPath: string, directory: string, worktreeDirOverrides?: string[]): boolean;
183
215
  export declare function provisionWorktree(directory: string, id: string, sessionId: string, options: WorktreeOptions): Promise<WorktreeProvisionResult>;
184
216
  /**
185
- * Removes a git worktree **without** `--force`.
217
+ * Removes a git worktree, **without** `--force` by default.
186
218
  *
187
219
  * On Windows (`process.platform === 'win32'`), retries up to 3 times with a
188
220
  * 2-second delay when the error contains `EBUSY` or `EPERM` (DD-10). After
189
221
  * exhausting retries the worktree is abandoned — the function returns an
190
222
  * error but does NOT throw.
191
223
  *
192
- * @param worktreePath - Absolute path to the worktree directory to remove.
193
- * @param projectRoot - Absolute path to the project root (a git repository)
194
- * used as `cwd` for the `git worktree remove` command.
195
- * Required because the worktree's parent directory may
196
- * not itself be a git repository.
224
+ * When `options.force` is set, the removal is escalated (issue #1708): if the
225
+ * default non-forced removal gives up (either a non-retryable error such as the
226
+ * "contains modified or untracked files, use --force" message on the first
227
+ * attempt, or EBUSY/EPERM retries exhausted on Windows), a single
228
+ * `git worktree remove --force` is attempted — but ONLY when `worktreePath`
229
+ * resolves inside the swarm-managed worktree base directory
230
+ * (`isPathUnderSwarmWorktreeBase`). A path outside that trusted base is NEVER
231
+ * force-removed even if `force` is requested. When `force` is not set, behavior
232
+ * is unchanged (non-force only; returns an error, does not throw, on exhaustion).
233
+ *
234
+ * @param worktreePath - Absolute path to the worktree directory to remove.
235
+ * @param projectRoot - Absolute path to the project root (a git repository)
236
+ * used as `cwd` for the `git worktree remove` command.
237
+ * Required because the worktree's parent directory may
238
+ * not itself be a git repository.
239
+ * @param options - Optional escalation controls.
240
+ * @param options.force - When true, opt into the forced-fallback removal
241
+ * described above (scoped to the trusted base).
242
+ * @param options.worktreeDir - The configured worktree-dir override, if any, so
243
+ * the containment check trusts the same base
244
+ * `provisionWorktree` used.
197
245
  * @returns `{ success: true }` on success or `{ error: string }` on failure.
198
246
  */
199
- export declare function removeWorktree(worktreePath: string, projectRoot: string): Promise<RemoveSuccess | RemoveFailure>;
247
+ export declare function removeWorktree(worktreePath: string, projectRoot: string, options?: {
248
+ force?: boolean;
249
+ worktreeDir?: string;
250
+ }): Promise<RemoveSuccess | RemoveFailure>;
200
251
  /**
201
252
  * Checks whether a worktree is clean — no uncommitted changes AND no
202
253
  * untracked files.
@@ -1,5 +1,5 @@
1
1
  export type { AutoCommitSkip, AutoCommitSuccess, CleanCheckFailure, CleanCheckSuccess, CleanFailure, CleanSuccess, ProvisionFailure, ProvisionSuccess, RemoveFailure, RemoveSuccess, } from './core';
2
- export { _internals as coreInternals, assertCleanWorkingTree, autoCommitDirty, checkPathBudget, cleanUntrackedFiles, isCleanWorktree, makeWorktreeBranchName, provisionWorktree, removeWorktree, shortenWorktreePath, } from './core';
2
+ export { _internals as coreInternals, assertCleanWorkingTree, autoCommitDirty, checkPathBudget, cleanUntrackedFiles, isCleanWorktree, isPathUnderSwarmWorktreeBase, makeWorktreeBranchName, provisionWorktree, removeWorktree, resolveWorktreeBaseDir, shortenWorktreePath, } from './core';
3
3
  export type { CleanupFailure, CleanupSuccess, ConflictHandlingError, ConflictInfo, DirtyMergeFailure, DirtyMergePartial, DirtyMergeSuccess, MergeConflict, MergeFailure, MergeSuccess, OrphanCleanupResult, StartupRecoveryResult, } from './merge';
4
4
  export { _internals as mergeInternals, attemptMergeBackFromDirty, cleanupOrphanedBranches, getMergeStrategy, handleMergeConflict, mergeLaneBranch, postMergeCleanup, startupOrphanRecovery, } from './merge';
5
5
  export * from './types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.108.0",
3
+ "version": "7.109.1",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",