cool-workflow 0.1.94 → 0.1.96
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/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +4 -0
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review/workflow.js +3 -3
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pdca-blackboard-loop/app.json +45 -0
- package/apps/pdca-blackboard-loop/workflow.js +59 -0
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/agent-config.js +2 -1
- package/dist/cli/command-surface.js +3 -1
- package/dist/dispatch.js +12 -6
- package/dist/evidence-grounding.js +18 -13
- package/dist/execution-backend/probes.js +22 -6
- package/dist/execution-backend.js +37 -4
- package/dist/node-snapshot.js +3 -3
- package/dist/orchestrator/lifecycle-operations.js +13 -5
- package/dist/orchestrator.js +28 -2
- package/dist/reclamation.js +8 -2
- package/dist/run-registry/derive.js +4 -1
- package/dist/scheduler.js +14 -14
- package/dist/schema-validate.js +8 -2
- package/dist/state-explosion/helpers.js +4 -21
- package/dist/state-explosion/size.js +63 -0
- package/dist/state-explosion.js +18 -71
- package/dist/state.js +47 -9
- package/dist/trust-audit.js +27 -2
- package/dist/util/fingerprint.js +19 -0
- package/dist/util/fingerprint.test.js +27 -0
- package/dist/version.js +1 -1
- package/dist/workbench-host.js +11 -0
- package/dist/workbench.js +19 -17
- package/dist/worker-isolation.js +25 -1
- package/docs/agent-delegation-drive.7.md +66 -1
- package/docs/cli-mcp-parity.7.md +4 -0
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +12 -0
- package/docs/multi-agent-eval-replay-harness.7.md +4 -0
- package/docs/multi-agent-operator-ux.7.md +4 -0
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/project-index.md +11 -4
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +20 -0
- package/docs/routines.md +30 -0
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/sandbox-profiles.7.md +15 -0
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +4 -0
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +8 -5
- package/scripts/agents/agent-adapter-core.js +22 -1
- package/scripts/agents/claude-p-agent.js +10 -2
- package/scripts/agents/codex-agent.js +22 -2
- package/scripts/agents/gemini-agent.js +10 -2
- package/scripts/agents/opencode-agent.js +10 -2
- package/scripts/bump-version.js +10 -0
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/release-check.js +7 -1
- package/scripts/release-flow.js +44 -3
- package/scripts/release-gate.sh +1 -1
- package/scripts/version-sync-check.js +2 -0
|
@@ -34,7 +34,7 @@ const { spawn, spawnSync } = require("node:child_process");
|
|
|
34
34
|
// wrappers instead of carrying a private copy. A drifted inline copy (ASCII
|
|
35
35
|
// hyphens silently became em-dashes here) meant claude was sent a different
|
|
36
36
|
// instruction text than the other providers for the same contract.
|
|
37
|
-
const { buildPrompt, createRenderer, toolLabel, summarizeToolResult } = require("./agent-adapter-core");
|
|
37
|
+
const { buildPrompt, createRenderer, persistStderr, toolLabel, summarizeToolResult } = require("./agent-adapter-core");
|
|
38
38
|
|
|
39
39
|
const inputPath = process.argv[2];
|
|
40
40
|
const resultPath = process.argv[3];
|
|
@@ -56,11 +56,14 @@ if (!streamEnabled) {
|
|
|
56
56
|
shell: false
|
|
57
57
|
});
|
|
58
58
|
if (child.error) {
|
|
59
|
+
persistStderr(resultPath, `claude spawn failed: ${child.error.message}`);
|
|
59
60
|
process.stderr.write(`claude spawn failed: ${child.error.message}\n`);
|
|
60
61
|
process.exit(1);
|
|
61
62
|
}
|
|
62
63
|
if (child.status !== 0) {
|
|
63
|
-
|
|
64
|
+
const detail = String(child.stderr || `claude exited ${child.status}`);
|
|
65
|
+
persistStderr(resultPath, detail);
|
|
66
|
+
process.stderr.write(detail);
|
|
64
67
|
process.exit(child.status === null ? 1 : child.status);
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -69,6 +72,7 @@ if (!streamEnabled) {
|
|
|
69
72
|
try {
|
|
70
73
|
parsed = JSON.parse(out);
|
|
71
74
|
} catch (error) {
|
|
75
|
+
persistStderr(resultPath, `claude output was not JSON: ${error.message}`);
|
|
72
76
|
process.stderr.write(`claude output was not JSON: ${error.message}\n`);
|
|
73
77
|
process.exit(1);
|
|
74
78
|
}
|
|
@@ -166,6 +170,7 @@ function renderEvent(ev) {
|
|
|
166
170
|
|
|
167
171
|
child.on("error", (err) => {
|
|
168
172
|
render.finishLive(); // restore the terminal before exiting
|
|
173
|
+
persistStderr(resultPath, `claude spawn failed: ${err.message}`);
|
|
169
174
|
process.stderr.write(`claude spawn failed: ${err.message}\n`);
|
|
170
175
|
process.exit(1);
|
|
171
176
|
});
|
|
@@ -174,12 +179,15 @@ child.on("close", (code) => {
|
|
|
174
179
|
render.finishLive(); // stop the spinner + restore the cursor BEFORE any further output
|
|
175
180
|
render.writeTranscript(transcriptPath); // full narration + tool I/O always saved
|
|
176
181
|
if (code !== 0) {
|
|
182
|
+
const detail = childStderr.trim() || `claude exited ${code === null ? "(timeout/killed)" : code}`;
|
|
183
|
+
persistStderr(resultPath, detail);
|
|
177
184
|
if (childStderr.trim()) process.stderr.write(`${childStderr.trim()}\n`);
|
|
178
185
|
process.stderr.write(`claude exited ${code === null ? "(timeout/killed)" : code}\n`);
|
|
179
186
|
process.exit(code === null ? 1 : code);
|
|
180
187
|
}
|
|
181
188
|
if (typeof resultText !== "string") {
|
|
182
189
|
// Fail closed: no result event ⇒ no result.md ⇒ CW records a failed hop.
|
|
190
|
+
persistStderr(resultPath, childStderr.trim() || "claude produced no result event — refusing to fabricate a result");
|
|
183
191
|
process.stderr.write("claude produced no result event — refusing to fabricate a result\n");
|
|
184
192
|
process.exit(1);
|
|
185
193
|
}
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
//
|
|
15
15
|
// stdout: one JSON object { model, usage, result } for CW provenance.
|
|
16
16
|
// stderr: optional live trace when CW_AGENT_STREAM=1 and attached to a TTY.
|
|
17
|
+
//
|
|
18
|
+
// SPEED: codex `exec` inherits the user's ~/.codex/config.toml, including a heavy
|
|
19
|
+
// `model_reasoning_effort` (e.g. "high"), which makes every read/grep turn slow.
|
|
20
|
+
// CW caps it for ITS runs only via `-c model_reasoning_effort=<effort>` — a
|
|
21
|
+
// per-run override that does NOT touch the user's interactive codex. Tune with
|
|
22
|
+
// CW_CODEX_REASONING_EFFORT (default "low"); set it to "medium"/"high" to opt back
|
|
23
|
+
// into more thinking.
|
|
17
24
|
|
|
18
25
|
const fs = require("node:fs");
|
|
19
26
|
const os = require("node:os");
|
|
@@ -25,6 +32,7 @@ const {
|
|
|
25
32
|
emitReport,
|
|
26
33
|
flushJsonLines,
|
|
27
34
|
parseJsonLines,
|
|
35
|
+
persistStderr,
|
|
28
36
|
writeResult
|
|
29
37
|
} = require("./agent-adapter-core");
|
|
30
38
|
|
|
@@ -74,9 +82,14 @@ function recordJsonLine(line) {
|
|
|
74
82
|
|
|
75
83
|
render.action("codex: reading the repo (read-only)…");
|
|
76
84
|
|
|
85
|
+
// Cap codex's reasoning effort for CW runs (speed) — overrides config.toml for
|
|
86
|
+
// THIS invocation only. Default "low"; CW_CODEX_REASONING_EFFORT opts back up.
|
|
87
|
+
const effort = process.env.CW_CODEX_REASONING_EFFORT || "low";
|
|
77
88
|
const args = [
|
|
78
89
|
"exec",
|
|
79
90
|
"--json",
|
|
91
|
+
"-c",
|
|
92
|
+
`model_reasoning_effort=${effort}`,
|
|
80
93
|
"--output-last-message",
|
|
81
94
|
finalPath,
|
|
82
95
|
"--sandbox",
|
|
@@ -108,6 +121,7 @@ child.stderr.on("data", (chunk) => {
|
|
|
108
121
|
|
|
109
122
|
child.on("error", (error) => {
|
|
110
123
|
render.finishLive();
|
|
124
|
+
persistStderr(resultPath, `codex spawn failed: ${error.message}`);
|
|
111
125
|
process.stderr.write(`codex spawn failed: ${error.message}\n`);
|
|
112
126
|
process.exit(1);
|
|
113
127
|
});
|
|
@@ -118,11 +132,14 @@ child.on("close", (code) => {
|
|
|
118
132
|
render.writeTranscript(transcriptPath);
|
|
119
133
|
if (code !== 0) {
|
|
120
134
|
const detail = childStderr.trim() || `codex exited ${code === null ? "(timeout/killed)" : code}`;
|
|
135
|
+
persistStderr(resultPath, detail);
|
|
121
136
|
process.stderr.write(`${detail}\n`);
|
|
122
137
|
process.exit(code === null ? 1 : code);
|
|
123
138
|
}
|
|
124
139
|
if (state.invalidJson) {
|
|
125
|
-
|
|
140
|
+
const detail = "codex --json produced a non-JSONL stdout line - refusing to trust the result";
|
|
141
|
+
persistStderr(resultPath, childStderr.trim() || detail);
|
|
142
|
+
process.stderr.write(`${detail}\n`);
|
|
126
143
|
process.exit(1);
|
|
127
144
|
}
|
|
128
145
|
|
|
@@ -130,7 +147,9 @@ child.on("close", (code) => {
|
|
|
130
147
|
try {
|
|
131
148
|
resultText = fs.readFileSync(finalPath, "utf8");
|
|
132
149
|
} catch {
|
|
133
|
-
|
|
150
|
+
const detail = "codex produced no final output file - refusing to fabricate a result";
|
|
151
|
+
persistStderr(resultPath, childStderr.trim() || detail);
|
|
152
|
+
process.stderr.write(`${detail}\n`);
|
|
134
153
|
process.exit(1);
|
|
135
154
|
} finally {
|
|
136
155
|
try {
|
|
@@ -143,6 +162,7 @@ child.on("close", (code) => {
|
|
|
143
162
|
try {
|
|
144
163
|
writeResult(resultPath, resultText);
|
|
145
164
|
} catch (error) {
|
|
165
|
+
persistStderr(resultPath, `codex produced no final result: ${error.message}`);
|
|
146
166
|
process.stderr.write(`codex produced no final result: ${error.message}\n`);
|
|
147
167
|
process.exit(1);
|
|
148
168
|
}
|
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
emitReport,
|
|
23
23
|
flushJsonLines,
|
|
24
24
|
parseJsonLines,
|
|
25
|
+
persistStderr,
|
|
25
26
|
writeResult
|
|
26
27
|
} = require("./agent-adapter-core");
|
|
27
28
|
|
|
@@ -83,6 +84,7 @@ child.stderr.on("data", (chunk) => {
|
|
|
83
84
|
|
|
84
85
|
child.on("error", (error) => {
|
|
85
86
|
render.finishLive();
|
|
87
|
+
persistStderr(resultPath, `gemini spawn failed: ${error.message}`);
|
|
86
88
|
process.stderr.write(`gemini spawn failed: ${error.message}\n`);
|
|
87
89
|
process.exit(1);
|
|
88
90
|
});
|
|
@@ -93,23 +95,29 @@ child.on("close", (code) => {
|
|
|
93
95
|
render.writeTranscript(transcriptPath);
|
|
94
96
|
if (code !== 0) {
|
|
95
97
|
const detail = childStderr.trim() || `gemini exited ${code === null ? "(timeout/killed)" : code}`;
|
|
98
|
+
persistStderr(resultPath, detail);
|
|
96
99
|
process.stderr.write(`${detail}\n`);
|
|
97
100
|
process.exit(code === null ? 1 : code);
|
|
98
101
|
}
|
|
99
102
|
if (state.invalidJson) {
|
|
100
|
-
|
|
103
|
+
const detail = "gemini --output-format stream-json produced a non-JSONL stdout line - refusing to trust the result";
|
|
104
|
+
persistStderr(resultPath, childStderr.trim() || detail);
|
|
105
|
+
process.stderr.write(`${detail}\n`);
|
|
101
106
|
process.exit(1);
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
const resultText = state.finalResult || state.textFragments.join("\n\n");
|
|
105
110
|
if (!resultText.trim()) {
|
|
106
|
-
|
|
111
|
+
const detail = "gemini produced no result text - refusing to fabricate a result";
|
|
112
|
+
persistStderr(resultPath, childStderr.trim() || detail);
|
|
113
|
+
process.stderr.write(`${detail}\n`);
|
|
107
114
|
process.exit(1);
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
try {
|
|
111
118
|
writeResult(resultPath, resultText);
|
|
112
119
|
} catch (error) {
|
|
120
|
+
persistStderr(resultPath, `gemini produced no final result: ${error.message}`);
|
|
113
121
|
process.stderr.write(`gemini produced no final result: ${error.message}\n`);
|
|
114
122
|
process.exit(1);
|
|
115
123
|
}
|
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
emitReport,
|
|
28
28
|
flushJsonLines,
|
|
29
29
|
parseJsonLines,
|
|
30
|
+
persistStderr,
|
|
30
31
|
writeResult
|
|
31
32
|
} = require("./agent-adapter-core");
|
|
32
33
|
|
|
@@ -136,6 +137,7 @@ child.stderr.on("data", (chunk) => {
|
|
|
136
137
|
|
|
137
138
|
child.on("error", (error) => {
|
|
138
139
|
render.finishLive();
|
|
140
|
+
persistStderr(resultPath, `opencode spawn failed: ${error.message}`);
|
|
139
141
|
process.stderr.write(`opencode spawn failed: ${error.message}\n`);
|
|
140
142
|
process.exit(1);
|
|
141
143
|
});
|
|
@@ -146,23 +148,29 @@ child.on("close", (code) => {
|
|
|
146
148
|
render.writeTranscript(transcriptPath);
|
|
147
149
|
if (code !== 0) {
|
|
148
150
|
const detail = childStderr.trim() || `opencode exited ${code === null ? "(timeout/killed)" : code}`;
|
|
151
|
+
persistStderr(resultPath, detail);
|
|
149
152
|
process.stderr.write(`${detail}\n`);
|
|
150
153
|
process.exit(code === null ? 1 : code);
|
|
151
154
|
}
|
|
152
155
|
if (state.invalidJson) {
|
|
153
|
-
|
|
156
|
+
const detail = "opencode --format json produced a non-JSONL stdout line - refusing to trust the result";
|
|
157
|
+
persistStderr(resultPath, childStderr.trim() || detail);
|
|
158
|
+
process.stderr.write(`${detail}\n`);
|
|
154
159
|
process.exit(1);
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
const resultText = state.finalResult || state.lastMessageText || state.textFragments.join("\n\n");
|
|
158
163
|
if (!resultText.trim()) {
|
|
159
|
-
|
|
164
|
+
const detail = "opencode produced no result text - refusing to fabricate a result";
|
|
165
|
+
persistStderr(resultPath, childStderr.trim() || detail);
|
|
166
|
+
process.stderr.write(`${detail}\n`);
|
|
160
167
|
process.exit(1);
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
try {
|
|
164
171
|
writeResult(resultPath, resultText);
|
|
165
172
|
} catch (error) {
|
|
173
|
+
persistStderr(resultPath, `opencode produced no final result: ${error.message}`);
|
|
166
174
|
process.stderr.write(`opencode produced no final result: ${error.message}\n`);
|
|
167
175
|
process.exit(1);
|
|
168
176
|
}
|
package/scripts/bump-version.js
CHANGED
|
@@ -79,6 +79,16 @@ function main() {
|
|
|
79
79
|
const lock = path.join(pluginRoot, "package-lock.json");
|
|
80
80
|
if (fs.existsSync(lock) && replaceFirstVersionField(lock, next)) note("package-lock.json");
|
|
81
81
|
|
|
82
|
+
// 2b. Official MCP Registry server metadata (top-level server version + npm package version).
|
|
83
|
+
const serverJson = path.join(pluginRoot, "server.json");
|
|
84
|
+
if (fs.existsSync(serverJson)) {
|
|
85
|
+
const json = JSON.parse(fs.readFileSync(serverJson, "utf8"));
|
|
86
|
+
json.version = next;
|
|
87
|
+
if (json.packages && json.packages[0]) json.packages[0].version = next;
|
|
88
|
+
fs.writeFileSync(serverJson, `${JSON.stringify(json, null, 2)}\n`);
|
|
89
|
+
note("server.json");
|
|
90
|
+
}
|
|
91
|
+
|
|
82
92
|
// 3. src/version.ts runtime constant
|
|
83
93
|
const versionTs = path.join(pluginRoot, "src", "version.ts");
|
|
84
94
|
const vtsText = fs.readFileSync(versionTs, "utf8");
|
|
@@ -83,7 +83,7 @@ const canonicalApps = [
|
|
|
83
83
|
"--source",
|
|
84
84
|
"plugins/cool-workflow/docs/workflow-app-framework.7.md",
|
|
85
85
|
"--scope",
|
|
86
|
-
"Cool Workflow v0.1.
|
|
86
|
+
"Cool Workflow v0.1.96",
|
|
87
87
|
"--freshness",
|
|
88
88
|
"as of release preparation"
|
|
89
89
|
]
|
|
@@ -117,14 +117,14 @@ function main() {
|
|
|
117
117
|
assert.ok(summary, `${app.id} must appear in app list`);
|
|
118
118
|
assert.equal(summary.sourceKind, "app-directory");
|
|
119
119
|
assert.equal(summary.legacy, false);
|
|
120
|
-
assert.equal(summary.version, "0.1.
|
|
120
|
+
assert.equal(summary.version, "0.1.96");
|
|
121
121
|
|
|
122
122
|
const validation = runJson(["app", "validate", manifestPath]);
|
|
123
123
|
assert.equal(validation.valid, true, `${app.id} manifest must validate`);
|
|
124
124
|
|
|
125
125
|
const shown = runJson(["app", "show", app.id]);
|
|
126
126
|
assert.equal(shown.app.id, app.id);
|
|
127
|
-
assert.equal(shown.app.version, "0.1.
|
|
127
|
+
assert.equal(shown.app.version, "0.1.96");
|
|
128
128
|
assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);
|
|
129
129
|
assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);
|
|
130
130
|
assertTaskIdsUnique(shown);
|
|
@@ -135,7 +135,7 @@ function main() {
|
|
|
135
135
|
const plan = runJson(["plan", app.id, ...app.args(workspace)]);
|
|
136
136
|
const state = JSON.parse(fs.readFileSync(plan.statePath, "utf8"));
|
|
137
137
|
assert.equal(state.workflow.app.id, app.id);
|
|
138
|
-
assert.equal(state.workflow.app.version, "0.1.
|
|
138
|
+
assert.equal(state.workflow.app.version, "0.1.96");
|
|
139
139
|
assert.equal(state.workflow.app.metadata.canonical, true);
|
|
140
140
|
assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);
|
|
141
141
|
assert.ok(state.tasks.every((task) => task.sandboxProfileId), `${app.id} plan must include sandbox hints`);
|
|
@@ -6,7 +6,7 @@ const fs = require("node:fs");
|
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
const { CoolWorkflowRunner } = require("../dist/orchestrator.js");
|
|
8
8
|
|
|
9
|
-
const TARGET_VERSION = "0.1.
|
|
9
|
+
const TARGET_VERSION = "0.1.96";
|
|
10
10
|
const PREVIOUS_VERSION = "0.1.31";
|
|
11
11
|
const pluginRoot = path.resolve(__dirname, "..");
|
|
12
12
|
const repoRoot = path.resolve(pluginRoot, "..", "..");
|
package/scripts/golden-path.js
CHANGED
|
@@ -33,7 +33,7 @@ function main() {
|
|
|
33
33
|
const appValidation = runJson(["app", "validate", "end-to-end-golden-path"], pluginRoot);
|
|
34
34
|
assert.equal(appValidation.valid, true);
|
|
35
35
|
assert.equal(appValidation.summary.id, "end-to-end-golden-path");
|
|
36
|
-
assert.equal(appValidation.summary.version, "0.1.
|
|
36
|
+
assert.equal(appValidation.summary.version, "0.1.96");
|
|
37
37
|
|
|
38
38
|
const plan = runJson(
|
|
39
39
|
[
|
|
@@ -42,7 +42,7 @@ function main() {
|
|
|
42
42
|
"--repo",
|
|
43
43
|
tmp,
|
|
44
44
|
"--question",
|
|
45
|
-
"Prove the deterministic v0.1.
|
|
45
|
+
"Prove the deterministic v0.1.96 end-to-end golden path."
|
|
46
46
|
],
|
|
47
47
|
pluginRoot
|
|
48
48
|
);
|
|
@@ -52,7 +52,7 @@ function main() {
|
|
|
52
52
|
|
|
53
53
|
let state = readJson(plan.statePath);
|
|
54
54
|
assert.equal(state.workflow.app.id, "end-to-end-golden-path");
|
|
55
|
-
assert.equal(state.workflow.app.version, "0.1.
|
|
55
|
+
assert.equal(state.workflow.app.version, "0.1.96");
|
|
56
56
|
assert.equal(state.loopStage, "interpret");
|
|
57
57
|
|
|
58
58
|
const dispatch = runJson(["dispatch", plan.runId, "--limit", "1", "--sandbox", "readonly"], tmp);
|
|
@@ -195,7 +195,7 @@ function main() {
|
|
|
195
195
|
assert.equal(reportPath, plan.reportPath);
|
|
196
196
|
assert.ok(fs.existsSync(reportPath));
|
|
197
197
|
const report = fs.readFileSync(reportPath, "utf8");
|
|
198
|
-
assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.
|
|
198
|
+
assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.96/);
|
|
199
199
|
assert.match(report, /## Candidates/);
|
|
200
200
|
assert.match(report, /## Trust Audit/);
|
|
201
201
|
assert.match(report, /## Acceptance Rationale/);
|
package/scripts/release-check.js
CHANGED
|
@@ -7,6 +7,7 @@ const path = require("node:path");
|
|
|
7
7
|
|
|
8
8
|
const pluginRoot = path.resolve(__dirname, "..");
|
|
9
9
|
const repoRoot = path.resolve(pluginRoot, "..", "..");
|
|
10
|
+
const skipTests = process.argv.includes("--skip-tests") || process.env.CW_RELEASE_CHECK_SKIP_TESTS === "1";
|
|
10
11
|
const checks = [
|
|
11
12
|
{
|
|
12
13
|
name: "docs presence",
|
|
@@ -87,6 +88,11 @@ function main() {
|
|
|
87
88
|
process.stdout.write(`release:check ${check.name} ... `);
|
|
88
89
|
const started = Date.now();
|
|
89
90
|
try {
|
|
91
|
+
if (skipTests && check.name === "tests") {
|
|
92
|
+
results.push({ name: check.name, ok: true, skipped: true, elapsedMs: 0 });
|
|
93
|
+
process.stdout.write("skipped\n");
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
90
96
|
if (check.run) check.run();
|
|
91
97
|
else runCommand(check.command);
|
|
92
98
|
const elapsedMs = Date.now() - started;
|
|
@@ -103,7 +109,7 @@ function main() {
|
|
|
103
109
|
const failed = results.filter((entry) => !entry.ok);
|
|
104
110
|
process.stdout.write("\nRelease Check Summary\n");
|
|
105
111
|
for (const result of results) {
|
|
106
|
-
process.stdout.write(`- ${result.ok ? "PASS" : "FAIL"} ${result.name}\n`);
|
|
112
|
+
process.stdout.write(`- ${result.skipped ? "SKIP" : result.ok ? "PASS" : "FAIL"} ${result.name}\n`);
|
|
107
113
|
}
|
|
108
114
|
process.stdout.write(`\nDry-run only: no tag, push, publish, or fixture mutation was requested.\n`);
|
|
109
115
|
if (failed.length > 0) {
|
package/scripts/release-flow.js
CHANGED
|
@@ -185,6 +185,11 @@ function buildReviewerInput(resultPath) {
|
|
|
185
185
|
"or:",
|
|
186
186
|
" REJECTED",
|
|
187
187
|
" <numbered gate failures with file:line references>",
|
|
188
|
+
"",
|
|
189
|
+
"If your host wrapper asks for Markdown, JSON, or a fenced result block, still put",
|
|
190
|
+
"the exact APPROVED/REJECTED verdict line above as a standalone line outside any",
|
|
191
|
+
"prose or code fence. The release tool extracts only strict standalone verdict",
|
|
192
|
+
"lines; prose such as 'Verdict: APPROVED' is not trusted.",
|
|
188
193
|
""
|
|
189
194
|
].join("\n");
|
|
190
195
|
}
|
|
@@ -250,14 +255,28 @@ function delegateReview(resultPath, inputPath) {
|
|
|
250
255
|
};
|
|
251
256
|
|
|
252
257
|
if (cfg.command) {
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
// cfg.command can arrive as a SINGLE unsplit multi-token string. A
|
|
259
|
+
// builtin:<vendor> template (CW_AGENT_COMMAND=builtin:claude) expands to
|
|
260
|
+
// "node /abs/claude-p-agent.js {{input}} {{result}}" with cfg.args undefined,
|
|
261
|
+
// and a durable agent-config.json command field is read verbatim (no split).
|
|
262
|
+
// Split it on whitespace into binary + inline args — the SAME way the
|
|
263
|
+
// orchestrator does (src/execution-backend/agent.ts resolveAgentInvocation) —
|
|
264
|
+
// so we spawn the real binary, never one literally named
|
|
265
|
+
// "node /abs/... {{input}}" (an instant ENOENT the old code mislabeled as a
|
|
266
|
+
// timeout). An explicit cfg.args array still follows the inline args. A plain
|
|
267
|
+
// CW_AGENT_COMMAND="claude -p {{input}}" is already pre-split by
|
|
268
|
+
// resolveAgentConfig, so this leaves it unchanged (no inline args).
|
|
269
|
+
const parts = cfg.command.split(/\s+/).filter(Boolean);
|
|
270
|
+
const bin = parts[0];
|
|
271
|
+
const argTemplate = [...parts.slice(1), ...(cfg.args || [])];
|
|
272
|
+
const args = argTemplate.map((a) => substitute(a, subMap));
|
|
273
|
+
say(`[2/3] reviewer — delegating to: ${bin} ${argTemplate.join(" ")} (model: ${cfg.model || "unreported"})`);
|
|
255
274
|
// RED LINE: argv-style, shell:false. The agent runs the model in its own
|
|
256
275
|
// process and inherits its own credentials; CW holds none.
|
|
257
276
|
// Capture stdout so agents that print verdicts (rather than writing the
|
|
258
277
|
// result file) are supported. Agents that DO write the file still work:
|
|
259
278
|
// the file takes precedence. stderr goes to the terminal for live output.
|
|
260
|
-
const r = spawnSync(
|
|
279
|
+
const r = spawnSync(bin, args, {
|
|
261
280
|
cwd: repoRoot,
|
|
262
281
|
env: { ...process.env },
|
|
263
282
|
encoding: "utf8",
|
|
@@ -266,6 +285,10 @@ function delegateReview(resultPath, inputPath) {
|
|
|
266
285
|
stdio: ["ignore", "pipe", "inherit"],
|
|
267
286
|
maxBuffer: 32 * 1024 * 1024
|
|
268
287
|
});
|
|
288
|
+
// A spawn that never started (ENOENT for a missing binary, ETIMEDOUT on the
|
|
289
|
+
// deadline) sets r.error — surface its real message instead of the old
|
|
290
|
+
// catch-all "(timeout/no-exit)" that hid the builtin: ENOENT for so long.
|
|
291
|
+
if (r.error) die(`reviewer agent could not be spawned (${bin}): ${r.error.message} — no verdict trusted.`);
|
|
269
292
|
if (r.status !== 0) die(`reviewer agent exited ${r.status === null ? "(timeout/no-exit)" : r.status} — no verdict trusted.`);
|
|
270
293
|
// If the agent already wrote the verdict file (backward compat), use it.
|
|
271
294
|
// Otherwise extract the verdict from stdout (headless agent path).
|
|
@@ -316,6 +339,14 @@ function verifyVerdict(resultPath) {
|
|
|
316
339
|
const lines = text.split(/\r?\n/);
|
|
317
340
|
const firstLine = lines[0] || "";
|
|
318
341
|
if (firstLine !== `APPROVED ${HEAD}`) {
|
|
342
|
+
if (firstLine.toUpperCase() !== "REJECTED") {
|
|
343
|
+
const normalized = extractVerdictFromStdout(text, resultPath);
|
|
344
|
+
if (normalized && normalized.split(/\r?\n/)[0] === `APPROVED ${HEAD}`) {
|
|
345
|
+
fs.writeFileSync(resultPath, `${normalized}\n`);
|
|
346
|
+
const normalizedLines = normalized.split(/\r?\n/);
|
|
347
|
+
return (normalizedLines[1] || "").trim();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
319
350
|
die(`verdict first line must be exactly "APPROVED ${HEAD}" — release blocked.`, text.trim());
|
|
320
351
|
}
|
|
321
352
|
const cap = (lines[1] || "").trim();
|
|
@@ -472,6 +503,16 @@ function cut(resultPath, capability) {
|
|
|
472
503
|
if (bump.status !== 0) die("bump:version failed");
|
|
473
504
|
// Regenerate the gated project index after the version bump (PR #87 gate).
|
|
474
505
|
spawnSync("npm", ["run", "sync:project-index", "--", "--repo-only"], { cwd: pluginRoot, stdio: "inherit" });
|
|
506
|
+
// Belt-and-suspenders: the reviewer agent writes a narration transcript into
|
|
507
|
+
// .cw-release/ that may carry local paths (the operator's home dir). It is
|
|
508
|
+
// .gitignored, but `git add -A` would still stage it if it were ever tracked,
|
|
509
|
+
// and a tracked transcript leaks PII into the immutable tag commit (it tripped
|
|
510
|
+
// pii-redaction-smoke and red-failed release-gate for v0.1.96). Remove any
|
|
511
|
+
// transcript before staging so it can never ride into the tag.
|
|
512
|
+
const releaseDir = path.join(repoRoot, ".cw-release");
|
|
513
|
+
for (const f of fs.existsSync(releaseDir) ? fs.readdirSync(releaseDir) : []) {
|
|
514
|
+
if (/^transcript.*\.md$/.test(f)) fs.rmSync(path.join(releaseDir, f), { force: true });
|
|
515
|
+
}
|
|
475
516
|
git(["add", "-A"]);
|
|
476
517
|
const commit = git(["commit", "-m", `chore(release): record APPROVED reviewer verdict for v${cutVersion}`]);
|
|
477
518
|
if (commit.code !== 0) die("verdict commit failed", commit.err);
|
package/scripts/release-gate.sh
CHANGED
|
@@ -30,7 +30,7 @@ say "[1/6] build"
|
|
|
30
30
|
npm run --prefix plugins/cool-workflow build >/dev/null 2>&1 || fail "build failed"
|
|
31
31
|
|
|
32
32
|
say "[2/6] tests"
|
|
33
|
-
CW_TEST_CONCURRENCY=1 npm test --prefix plugins/cool-workflow >/dev/null 2>&1 || fail "tests failed"
|
|
33
|
+
CW_TEST_CONCURRENCY=1 npm run test:gate --prefix plugins/cool-workflow >/dev/null 2>&1 || fail "tests failed"
|
|
34
34
|
|
|
35
35
|
if [[ -n "$PREV_TAG" ]]; then
|
|
36
36
|
RANGE="$PREV_TAG..HEAD"
|
|
@@ -60,6 +60,8 @@ function main() {
|
|
|
60
60
|
// uses `npm install --no-package-lock`), so only validate it when present.
|
|
61
61
|
checkJsonIfPresent("plugins/cool-workflow/package-lock.json", "version", VERSION, checks);
|
|
62
62
|
checkJson("plugins/cool-workflow/.codex-plugin/plugin.json", "version", VERSION, checks);
|
|
63
|
+
checkJson("plugins/cool-workflow/server.json", "version", VERSION, checks);
|
|
64
|
+
checkNestedJson("plugins/cool-workflow/server.json", ["packages", 0, "version"], VERSION, checks);
|
|
63
65
|
checkNestedJson("plugins/cool-workflow/manifest/plugin.manifest.json", ["identity", "version"], VERSION, checks);
|
|
64
66
|
checkJson("plugins/cool-workflow/.claude-plugin/plugin.json", "version", VERSION, checks);
|
|
65
67
|
checkJson("plugins/cool-workflow/.gemini-plugin/plugin.json", "version", VERSION, checks);
|