@opengsd/gsd-core 1.7.0 → 1.9.0
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +45 -1
- package/README.md +2 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debug-session-manager.md +78 -4
- package/agents/gsd-debugger.md +87 -29
- package/agents/gsd-executor.md +49 -9
- package/agents/gsd-intel-updater.md +3 -3
- package/agents/gsd-phase-researcher.md +4 -2
- package/agents/gsd-plan-checker.md +20 -0
- package/agents/gsd-planner.md +44 -59
- package/agents/gsd-project-researcher.md +2 -2
- package/agents/gsd-ui-auditor.md +0 -40
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +1338 -135
- package/commands/gsd/ai-integration-phase.md +1 -1
- package/commands/gsd/mempalace-capture.md +9 -5
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/plan-phase.md +5 -3
- package/commands/gsd/plan-review-convergence.md +7 -2
- package/gsd-core/bin/gsd-tools.cjs +2690 -2472
- package/gsd-core/bin/lib/adapter-imperative.cjs +8 -1
- package/gsd-core/bin/lib/agent-command-router.cjs +20 -5
- package/gsd-core/bin/lib/api-coverage.cjs +360 -53
- package/gsd-core/bin/lib/audit.cjs +8 -8
- package/gsd-core/bin/lib/broken-windows.cjs +716 -0
- package/gsd-core/bin/lib/capability-command-router.cjs +733 -0
- package/gsd-core/bin/lib/capability-consent.cjs +40 -1
- package/gsd-core/bin/lib/capability-lifecycle.cjs +58 -0
- package/gsd-core/bin/lib/capability-loader.cjs +23 -1
- package/gsd-core/bin/lib/capability-registry.cjs +1450 -160
- package/gsd-core/bin/lib/capability-trust.cjs +468 -33
- package/gsd-core/bin/lib/capability-validator.cjs +882 -6
- package/gsd-core/bin/lib/capability-writer.cjs +6 -1
- package/gsd-core/bin/lib/check-command-router.cjs +140 -27
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +15 -0
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +209 -31
- package/gsd-core/bin/lib/claude-orchestration.cjs +203 -25
- package/gsd-core/bin/lib/command-aliases.cjs +14 -0
- package/gsd-core/bin/lib/commands.cjs +326 -21
- package/gsd-core/bin/lib/config-loader.cjs +214 -30
- package/gsd-core/bin/lib/config.cjs +158 -22
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/decisions.cjs +32 -8
- package/gsd-core/bin/lib/docs.cjs +6 -0
- package/gsd-core/bin/lib/estimate-cli.cjs +336 -0
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +14 -2
- package/gsd-core/bin/lib/frontmatter.cjs +125 -15
- package/gsd-core/bin/lib/gap-checker.cjs +17 -2
- package/gsd-core/bin/lib/host-integration.cjs +215 -8
- package/gsd-core/bin/lib/init.cjs +155 -66
- package/gsd-core/bin/lib/install-engine.cjs +299 -23
- package/gsd-core/bin/lib/install-profiles.cjs +239 -1
- package/gsd-core/bin/lib/installer-migrations/005-opencode-baseline-commands-dir.cjs +146 -0
- package/gsd-core/bin/lib/installer-migrations/006-pi-extension-cjs-to-js.cjs +91 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +44 -5
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +107 -0
- package/gsd-core/bin/lib/milestone.cjs +248 -14
- package/gsd-core/bin/lib/model-catalog.cjs +69 -4
- package/gsd-core/bin/lib/model-resolver.cjs +189 -7
- package/gsd-core/bin/lib/observability/logger.cjs +7 -2
- package/gsd-core/bin/lib/onboard-projection.cjs +11 -8
- package/gsd-core/bin/lib/phase-command-router.cjs +10 -1
- package/gsd-core/bin/lib/phase-estimation.cjs +398 -0
- package/gsd-core/bin/lib/phase-id.cjs +304 -9
- package/gsd-core/bin/lib/phase.cjs +258 -17
- package/gsd-core/bin/lib/plan-drift-guard.cjs +1 -1
- package/gsd-core/bin/lib/plan-scan.cjs +70 -2
- package/gsd-core/bin/lib/planning-workspace.cjs +9 -2
- package/gsd-core/bin/lib/profile-output.cjs +34 -8
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +927 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +348 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +594 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +114 -32
- package/gsd-core/bin/lib/roadmap-parser.cjs +61 -10
- package/gsd-core/bin/lib/roadmap.cjs +23 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +38 -5
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +23 -9
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +156 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +15 -2
- package/gsd-core/bin/lib/smart-entry.cjs +70 -5
- package/gsd-core/bin/lib/state-document.cjs +171 -24
- package/gsd-core/bin/lib/state-transition.cjs +50 -11
- package/gsd-core/bin/lib/state.cjs +206 -32
- package/gsd-core/bin/lib/surface.cjs +51 -9
- package/gsd-core/bin/lib/uat-predicate.cjs +6 -4
- package/gsd-core/bin/lib/uat.cjs +428 -11
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +2 -2
- package/gsd-core/bin/lib/unusable-input.cjs +216 -0
- package/gsd-core/bin/lib/validate.cjs +44 -8
- package/gsd-core/bin/lib/verification.cjs +163 -31
- package/gsd-core/bin/lib/verify.cjs +348 -42
- package/gsd-core/bin/lib/worktree-safety.cjs +360 -15
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +4 -15
- package/gsd-core/bin/shared/model-catalog.json +5 -0
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +5 -0
- package/gsd-core/references/api-coverage.md +37 -7
- package/gsd-core/references/checkpoints.md +1 -1
- package/gsd-core/references/common-bug-patterns.md +13 -0
- package/gsd-core/references/context-budget.md +40 -0
- package/gsd-core/references/debugger-bug-taxonomy.md +111 -0
- package/gsd-core/references/debugger-fix-acceptance.md +157 -0
- package/gsd-core/references/debugger-philosophy.md +1 -0
- package/gsd-core/references/debugger-prevention.md +98 -0
- package/gsd-core/references/debugger-rca-branching.md +98 -0
- package/gsd-core/references/debugger-repro-hardening.md +130 -0
- package/gsd-core/references/debugger-sbfl.md +110 -0
- package/gsd-core/references/debugger-semantic-recall.md +81 -0
- package/gsd-core/references/execute-phase-quota-recovery.md +55 -0
- package/gsd-core/references/execute-phase-requirement-revert.md +8 -0
- package/gsd-core/references/execute-phase-response-language.md +7 -0
- package/gsd-core/references/gate-prompts.md +6 -3
- package/gsd-core/references/model-profile-resolution.md +64 -13
- package/gsd-core/references/offer-next.md +88 -0
- package/gsd-core/references/planner-antipatterns.md +6 -0
- package/gsd-core/references/planner-mvp-mode.md +12 -13
- package/gsd-core/references/planner-preconditions.md +156 -0
- package/gsd-core/references/planner-reversibility.md +132 -0
- package/gsd-core/references/planning-config.md +2 -1
- package/gsd-core/references/reviewer-instances.md +28 -19
- package/gsd-core/references/runtime-aware-dispatch.md +42 -0
- package/gsd-core/references/skeleton-template.md +1 -1
- package/gsd-core/references/thinking-models-planning.md +3 -1
- package/gsd-core/references/ui-consideration-probe.md +2 -2
- package/gsd-core/references/worktree-branch-check.md +4 -4
- package/gsd-core/templates/DEBUG.md +5 -3
- package/gsd-core/templates/summary-minimal.md +4 -0
- package/gsd-core/templates/summary-standard.md +4 -0
- package/gsd-core/templates/summary.md +7 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +3 -1
- package/gsd-core/workflows/add-todo.md +32 -1
- package/gsd-core/workflows/ai-integration-phase.md +8 -6
- package/gsd-core/workflows/audit-fix.md +6 -2
- package/gsd-core/workflows/audit-milestone.md +8 -0
- package/gsd-core/workflows/autonomous.md +19 -15
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +7 -1
- package/gsd-core/workflows/code-review-fix.md +14 -6
- package/gsd-core/workflows/code-review.md +93 -24
- package/gsd-core/workflows/complete-milestone.md +3 -0
- package/gsd-core/workflows/debug.md +35 -7
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discovery-phase.md +7 -0
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -4
- package/gsd-core/workflows/discuss-phase/modes/auto.md +0 -6
- package/gsd-core/workflows/discuss-phase/templates/context.md +16 -2
- package/gsd-core/workflows/discuss-phase-assumptions.md +18 -9
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +7 -1
- package/gsd-core/workflows/docs-update.md +9 -0
- package/gsd-core/workflows/eval-review.md +4 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +4 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +160 -0
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +4 -4
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -2
- package/gsd-core/workflows/execute-phase.md +110 -149
- package/gsd-core/workflows/execute-plan.md +20 -8
- package/gsd-core/workflows/explore.md +4 -0
- package/gsd-core/workflows/extract-learnings.md +21 -0
- package/gsd-core/workflows/graduation.md +3 -0
- package/gsd-core/workflows/health.md +7 -1
- package/gsd-core/workflows/help/modes/full.md +9 -5
- package/gsd-core/workflows/import.md +11 -2
- package/gsd-core/workflows/inbox.md +7 -0
- package/gsd-core/workflows/ingest-docs.md +19 -10
- package/gsd-core/workflows/manager.md +3 -1
- package/gsd-core/workflows/map-codebase.md +17 -10
- package/gsd-core/workflows/mvp-phase.md +3 -0
- package/gsd-core/workflows/new-milestone.md +79 -23
- package/gsd-core/workflows/new-project.md +28 -19
- package/gsd-core/workflows/new-workspace.md +3 -1
- package/gsd-core/workflows/next.md +5 -2
- package/gsd-core/workflows/onboard.md +3 -0
- package/gsd-core/workflows/plan-phase.md +56 -51
- package/gsd-core/workflows/plan-review-convergence.md +61 -12
- package/gsd-core/workflows/plant-seed.md +3 -0
- package/gsd-core/workflows/profile-user.md +7 -1
- package/gsd-core/workflows/progress.md +31 -3
- package/gsd-core/workflows/quick.md +33 -10
- package/gsd-core/workflows/remove-workspace.md +3 -0
- package/gsd-core/workflows/review.md +172 -585
- package/gsd-core/workflows/scan.md +10 -2
- package/gsd-core/workflows/secure-phase.md +13 -2
- package/gsd-core/workflows/settings-integrations.md +3 -0
- package/gsd-core/workflows/settings.md +3 -0
- package/gsd-core/workflows/ship.md +88 -11
- package/gsd-core/workflows/sketch.md +3 -0
- package/gsd-core/workflows/smart-entry.md +4 -1
- package/gsd-core/workflows/spike.md +7 -1
- package/gsd-core/workflows/ui-phase.md +11 -2
- package/gsd-core/workflows/ui-review.md +11 -1
- package/gsd-core/workflows/undo.md +7 -0
- package/gsd-core/workflows/update.md +106 -5
- package/gsd-core/workflows/validate-phase.md +13 -2
- package/gsd-core/workflows/verify-phase.md +2 -2
- package/gsd-core/workflows/verify-work.md +15 -4
- package/hooks/dist/gsd-context-monitor.js +27 -9
- package/hooks/dist/gsd-cursor-session-start.js +6 -2
- package/hooks/dist/gsd-cursor-stop.js +6 -2
- package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
- package/hooks/dist/gsd-graphify-update.sh +9 -0
- package/hooks/dist/gsd-phase-boundary.sh +14 -2
- package/hooks/dist/gsd-prompt-guard.js +101 -2
- package/hooks/dist/gsd-read-guard.js +100 -2
- package/hooks/dist/gsd-read-injection-scanner.js +109 -2
- package/hooks/dist/gsd-statusline.js +97 -9
- package/hooks/dist/gsd-workflow-guard.js +110 -6
- package/hooks/dist/gsd-worktree-path-guard.js +132 -8
- package/hooks/dist/lib/cursor-workspace.js +74 -0
- package/hooks/gsd-context-monitor.js +27 -9
- package/hooks/gsd-cursor-session-start.js +6 -2
- package/hooks/gsd-cursor-stop.js +6 -2
- package/hooks/gsd-cursor-subagent-start.js +6 -2
- package/hooks/gsd-graphify-update.sh +9 -0
- package/hooks/gsd-phase-boundary.sh +14 -2
- package/hooks/gsd-prompt-guard.js +101 -2
- package/hooks/gsd-read-guard.js +100 -2
- package/hooks/gsd-read-injection-scanner.js +109 -2
- package/hooks/gsd-statusline.js +97 -9
- package/hooks/gsd-workflow-guard.js +110 -6
- package/hooks/gsd-worktree-path-guard.js +132 -8
- package/hooks/lib/cursor-workspace.js +74 -0
- package/package.json +10 -8
- package/pi/gsd.cjs +34 -3
- package/scripts/changeset/lint.cjs +1 -0
- package/scripts/changeset/parse.cjs +26 -0
- package/scripts/check-coverage-gate.cjs +51 -0
- package/scripts/check-glossary-refs.cjs +244 -0
- package/scripts/ci-rebase-check.cjs +48 -4
- package/scripts/ci-test-scope.cjs +67 -17
- package/scripts/gen-adr-index.cjs +528 -0
- package/scripts/gen-capability-matrix.cjs +26 -2
- package/scripts/gen-capability-registry.cjs +132 -34
- package/scripts/gen-emitted-baseline.cjs +145 -0
- package/scripts/gen-test-timings.cjs +201 -0
- package/scripts/lint-compiled-artifact-sync.cjs +146 -0
- package/scripts/lint-emitted-drift-ack.cjs +149 -0
- package/scripts/lint-fix-has-regression-test.cjs +131 -0
- package/scripts/lint-portable-timeout.cjs +140 -0
- package/scripts/lint-resolution-provenance.cjs +9 -0
- package/scripts/lint-test-file-count.allowlist.json +1 -0
- package/scripts/mutation-matrix.cjs +4 -0
- package/scripts/prompt-injection-scan.sh +6 -0
- package/scripts/registry-schema.cjs +57 -8
- package/scripts/release-notes/conventional-title.cjs +19 -1
- package/scripts/release-notes/format-github-release-notes.cjs +7 -3
- package/scripts/release-tarball-smoke.cjs +18 -11
- package/scripts/run-tests.cjs +420 -58
- package/scripts/workflow-size.cjs +16 -8
- package/skills/gsd-ai-integration-phase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +9 -5
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-plan-phase/SKILL.md +5 -3
- package/skills/gsd-plan-review-convergence/SKILL.md +7 -2
- package/vscode/package.json +1 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +0 -77
- package/scripts/update-size-baseline.cjs +0 -68
|
@@ -16,15 +16,20 @@
|
|
|
16
16
|
* → { ok:true, script, summary } | { ok:false, reason }
|
|
17
17
|
* Maps GSD's wave/plan model 1:1 onto Workflow primitives:
|
|
18
18
|
* wave → sequential `parallel()` stage barriers,
|
|
19
|
-
* plan → `agent(brief, { agentType:'gsd-executor', isolation:'worktree' })
|
|
19
|
+
* plan → `agent(brief, { agentType:'gsd-executor', isolation:'worktree' })`
|
|
20
|
+
* — UNLESS the plan's `use_worktree` is explicitly `false`, in which case
|
|
21
|
+
* `isolation` is omitted entirely for that plan (#2772 / #2285 finding 1:
|
|
22
|
+
* a submodule-touching plan must never be forced into worktree isolation
|
|
23
|
+
* the inline path (execute-phase.md step 2.5) would keep it out of),
|
|
20
24
|
* files_modified overlap → forces plans into separate sequential stages
|
|
21
25
|
* (the same overlap rule execute-phase already applies inline),
|
|
22
26
|
* resumeFromRunId → wired to the phase run id,
|
|
23
27
|
* budgetTokens → a shared token pool.
|
|
24
|
-
* The emitted script composes the SAME gsd-executor agent
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
+
* The emitted script composes the SAME gsd-executor agent the inline path
|
|
29
|
+
* uses, with per-plan worktree isolation mirroring the inline path's own
|
|
30
|
+
* per-plan decision, so it produces the same artifacts/commits (criterion 2).
|
|
31
|
+
* It is a generated string consumed by the orchestrator; this module never
|
|
32
|
+
* invokes the Workflow tool itself.
|
|
28
33
|
*
|
|
29
34
|
* Design laws:
|
|
30
35
|
* - Gall's Law: ship a small working slice that composes existing primitives
|
|
@@ -259,6 +264,53 @@ function partitionStages(plans) {
|
|
|
259
264
|
function quoteString(s) {
|
|
260
265
|
return JSON.stringify(s);
|
|
261
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* #2686 — the single decision of whether a resolved executor model is emittable,
|
|
269
|
+
* and what to emit. Shared by `agentOptions` (the emission) and the provenance
|
|
270
|
+
* comment (the claim about it) so the two can never disagree — a generated
|
|
271
|
+
* comment asserting something the generator does not actually do is the exact
|
|
272
|
+
* failure #2686 was filed for.
|
|
273
|
+
*
|
|
274
|
+
* Returns the model to emit, or `undefined` for "emit nothing":
|
|
275
|
+
* - non-string → malformed config; omit rather than throw, matching the
|
|
276
|
+
* defensive typeof guard `mapClaudeOverrideForRuntime`
|
|
277
|
+
* already carries in model-resolver for the same reason.
|
|
278
|
+
* - empty/whitespace → #2517: emitting `model: ""` 404s on runtimes without
|
|
279
|
+
* native tier aliases. Trimmed, so `" "` is also "none".
|
|
280
|
+
* - "inherit" → same rule; matched case-insensitively after trimming,
|
|
281
|
+
* since config is user-authored free text.
|
|
282
|
+
*
|
|
283
|
+
* NOTE it does NOT reject unscriptable characters — that is a hard input error,
|
|
284
|
+
* not a silent omission, and is rejected up front by `emitWorkflowScript` so the
|
|
285
|
+
* caller sees a reason instead of quietly losing their model routing.
|
|
286
|
+
*/
|
|
287
|
+
function emittableModel(executorModel) {
|
|
288
|
+
if (typeof executorModel !== 'string')
|
|
289
|
+
return undefined;
|
|
290
|
+
const trimmed = executorModel.trim();
|
|
291
|
+
if (trimmed.length === 0)
|
|
292
|
+
return undefined;
|
|
293
|
+
if (trimmed.toLowerCase() === 'inherit')
|
|
294
|
+
return undefined;
|
|
295
|
+
return trimmed;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Render the `agent()` options object for a single plan — `isolation: "worktree"`
|
|
299
|
+
* ONLY when the plan's `use_worktree` is not explicitly `false` (#2772 / #2285
|
|
300
|
+
* finding 1). This is the single place that decides worktree isolation for the
|
|
301
|
+
* Workflow backend; it must never diverge from the inline path's per-plan gate.
|
|
302
|
+
*/
|
|
303
|
+
function agentOptions(p, executorModel) {
|
|
304
|
+
const parts = ['agentType: "gsd-executor"'];
|
|
305
|
+
if (p.use_worktree !== false)
|
|
306
|
+
parts.push('isolation: "worktree"');
|
|
307
|
+
// #2686: carry the resolved executor model so this backend honors
|
|
308
|
+
// model_overrides / model_policy / model_profile exactly as the inline path.
|
|
309
|
+
const model = emittableModel(executorModel);
|
|
310
|
+
if (model !== undefined)
|
|
311
|
+
parts.push('model: ' + quoteString(model));
|
|
312
|
+
return '{ ' + parts.join(', ') + ' }';
|
|
313
|
+
}
|
|
262
314
|
/**
|
|
263
315
|
* True if `s` is a safe identifier/path token to interpolate into the generated
|
|
264
316
|
* script WITHOUT requiring a string-literal context — i.e. it contains no
|
|
@@ -285,7 +337,7 @@ function emitWorkflowScript(input) {
|
|
|
285
337
|
if (input === null || input === undefined || typeof input !== 'object') {
|
|
286
338
|
return { ok: false, reason: 'invalid_input' };
|
|
287
339
|
}
|
|
288
|
-
const { phaseDir, waves, runId } = input;
|
|
340
|
+
const { phaseDir, waves, runId, executorModel } = input;
|
|
289
341
|
// Identifiers/paths interpolated into the generated script must be free of any
|
|
290
342
|
// character that could terminate a comment, break out of a string literal, or
|
|
291
343
|
// smuggle control bytes — reject up front (security: #1143 review Finding 1).
|
|
@@ -295,9 +347,28 @@ function emitWorkflowScript(input) {
|
|
|
295
347
|
if (!isScriptableIdentifier(runId)) {
|
|
296
348
|
return { ok: false, reason: 'runId must be a non-empty string without newlines/quotes/backslash/control chars' };
|
|
297
349
|
}
|
|
350
|
+
// #2686 security: the resolved model is interpolated into BOTH an object
|
|
351
|
+
// literal (safe under quoteString) and a `//` provenance comment (NOT safe
|
|
352
|
+
// under quoteString — U+2028/U+2029 are LineTerminators that end a single-line
|
|
353
|
+
// comment in every engine, so a hostile model id would make the rest of the
|
|
354
|
+
// line live code). Reject the whole emission rather than silently dropping the
|
|
355
|
+
// model: an unscriptable id is malformed input, and `resolveWaveDispatch` maps
|
|
356
|
+
// an emit failure to the inline backend WITH a reason, so the user sees it.
|
|
357
|
+
// Only a STRING carrying such a character is rejected. A non-string is a
|
|
358
|
+
// malformed config rather than an injection attempt, and stays on the existing
|
|
359
|
+
// defensive path: `emittableModel` omits it and emission proceeds.
|
|
360
|
+
if (typeof executorModel === 'string' && UNSCRIPTABLE_CHAR_RE.test(executorModel)) {
|
|
361
|
+
return { ok: false, reason: 'executorModel must not contain newlines/quotes/backslash/control/line-separator chars' };
|
|
362
|
+
}
|
|
298
363
|
if (!Array.isArray(waves) || waves.length === 0) {
|
|
299
364
|
return { ok: false, reason: 'waves must be a non-empty array' };
|
|
300
365
|
}
|
|
366
|
+
// Wave ids must be unique ACROSS waves, not just plan ids within one (#2590).
|
|
367
|
+
// Each wave emits a `phase("Wave <id>")` call plus a matching meta.phases
|
|
368
|
+
// entry, and the Workflow tool matches phase titles by exact string — two
|
|
369
|
+
// waves sharing an id would collapse into one progress group and misattribute
|
|
370
|
+
// every agent in the second wave to the first.
|
|
371
|
+
const seenWaveIds = new Set();
|
|
301
372
|
for (let i = 0; i < waves.length; i++) {
|
|
302
373
|
const w = waves[i];
|
|
303
374
|
if (w === null || typeof w !== 'object' || typeof w.id !== 'string') {
|
|
@@ -306,6 +377,10 @@ function emitWorkflowScript(input) {
|
|
|
306
377
|
if (!isScriptableIdentifier(w.id)) {
|
|
307
378
|
return { ok: false, reason: 'waves[' + i + '].id must not contain newlines/quotes/backslash/control chars' };
|
|
308
379
|
}
|
|
380
|
+
if (seenWaveIds.has(w.id)) {
|
|
381
|
+
return { ok: false, reason: 'duplicate wave id "' + w.id + '" — wave ids must be unique (phase titles must map 1:1)' };
|
|
382
|
+
}
|
|
383
|
+
seenWaveIds.add(w.id);
|
|
309
384
|
if (!Array.isArray(w.plans) || w.plans.length === 0) {
|
|
310
385
|
return { ok: false, reason: 'waves[' + i + '] must have a non-empty plans array' };
|
|
311
386
|
}
|
|
@@ -318,6 +393,9 @@ function emitWorkflowScript(input) {
|
|
|
318
393
|
if (!isScriptableIdentifier(p.id)) {
|
|
319
394
|
return { ok: false, reason: 'waves[' + i + '].plans[' + j + '].id must not contain newlines/quotes/backslash/control chars' };
|
|
320
395
|
}
|
|
396
|
+
if (p.use_worktree !== undefined && typeof p.use_worktree !== 'boolean') {
|
|
397
|
+
return { ok: false, reason: 'waves[' + i + '].plans[' + j + '].use_worktree must be a boolean if present' };
|
|
398
|
+
}
|
|
321
399
|
if (seenIds.has(p.id)) {
|
|
322
400
|
return { ok: false, reason: 'waves[' + i + '] has duplicate plan id "' + p.id + '"' };
|
|
323
401
|
}
|
|
@@ -333,14 +411,63 @@ function emitWorkflowScript(input) {
|
|
|
333
411
|
? Math.floor(input.budgetTokens)
|
|
334
412
|
: null;
|
|
335
413
|
const lines = [];
|
|
414
|
+
// `export const meta = {…}` MUST be the first statement in the script — the
|
|
415
|
+
// Workflow tool rejects the whole script otherwise (#2590). Leading comments
|
|
416
|
+
// are not statements, but the meta block is emitted first regardless so the
|
|
417
|
+
// contract holds under the strictest reading of "first statement".
|
|
418
|
+
//
|
|
419
|
+
// meta.phases must be a PURE LITERAL (no variables, calls, spreads, or
|
|
420
|
+
// template interpolation), and its titles are matched EXACTLY against the
|
|
421
|
+
// phase() calls emitted below.
|
|
422
|
+
lines.push('export const meta = {');
|
|
423
|
+
lines.push(' name: ' + quoteString('gsd-execute-' + runId) + ',');
|
|
424
|
+
lines.push(' description: ' + quoteString('GSD wave dispatch for ' + phaseDir) + ',');
|
|
425
|
+
lines.push(' phases: [');
|
|
426
|
+
for (const w of waves) {
|
|
427
|
+
lines.push(' { title: ' + quoteString('Wave ' + w.id) + ', detail: '
|
|
428
|
+
+ quoteString(w.plans.length + ' plan(s)') + ' },');
|
|
429
|
+
}
|
|
430
|
+
lines.push(' ],');
|
|
431
|
+
lines.push('}');
|
|
432
|
+
lines.push('');
|
|
336
433
|
lines.push('// GSD Workflow script — generated by the claude-orchestration capability (#1143)');
|
|
337
434
|
lines.push('// phase: ' + phaseDir);
|
|
338
435
|
lines.push('// BETA: preview-grade; on any failure the orchestrator falls back to inline dispatch.');
|
|
339
|
-
lines.push('// Composes the SAME gsd-executor agent
|
|
340
|
-
lines.push('//
|
|
341
|
-
lines.push('
|
|
436
|
+
lines.push('// Composes the SAME gsd-executor agent as the inline path, so artifacts (SUMMARY.md)');
|
|
437
|
+
lines.push('// and commits are produced identically. Worktree isolation is per-plan (use_worktree)');
|
|
438
|
+
lines.push('// and mirrors execute-phase.md step 2.5\'s submodule gate exactly (#2772 / #2285).');
|
|
439
|
+
// #2686 / ADR-1411: state which model was applied — or that none resolved —
|
|
440
|
+
// so an opted-in user can SEE the routing decision instead of having to read
|
|
441
|
+
// the emitted options. A fallback must be a visible value, never silent.
|
|
442
|
+
//
|
|
443
|
+
// SECURITY: this is a `//` comment, and U+2028/U+2029 are ECMAScript
|
|
444
|
+
// LineTerminators that END a single-line comment in every engine — the ES2019
|
|
445
|
+
// change legalized them inside string LITERALS only, so `quoteString` alone is
|
|
446
|
+
// NOT sufficient here even though it is sufficient in the object literal
|
|
447
|
+
// above. An unscriptable model id would otherwise close the comment and make
|
|
448
|
+
// the remainder live top-level code. `emitWorkflowScript` rejects such ids
|
|
449
|
+
// before reaching this point (see the validation above), which is what makes
|
|
450
|
+
// interpolating here safe.
|
|
451
|
+
const provenanceModel = emittableModel(executorModel);
|
|
452
|
+
if (provenanceModel !== undefined) {
|
|
453
|
+
lines.push('// model: ' + quoteString(provenanceModel) + ' (resolved for gsd-executor, same source as the inline path)');
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
lines.push('// model: none applied — resolved to "inherit"/empty, so each agent inherits the');
|
|
457
|
+
lines.push('// orchestrator model (#2517: emitting an empty model 404s on some runtimes).');
|
|
458
|
+
}
|
|
459
|
+
lines.push('//');
|
|
460
|
+
// resumeFromRunId is a Workflow TOOL INPUT parameter, not a script function —
|
|
461
|
+
// calling it threw "resumeFromRunId is not defined" (#2590). The run id is
|
|
462
|
+
// carried in summary.resumeRunId for the caller to pass as that input.
|
|
463
|
+
lines.push('// resume: pass ' + quoteString(runId) + ' as the Workflow tool\'s resumeFromRunId input');
|
|
464
|
+
lines.push('// (it is a tool parameter, NOT a script function).');
|
|
342
465
|
if (budgetTokens !== null) {
|
|
343
|
-
|
|
466
|
+
// `budget` is a read-only object ({ total, spent(), remaining() }) supplied
|
|
467
|
+
// by the caller's token directive — a script cannot SET it, and `budget(n)`
|
|
468
|
+
// threw "budget is not a function" (#2590). Recorded as intent only.
|
|
469
|
+
lines.push('// budget: ' + budgetTokens + ' output tokens intended for this run; `budget` is');
|
|
470
|
+
lines.push('// read-only in a Workflow script — set it via the caller\'s token directive.');
|
|
344
471
|
}
|
|
345
472
|
lines.push('');
|
|
346
473
|
const stagesByWave = [];
|
|
@@ -351,6 +478,8 @@ function emitWorkflowScript(input) {
|
|
|
351
478
|
stagesByWave.push(stages);
|
|
352
479
|
totalPlans += wave.plans.length;
|
|
353
480
|
lines.push('// Wave ' + wave.id);
|
|
481
|
+
// Title must match this wave's meta.phases entry EXACTLY.
|
|
482
|
+
lines.push('phase(' + quoteString('Wave ' + wave.id) + ')');
|
|
354
483
|
for (let si = 0; si < stages.length; si++) {
|
|
355
484
|
const stagePlanIds = stages[si];
|
|
356
485
|
// Resolve back to plan objects for briefs (ids are unique within a wave — validated above).
|
|
@@ -358,22 +487,15 @@ function emitWorkflowScript(input) {
|
|
|
358
487
|
if (stages.length > 1) {
|
|
359
488
|
lines.push('// Stage ' + si + (si > 0 ? ' (sequential — files_modified overlap)' : ''));
|
|
360
489
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
lines.push('parallel(');
|
|
369
|
-
for (const p of stagePlans) {
|
|
370
|
-
lines.push(' agent(' + quoteString(p.brief) + ', { agentType: "gsd-executor", isolation: "worktree" }),');
|
|
371
|
-
}
|
|
372
|
-
// Replace trailing comma on the last agent line with nothing.
|
|
373
|
-
const lastIdx = lines.length - 1;
|
|
374
|
-
lines[lastIdx] = lines[lastIdx].replace(/,$/, '');
|
|
375
|
-
lines.push(')');
|
|
490
|
+
// parallel() takes an ARRAY OF THUNKS — `parallel(agent(…), agent(…))`
|
|
491
|
+
// threw "parallel() expects an array of functions" (#2590). Passing
|
|
492
|
+
// agent() results directly would also start every agent eagerly, before
|
|
493
|
+
// parallel() could bound concurrency.
|
|
494
|
+
lines.push('await parallel([');
|
|
495
|
+
for (const p of stagePlans) {
|
|
496
|
+
lines.push(' () => agent(' + quoteString(p.brief) + ', ' + agentOptions(p, executorModel) + '),');
|
|
376
497
|
}
|
|
498
|
+
lines.push('])');
|
|
377
499
|
}
|
|
378
500
|
if (wi < waves.length - 1)
|
|
379
501
|
lines.push('');
|
|
@@ -393,9 +515,65 @@ function emitWorkflowScript(input) {
|
|
|
393
515
|
},
|
|
394
516
|
};
|
|
395
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* #2285 — single composed decision seam for a PRE-wave dispatch-backend selector
|
|
520
|
+
* (e.g. the `execute:wave:pre` claude-orchestration contribution). Composes
|
|
521
|
+
* `detectWorkflowBackend` (gate ladder) with `emitWorkflowScript` (wave→plan
|
|
522
|
+
* mapping) into ONE call so the orchestrator (and its CLI wrapper,
|
|
523
|
+
* `claude-orchestration resolve-wave-dispatch`) never has to re-implement the
|
|
524
|
+
* two-step "detect, then maybe emit" sequencing.
|
|
525
|
+
*
|
|
526
|
+
* Fail-closed at every layer, matching the two composed functions:
|
|
527
|
+
* - `detectWorkflowBackend` resolving anything other than `'workflow'` →
|
|
528
|
+
* `inline` immediately; `emitWorkflowScript` is never invoked (no wasted
|
|
529
|
+
* work, no risk of a bad emit masking a correct inline fallback).
|
|
530
|
+
* - `detectWorkflowBackend` resolves `'workflow'` but `emitWorkflowScript`
|
|
531
|
+
* fails (`ok:false` — e.g. a malformed wave manifest) → `inline`, carrying
|
|
532
|
+
* the emit failure reason so the caller can surface it. Never a partial or
|
|
533
|
+
* broken script.
|
|
534
|
+
*
|
|
535
|
+
* This is the designated non-CLI-router, non-test caller of
|
|
536
|
+
* `detectWorkflowBackend` and `emitWorkflowScript` — the standalone CLI
|
|
537
|
+
* subcommands (`detect-backend`, `emit-workflow`) remain for inspection/
|
|
538
|
+
* debugging, but the orchestrator's real per-wave dispatch decision goes
|
|
539
|
+
* through this seam.
|
|
540
|
+
*
|
|
541
|
+
* Never throws on bad input.
|
|
542
|
+
*/
|
|
543
|
+
function resolveWaveDispatch(input) {
|
|
544
|
+
if (input === null || input === undefined || typeof input !== 'object') {
|
|
545
|
+
return { backend: 'inline', reason: 'invalid_input' };
|
|
546
|
+
}
|
|
547
|
+
const detected = detectWorkflowBackend({
|
|
548
|
+
runtimeId: input.runtimeId,
|
|
549
|
+
hostIntegration: input.hostIntegration,
|
|
550
|
+
config: input.config,
|
|
551
|
+
agentSdkVersion: input.agentSdkVersion,
|
|
552
|
+
});
|
|
553
|
+
if (detected.backend !== 'workflow') {
|
|
554
|
+
return { backend: 'inline', reason: detected.reason };
|
|
555
|
+
}
|
|
556
|
+
const emitted = emitWorkflowScript({
|
|
557
|
+
phaseDir: input.phaseDir,
|
|
558
|
+
waves: input.waves,
|
|
559
|
+
runId: input.runId,
|
|
560
|
+
budgetTokens: input.budgetTokens,
|
|
561
|
+
executorModel: input.executorModel,
|
|
562
|
+
});
|
|
563
|
+
if (!emitted.ok) {
|
|
564
|
+
return { backend: 'inline', reason: 'emit_failed: ' + emitted.reason };
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
backend: 'workflow',
|
|
568
|
+
reason: detected.reason,
|
|
569
|
+
script: emitted.script,
|
|
570
|
+
summary: emitted.summary,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
396
573
|
module.exports = {
|
|
397
574
|
detectWorkflowBackend,
|
|
398
575
|
emitWorkflowScript,
|
|
576
|
+
resolveWaveDispatch,
|
|
399
577
|
compareSemver,
|
|
400
578
|
isValidSemver,
|
|
401
579
|
WORKFLOW_TOOL_FLOOR_VERSION,
|
|
@@ -728,6 +728,20 @@ exports.NON_FAMILY_COMMAND_ALIASES = [
|
|
|
728
728
|
],
|
|
729
729
|
"mutation": true
|
|
730
730
|
},
|
|
731
|
+
{
|
|
732
|
+
"canonical": "requirements.ready-ids",
|
|
733
|
+
"aliases": [
|
|
734
|
+
"requirements ready-ids"
|
|
735
|
+
],
|
|
736
|
+
"mutation": false
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
"canonical": "requirements.revert-phase",
|
|
740
|
+
"aliases": [
|
|
741
|
+
"requirements revert-phase"
|
|
742
|
+
],
|
|
743
|
+
"mutation": true
|
|
744
|
+
},
|
|
731
745
|
{
|
|
732
746
|
"canonical": "stats.json",
|
|
733
747
|
"aliases": [
|