@opengsd/gsd-core 1.20.0 → 1.21.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 +33 -2
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-plan-checker.md +1 -1
- package/agents/gsd-planner.md +3 -3
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +265 -161
- package/commands/gsd/autonomous.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/new-project.md +2 -2
- package/gsd-core/bin/gsd-tools.cjs +554 -823
- package/gsd-core/bin/lib/capability-activation.cjs +8 -2
- package/gsd-core/bin/lib/capability-command-router.cjs +6 -2
- package/gsd-core/bin/lib/capability-ledger.cjs +1 -1
- package/gsd-core/bin/lib/capability-lock.cjs +19 -27
- package/gsd-core/bin/lib/capability-registry.cjs +74 -71
- package/gsd-core/bin/lib/capability-validator.cjs +1 -0
- package/gsd-core/bin/lib/capability-writer.cjs +7 -4
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +142 -62
- package/gsd-core/bin/lib/claude-orchestration.cjs +214 -122
- package/gsd-core/bin/lib/cli-surface.cjs +545 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +356 -103
- package/gsd-core/bin/lib/commands.cjs +99 -2
- package/gsd-core/bin/lib/config-file-read.cjs +85 -0
- package/gsd-core/bin/lib/config-loader.cjs +23 -15
- package/gsd-core/bin/lib/config.cjs +139 -101
- package/gsd-core/bin/lib/configuration.cjs +41 -2
- package/gsd-core/bin/lib/criterion-baseline.cjs +346 -0
- package/gsd-core/bin/lib/criterion-replay.cjs +380 -0
- package/gsd-core/bin/lib/estimate-cli.cjs +5 -1
- package/gsd-core/bin/lib/gap-checker.cjs +12 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +593 -27
- package/gsd-core/bin/lib/gate-phase-context.cjs +16 -4
- package/gsd-core/bin/lib/graphify.cjs +19 -2
- package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +5 -1
- package/gsd-core/bin/lib/init.cjs +20 -18
- package/gsd-core/bin/lib/install-effort-resolver.cjs +24 -4
- package/gsd-core/bin/lib/install-engine.cjs +20 -28
- package/gsd-core/bin/lib/install-fs-adapter.cjs +5 -1
- package/gsd-core/bin/lib/install-manifest-record.cjs +86 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -3
- package/gsd-core/bin/lib/install-profiles.cjs +4 -2
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +62 -31
- package/gsd-core/bin/lib/markdown-table.cjs +39 -1
- package/gsd-core/bin/lib/milestone.cjs +8 -6
- package/gsd-core/bin/lib/model-resolver.cjs +8 -4
- package/gsd-core/bin/lib/phase-entry-grammar.cjs +14 -0
- package/gsd-core/bin/lib/phase-id.cjs +26 -9
- package/gsd-core/bin/lib/phase-locator.cjs +8 -3
- package/gsd-core/bin/lib/phase.cjs +170 -45
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +46 -23
- package/gsd-core/bin/lib/planning-snapshot.cjs +10 -6
- package/gsd-core/bin/lib/planning-workspace.cjs +20 -10
- package/gsd-core/bin/lib/profile-output.cjs +9 -6
- package/gsd-core/bin/lib/project-context.cjs +439 -0
- package/gsd-core/bin/lib/project-root.cjs +9 -230
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +1 -1
- package/gsd-core/bin/lib/report-parser.cjs +16 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +357 -50
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +24 -5
- package/gsd-core/bin/lib/roadmap.cjs +2 -2
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +317 -134
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +81 -3
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +6 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +279 -8
- package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +1010 -10
- package/gsd-core/bin/lib/stale-bake-guard.cjs +4 -1
- package/gsd-core/bin/lib/state-transition.cjs +103 -46
- package/gsd-core/bin/lib/state.cjs +448 -63
- package/gsd-core/bin/lib/surface.cjs +4 -7
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +10 -3
- package/gsd-core/bin/lib/verify-command-router.cjs +6 -1
- package/gsd-core/bin/lib/verify.cjs +49 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +4 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +8 -111
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/autonomous-smart-discuss.md +99 -18
- package/gsd-core/references/checkpoints.md +1 -1
- package/gsd-core/references/planner-guidance.md +11 -0
- package/gsd-core/references/planner-human-verify-mode.md +1 -1
- package/gsd-core/references/planner-reversibility.md +2 -0
- package/gsd-core/references/planner-verify-command-grounding.md +22 -6
- package/gsd-core/workflows/autonomous.md +4 -4
- package/gsd-core/workflows/code-review-fix.md +5 -7
- package/gsd-core/workflows/execute-phase.md +21 -7
- package/gsd-core/workflows/plan-phase/steps/plan-pre-dispatch.md +101 -0
- package/gsd-core/workflows/plan-phase.md +18 -19
- package/hooks/dist/gsd-agent-isolation-guard.js +4 -3
- package/hooks/dist/gsd-config-reload.js +2 -2
- package/hooks/dist/gsd-context-monitor.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +9 -6
- package/hooks/dist/gsd-read-injection-scanner.js +2 -2
- package/hooks/dist/gsd-secret-read-guard.js +141 -6
- package/hooks/dist/gsd-statusline.js +3 -2
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/lib/config-read.js +49 -0
- package/hooks/dist/lib/git-cmd.js +3 -1
- package/hooks/dist/lib/git-head-tree.js +474 -0
- package/hooks/dist/lib/isolation-sentinel.js +168 -13
- package/hooks/dist/lib/secret-read-exemptions.js +161 -0
- package/hooks/gsd-agent-isolation-guard.js +4 -3
- package/hooks/gsd-config-reload.js +2 -2
- package/hooks/gsd-context-monitor.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +9 -6
- package/hooks/gsd-read-injection-scanner.js +2 -2
- package/hooks/gsd-secret-read-guard.js +141 -6
- package/hooks/gsd-statusline.js +3 -2
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/lib/config-read.js +49 -0
- package/hooks/lib/git-cmd.js +3 -1
- package/hooks/lib/git-head-tree.js +474 -0
- package/hooks/lib/isolation-sentinel.js +168 -13
- package/hooks/lib/secret-read-exemptions.js +161 -0
- package/package.json +7 -2
- package/pi/package-agents/gsd-executor.md +1 -1
- package/pi/package-agents/gsd-plan-checker.md +1 -1
- package/pi/package-agents/gsd-planner.md +3 -3
- package/pi/package-agents/gsd-verifier.md +2 -2
- package/pi/package-agents/manifest.json +4 -4
- package/pi/package-skills/gsd-autonomous/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
- package/scripts/changeset/cli.cjs +2 -2
- package/scripts/changeset/lint.cjs +28 -3
- package/scripts/changeset/serialize.cjs +12 -2
- package/scripts/ci-compact-v8-coverage.cjs +204 -0
- package/scripts/ci-fix-next-rerun.cjs +149 -0
- package/scripts/ci-next-health.cjs +52 -9
- package/scripts/ci-test-scope.cjs +77 -71
- package/scripts/command-contract-helpers.cjs +56 -7
- package/scripts/docs-guard-registry.cjs +3 -0
- package/scripts/gen-cli-surface-manifest.cjs +287 -0
- package/scripts/gen-platform-conformance-tier.cjs +258 -19
- package/scripts/lib/macos-conformance-tier.generated.cjs +59 -47
- package/scripts/lib/platform-conformance-tier.generated.cjs +23 -64
- package/scripts/lib/shellcheck-fetch.cjs +71 -4
- package/scripts/lint-removed-but-needed.cjs +562 -50
- package/scripts/merge-commit-pr.cjs +186 -0
- package/scripts/release-ancestry.cjs +86 -0
- package/scripts/release-notes/changelog-release-notes.cjs +345 -0
- package/scripts/release-notes/discord-release-summary.cjs +82 -1
- package/scripts/release-pipeline-pr.cjs +92 -0
- package/scripts/require-issue-link-policy.cjs +18 -9
- package/scripts/sync-next-version.cjs +11 -2
- package/scripts/timeout-evidence-from-ci.cjs +964 -0
- package/skills/gsd-autonomous/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-new-project/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -5,12 +5,20 @@
|
|
|
5
5
|
* #1143 — adopts Claude Code's Workflow tool (the engine behind `/effort ultracode`)
|
|
6
6
|
* as an optional, runtime-gated parallel-execution backend for the GSD loop.
|
|
7
7
|
*
|
|
8
|
-
* This module is the pure, testable core of the capability. It owns
|
|
8
|
+
* This module is the pure, testable core of the capability. It owns three seams:
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* resolveWorkflowAvailability({ env, sources })
|
|
11
|
+
* → { disabled: boolean, reason: string }
|
|
12
|
+
* Reads Claude Code's documented workflow kill switches (the
|
|
13
|
+
* CLAUDE_CODE_DISABLE_WORKFLOWS env var and the `disableWorkflows` /
|
|
14
|
+
* `enableWorkflows` settings keys) from precedence-ordered settings sources
|
|
15
|
+
* the caller has already read. Fail-closed on unreadable or malformed input.
|
|
16
|
+
*
|
|
17
|
+
* detectWorkflowBackend({ runtimeId, hostIntegration, config, workflowAvailability })
|
|
11
18
|
* → { available: boolean, backend: 'workflow'|'inline', reason: string }
|
|
12
19
|
* Fail-closed: every miss degrades to `inline` (today's behaviour), so the
|
|
13
20
|
* core loop is byte-identical unless every gate opens. This is criteria 3 + 6.
|
|
21
|
+
* `workflowAvailability` is the resolveWorkflowAvailability result (#4747).
|
|
14
22
|
*
|
|
15
23
|
* emitWorkflowScript({ phaseDir, waves, runId, budgetTokens? })
|
|
16
24
|
* → { ok:true, script, summary } | { ok:false, reason }
|
|
@@ -25,6 +33,14 @@
|
|
|
25
33
|
* (the same overlap rule execute-phase already applies inline),
|
|
26
34
|
* resumeFromRunId → wired to the phase run id,
|
|
27
35
|
* budgetTokens → a shared token pool.
|
|
36
|
+
* Runtime caps (#4747): a single parallel() call accepts at most
|
|
37
|
+
* WORKFLOW_MAX_ITEMS_PER_CALL items and a run at most
|
|
38
|
+
* WORKFLOW_MAX_AGENTS_PER_RUN agents; a manifest over either cap is rejected
|
|
39
|
+
* at emit time (`ok:false`) instead of failing inside the Workflow tool.
|
|
40
|
+
* Hand-back (#3302 / #4747): worktree plans pass a JSON `schema`
|
|
41
|
+
* (WORKTREE_METADATA_SCHEMA) to agent(), so the result is a validated object;
|
|
42
|
+
* every agent() call is wrapped so a throw becomes `{ error }` for that plan
|
|
43
|
+
* only, and each outcome entry is { plan, expects_worktree, metadata, error }.
|
|
28
44
|
* The emitted script composes the SAME gsd-executor agent the inline path
|
|
29
45
|
* uses, with per-plan worktree isolation mirroring the inline path's own
|
|
30
46
|
* per-plan decision, so it produces the same artifacts/commits (criterion 2).
|
|
@@ -38,7 +54,7 @@
|
|
|
38
54
|
* barrier/pipeline/budget/resume semantics instead of hand-rolling them.
|
|
39
55
|
* - Postel's Law: liberal in input (missing fields → inline), conservative in
|
|
40
56
|
* output (workflow only when every gate opens).
|
|
41
|
-
* - Fail-closed:
|
|
57
|
+
* - Fail-closed: unknown availability, a missing descriptor, or a disabled
|
|
42
58
|
* toggle all resolve to `inline`, never to `workflow`.
|
|
43
59
|
*
|
|
44
60
|
* Zero external dependencies. Pure functions. Never throws on bad input.
|
|
@@ -48,98 +64,126 @@ const fileOverlapPartitionerMod = require("./file-overlap-partitioner.cjs");
|
|
|
48
64
|
const { partitionByFileOverlap } = fileOverlapPartitionerMod;
|
|
49
65
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
50
66
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
67
|
+
* Documented Workflow-tool runtime caps (code.claude.com/docs/en/workflows):
|
|
68
|
+
* parallel()/pipeline() reject a list longer than 4,096 items, and one run may
|
|
69
|
+
* start at most 1,000 agents (#4747).
|
|
70
|
+
*/
|
|
71
|
+
const WORKFLOW_MAX_ITEMS_PER_CALL = 4096;
|
|
72
|
+
const WORKFLOW_MAX_AGENTS_PER_RUN = 1000;
|
|
73
|
+
/** Every reason code detectWorkflowBackend can return (#4747; hyrums-law). */
|
|
74
|
+
const DETECT_REASONS = Object.freeze([
|
|
75
|
+
'workflow_backend_active', 'capability_disabled', 'runtime_not_claude', 'backend_inline',
|
|
76
|
+
'workflow_tool_unavailable', 'workflows_disabled_by_env', 'workflows_disabled_by_managed_settings',
|
|
77
|
+
'workflows_disabled_by_settings', 'workflow_settings_unreadable', 'workflow_availability_unknown',
|
|
78
|
+
]);
|
|
79
|
+
/** The subset of DETECT_REASONS a resolveWorkflowAvailability result may carry when disabled. */
|
|
80
|
+
const AVAILABILITY_DISABLE_REASONS = new Set([
|
|
81
|
+
'workflows_disabled_by_env', 'workflows_disabled_by_managed_settings', 'workflows_disabled_by_settings',
|
|
82
|
+
'workflow_settings_unreadable', 'workflow_availability_unknown',
|
|
83
|
+
]);
|
|
84
|
+
/**
|
|
85
|
+
* JSON schema a worktree plan's agent() must satisfy: the four fields the
|
|
86
|
+
* orchestrator records via worktree.record-agent (#3302 / #4747). Deep-frozen so
|
|
87
|
+
* the exported literal cannot drift from what the emitted script declares.
|
|
54
88
|
*/
|
|
55
|
-
const
|
|
89
|
+
const WORKTREE_METADATA_SCHEMA = Object.freeze({
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: Object.freeze({
|
|
92
|
+
agent_id: Object.freeze({ type: 'string', minLength: 1 }),
|
|
93
|
+
worktree_path: Object.freeze({ type: 'string', minLength: 1 }),
|
|
94
|
+
branch: Object.freeze({ type: 'string', minLength: 1 }),
|
|
95
|
+
expected_base: Object.freeze({ type: 'string', minLength: 1 }),
|
|
96
|
+
}),
|
|
97
|
+
required: Object.freeze(['agent_id', 'worktree_path', 'branch', 'expected_base']),
|
|
98
|
+
});
|
|
56
99
|
/** Closed enum for the `claude_orchestration.execution_backend` config key. */
|
|
57
100
|
const BACKEND_VALUES = new Set(['auto', 'workflow', 'inline']);
|
|
58
101
|
/** Only this runtime can host the Workflow tool (Claude Code / Agent SDK). */
|
|
59
102
|
const WORKFLOW_RUNTIME = 'claude';
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
63
|
-
/** True for a syntactically valid semver string. */
|
|
64
|
-
function isValidSemver(s) {
|
|
65
|
-
return typeof s === 'string' && SEMVER_RE.test(s);
|
|
103
|
+
function isPlainObject(v) {
|
|
104
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
66
105
|
}
|
|
106
|
+
const SETTINGS_SCOPES = new Set(['managed', 'projectLocal', 'project', 'user']);
|
|
107
|
+
const SETTINGS_STATUSES = new Set(['absent', 'ok', 'unreadable']);
|
|
67
108
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
109
|
+
* Resolve whether a Claude Code workflow kill switch is set (#4747).
|
|
110
|
+
*
|
|
111
|
+
* Sources: code.claude.com/docs/en/workflows (kill switches
|
|
112
|
+
* `CLAUDE_CODE_DISABLE_WORKFLOWS` and `disableWorkflows`/`enableWorkflows`) and
|
|
113
|
+
* code.claude.com/docs/en/settings (precedence managed > --settings >
|
|
114
|
+
* project-local > project > user; the highest level that sets a key wins).
|
|
115
|
+
* `--settings` is not observable and is not read.
|
|
116
|
+
*
|
|
117
|
+
* `input.sources` MUST be precedence-ordered, highest first (caller's
|
|
118
|
+
* responsibility): managed, projectLocal, project, user. Each entry is
|
|
119
|
+
* `{ scope, status: 'absent'|'ok'|'unreadable', settings? }`.
|
|
72
120
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
121
|
+
* Fail-closed: a malformed input, an unreadable source, or a non-boolean kill
|
|
122
|
+
* switch value disables. A truthy env value other than `0`/`false`/empty disables.
|
|
123
|
+
*
|
|
124
|
+
* Never throws.
|
|
76
125
|
*/
|
|
77
|
-
function
|
|
78
|
-
if (!
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const ai = preIdentifiers(a);
|
|
109
|
-
const bi = preIdentifiers(b);
|
|
110
|
-
const len = Math.min(ai.length, bi.length);
|
|
111
|
-
for (let i = 0; i < len; i++) {
|
|
112
|
-
const ax = ai[i];
|
|
113
|
-
const bx = bi[i];
|
|
114
|
-
const aNum = /^\d+$/.test(ax);
|
|
115
|
-
const bNum = /^\d+$/.test(bx);
|
|
116
|
-
if (aNum && bNum) {
|
|
117
|
-
const an = parseInt(ax, 10);
|
|
118
|
-
const bn = parseInt(bx, 10);
|
|
119
|
-
if (an < bn)
|
|
120
|
-
return -1;
|
|
121
|
-
if (an > bn)
|
|
122
|
-
return 1;
|
|
123
|
-
}
|
|
124
|
-
else if (aNum && !bNum) {
|
|
125
|
-
return -1; // numeric identifiers always lower than alphanumeric
|
|
126
|
+
function resolveWorkflowAvailability(input) {
|
|
127
|
+
if (!isPlainObject(input)) {
|
|
128
|
+
return { disabled: true, reason: 'workflow_availability_unknown' };
|
|
129
|
+
}
|
|
130
|
+
const env = isPlainObject(input['env']) ? input['env'] : {};
|
|
131
|
+
const raw = env['CLAUDE_CODE_DISABLE_WORKFLOWS'];
|
|
132
|
+
if (typeof raw === 'string') {
|
|
133
|
+
const v = raw.trim().toLowerCase();
|
|
134
|
+
if (v !== '' && v !== '0' && v !== 'false') {
|
|
135
|
+
return { disabled: true, reason: 'workflows_disabled_by_env' };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const sources = input['sources'];
|
|
139
|
+
if (!Array.isArray(sources)) {
|
|
140
|
+
return { disabled: true, reason: 'workflow_availability_unknown' };
|
|
141
|
+
}
|
|
142
|
+
for (const s of sources) {
|
|
143
|
+
if (!isPlainObject(s)
|
|
144
|
+
|| typeof s['scope'] !== 'string' || !SETTINGS_SCOPES.has(s['scope'])
|
|
145
|
+
|| typeof s['status'] !== 'string' || !SETTINGS_STATUSES.has(s['status'])) {
|
|
146
|
+
return { disabled: true, reason: 'workflow_availability_unknown' };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const okSources = [];
|
|
150
|
+
for (const s of sources) {
|
|
151
|
+
if (s['status'] === 'unreadable') {
|
|
152
|
+
return { disabled: true, reason: 'workflow_settings_unreadable' };
|
|
153
|
+
}
|
|
154
|
+
if (s['status'] === 'ok') {
|
|
155
|
+
if (!isPlainObject(s['settings'])) {
|
|
156
|
+
return { disabled: true, reason: 'workflow_settings_unreadable' };
|
|
126
157
|
}
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
okSources.push({ scope: s['scope'], settings: s['settings'] });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const KEYS = ['disableWorkflows', 'enableWorkflows'];
|
|
162
|
+
for (const src of okSources) {
|
|
163
|
+
for (const key of KEYS) {
|
|
164
|
+
if (Object.prototype.hasOwnProperty.call(src.settings, key) && typeof src.settings[key] !== 'boolean') {
|
|
165
|
+
return { disabled: true, reason: 'workflow_settings_unreadable' };
|
|
129
166
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const winner = (key) => {
|
|
170
|
+
for (const src of okSources) {
|
|
171
|
+
if (Object.prototype.hasOwnProperty.call(src.settings, key)) {
|
|
172
|
+
return { scope: src.scope, value: src.settings[key] };
|
|
135
173
|
}
|
|
136
174
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
175
|
+
return null;
|
|
176
|
+
};
|
|
177
|
+
const reasonFor = (scope) => scope === 'managed' ? 'workflows_disabled_by_managed_settings' : 'workflows_disabled_by_settings';
|
|
178
|
+
const dis = winner('disableWorkflows');
|
|
179
|
+
if (dis !== null && dis.value === true) {
|
|
180
|
+
return { disabled: true, reason: reasonFor(dis.scope) };
|
|
181
|
+
}
|
|
182
|
+
const en = winner('enableWorkflows');
|
|
183
|
+
if (en !== null && en.value === false) {
|
|
184
|
+
return { disabled: true, reason: reasonFor(en.scope) };
|
|
141
185
|
}
|
|
142
|
-
return
|
|
186
|
+
return { disabled: false, reason: 'workflows_available' };
|
|
143
187
|
}
|
|
144
188
|
/** Inline result shorthand. */
|
|
145
189
|
function inline(reason, available = false) {
|
|
@@ -153,10 +197,16 @@ function inline(reason, available = false) {
|
|
|
153
197
|
* 2. runtime is Claude (the only runtime that exposes the Workflow tool)
|
|
154
198
|
* 3. execution_backend !== 'inline'
|
|
155
199
|
* 4. host descriptor signals nested+background dispatch (Workflow-tool capable)
|
|
156
|
-
* 5.
|
|
157
|
-
*
|
|
200
|
+
* 5. workflowAvailability is a well-formed resolveWorkflowAvailability result
|
|
201
|
+
* (else `workflow_availability_unknown`)
|
|
202
|
+
* 6. workflowAvailability.disabled is false (else inline with its own reason:
|
|
203
|
+
* env / managed settings / settings kill switch, or unreadable settings)
|
|
158
204
|
* 7. execution_backend === 'workflow' OR 'auto' (both reach here; 'inline' exited at 3)
|
|
159
205
|
*
|
|
206
|
+
* The `claude_orchestration.min_agent_sdk_version` config key is deprecated and
|
|
207
|
+
* ignored (#4747): the Agent SDK version does not reflect whether a Claude Code
|
|
208
|
+
* session has dynamic workflows.
|
|
209
|
+
*
|
|
160
210
|
* Never throws. Destructures defensively.
|
|
161
211
|
*/
|
|
162
212
|
function detectWorkflowBackend(input) {
|
|
@@ -203,15 +253,14 @@ function detectWorkflowBackend(input) {
|
|
|
203
253
|
if (nested !== true || background !== true) {
|
|
204
254
|
return inline('workflow_tool_unavailable');
|
|
205
255
|
}
|
|
206
|
-
// 5.
|
|
207
|
-
|
|
208
|
-
|
|
256
|
+
// 5-6. Claude Code's documented workflow kill switches (#4747).
|
|
257
|
+
const av = input.workflowAvailability;
|
|
258
|
+
if (av === null || typeof av !== 'object' || Array.isArray(av) || typeof av.disabled !== 'boolean') {
|
|
259
|
+
return inline('workflow_availability_unknown');
|
|
209
260
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (compareSemver(input.agentSdkVersion, floor) < 0) {
|
|
214
|
-
return inline('agent_sdk_version_below_floor');
|
|
261
|
+
if (av.disabled) {
|
|
262
|
+
const r = av.reason;
|
|
263
|
+
return inline(typeof r === 'string' && AVAILABILITY_DISABLE_REASONS.has(r) ? r : 'workflow_availability_unknown');
|
|
215
264
|
}
|
|
216
265
|
// 7. auto/workflow both reach the workflow backend once every gate passes.
|
|
217
266
|
return { available: true, backend: 'workflow', reason: 'workflow_backend_active' };
|
|
@@ -294,6 +343,9 @@ function agentOptions(p, executorModel) {
|
|
|
294
343
|
const model = emittableModel(executorModel);
|
|
295
344
|
if (model !== undefined)
|
|
296
345
|
parts.push('model: ' + quoteString(model));
|
|
346
|
+
// #4747: worktree plans must return the validated metadata object.
|
|
347
|
+
if (p.use_worktree !== false)
|
|
348
|
+
parts.push('schema: GSD_WORKTREE_METADATA_SCHEMA');
|
|
297
349
|
return '{ ' + parts.join(', ') + ' }';
|
|
298
350
|
}
|
|
299
351
|
/**
|
|
@@ -395,6 +447,31 @@ function emitWorkflowScript(input) {
|
|
|
395
447
|
const budgetTokens = (typeof input.budgetTokens === 'number' && Number.isFinite(input.budgetTokens) && input.budgetTokens > 0)
|
|
396
448
|
? Math.floor(input.budgetTokens)
|
|
397
449
|
: null;
|
|
450
|
+
// #4747: partition once; enforce the documented Workflow runtime caps at emit
|
|
451
|
+
// time (per-call first, then per-run) instead of failing inside the tool.
|
|
452
|
+
const stagesByWave = waves.map((w) => partitionStages(w.plans));
|
|
453
|
+
for (let wi = 0; wi < waves.length; wi++) {
|
|
454
|
+
const stages = stagesByWave[wi];
|
|
455
|
+
for (let si = 0; si < stages.length; si++) {
|
|
456
|
+
if (stages[si].length > WORKFLOW_MAX_ITEMS_PER_CALL) {
|
|
457
|
+
return {
|
|
458
|
+
ok: false,
|
|
459
|
+
reason: 'wave "' + waves[wi].id + '" stage ' + si + ' has ' + stages[si].length
|
|
460
|
+
+ ' plans — exceeds the Workflow runtime cap of ' + WORKFLOW_MAX_ITEMS_PER_CALL + ' items per parallel() call',
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
let totalPlans = 0;
|
|
466
|
+
for (const w of waves)
|
|
467
|
+
totalPlans += w.plans.length;
|
|
468
|
+
if (totalPlans > WORKFLOW_MAX_AGENTS_PER_RUN) {
|
|
469
|
+
return {
|
|
470
|
+
ok: false,
|
|
471
|
+
reason: 'manifest has ' + totalPlans + ' plans — exceeds the Workflow runtime cap of '
|
|
472
|
+
+ WORKFLOW_MAX_AGENTS_PER_RUN + ' agents per run',
|
|
473
|
+
};
|
|
474
|
+
}
|
|
398
475
|
const lines = [];
|
|
399
476
|
// `export const meta = {…}` MUST be the first statement in the script — the
|
|
400
477
|
// Workflow tool rejects the whole script otherwise (#2590). Leading comments
|
|
@@ -455,39 +532,45 @@ function emitWorkflowScript(input) {
|
|
|
455
532
|
lines.push('// read-only in a Workflow script — set it via the caller\'s token directive.');
|
|
456
533
|
}
|
|
457
534
|
lines.push('');
|
|
458
|
-
// #3302: the generated script
|
|
459
|
-
// to the orchestrator so it can feed the wave merge chain.
|
|
460
|
-
//
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
lines.push('// #3302:
|
|
467
|
-
lines.push('// orchestrator
|
|
535
|
+
// #3302 / #4747: the generated script hands the per-agent executor results
|
|
536
|
+
// back to the orchestrator so it can feed the wave merge chain. Worktree plans
|
|
537
|
+
// pass GSD_WORKTREE_METADATA_SCHEMA to agent(), so the result is a validated
|
|
538
|
+
// object; gsdWorktreeMetadata re-checks the four fields and returns null on
|
|
539
|
+
// any miss (the LOUD-failure input the orchestrator halts on for
|
|
540
|
+
// expects_worktree plans — never a silent skip). gsdRunAgent catches a throw
|
|
541
|
+
// (schema retries exhausted, budget hit) so one plan's failure becomes
|
|
542
|
+
// { error } for that plan only and its siblings still complete.
|
|
543
|
+
lines.push('// #3302 / #4747: worktree plans return schema-validated metadata (an object);');
|
|
544
|
+
lines.push('// the orchestrator records it into WAVE_WORKTREE_MANIFEST after the run');
|
|
468
545
|
lines.push('// (worktree.record-agent -> worktree.cleanup-wave, the same manifest-scoped');
|
|
469
|
-
lines.push('// merge chain inline dispatch feeds). null = absent/
|
|
546
|
+
lines.push('// merge chain inline dispatch feeds). null = absent/invalid/interrupted.');
|
|
547
|
+
lines.push('const GSD_WORKTREE_METADATA_SCHEMA = ' + JSON.stringify(WORKTREE_METADATA_SCHEMA));
|
|
470
548
|
lines.push('function gsdWorktreeMetadata(agentResult) {');
|
|
471
|
-
lines.push(' if (typeof agentResult !== \'
|
|
472
|
-
lines.push(' const
|
|
473
|
-
lines.push('
|
|
549
|
+
lines.push(' if (agentResult === null || typeof agentResult !== \'object\' || Array.isArray(agentResult)) return null;');
|
|
550
|
+
lines.push(' const out = {};');
|
|
551
|
+
lines.push(' for (const k of GSD_WORKTREE_METADATA_SCHEMA.required) {');
|
|
552
|
+
lines.push(' const v = agentResult[k];');
|
|
553
|
+
lines.push(' if (typeof v !== \'string\' || v.length === 0) return null;');
|
|
554
|
+
lines.push(' out[k] = v;');
|
|
555
|
+
lines.push(' }');
|
|
556
|
+
lines.push(' return out;');
|
|
557
|
+
lines.push('}');
|
|
558
|
+
lines.push('function gsdAgentError(agentResult) {');
|
|
559
|
+
lines.push(' return (agentResult !== null && typeof agentResult === \'object\' && typeof agentResult.gsdAgentError === \'string\') ? agentResult.gsdAgentError : null;');
|
|
560
|
+
lines.push('}');
|
|
561
|
+
lines.push('async function gsdRunAgent(brief, opts) {');
|
|
474
562
|
lines.push(' try {');
|
|
475
|
-
lines.push('
|
|
476
|
-
lines.push(' return (parsed !== null && typeof parsed === \'object\') ? parsed : null;');
|
|
563
|
+
lines.push(' return await agent(brief, opts);');
|
|
477
564
|
lines.push(' } catch (e) {');
|
|
478
|
-
lines.push(' return null;');
|
|
565
|
+
lines.push(' return { gsdAgentError: (e !== null && typeof e === \'object\' && typeof e.message === \'string\') ? e.message : String(e) };');
|
|
479
566
|
lines.push(' }');
|
|
480
567
|
lines.push('}');
|
|
481
568
|
lines.push('const gsdAgentOutcomes = [];');
|
|
482
569
|
lines.push('');
|
|
483
|
-
const stagesByWave = [];
|
|
484
|
-
let totalPlans = 0;
|
|
485
570
|
let worktreePlans = 0;
|
|
486
571
|
for (let wi = 0; wi < waves.length; wi++) {
|
|
487
572
|
const wave = waves[wi];
|
|
488
|
-
const stages =
|
|
489
|
-
stagesByWave.push(stages);
|
|
490
|
-
totalPlans += wave.plans.length;
|
|
573
|
+
const stages = stagesByWave[wi];
|
|
491
574
|
for (const p of wave.plans) {
|
|
492
575
|
if (p.use_worktree !== false)
|
|
493
576
|
worktreePlans += 1;
|
|
@@ -512,7 +595,7 @@ function emitWorkflowScript(input) {
|
|
|
512
595
|
// worktree-wf_* branch: the merge chain had no input (#3302).
|
|
513
596
|
lines.push('const gsdStage_' + wi + '_' + si + ' = await parallel([');
|
|
514
597
|
for (const p of stagePlans) {
|
|
515
|
-
lines.push(' () =>
|
|
598
|
+
lines.push(' () => gsdRunAgent(' + quoteString(p.brief) + ', ' + agentOptions(p, executorModel) + '),');
|
|
516
599
|
}
|
|
517
600
|
lines.push('])');
|
|
518
601
|
// Positional tagging is decided at EMIT time from the validated manifest,
|
|
@@ -523,7 +606,13 @@ function emitWorkflowScript(input) {
|
|
|
523
606
|
for (let pi = 0; pi < stagePlans.length; pi++) {
|
|
524
607
|
const p = stagePlans[pi];
|
|
525
608
|
const tail = pi < stagePlans.length - 1 ? ',' : '';
|
|
526
|
-
|
|
609
|
+
const ref = 'gsdStage_' + wi + '_' + si + '[' + pi + ']';
|
|
610
|
+
if (p.use_worktree !== false) {
|
|
611
|
+
lines.push(' { plan: ' + quoteString(p.id) + ', expects_worktree: true, metadata: gsdWorktreeMetadata(' + ref + '), error: gsdAgentError(' + ref + ') }' + tail);
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
lines.push(' { plan: ' + quoteString(p.id) + ', expects_worktree: false, metadata: null, error: gsdAgentError(' + ref + ') }' + tail);
|
|
615
|
+
}
|
|
527
616
|
}
|
|
528
617
|
lines.push(')');
|
|
529
618
|
}
|
|
@@ -531,10 +620,11 @@ function emitWorkflowScript(input) {
|
|
|
531
620
|
lines.push('');
|
|
532
621
|
}
|
|
533
622
|
// #3302: the script's top-level return value is what the Workflow tool hands
|
|
534
|
-
// back to the orchestrator. One { plan, expects_worktree, metadata }
|
|
535
|
-
// per dispatched plan; the orchestrator records every worktree entry via
|
|
623
|
+
// back to the orchestrator. One { plan, expects_worktree, metadata, error }
|
|
624
|
+
// entry per dispatched plan; the orchestrator records every worktree entry via
|
|
536
625
|
// `gsd_run query worktree.record-agent` and HALTS on a null metadata entry
|
|
537
|
-
// for an expects_worktree plan (see
|
|
626
|
+
// for an expects_worktree plan (see
|
|
627
|
+
// capabilities/claude-orchestration/docs/workflow-backend-dispatch.md) — a
|
|
538
628
|
// silently-empty manifest is the exact #3302 failure mode.
|
|
539
629
|
lines.push('return gsdAgentOutcomes');
|
|
540
630
|
const script = lines.join('\n');
|
|
@@ -586,7 +676,7 @@ function resolveWaveDispatch(input) {
|
|
|
586
676
|
runtimeId: input.runtimeId,
|
|
587
677
|
hostIntegration: input.hostIntegration,
|
|
588
678
|
config: input.config,
|
|
589
|
-
|
|
679
|
+
workflowAvailability: input.workflowAvailability,
|
|
590
680
|
});
|
|
591
681
|
if (detected.backend !== 'workflow') {
|
|
592
682
|
return { backend: 'inline', reason: detected.reason };
|
|
@@ -612,9 +702,11 @@ module.exports = {
|
|
|
612
702
|
detectWorkflowBackend,
|
|
613
703
|
emitWorkflowScript,
|
|
614
704
|
resolveWaveDispatch,
|
|
615
|
-
|
|
616
|
-
isValidSemver,
|
|
617
|
-
WORKFLOW_TOOL_FLOOR_VERSION,
|
|
705
|
+
resolveWorkflowAvailability,
|
|
618
706
|
BACKEND_VALUES,
|
|
619
707
|
WORKFLOW_RUNTIME,
|
|
708
|
+
DETECT_REASONS,
|
|
709
|
+
WORKFLOW_MAX_ITEMS_PER_CALL,
|
|
710
|
+
WORKFLOW_MAX_AGENTS_PER_RUN,
|
|
711
|
+
WORKTREE_METADATA_SCHEMA,
|
|
620
712
|
};
|