cool-workflow 0.2.3 → 0.2.5
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/.gemini-plugin/mcp.json +10 -0
- package/.gemini-plugin/plugin.json +40 -0
- package/.opencode-plugin/mcp.json +10 -0
- package/.opencode-plugin/plugin.json +40 -0
- package/README.md +202 -48
- package/apps/architecture-review/app.json +1 -1
- 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/cli/dispatch.js +33 -4
- package/dist/cli/entry.js +11 -19
- package/dist/cli/global-flags.js +113 -0
- package/dist/cli/io.js +6 -20
- package/dist/cli/parseargv.js +7 -1
- package/dist/core/capability-data.js +339 -10
- package/dist/core/format/help.js +80 -4
- package/dist/core/format/recovery-hint.js +32 -0
- package/dist/core/multi-agent/collaboration.js +35 -6
- package/dist/core/multi-agent/runtime.js +7 -0
- package/dist/core/multi-agent/trust-policy.js +7 -1
- package/dist/core/pipeline/contract.js +7 -0
- package/dist/core/pipeline/error-feedback.js +2 -2
- package/dist/core/trust/evidence-grounding.js +13 -1
- package/dist/core/util/cli-args.js +55 -0
- package/dist/core/version.js +1 -1
- package/dist/mcp/dispatch.js +22 -2
- package/dist/mcp/server.js +124 -13
- package/dist/mcp-server.js +20 -0
- package/dist/shell/commit.js +8 -2
- package/dist/shell/coordinator-io.js +73 -1
- package/dist/shell/drive.js +129 -64
- package/dist/shell/error-feedback-io.js +6 -0
- package/dist/shell/execution-backend/agent.js +205 -24
- package/dist/shell/execution-backend/container.js +44 -10
- package/dist/shell/execution-backend/local.js +47 -10
- package/dist/shell/fs-atomic.js +93 -12
- package/dist/shell/ledger-cli.js +9 -2
- package/dist/shell/multi-agent-cli.js +5 -1
- package/dist/shell/onramp.js +48 -5
- package/dist/shell/pipeline-cli.js +20 -2
- package/dist/shell/pipeline.js +2 -1
- package/dist/shell/reclamation-io.js +76 -7
- package/dist/shell/registry-cli.js +4 -0
- package/dist/shell/run-export.js +52 -4
- package/dist/shell/run-registry-io.js +1 -0
- package/dist/shell/run-store.js +156 -0
- package/dist/shell/scheduler-io.js +101 -10
- package/dist/shell/telemetry-ledger-io.js +36 -24
- package/dist/shell/trust-audit.js +104 -10
- package/dist/shell/workbench-host.js +121 -10
- package/dist/shell/workbench.js +79 -5
- package/dist/shell/worker-isolation.js +1 -1
- package/dist/shell/workflow-app-loader.js +67 -1
- package/dist/wiring/capability-table/basics.js +7 -2
- package/dist/wiring/capability-table/exec-backend.js +42 -24
- package/dist/wiring/capability-table/multi-agent.js +69 -69
- package/dist/wiring/capability-table/pipeline.js +60 -28
- package/dist/wiring/capability-table/registry-core.js +29 -6
- package/dist/wiring/capability-table/reporting.js +45 -39
- package/dist/wiring/capability-table/scheduling-registry.js +66 -60
- package/dist/wiring/capability-table/state.js +34 -34
- package/dist/wiring/capability-table/trust-ledger.js +66 -24
- package/dist/wiring/capability-table/workflow-apps.js +15 -15
- package/docs/agent-delegation-drive.7.md +17 -0
- package/docs/cli-mcp-parity.7.md +43 -13
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +38 -0
- package/docs/cross-agent-ledger.7.md +20 -8
- 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 +4 -4
- package/docs/getting-started.md +40 -32
- package/docs/index.md +17 -0
- package/docs/launch/demo.tape +4 -3
- package/docs/mcp-app-surface.7.md +6 -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/project-index.md +43 -9
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +22 -0
- package/docs/routine.7.md +22 -0
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +28 -3
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/trust-audit-anchor.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +26 -2
- package/docs/workflow-app-framework.7.md +31 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +4 -3
- package/package.json +5 -2
- package/scripts/agents/agent-adapter-core.js +26 -0
- package/scripts/agents/claude-p-agent.js +4 -1
- package/scripts/agents/codex-agent.js +4 -0
- package/scripts/agents/cw-attest-wrap.js +1 -1
- package/scripts/agents/gemini-agent.js +4 -0
- package/scripts/agents/opencode-agent.js +5 -0
- package/scripts/block-unapproved-tag.js +160 -0
- package/scripts/bump-version.js +27 -10
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +72 -4
- package/scripts/children/http-batch-delegate-child.js +132 -0
- package/scripts/children/http-delegate-child.js +8 -0
- package/scripts/dogfood-release.js +2 -2
- package/scripts/fake-date-for-reproduction.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/lang-policy-check.js +82 -0
- package/scripts/purity-baseline.json +0 -30
- package/scripts/purity-gate.js +16 -3
- package/scripts/release-check.js +2 -1
- package/scripts/release-flow.js +264 -41
- package/scripts/release-gate.js +184 -0
- package/scripts/release-oneclick.js +438 -0
- package/scripts/release-tags.js +47 -0
- package/scripts/verdict-keygen.js +2 -2
- package/scripts/verify-bump-reproduction.js +193 -0
- package/scripts/verify-verdict-signature.js +1 -1
- package/scripts/version-sync-check.js +39 -22
- package/ui/workbench/app.css +41 -9
- package/ui/workbench/app.js +160 -27
- package/ui/workbench/index.html +2 -2
- package/docs/agent-framework.md +0 -177
- package/docs/designs/handoff-ledger.md +0 -145
- package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +0 -40
- package/docs/launch/launch-kit.md +0 -195
- package/docs/launch/pre-launch-checklist.md +0 -53
- package/docs/readme-v0.1.87-full.md +0 -301
- package/docs/routines.md +0 -101
- package/docs/scheduled-tasks.md +0 -80
- package/scripts/agents/claude-p-agent.sh +0 -9
- package/scripts/block-unapproved-tag.sh +0 -60
- package/scripts/release-gate.sh +0 -94
- package/scripts/verify-bump-reproduction.sh +0 -148
- package/scripts/verify-container-selfref.js +0 -64
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// HTTP batch delegate child — the endpoint-mode sibling of batch-delegate-child.js.
|
|
5
|
+
// Spawned via `node <this-path>` (shell:false) by runEndpointBatchOutcomes when a
|
|
6
|
+
// concurrent drive round (`--concurrency N`) has one or more ENDPOINT-configured
|
|
7
|
+
// agents. Without it, endpoint tasks in a concurrent round each spawned their own
|
|
8
|
+
// blocking http-delegate-child one after another, so `--concurrency N` ran N
|
|
9
|
+
// endpoint delegations strictly SERIALLY. This child POSTs all N at once from a
|
|
10
|
+
// single process (one Node process holds N in-flight fetches trivially — no per-job
|
|
11
|
+
// child process is needed for HTTP the way it is for a vendor CLI).
|
|
12
|
+
//
|
|
13
|
+
// Reads a JSON array `[{ endpoint, job, timeoutMs }, ...]` on stdin, where `job` is
|
|
14
|
+
// the ALREADY-serialized POST body (byte-identical to the string the serial
|
|
15
|
+
// runAgentEndpoint builds, so the concurrent path posts the same bytes). Fires all
|
|
16
|
+
// jobs concurrently, each POST + optional jobId poll matching http-delegate-child.js,
|
|
17
|
+
// and streams ONE NDJSON line per job — `{i, spawnError?, exitCode, stdout}\n` — the
|
|
18
|
+
// INSTANT that job settles. `i` is the job's index (settle order is concurrent, not
|
|
19
|
+
// submission order, so the parent cannot map a line to its job without it). The line
|
|
20
|
+
// shape and the incremental-streaming reason are shared with batch-delegate-child.js:
|
|
21
|
+
// the parent (runEndpointBatchOutcomes) reuses reconcileBatchOutcomes to read them.
|
|
22
|
+
//
|
|
23
|
+
// THE RED LINE: this child speaks ONLY plain HTTP to the operator-configured
|
|
24
|
+
// endpoint. It imports NO model SDK, holds NO API key, and constructs NO model API
|
|
25
|
+
// request. It re-uses the caller's inherited env for any endpoint auth exactly as
|
|
26
|
+
// http-delegate-child.js does; it reads no credential itself.
|
|
27
|
+
|
|
28
|
+
const MAX_STDIN_BYTES = 32 * 1024 * 1024;
|
|
29
|
+
// Per-job caps mirror batch-delegate-child.js: CAP bounds each job's RAW stdout
|
|
30
|
+
// bytes; LINE_CAP bounds the ESCAPED NDJSON line (JSON escaping grows bytes), held
|
|
31
|
+
// under the parent's 34MB-per-job maxBuffer grant with a small margin so one job's
|
|
32
|
+
// line can never push the combined stream past the parent's buffer and ENOBUFS the
|
|
33
|
+
// whole batch.
|
|
34
|
+
const CAP = 32 * 1024 * 1024;
|
|
35
|
+
const LINE_CAP = 33 * 1024 * 1024;
|
|
36
|
+
|
|
37
|
+
// In-flight fetch controllers so a stop signal aborts every open request at once.
|
|
38
|
+
// No grandchild processes exist here (unlike batch-delegate-child.js), so there is
|
|
39
|
+
// no wedged-pipe deadlock to guard — aborting the fetches empties the event loop
|
|
40
|
+
// and the process exits on its own.
|
|
41
|
+
const controllers = new Set();
|
|
42
|
+
let stopSignalReceived = false;
|
|
43
|
+
function onStopSignal(signal) {
|
|
44
|
+
if (stopSignalReceived) {
|
|
45
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
stopSignalReceived = true;
|
|
49
|
+
for (const controller of controllers) {
|
|
50
|
+
try { controller.abort(); } catch {}
|
|
51
|
+
}
|
|
52
|
+
// If aborting does not drain the loop promptly (a hung socket), force-exit.
|
|
53
|
+
setTimeout(() => process.exit(signal === "SIGINT" ? 130 : 143), 5000).unref();
|
|
54
|
+
}
|
|
55
|
+
process.on("SIGINT", () => onStopSignal("SIGINT"));
|
|
56
|
+
process.on("SIGTERM", () => onStopSignal("SIGTERM"));
|
|
57
|
+
|
|
58
|
+
function writeSettleLine(i, outcome) {
|
|
59
|
+
let line = JSON.stringify({ i, ...outcome }) + "\n";
|
|
60
|
+
if (Buffer.byteLength(line) > LINE_CAP) {
|
|
61
|
+
// Same fail-closed shape as the raw-cap path: capped output is never evidence,
|
|
62
|
+
// so drop it entirely and name the cap — never ship a line the parent's buffer
|
|
63
|
+
// cannot hold.
|
|
64
|
+
line = JSON.stringify({ i, spawnError: `serialized stdout line exceeded ${LINE_CAP} byte cap (${Buffer.byteLength(line)} bytes)`, exitCode: null, stdout: "" }) + "\n";
|
|
65
|
+
}
|
|
66
|
+
process.stdout.write(line);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// One job: POST the pre-serialized body, poll a returned jobId until done, and
|
|
70
|
+
// return { exitCode, stdout } or throw. Byte-for-byte the same POST + poll contract
|
|
71
|
+
// as http-delegate-child.js, plus a per-job deadline so one hung endpoint settles as
|
|
72
|
+
// its own error instead of pinning the whole batch to the parent's backstop timeout.
|
|
73
|
+
async function runOne(entry) {
|
|
74
|
+
const { endpoint, job, timeoutMs } = entry;
|
|
75
|
+
if (!endpoint) throw new Error("no endpoint");
|
|
76
|
+
const deadline = Date.now() + (Number(timeoutMs) > 0 ? Number(timeoutMs) : 600000);
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
controllers.add(controller);
|
|
79
|
+
const abortAt = setTimeout(() => { try { controller.abort(); } catch {} }, Math.max(0, deadline - Date.now()));
|
|
80
|
+
try {
|
|
81
|
+
const post = await fetch(endpoint, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: { "content-type": "application/json" },
|
|
84
|
+
body: typeof job === "string" ? job : JSON.stringify(job),
|
|
85
|
+
signal: controller.signal,
|
|
86
|
+
});
|
|
87
|
+
if (!post.ok) throw new Error("runner responded " + post.status);
|
|
88
|
+
let data = await post.json();
|
|
89
|
+
let guard = 0;
|
|
90
|
+
while (data && data.jobId && data.done !== true && guard++ < 600) {
|
|
91
|
+
if (Date.now() >= deadline) throw new Error("endpoint poll timed out");
|
|
92
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
93
|
+
const poll = await fetch(endpoint + (endpoint.includes("?") ? "&" : "?") + "jobId=" + encodeURIComponent(data.jobId), { signal: controller.signal });
|
|
94
|
+
if (!poll.ok) throw new Error("poll responded " + poll.status);
|
|
95
|
+
data = await poll.json();
|
|
96
|
+
}
|
|
97
|
+
if (typeof data.exitCode !== "number") throw new Error("runner did not report an exitCode");
|
|
98
|
+
let stdout = String(data.stdout || "");
|
|
99
|
+
if (Buffer.byteLength(stdout) > CAP) throw new Error(`stdout exceeded ${CAP} byte cap (${Buffer.byteLength(stdout)} bytes)`);
|
|
100
|
+
return { exitCode: data.exitCode, stdout };
|
|
101
|
+
} finally {
|
|
102
|
+
clearTimeout(abortAt);
|
|
103
|
+
controllers.delete(controller);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
(async () => {
|
|
108
|
+
// setEncoding("utf8") (as batch-delegate-child.js does), NOT `b += <Buffer>`:
|
|
109
|
+
// without it each data chunk is a raw Buffer coerced to a string on its own, so
|
|
110
|
+
// a multibyte char split across a ~64KB pipe-chunk boundary decodes to U+FFFD
|
|
111
|
+
// and the POSTed prompt is silently corrupted. With the encoding set, Node's
|
|
112
|
+
// StringDecoder carries partial bytes across chunk boundaries.
|
|
113
|
+
const read = () => new Promise((res) => { let b = ""; process.stdin.setEncoding("utf8"); process.stdin.on("data", (c) => { if (b.length < MAX_STDIN_BYTES) b += c; }); process.stdin.on("end", () => res(b)); });
|
|
114
|
+
let jobs;
|
|
115
|
+
try {
|
|
116
|
+
jobs = JSON.parse((await read()) || "[]");
|
|
117
|
+
} catch (e) {
|
|
118
|
+
process.stdout.write(JSON.stringify([{ spawnError: `invalid stdin JSON: ${String((e && e.message) || e)}`, exitCode: null, stdout: "" }]));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!Array.isArray(jobs) || !jobs.length) { process.stdout.write("[]"); return; }
|
|
122
|
+
// allSettled, not Promise.all: one failing job never rejects the others, and
|
|
123
|
+
// every job's line is already written by runOne's settle below regardless.
|
|
124
|
+
await Promise.allSettled(jobs.map(async (entry, i) => {
|
|
125
|
+
try {
|
|
126
|
+
const out = await runOne(entry);
|
|
127
|
+
writeSettleLine(i, { exitCode: out.exitCode, stdout: out.stdout });
|
|
128
|
+
} catch (e) {
|
|
129
|
+
writeSettleLine(i, { spawnError: String((e && e.message) || e), exitCode: null, stdout: "" });
|
|
130
|
+
}
|
|
131
|
+
}));
|
|
132
|
+
})();
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
|
|
17
17
|
(async () => {
|
|
18
18
|
const MAX_STDIN_BYTES = 32 * 1024 * 1024;
|
|
19
|
+
// setEncoding("utf8") before reading: Node's StringDecoder then carries a
|
|
20
|
+
// multibyte UTF-8 char that straddles a ~64KB pipe-chunk boundary across the
|
|
21
|
+
// two chunks. Without it, each Buffer chunk was coerced to a string on its
|
|
22
|
+
// own and a split char decoded to U+FFFD on both sides — silently corrupting
|
|
23
|
+
// a > ~64KB non-ASCII job (this project's prompts are frequently Chinese), or
|
|
24
|
+
// making the outer JSON.parse throw so the delegation refused. Matches the
|
|
25
|
+
// batch-delegate-child.js reader.
|
|
26
|
+
process.stdin.setEncoding("utf8");
|
|
19
27
|
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)); });
|
|
20
28
|
try {
|
|
21
29
|
const job = JSON.parse((await read()) || "{}");
|
|
@@ -6,7 +6,7 @@ const fs = require("node:fs");
|
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
const { CoolWorkflowRunner } = require("../dist/shell/orchestrator.js");
|
|
8
8
|
|
|
9
|
-
const TARGET_VERSION = "0.2.
|
|
9
|
+
const TARGET_VERSION = "0.2.5";
|
|
10
10
|
const PREVIOUS_VERSION = "0.1.31";
|
|
11
11
|
const pluginRoot = path.resolve(__dirname, "..");
|
|
12
12
|
const repoRoot = path.resolve(pluginRoot, "..", "..");
|
|
@@ -291,7 +291,7 @@ function commandsForTask(taskId, context) {
|
|
|
291
291
|
"-e",
|
|
292
292
|
[
|
|
293
293
|
releaseSourceReaderSnippet(),
|
|
294
|
-
"const files=['
|
|
294
|
+
"const files=['plugins/cool-workflow/docs/dogfood-one-real-repo.7.md','README.md','plugins/cool-workflow/README.md','CHANGELOG.md','RELEASE.md'];",
|
|
295
295
|
"for (const f of files) { if (!surfaceExists(f)) throw new Error('missing '+f); }",
|
|
296
296
|
"const changelog=readSurface('CHANGELOG.md');",
|
|
297
297
|
"if (changelog===null) throw new Error('CHANGELOG.md missing from release commit');",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
// fake-date-for-reproduction.js — a Node `--require` preload used ONLY by
|
|
4
|
-
// verify-bump-reproduction.
|
|
4
|
+
// verify-bump-reproduction.js. It pins `new Date()` (no-args construction)
|
|
5
5
|
// and `Date.now()` to a fixed instant, read from CW_FAKE_DATE (an ISO date or
|
|
6
6
|
// YYYY-MM-DD string). `new Date(explicit args)` is left untouched.
|
|
7
7
|
//
|
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.2.
|
|
36
|
+
assert.equal(appValidation.summary.version, "0.2.5");
|
|
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.2.
|
|
45
|
+
"Prove the deterministic v0.2.5 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.2.
|
|
55
|
+
assert.equal(state.workflow.app.version, "0.2.5");
|
|
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\.2\.
|
|
198
|
+
assert.match(report, /Workflow App: end-to-end-golden-path@0\.2\.5/);
|
|
199
199
|
assert.match(report, /## Candidates/);
|
|
200
200
|
assert.match(report, /## Trust Audit/);
|
|
201
201
|
assert.match(report, /## Acceptance Rationale/);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// lang-policy-check.js — this project is written in JavaScript and
|
|
4
|
+
// TypeScript only (AGENTS.md hard rule, 2026-07-13). Every git-tracked file
|
|
5
|
+
// must be JS/TS, a recognized non-code file (docs, data, config, binary
|
|
6
|
+
// asset), or one of the explicit, path-scoped exceptions below — a real
|
|
7
|
+
// non-JS/TS file the project genuinely needs. Fail closed: an unrecognized
|
|
8
|
+
// extension/basename is a hard stop, forcing a deliberate choice (write it
|
|
9
|
+
// in JS/TS, or add a new exception here with a reason) instead of a silent
|
|
10
|
+
// pass. Mechanism only; the allowlists ARE the policy, kept as plain data
|
|
11
|
+
// in this one file — no separate config to drift from it.
|
|
12
|
+
|
|
13
|
+
const { execFileSync } = require("node:child_process");
|
|
14
|
+
const path = require("node:path");
|
|
15
|
+
|
|
16
|
+
const CODE_EXTENSIONS = new Set(["js", "mjs", "cjs", "ts", "tsx", "jsx"]);
|
|
17
|
+
|
|
18
|
+
// Real, needed files this project cannot write in JS/TS -- the exact same
|
|
19
|
+
// set already carved out of GitHub's Languages stats in .gitattributes, for
|
|
20
|
+
// the same reasons (see that file's comments). Scoped to an EXACT path,
|
|
21
|
+
// never a whole directory, so a new file dropped in the same folder does
|
|
22
|
+
// NOT silently inherit the exception.
|
|
23
|
+
const EXCEPT_PATHS = new Set([
|
|
24
|
+
"Formula/cool-workflow.rb", // Homebrew formula must be Ruby
|
|
25
|
+
"plugins/cool-workflow/ui/workbench/index.html", // served from disk by design
|
|
26
|
+
"plugins/cool-workflow/ui/workbench/app.css" // served from disk by design
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
// Not "code" at all -- docs, data, config, and binary assets. Fine
|
|
30
|
+
// anywhere in the tree; this policy is only about what the project is
|
|
31
|
+
// WRITTEN in, not about every file it ships or tracks.
|
|
32
|
+
const NON_CODE_EXTENSIONS = new Set([
|
|
33
|
+
"md", "mdc", "txt", "json", "jsonl", "yml", "yaml", "toml",
|
|
34
|
+
"verdict", "sig", "pub", "tape",
|
|
35
|
+
"svg", "png", "jpg", "jpeg", "gif", "webp", "ico", "pdf",
|
|
36
|
+
"woff", "woff2", "gz", "tgz", "zip", "lock"
|
|
37
|
+
]);
|
|
38
|
+
// Extensionless / dotfile basenames that are also not code.
|
|
39
|
+
const NON_CODE_BASENAMES = new Set([
|
|
40
|
+
"LICENSE", ".gitignore", ".gitattributes", ".editorconfig", ".windsurfrules", ".npmrc", ".nvmrc"
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
// `EXCEPT_PATHS` are written repo-root-relative, so tracked files must be
|
|
44
|
+
// resolved the same way regardless of the invocation cwd (this runs both as
|
|
45
|
+
// `npm run lang:check` from plugins/cool-workflow/ AND from release-check.js
|
|
46
|
+
// at various cwds) -- resolve the real top-level first, never trust the
|
|
47
|
+
// invocation cwd to already BE it.
|
|
48
|
+
function trackedFiles() {
|
|
49
|
+
const repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
|
|
50
|
+
return execFileSync("git", ["-C", repoRoot, "ls-files"], { encoding: "utf8" })
|
|
51
|
+
.split("\n")
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function classify(file) {
|
|
56
|
+
if (EXCEPT_PATHS.has(file)) return { ok: true };
|
|
57
|
+
const base = path.basename(file);
|
|
58
|
+
if (NON_CODE_BASENAMES.has(base)) return { ok: true };
|
|
59
|
+
const dot = base.lastIndexOf(".");
|
|
60
|
+
if (dot <= 0) return { ok: false, ext: null };
|
|
61
|
+
const ext = base.slice(dot + 1).toLowerCase();
|
|
62
|
+
if (CODE_EXTENSIONS.has(ext) || NON_CODE_EXTENSIONS.has(ext)) return { ok: true };
|
|
63
|
+
return { ok: false, ext };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const files = trackedFiles();
|
|
67
|
+
const violations = [];
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
const result = classify(file);
|
|
70
|
+
if (!result.ok) violations.push({ file, ext: result.ext });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (violations.length > 0) {
|
|
74
|
+
process.stderr.write("lang-policy-check: this project is JavaScript/TypeScript only. Unrecognized file(s):\n");
|
|
75
|
+
for (const v of violations) {
|
|
76
|
+
process.stderr.write(` - ${v.file}${v.ext ? ` (.${v.ext})` : " (no extension)"}\n`);
|
|
77
|
+
}
|
|
78
|
+
process.stderr.write("Write it in JavaScript/TypeScript, or add a scoped, reasoned exception to scripts/lang-policy-check.js.\n");
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
process.stdout.write(`lang-policy-check: ${files.length} tracked files, all JS/TS or an allowed non-code/exception file.\n`);
|
|
@@ -36,36 +36,6 @@
|
|
|
36
36
|
"layerViolations": {
|
|
37
37
|
"core/capability-table.ts": [
|
|
38
38
|
"../wiring/capability-table"
|
|
39
|
-
],
|
|
40
|
-
"wiring/capability-table/basics.ts": [
|
|
41
|
-
"../../cli/io"
|
|
42
|
-
],
|
|
43
|
-
"wiring/capability-table/exec-backend.ts": [
|
|
44
|
-
"../../cli/io"
|
|
45
|
-
],
|
|
46
|
-
"wiring/capability-table/multi-agent.ts": [
|
|
47
|
-
"../../cli/io"
|
|
48
|
-
],
|
|
49
|
-
"wiring/capability-table/pipeline.ts": [
|
|
50
|
-
"../../cli/io"
|
|
51
|
-
],
|
|
52
|
-
"wiring/capability-table/registry-core.ts": [
|
|
53
|
-
"../../cli/io"
|
|
54
|
-
],
|
|
55
|
-
"wiring/capability-table/reporting.ts": [
|
|
56
|
-
"../../cli/io"
|
|
57
|
-
],
|
|
58
|
-
"wiring/capability-table/scheduling-registry.ts": [
|
|
59
|
-
"../../cli/io"
|
|
60
|
-
],
|
|
61
|
-
"wiring/capability-table/state.ts": [
|
|
62
|
-
"../../cli/io"
|
|
63
|
-
],
|
|
64
|
-
"wiring/capability-table/trust-ledger.ts": [
|
|
65
|
-
"../../cli/io"
|
|
66
|
-
],
|
|
67
|
-
"wiring/capability-table/workflow-apps.ts": [
|
|
68
|
-
"../../cli/io"
|
|
69
39
|
]
|
|
70
40
|
}
|
|
71
41
|
}
|
package/scripts/purity-gate.js
CHANGED
|
@@ -112,8 +112,12 @@ function stripComments(text) {
|
|
|
112
112
|
|
|
113
113
|
// Every `from "spec"` (import or export-from, type or value — a type-only
|
|
114
114
|
// import still names a module, so it counts the same way a real dependency
|
|
115
|
-
// scan would) plus every `require("spec")
|
|
116
|
-
// each import's own clause without needing a
|
|
115
|
+
// scan would) plus every `require("spec")` and every dynamic `import("spec")`.
|
|
116
|
+
// Non-greedy across `from` finds each import's own clause without needing a
|
|
117
|
+
// real parser. The dynamic-import form matters because a core/ file could
|
|
118
|
+
// otherwise smuggle in an impure or cross-layer module through
|
|
119
|
+
// `await import("node:fs")` — the one import shape the static and require
|
|
120
|
+
// scans did not see, so the ratchet was blind to it.
|
|
117
121
|
function extractSpecifiers(codeText) {
|
|
118
122
|
const specs = [];
|
|
119
123
|
const fromRe = /(?:^|\n)\s*(?:import|export)\s[\s\S]*?from\s+["']([^"']+)["']/g;
|
|
@@ -121,6 +125,8 @@ function extractSpecifiers(codeText) {
|
|
|
121
125
|
while ((m = fromRe.exec(codeText))) specs.push(m[1]);
|
|
122
126
|
const requireRe = /require\(\s*["']([^"']+)["']\s*\)/g;
|
|
123
127
|
while ((m = requireRe.exec(codeText))) specs.push(m[1]);
|
|
128
|
+
const dynamicImportRe = /import\(\s*["']([^"']+)["']\s*\)/g;
|
|
129
|
+
while ((m = dynamicImportRe.exec(codeText))) specs.push(m[1]);
|
|
124
130
|
return specs;
|
|
125
131
|
}
|
|
126
132
|
|
|
@@ -236,4 +242,11 @@ function main() {
|
|
|
236
242
|
process.stdout.write("purity gate: src/ matches the committed baseline (no new core/shell layer breaks, no clock/env drift).\n");
|
|
237
243
|
}
|
|
238
244
|
|
|
239
|
-
|
|
245
|
+
// Run the gate only when invoked directly (node scripts/purity-gate.js). When
|
|
246
|
+
// required by a test, expose the internal helpers so the specifier scan can be
|
|
247
|
+
// unit-checked without shelling out or writing into the real src/ tree.
|
|
248
|
+
if (require.main === module) {
|
|
249
|
+
main();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = { stripComments, extractSpecifiers, layerOf, scan };
|
package/scripts/release-check.js
CHANGED
|
@@ -60,12 +60,13 @@ const checks = [
|
|
|
60
60
|
// from that fresh build — strictly stronger than a bare `npm run build`.
|
|
61
61
|
{ name: "dist freshness", command: ["npm", "run", "dist:check"] },
|
|
62
62
|
{ name: "core/shell purity", command: ["npm", "run", "purity:check"] },
|
|
63
|
+
{ name: "language policy (JS/TS only)", command: ["npm", "run", "lang:check"] },
|
|
63
64
|
{ name: "type check", command: ["npm", "run", "check"] },
|
|
64
65
|
{ name: "onramp contract", command: ["npm", "run", "onramp:check"] },
|
|
65
66
|
{ name: "run-state schema consistency", command: ["node", "scripts/validate-run-state-schema.js"] },
|
|
66
67
|
// Every test path uses --concurrency auto by default (parallel, race-free:
|
|
67
68
|
// each smoke runs in a private cwd + state roots). The release tag-gate
|
|
68
|
-
// (release-gate.
|
|
69
|
+
// (release-gate.js) forces CW_TEST_CONCURRENCY=1 to stay sequential as the
|
|
69
70
|
// deterministic backstop.
|
|
70
71
|
{ name: "tests", command: ["npm", "run", "test:ci"] },
|
|
71
72
|
// Pure core/ unit tests (test/*.test.js) — a separate suite from the smoke
|