cool-workflow 0.1.95 → 0.1.97
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/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/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/candidate-scoring.js +3 -3
- package/dist/capability-core.js +6 -1
- package/dist/cli/handlers/scheduling.js +7 -1
- package/dist/dispatch.js +12 -6
- package/dist/drive.js +10 -0
- package/dist/evidence-grounding.js +18 -13
- package/dist/execution-backend/agent.js +1 -1
- package/dist/execution-backend/probes.js +22 -6
- package/dist/execution-backend.js +61 -8
- package/dist/mcp-server.js +4 -0
- package/dist/node-snapshot.js +3 -3
- package/dist/onramp.js +2 -0
- package/dist/orchestrator/app-operations.js +6 -0
- package/dist/orchestrator/cli-options.js +8 -2
- package/dist/orchestrator/lifecycle-operations.js +16 -5
- package/dist/orchestrator/migration-operations.js +1 -1
- package/dist/orchestrator.js +26 -1
- package/dist/reclamation.js +8 -2
- package/dist/run-export.js +10 -1
- package/dist/run-registry/derive.js +4 -1
- package/dist/sandbox-profile.js +6 -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/triggers.js +7 -1
- 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 +17 -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/demo.7.md +80 -0
- package/docs/doctor.7.md +97 -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/fix.7.md +44 -0
- package/docs/init.7.md +62 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +4 -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/pipeline-verbs.7.md +93 -0
- package/docs/project-index.md +20 -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/routine.7.md +73 -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/README.md +16 -10
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +5 -3
- package/scripts/agents/agent-adapter-core.js +25 -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/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +10 -3
- package/scripts/children/http-delegate-child.js +2 -1
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/release-flow.js +24 -6
- package/scripts/release-gate.sh +1 -1
|
@@ -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
|
}
|
|
@@ -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.97",
|
|
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.97");
|
|
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.97");
|
|
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.97");
|
|
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`);
|
|
@@ -18,10 +18,17 @@
|
|
|
18
18
|
|
|
19
19
|
const { spawn } = require("node:child_process");
|
|
20
20
|
let raw = "";
|
|
21
|
+
const MAX_STDIN_BYTES = 32 * 1024 * 1024;
|
|
21
22
|
process.stdin.setEncoding("utf8");
|
|
22
|
-
process.stdin.on("data", (d) => (raw += d)
|
|
23
|
+
process.stdin.on("data", (d) => { if (raw.length < MAX_STDIN_BYTES) raw += d; });
|
|
23
24
|
process.stdin.on("end", () => {
|
|
24
|
-
|
|
25
|
+
let jobs;
|
|
26
|
+
try {
|
|
27
|
+
jobs = JSON.parse(raw);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
process.stdout.write(JSON.stringify([{ spawnError: `invalid stdin JSON: ${String(e && e.message || e)}`, exitCode: null, stdout: "" }]));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
25
32
|
if (!jobs.length) { process.stdout.write("[]"); return; }
|
|
26
33
|
const out = new Array(jobs.length);
|
|
27
34
|
let pending = jobs.length;
|
|
@@ -37,7 +44,7 @@ process.stdin.on("end", () => {
|
|
|
37
44
|
};
|
|
38
45
|
let child;
|
|
39
46
|
try {
|
|
40
|
-
child = spawn(job.binary, job.args, { cwd: job.cwd, env: process.env, shell: false });
|
|
47
|
+
child = spawn(job.binary, job.args, { cwd: job.cwd, env: job.env || process.env, shell: false });
|
|
41
48
|
} catch (error) {
|
|
42
49
|
settle({ spawnError: String((error && error.message) || error), exitCode: null, stdout: "" });
|
|
43
50
|
return;
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
// API request. Behavior MUST stay byte-identical to the previous embedded string.
|
|
16
16
|
|
|
17
17
|
(async () => {
|
|
18
|
-
const
|
|
18
|
+
const MAX_STDIN_BYTES = 32 * 1024 * 1024;
|
|
19
|
+
const read = () => new Promise((res) => { let b = ""; process.stdin.on("data", (c) => { if (b.length < MAX_STDIN_BYTES) b += c; }); process.stdin.on("end", () => res(b)); });
|
|
19
20
|
try {
|
|
20
21
|
const job = JSON.parse((await read()) || "{}");
|
|
21
22
|
const endpoint = process.env.CW_DELEGATE_ENDPOINT;
|
|
@@ -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.97";
|
|
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.97");
|
|
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.97 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.97");
|
|
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\.97/);
|
|
199
199
|
assert.match(report, /## Candidates/);
|
|
200
200
|
assert.match(report, /## Trust Audit/);
|
|
201
201
|
assert.match(report, /## Acceptance Rationale/);
|
package/scripts/release-flow.js
CHANGED
|
@@ -154,6 +154,11 @@ function runVendorPreflight() {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// ---- 2. independent reviewer, delegated to the configured agent -------------
|
|
157
|
+
// Default reviewer deadline. The zero-trust reviewer re-runs release-gate.sh,
|
|
158
|
+
// whose sequential test suite alone is ~12 min, then reads + reasons over the
|
|
159
|
+
// diff — so a 10-min default guaranteed a timeout on a real release. 30 min gives
|
|
160
|
+
// headroom; override with CW_AGENT_TIMEOUT_MS (or --agent-timeout-ms).
|
|
161
|
+
const REVIEWER_TIMEOUT_MS = 1800000;
|
|
157
162
|
function reviewerPromptBody() {
|
|
158
163
|
// Reuse the committed reviewer spec as the prompt; strip YAML frontmatter.
|
|
159
164
|
const specPath = path.join(pluginRoot, "agents", "release-reviewer.md");
|
|
@@ -280,7 +285,7 @@ function delegateReview(resultPath, inputPath) {
|
|
|
280
285
|
cwd: repoRoot,
|
|
281
286
|
env: { ...process.env },
|
|
282
287
|
encoding: "utf8",
|
|
283
|
-
timeout: cfg.timeoutMs ||
|
|
288
|
+
timeout: cfg.timeoutMs || REVIEWER_TIMEOUT_MS,
|
|
284
289
|
shell: false,
|
|
285
290
|
stdio: ["ignore", "pipe", "inherit"],
|
|
286
291
|
maxBuffer: 32 * 1024 * 1024
|
|
@@ -310,7 +315,7 @@ function delegateReview(resultPath, inputPath) {
|
|
|
310
315
|
say(`[2/3] reviewer — POSTing to endpoint ${cfg.endpoint} (model: ${cfg.model || "unreported"})`);
|
|
311
316
|
const body = JSON.stringify({ prompt: subMap.prompt, model: cfg.model, sha: HEAD });
|
|
312
317
|
const lib = cfg.endpoint.startsWith("https:") ? https : http;
|
|
313
|
-
const text = postSync(lib, cfg.endpoint, body, cfg.timeoutMs ||
|
|
318
|
+
const text = postSync(lib, cfg.endpoint, body, cfg.timeoutMs || REVIEWER_TIMEOUT_MS);
|
|
314
319
|
if (text === null) die("reviewer endpoint call failed — no verdict trusted.");
|
|
315
320
|
fs.writeFileSync(resultPath, text.endsWith("\n") ? text : `${text}\n`);
|
|
316
321
|
}
|
|
@@ -502,15 +507,28 @@ function cut(resultPath, capability) {
|
|
|
502
507
|
const bump = spawnSync("npm", ["run", "bump:version", "--", cutVersion], { cwd: pluginRoot, encoding: "utf8", stdio: "inherit" });
|
|
503
508
|
if (bump.status !== 0) die("bump:version failed");
|
|
504
509
|
// Regenerate the gated project index after the version bump (PR #87 gate).
|
|
505
|
-
|
|
506
|
-
|
|
510
|
+
// Fail closed: a failed regen must not bake a stale index into the immutable tag.
|
|
511
|
+
const sync = spawnSync("npm", ["run", "sync:project-index", "--", "--repo-only"], { cwd: pluginRoot, encoding: "utf8", stdio: "inherit" });
|
|
512
|
+
if (sync.status !== 0) die("sync:project-index failed — refusing to cut with a stale project index");
|
|
513
|
+
// Stage ONLY tracked-file modifications (the bump surfaces, project-index, dist)
|
|
514
|
+
// plus the ONE intended new file: the reviewer verdict. NEVER `git add -A` — an
|
|
515
|
+
// untracked stray (e.g. the reviewer's narration transcript, which carries the
|
|
516
|
+
// operator's local home path) must never ride into the immutable tag commit
|
|
517
|
+
// (that tripped pii-redaction-smoke and red-failed release-gate for v0.1.96).
|
|
518
|
+
// `git add -u` touches tracked files only, so no untracked file can be swept in;
|
|
519
|
+
// the verdict is the single new path the cut is allowed to add.
|
|
520
|
+
git(["add", "-u"]);
|
|
521
|
+
git(["add", "--", path.relative(repoRoot, resultPath)]);
|
|
507
522
|
const commit = git(["commit", "-m", `chore(release): record APPROVED reviewer verdict for v${cutVersion}`]);
|
|
508
523
|
if (commit.code !== 0) die("verdict commit failed", commit.err);
|
|
509
524
|
const tag = git(["tag", "-a", `v${cutVersion}`, "-m", `v${cutVersion}: ${capability || "release"}`]);
|
|
510
525
|
if (tag.code !== 0) die("git tag failed", tag.err);
|
|
511
526
|
if (PUSH) {
|
|
512
|
-
|
|
513
|
-
|
|
527
|
+
// Atomic: the verdict commit on HEAD and the tag land together or not at all.
|
|
528
|
+
// A non-atomic two-push could leave main advanced with no tag, so CI's
|
|
529
|
+
// release-gate (which fires on the tag) never runs and the release silently stalls.
|
|
530
|
+
const push = git(["push", "--atomic", "origin", "HEAD", `v${cutVersion}`]);
|
|
531
|
+
if (push.code !== 0) die("atomic push of HEAD + tag failed (nothing partially pushed)", push.err);
|
|
514
532
|
}
|
|
515
533
|
say(`tagged v${cutVersion}${PUSH ? " and pushed" : " (local only; push when ready)"}`);
|
|
516
534
|
// Finishing step: create the GitHub Release for the just-pushed tag. Only when
|
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"
|