cool-workflow 0.2.0 → 0.2.2
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 +7 -5
- package/apps/architecture-review/app.json +2 -2
- package/apps/architecture-review/workflow.js +12 -12
- 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 +20 -88
- package/dist/cli/parseargv.js +6 -3
- package/dist/core/capability-data.js +270 -0
- package/dist/core/capability-table.js +15 -3519
- package/dist/core/format/help.js +30 -7
- package/dist/core/hash.js +1 -1
- package/dist/core/multi-agent/candidate-scoring.js +1 -1
- package/dist/core/multi-agent/collaboration.js +3 -2
- package/dist/core/multi-agent/coordinator.js +4 -3
- package/dist/core/multi-agent/eval-replay.js +1 -1
- package/dist/core/multi-agent/runtime.js +2 -2
- package/dist/core/multi-agent/trust-policy.js +1 -1
- package/dist/core/pipeline/commit-gate.js +2 -1
- package/dist/core/pipeline/contract.js +1 -1
- package/dist/core/pipeline/drive-decide.js +1 -1
- package/dist/core/state/contract-migration.js +1 -1
- package/dist/core/state/migrations.js +2 -2
- package/dist/core/state/node-snapshot.js +1 -1
- package/dist/core/state/state-explosion/digest.js +3 -2
- package/dist/core/state/state-explosion/graph.js +13 -12
- package/dist/core/state/state-explosion/helpers.js +5 -4
- package/dist/core/state/state-explosion/report.js +1 -1
- package/dist/core/state/state-explosion/size.js +1 -1
- package/dist/core/state/state-node.js +2 -2
- package/dist/core/state/types.js +1 -1
- package/dist/core/trust/ledger.js +3 -2
- package/dist/core/trust/telemetry-attestation.js +3 -3
- package/dist/core/trust/telemetry-ledger.js +2 -2
- package/dist/core/types/execution-backend.js +18 -0
- package/dist/core/types/observability.js +7 -0
- package/dist/core/util/collate.js +23 -0
- package/dist/core/version.js +1 -1
- package/dist/core/workflow-apps/app-schema.js +1 -1
- package/dist/mcp/dispatch.js +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/shell/agent-config.js +1 -1
- package/dist/shell/audit-cli.js +46 -2
- package/dist/shell/commit-summary.js +2 -1
- package/dist/shell/doctor.js +17 -4
- package/dist/shell/drive.js +47 -19
- package/dist/shell/evidence-reasoning.js +4 -3
- package/dist/shell/execution-backend/agent.js +42 -10
- package/dist/shell/execution-backend/ci.js +1 -1
- package/dist/shell/execution-backend/container.js +1 -1
- package/dist/shell/execution-backend/envelopes.js +1 -1
- package/dist/shell/execution-backend/local.js +1 -1
- package/dist/shell/execution-backend/probes.js +1 -1
- package/dist/shell/execution-backend/registry.js +3 -2
- package/dist/shell/execution-backend/remote.js +1 -1
- package/dist/shell/execution-backend/types.js +0 -9
- package/dist/shell/fs-atomic.js +15 -2
- package/dist/shell/ledger-io.js +1 -1
- package/dist/shell/metrics-cli.js +4 -2
- package/dist/shell/multi-agent-host.js +1 -1
- package/dist/shell/multi-agent-operator-ux.js +4 -3
- package/dist/shell/observability.js +12 -11
- package/dist/shell/onramp.js +17 -1
- package/dist/shell/operator-ux-text.js +2 -1
- package/dist/shell/operator-ux.js +7 -6
- package/dist/shell/pipeline-cli.js +82 -72
- package/dist/shell/reclamation-io.js +3 -2
- package/dist/shell/registry-cli.js +15 -0
- package/dist/shell/report.js +2 -1
- package/dist/shell/run-registry-io.js +1 -1
- package/dist/shell/run-store.js +17 -0
- package/dist/shell/sandbox-profile.js +1 -1
- package/dist/shell/scheduler-io.js +46 -37
- package/dist/shell/scheduling-io.js +32 -22
- package/dist/shell/state-explosion-cli.js +2 -1
- package/dist/shell/telemetry-ledger-io.js +1 -1
- package/dist/shell/term.js +1 -1
- package/dist/shell/topology-io.js +2 -1
- package/dist/shell/trust-audit.js +45 -5
- package/dist/shell/workbench-host.js +9 -1
- package/dist/shell/workbench.js +6 -11
- package/dist/shell/worker-cli.js +9 -1
- package/dist/shell/worker-isolation.js +34 -9
- package/dist/shell/workflow-app-loader.js +3 -2
- package/dist/wiring/capability-table/basics.js +66 -0
- package/dist/wiring/capability-table/exec-backend.js +162 -0
- package/dist/wiring/capability-table/index.js +42 -0
- package/dist/wiring/capability-table/multi-agent.js +623 -0
- package/dist/wiring/capability-table/parity.js +466 -0
- package/dist/wiring/capability-table/pipeline.js +280 -0
- package/dist/wiring/capability-table/registry-core.js +189 -0
- package/dist/wiring/capability-table/reporting.js +394 -0
- package/dist/wiring/capability-table/scheduling-registry.js +558 -0
- package/dist/wiring/capability-table/state.js +173 -0
- package/dist/wiring/capability-table/trust-ledger.js +134 -0
- package/dist/wiring/capability-table/workflow-apps.js +366 -0
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/cli-mcp-parity.7.md +9 -2
- 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 +24 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/index.md +1 -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 +6 -1
- package/docs/project-index.md +30 -5
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +4 -0
- package/docs/remote-source-review.7.md +4 -4
- package/docs/report-verifiable-bundle.7.md +1 -1
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/security-trust-hardening.7.md +33 -1
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/trust-audit-anchor.7.md +69 -0
- package/docs/web-desktop-workbench.7.md +15 -1
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +3 -1
- package/scripts/agents/agent-adapter-core.js +22 -2
- package/scripts/agents/claude-p-agent.js +8 -3
- package/scripts/agents/gemini-agent.js +5 -1
- package/scripts/agents/opencode-agent.js +5 -1
- package/scripts/bump-version.js +24 -2
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/purity-baseline.json +68 -0
- package/scripts/purity-gate.js +239 -0
- package/scripts/release-check.js +8 -1
- package/scripts/version-sync-check.js +33 -12
- package/workflows/README.md +19 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"builtinViolations": {
|
|
3
|
+
"core/state/run-paths.ts": [
|
|
4
|
+
"node:fs"
|
|
5
|
+
],
|
|
6
|
+
"core/trust/telemetry-attestation.ts": [
|
|
7
|
+
"node:fs"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"clockEnvCounts": {
|
|
11
|
+
"core/multi-agent/candidate-scoring.ts": {
|
|
12
|
+
"new Date()": 1
|
|
13
|
+
},
|
|
14
|
+
"core/pipeline/runner.ts": {
|
|
15
|
+
"new Date()": 1
|
|
16
|
+
},
|
|
17
|
+
"core/state/migrations.ts": {
|
|
18
|
+
"process.cwd()": 1
|
|
19
|
+
},
|
|
20
|
+
"core/state/state-explosion/digest.ts": {
|
|
21
|
+
"new Date()": 1
|
|
22
|
+
},
|
|
23
|
+
"core/state/state-explosion/graph.ts": {
|
|
24
|
+
"new Date()": 1
|
|
25
|
+
},
|
|
26
|
+
"core/state/state-explosion/report.ts": {
|
|
27
|
+
"new Date()": 1
|
|
28
|
+
},
|
|
29
|
+
"core/state/state-node.ts": {
|
|
30
|
+
"new Date()": 5
|
|
31
|
+
},
|
|
32
|
+
"core/trust/evidence-grounding.ts": {
|
|
33
|
+
"process.env": 3
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"layerViolations": {
|
|
37
|
+
"core/capability-table.ts": [
|
|
38
|
+
"../wiring/capability-table"
|
|
39
|
+
],
|
|
40
|
+
"wiring/capability-table/exec-backend.ts": [
|
|
41
|
+
"../../cli/io"
|
|
42
|
+
],
|
|
43
|
+
"wiring/capability-table/multi-agent.ts": [
|
|
44
|
+
"../../cli/io"
|
|
45
|
+
],
|
|
46
|
+
"wiring/capability-table/pipeline.ts": [
|
|
47
|
+
"../../cli/io"
|
|
48
|
+
],
|
|
49
|
+
"wiring/capability-table/registry-core.ts": [
|
|
50
|
+
"../../cli/io"
|
|
51
|
+
],
|
|
52
|
+
"wiring/capability-table/reporting.ts": [
|
|
53
|
+
"../../cli/io"
|
|
54
|
+
],
|
|
55
|
+
"wiring/capability-table/scheduling-registry.ts": [
|
|
56
|
+
"../../cli/io"
|
|
57
|
+
],
|
|
58
|
+
"wiring/capability-table/state.ts": [
|
|
59
|
+
"../../cli/io"
|
|
60
|
+
],
|
|
61
|
+
"wiring/capability-table/trust-ledger.ts": [
|
|
62
|
+
"../../cli/io"
|
|
63
|
+
],
|
|
64
|
+
"wiring/capability-table/workflow-apps.ts": [
|
|
65
|
+
"../../cli/io"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// purity-gate — fail closed if src/ crosses a layer boundary the rebuild
|
|
5
|
+
// docs claim is enforced, or if src/core/** reads an impure primitive
|
|
6
|
+
// (node:fs/child_process/net/http, process.env, process.cwd(), Date.now(),
|
|
7
|
+
// new Date(), Math.random()), beyond a committed, itemized baseline.
|
|
8
|
+
//
|
|
9
|
+
// Why this exists: docs/rebuild/PLAN.md and AGENTS.md both describe core/
|
|
10
|
+
// as pure (no IO) and shell/ as the only impure layer, and say this is
|
|
11
|
+
// "enforced by a lint rule". No such lint exists anywhere in scripts/ or
|
|
12
|
+
// package.json — only `tsc --noEmit`, which does not check import
|
|
13
|
+
// direction. The one place the rule is already broken is the hub itself:
|
|
14
|
+
// core/capability-table.ts imports ~30 things from ../shell.
|
|
15
|
+
//
|
|
16
|
+
// This is a RATCHET, not a clean-slate rule (matching dist-drift-check.js's
|
|
17
|
+
// and version-sync-check.js's style: node + git only, no new dependency).
|
|
18
|
+
// scripts/purity-baseline.json lists every violation that exists TODAY.
|
|
19
|
+
// The gate fails on:
|
|
20
|
+
// - a violation NOT in the baseline (a NEW break), and
|
|
21
|
+
// - a baseline entry that no longer matches reality (STALE — either the
|
|
22
|
+
// violation was fixed, in which case delete the entry, or the count
|
|
23
|
+
// changed, in which case update it consciously).
|
|
24
|
+
// Fail-closed both directions, same as dist-drift-check.js's added/changed/
|
|
25
|
+
// removed three-way diff.
|
|
26
|
+
//
|
|
27
|
+
// Layers (by top-level directory under src/):
|
|
28
|
+
// core/** -> may only import core/** (+ node:path, node:crypto)
|
|
29
|
+
// shell/** -> may import core/** or shell/**
|
|
30
|
+
// wiring/** -> may import core/**, shell/**, or wiring/** (not yet used;
|
|
31
|
+
// reserved for the capability-table split)
|
|
32
|
+
// cli/** -> may import core/**, shell/**, or cli/** (never mcp/**)
|
|
33
|
+
// mcp/** -> may import core/**, shell/**, or mcp/** (never cli/**)
|
|
34
|
+
//
|
|
35
|
+
// Approach: a plain-text specifier scan (import/export-from/require), not a
|
|
36
|
+
// real TS parser — this codebase uses only those three forms, verified
|
|
37
|
+
// against every current violation this baseline lists.
|
|
38
|
+
|
|
39
|
+
const fs = require("node:fs");
|
|
40
|
+
const path = require("node:path");
|
|
41
|
+
|
|
42
|
+
const packageDir = path.resolve(__dirname, "..");
|
|
43
|
+
const srcDir = path.join(packageDir, "src");
|
|
44
|
+
const baselinePath = path.join(__dirname, "purity-baseline.json");
|
|
45
|
+
|
|
46
|
+
const CORE_ALLOWED_BUILTINS = new Set(["node:path", "node:crypto"]);
|
|
47
|
+
const IMPURE_PATTERNS = ["process.env", "process.cwd()", "Date.now()", "new Date()", "Math.random()"];
|
|
48
|
+
|
|
49
|
+
const ALLOWED_TARGET_LAYERS = {
|
|
50
|
+
core: new Set(["core"]),
|
|
51
|
+
shell: new Set(["core", "shell"]),
|
|
52
|
+
wiring: new Set(["core", "shell", "wiring"]),
|
|
53
|
+
cli: new Set(["core", "shell", "cli"]),
|
|
54
|
+
mcp: new Set(["core", "shell", "mcp"]),
|
|
55
|
+
other: new Set(["core", "shell", "wiring", "cli", "mcp", "other"]),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function layerOf(relPath) {
|
|
59
|
+
if (relPath.startsWith("core/")) return "core";
|
|
60
|
+
if (relPath.startsWith("shell/")) return "shell";
|
|
61
|
+
if (relPath.startsWith("wiring/")) return "wiring";
|
|
62
|
+
if (relPath.startsWith("cli/") || relPath === "cli") return "cli";
|
|
63
|
+
if (relPath.startsWith("mcp/") || relPath === "mcp-server") return "mcp";
|
|
64
|
+
return "other";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function listTsFiles(dir) {
|
|
68
|
+
const out = [];
|
|
69
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
70
|
+
const full = path.join(dir, entry.name);
|
|
71
|
+
if (entry.isDirectory()) out.push(...listTsFiles(full));
|
|
72
|
+
else if (entry.name.endsWith(".ts")) out.push(full);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// This codebase's file-header comments routinely quote the EXACT patterns
|
|
78
|
+
// this gate looks for, in prose explaining why a file is pure (e.g. "never
|
|
79
|
+
// a top-level `require(\"node:fs\")`", "no fs, no process.env, no clock").
|
|
80
|
+
// A naive text scan reads those quotes as violations. Strip `//` and
|
|
81
|
+
// `/* */` comments before scanning (string/template literal contents are
|
|
82
|
+
// left untouched, so a real `from "..."` clause is never damaged); newlines
|
|
83
|
+
// are preserved so extractSpecifiers' `(?:^|\n)` anchor still lines up.
|
|
84
|
+
function stripComments(text) {
|
|
85
|
+
let out = "";
|
|
86
|
+
let mode = "code"; // "code" | "line" | "block" | quote character
|
|
87
|
+
for (let i = 0; i < text.length; i++) {
|
|
88
|
+
const c = text[i];
|
|
89
|
+
const c2 = text[i + 1];
|
|
90
|
+
if (mode === "line") {
|
|
91
|
+
if (c === "\n") { mode = "code"; out += c; }
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (mode === "block") {
|
|
95
|
+
if (c === "*" && c2 === "/") { mode = "code"; i++; continue; }
|
|
96
|
+
if (c === "\n") out += c;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (mode === '"' || mode === "'" || mode === "`") {
|
|
100
|
+
out += c;
|
|
101
|
+
if (c === "\\") { out += c2 ?? ""; i++; continue; }
|
|
102
|
+
if (c === mode) mode = "code";
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (c === "/" && c2 === "/") { mode = "line"; i++; continue; }
|
|
106
|
+
if (c === "/" && c2 === "*") { mode = "block"; i++; continue; }
|
|
107
|
+
if (c === '"' || c === "'" || c === "`") { mode = c; out += c; continue; }
|
|
108
|
+
out += c;
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Every `from "spec"` (import or export-from, type or value — a type-only
|
|
114
|
+
// import still names a module, so it counts the same way a real dependency
|
|
115
|
+
// scan would) plus every `require("spec")`. Non-greedy across `from` finds
|
|
116
|
+
// each import's own clause without needing a real parser.
|
|
117
|
+
function extractSpecifiers(codeText) {
|
|
118
|
+
const specs = [];
|
|
119
|
+
const fromRe = /(?:^|\n)\s*(?:import|export)\s[\s\S]*?from\s+["']([^"']+)["']/g;
|
|
120
|
+
let m;
|
|
121
|
+
while ((m = fromRe.exec(codeText))) specs.push(m[1]);
|
|
122
|
+
const requireRe = /require\(\s*["']([^"']+)["']\s*\)/g;
|
|
123
|
+
while ((m = requireRe.exec(codeText))) specs.push(m[1]);
|
|
124
|
+
return specs;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function countOccurrences(text, needle) {
|
|
128
|
+
let count = 0;
|
|
129
|
+
let index = 0;
|
|
130
|
+
while ((index = text.indexOf(needle, index)) !== -1) {
|
|
131
|
+
count += 1;
|
|
132
|
+
index += needle.length;
|
|
133
|
+
}
|
|
134
|
+
return count;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function relSrcPath(absPath) {
|
|
138
|
+
return path.relative(srcDir, absPath).split(path.sep).join("/");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function scan() {
|
|
142
|
+
const layerViolations = {}; // relFile -> string[] (specs that cross a disallowed layer)
|
|
143
|
+
const builtinViolations = {}; // relFile -> string[] (banned node builtins imported from core)
|
|
144
|
+
const clockEnvCounts = {}; // relFile -> { pattern: count } (core only, count > 0)
|
|
145
|
+
|
|
146
|
+
for (const absFile of listTsFiles(srcDir)) {
|
|
147
|
+
const relFile = relSrcPath(absFile);
|
|
148
|
+
const fileLayer = layerOf(relFile.replace(/\.ts$/, ""));
|
|
149
|
+
const text = stripComments(fs.readFileSync(absFile, "utf8"));
|
|
150
|
+
|
|
151
|
+
for (const spec of extractSpecifiers(text)) {
|
|
152
|
+
if (spec.startsWith(".")) {
|
|
153
|
+
const targetAbs = path.resolve(path.dirname(absFile), spec);
|
|
154
|
+
const targetRel = relSrcPath(targetAbs);
|
|
155
|
+
const targetLayer = layerOf(targetRel);
|
|
156
|
+
if (!ALLOWED_TARGET_LAYERS[fileLayer].has(targetLayer)) {
|
|
157
|
+
(layerViolations[relFile] = layerViolations[relFile] || []).push(spec);
|
|
158
|
+
}
|
|
159
|
+
} else if (spec.startsWith("node:")) {
|
|
160
|
+
if (fileLayer === "core" && !CORE_ALLOWED_BUILTINS.has(spec)) {
|
|
161
|
+
(builtinViolations[relFile] = builtinViolations[relFile] || []).push(spec);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Bare package specifiers (no "." or "node:" prefix): this package
|
|
165
|
+
// has zero runtime dependencies, so there are none to check.
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (fileLayer === "core") {
|
|
169
|
+
const counts = {};
|
|
170
|
+
for (const pattern of IMPURE_PATTERNS) {
|
|
171
|
+
const n = countOccurrences(text, pattern);
|
|
172
|
+
if (n > 0) counts[pattern] = n;
|
|
173
|
+
}
|
|
174
|
+
if (Object.keys(counts).length > 0) clockEnvCounts[relFile] = counts;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (const bucket of [layerViolations, builtinViolations]) {
|
|
179
|
+
for (const file of Object.keys(bucket)) bucket[file] = [...new Set(bucket[file])].sort();
|
|
180
|
+
}
|
|
181
|
+
return { layerViolations, builtinViolations, clockEnvCounts };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function sortedKeys(obj) {
|
|
185
|
+
return Object.keys(obj).sort();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function diffListBuckets(actual, baseline, label, problems) {
|
|
189
|
+
const files = new Set([...sortedKeys(actual), ...sortedKeys(baseline)]);
|
|
190
|
+
for (const file of [...files].sort()) {
|
|
191
|
+
const actualList = actual[file] || [];
|
|
192
|
+
const baseList = baseline[file] || [];
|
|
193
|
+
const actualSet = new Set(actualList);
|
|
194
|
+
const baseSet = new Set(baseList);
|
|
195
|
+
const added = actualList.filter((s) => !baseSet.has(s));
|
|
196
|
+
const removed = baseList.filter((s) => !actualSet.has(s));
|
|
197
|
+
for (const spec of added) problems.push(`NEW ${label}: ${file} imports ${spec} (not in purity-baseline.json)`);
|
|
198
|
+
for (const spec of removed) problems.push(`STALE ${label} baseline entry: ${file} no longer imports ${spec} — delete it from purity-baseline.json`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function diffCountBuckets(actual, baseline, problems) {
|
|
203
|
+
const files = new Set([...sortedKeys(actual), ...sortedKeys(baseline)]);
|
|
204
|
+
for (const file of [...files].sort()) {
|
|
205
|
+
const actualCounts = actual[file] || {};
|
|
206
|
+
const baseCounts = baseline[file] || {};
|
|
207
|
+
const patterns = new Set([...Object.keys(actualCounts), ...Object.keys(baseCounts)]);
|
|
208
|
+
for (const pattern of [...patterns].sort()) {
|
|
209
|
+
const a = actualCounts[pattern] || 0;
|
|
210
|
+
const b = baseCounts[pattern] || 0;
|
|
211
|
+
if (a !== b) {
|
|
212
|
+
problems.push(`clock/env count drift: ${file} has ${a} occurrence(s) of "${pattern}", purity-baseline.json expects ${b} — update the baseline consciously (this catches new AND removed occurrences)`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function main() {
|
|
219
|
+
if (!fs.existsSync(baselinePath)) {
|
|
220
|
+
process.stderr.write(`purity gate: missing ${path.relative(packageDir, baselinePath)}\n`);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
const baseline = JSON.parse(fs.readFileSync(baselinePath, "utf8"));
|
|
224
|
+
const actual = scan();
|
|
225
|
+
|
|
226
|
+
const problems = [];
|
|
227
|
+
diffListBuckets(actual.layerViolations, baseline.layerViolations || {}, "layer violation", problems);
|
|
228
|
+
diffListBuckets(actual.builtinViolations, baseline.builtinViolations || {}, "core builtin violation", problems);
|
|
229
|
+
diffCountBuckets(actual.clockEnvCounts, baseline.clockEnvCounts || {}, problems);
|
|
230
|
+
|
|
231
|
+
if (problems.length > 0) {
|
|
232
|
+
process.stderr.write(`purity gate: ${problems.length} problem(s)\n`);
|
|
233
|
+
for (const p of problems) process.stderr.write(` ${p}\n`);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
process.stdout.write("purity gate: src/ matches the committed baseline (no new core/shell layer breaks, no clock/env drift).\n");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
main();
|
package/scripts/release-check.js
CHANGED
|
@@ -39,6 +39,7 @@ const checks = [
|
|
|
39
39
|
"docs/run-retention-reclamation.7.md",
|
|
40
40
|
"docs/durable-state-and-locking.7.md",
|
|
41
41
|
"docs/security-trust-hardening.7.md",
|
|
42
|
+
"docs/trust-audit-anchor.7.md",
|
|
42
43
|
"../../CHANGELOG.md",
|
|
43
44
|
"../../RELEASE.md"
|
|
44
45
|
]) {
|
|
@@ -58,6 +59,7 @@ const checks = [
|
|
|
58
59
|
// `dist:check` builds from src/ AND fails closed if the committed dist/ drifted
|
|
59
60
|
// from that fresh build — strictly stronger than a bare `npm run build`.
|
|
60
61
|
{ name: "dist freshness", command: ["npm", "run", "dist:check"] },
|
|
62
|
+
{ name: "core/shell purity", command: ["npm", "run", "purity:check"] },
|
|
61
63
|
{ name: "type check", command: ["npm", "run", "check"] },
|
|
62
64
|
{ name: "onramp contract", command: ["npm", "run", "onramp:check"] },
|
|
63
65
|
{ name: "run-state schema consistency", command: ["node", "scripts/validate-run-state-schema.js"] },
|
|
@@ -66,6 +68,11 @@ const checks = [
|
|
|
66
68
|
// (release-gate.sh) forces CW_TEST_CONCURRENCY=1 to stay sequential as the
|
|
67
69
|
// deterministic backstop.
|
|
68
70
|
{ name: "tests", command: ["npm", "run", "test:ci"] },
|
|
71
|
+
// Pure core/ unit tests (test/*.test.js) — a separate suite from the smoke
|
|
72
|
+
// tests above (see test/run-unit.js's header). --skip-tests also skips
|
|
73
|
+
// this one, matching "tests": ci.yml runs test:unit directly, so this
|
|
74
|
+
// entry would just repeat it a second time in the release-check pass.
|
|
75
|
+
{ name: "unit tests", command: ["npm", "run", "test:unit"] },
|
|
69
76
|
{ name: "canonical apps", command: ["npm", "run", "canonical-apps"] },
|
|
70
77
|
{ name: "golden path", command: ["npm", "run", "golden-path"] },
|
|
71
78
|
{ name: "CLI MCP parity", command: ["npm", "run", "parity:check"] },
|
|
@@ -88,7 +95,7 @@ function main() {
|
|
|
88
95
|
process.stdout.write(`release:check ${check.name} ... `);
|
|
89
96
|
const started = Date.now();
|
|
90
97
|
try {
|
|
91
|
-
if (skipTests && check.name === "tests") {
|
|
98
|
+
if (skipTests && (check.name === "tests" || check.name === "unit tests")) {
|
|
92
99
|
results.push({ name: check.name, ok: true, skipped: true, elapsedMs: 0 });
|
|
93
100
|
process.stdout.write("skipped\n");
|
|
94
101
|
continue;
|
|
@@ -20,7 +20,7 @@ const repoRoot = path.resolve(pluginRoot, "..", "..");
|
|
|
20
20
|
// CI checks out HEAD, so HEAD === the tree it is releasing.
|
|
21
21
|
//
|
|
22
22
|
// Fallback to the filesystem only when we cannot read from HEAD: not a git work
|
|
23
|
-
// tree, or the path is not tracked at HEAD
|
|
23
|
+
// tree, or the path is not tracked at HEAD.
|
|
24
24
|
// node + git only — no ripgrep (CI portability rule).
|
|
25
25
|
const insideGitWorkTree = (() => {
|
|
26
26
|
const r = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: repoRoot, encoding: "utf8" });
|
|
@@ -36,7 +36,7 @@ function readReleaseSource(relativePath) {
|
|
|
36
36
|
maxBuffer: 1024 * 1024 * 32
|
|
37
37
|
});
|
|
38
38
|
if (r.status === 0) return { text: r.stdout, exists: true, fromHead: true };
|
|
39
|
-
// Not tracked at HEAD — fall through to the working tree
|
|
39
|
+
// Not tracked at HEAD — fall through to the working tree.
|
|
40
40
|
}
|
|
41
41
|
const abs = path.join(repoRoot, relativePath);
|
|
42
42
|
if (!fs.existsSync(abs)) return { text: null, exists: false, fromHead: false };
|
|
@@ -56,9 +56,14 @@ const canonicalApps = CANONICAL_APP_IDS;
|
|
|
56
56
|
function main() {
|
|
57
57
|
const checks = [];
|
|
58
58
|
checkJson("plugins/cool-workflow/package.json", "version", VERSION, checks);
|
|
59
|
-
// package-lock.json is
|
|
60
|
-
//
|
|
59
|
+
// package-lock.json is tracked now, but the documented install still works
|
|
60
|
+
// without it (`npm install --no-package-lock`), so only validate it when
|
|
61
|
+
// present. Check BOTH version fields: the top-level one and the root-package
|
|
62
|
+
// entry packages[""].version — the second one is the field npm keeps in step
|
|
63
|
+
// with package.json on `npm install`, and it is the one that went stale
|
|
64
|
+
// (0.1.97) through the v0.2.0/v0.2.1 cuts while only the first was checked.
|
|
61
65
|
checkJsonIfPresent("plugins/cool-workflow/package-lock.json", "version", VERSION, checks);
|
|
66
|
+
checkNestedJsonIfPresent("plugins/cool-workflow/package-lock.json", ["packages", "", "version"], VERSION, checks);
|
|
62
67
|
checkJson("plugins/cool-workflow/.codex-plugin/plugin.json", "version", VERSION, checks);
|
|
63
68
|
checkJson("plugins/cool-workflow/server.json", "version", VERSION, checks);
|
|
64
69
|
checkNestedJson("plugins/cool-workflow/server.json", ["packages", 0, "version"], VERSION, checks);
|
|
@@ -160,7 +165,7 @@ function main() {
|
|
|
160
165
|
checkIncludes("plugins/cool-workflow/docs/run-retention-reclamation.7.md", VERSION, checks);
|
|
161
166
|
checkIncludes("plugins/cool-workflow/docs/index.md", "run-retention-reclamation.7.md", checks);
|
|
162
167
|
checkIncludes("plugins/cool-workflow/test/run-retention-reclamation-smoke.js", "run-retention-reclamation-smoke", checks);
|
|
163
|
-
checkIncludes("plugins/cool-workflow/src/
|
|
168
|
+
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/scheduling-registry.ts", "gc.plan", checks);
|
|
164
169
|
checkIncludes("plugins/cool-workflow/docs/durable-state-and-locking.7.md", "Durable State & Locking", checks);
|
|
165
170
|
checkIncludes("plugins/cool-workflow/docs/durable-state-and-locking.7.md", VERSION, checks);
|
|
166
171
|
checkIncludes("plugins/cool-workflow/docs/index.md", "durable-state-and-locking.7.md", checks);
|
|
@@ -168,33 +173,37 @@ function main() {
|
|
|
168
173
|
checkIncludes("plugins/cool-workflow/src/shell/fs-atomic.ts", "withFileLock", checks);
|
|
169
174
|
checkIncludes("plugins/cool-workflow/src/shell/drive.ts", "driveStep", checks);
|
|
170
175
|
checkIncludes("plugins/cool-workflow/dist/shell/drive.js", "driveStep", checks);
|
|
171
|
-
checkIncludes("plugins/cool-workflow/src/
|
|
176
|
+
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/pipeline.ts", "run.drive", checks);
|
|
172
177
|
checkIncludes("plugins/cool-workflow/src/shell/collaboration-io.ts", "deriveReviewState", checks);
|
|
173
178
|
checkIncludes("plugins/cool-workflow/dist/shell/collaboration-io.js", "deriveReviewState", checks);
|
|
174
|
-
checkIncludes("plugins/cool-workflow/src/
|
|
179
|
+
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/multi-agent.ts", "review.status", checks);
|
|
175
180
|
checkIncludes("plugins/cool-workflow/src/shell/observability.ts", "deriveMetricsReport", checks);
|
|
176
181
|
checkIncludes("plugins/cool-workflow/dist/shell/observability.js", "deriveMetricsReport", checks);
|
|
177
|
-
checkIncludes("plugins/cool-workflow/src/
|
|
182
|
+
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/reporting.ts", "metrics.show", checks);
|
|
178
183
|
checkIncludes("plugins/cool-workflow/manifest/pricing.policy.json", "schemaVersion", checks);
|
|
179
184
|
checkIncludes("plugins/cool-workflow/src/shell/workbench.ts", "buildWorkbenchRunView", checks);
|
|
180
185
|
checkIncludes("plugins/cool-workflow/dist/shell/workbench.js", "buildWorkbenchRunView", checks);
|
|
181
|
-
checkIncludes("plugins/cool-workflow/src/
|
|
186
|
+
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/reporting.ts", "workbench.view", checks);
|
|
182
187
|
checkIncludes("plugins/cool-workflow/src/shell/execution-backend/registry.ts", "ExecutionBackend", checks);
|
|
183
188
|
checkIncludes("plugins/cool-workflow/dist/shell/execution-backend/registry.js", "ExecutionBackend", checks);
|
|
184
|
-
checkIncludes("plugins/cool-workflow/src/
|
|
189
|
+
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/exec-backend.ts", "backend.list", checks);
|
|
185
190
|
checkIncludes("plugins/cool-workflow/src/shell/run-registry-io.ts", "RunRegistry", checks);
|
|
186
191
|
checkIncludes("plugins/cool-workflow/dist/shell/run-registry-io.js", "RunRegistry", checks);
|
|
187
|
-
checkIncludes("plugins/cool-workflow/src/
|
|
192
|
+
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/scheduling-registry.ts", "registry.refresh", checks);
|
|
188
193
|
checkIncludes("plugins/cool-workflow/package.json", "parity:check", checks);
|
|
189
194
|
checkIncludes("plugins/cool-workflow/scripts/parity-check.js", "buildParityReport", checks);
|
|
190
195
|
checkIncludes("plugins/cool-workflow/test/cli-mcp-parity-smoke.js", "cli-mcp-parity-smoke", checks);
|
|
191
|
-
checkIncludes("plugins/cool-workflow/src/
|
|
196
|
+
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/registry-core.ts", "export const REGISTRY", checks);
|
|
192
197
|
checkIncludes("plugins/cool-workflow/src/shell/pipeline-cli.ts", "planSummary", checks);
|
|
193
198
|
checkIncludes("plugins/cool-workflow/dist/core/capability-table.js", "REGISTRY", checks);
|
|
194
199
|
checkIncludes("plugins/cool-workflow/docs/multi-agent-runtime-core.7.md", "Multi-Agent Runtime Core", checks);
|
|
195
200
|
checkIncludes("plugins/cool-workflow/docs/dogfood-one-real-repo.7.md", "Dogfood One Real Repo", checks);
|
|
196
201
|
checkIncludes("plugins/cool-workflow/docs/getting-started.md", "npm run release:check", checks);
|
|
197
202
|
checkIncludes("plugins/cool-workflow/package.json", "eval:replay", checks);
|
|
203
|
+
checkIncludes("plugins/cool-workflow/docs/trust-audit-anchor.7.md", "Trust Audit Anchor", checks);
|
|
204
|
+
checkIncludes("plugins/cool-workflow/docs/trust-audit-anchor.7.md", VERSION, checks);
|
|
205
|
+
checkIncludes("plugins/cool-workflow/docs/index.md", "trust-audit-anchor.7.md", checks);
|
|
206
|
+
checkIncludes("plugins/cool-workflow/test/trust-audit-anchor-smoke.js", "trust-audit-anchor-smoke", checks);
|
|
198
207
|
checkIncludes("plugins/cool-workflow/docs/release-and-migration.7.md", VERSION, checks);
|
|
199
208
|
checkIncludes("CHANGELOG.md", `## ${VERSION}`, checks);
|
|
200
209
|
checkIncludes("RELEASE.md", VERSION, checks);
|
|
@@ -227,6 +236,18 @@ function checkNestedJson(relativePath, keyPath, expected, checks) {
|
|
|
227
236
|
checks.push({ path: relativePath, key: keyPath.join("."), value });
|
|
228
237
|
}
|
|
229
238
|
|
|
239
|
+
function checkNestedJsonIfPresent(relativePath, keyPath, expected, checks) {
|
|
240
|
+
const src = readReleaseSource(relativePath);
|
|
241
|
+
if (!src.exists) {
|
|
242
|
+
checks.push({ path: relativePath, key: keyPath.join("."), skipped: "absent" });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
let value = JSON.parse(src.text);
|
|
246
|
+
for (const key of keyPath) value = value?.[key];
|
|
247
|
+
assert.equal(value, expected, `${relativePath}.${keyPath.join(".")} must be ${expected}`);
|
|
248
|
+
checks.push({ path: relativePath, key: keyPath.join("."), value });
|
|
249
|
+
}
|
|
250
|
+
|
|
230
251
|
function checkJsonIfPresent(relativePath, key, expected, checks) {
|
|
231
252
|
const src = readReleaseSource(relativePath);
|
|
232
253
|
if (!src.exists) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# workflows/ — legacy compatibility surface (pinned, do not remove)
|
|
2
|
+
|
|
3
|
+
`architecture-review.workflow.js` and `research-synthesis.workflow.js` are
|
|
4
|
+
the old, pre-`apps/` workflow-file format. The real, current homes for
|
|
5
|
+
these two workflows are `apps/architecture-review/` and
|
|
6
|
+
`apps/research-synthesis/` — these files exist ONLY so an id from before
|
|
7
|
+
the `apps/` format still resolves.
|
|
8
|
+
|
|
9
|
+
This is deliberate duplication, not drift to clean up: each file here
|
|
10
|
+
registers its OWN distinct id (`legacy-architecture-review`,
|
|
11
|
+
`legacy-research-synthesis`), pinned by
|
|
12
|
+
`v2/conformance/cases/multiagent-app-list.case.js` and
|
|
13
|
+
`plugins/cool-workflow/test/workflow-app-framework-smoke.js`. `cw list` /
|
|
14
|
+
`cw app list` show both the real app and its legacy id side by side
|
|
15
|
+
(`src/shell/workflow-app-loader.ts` discovers both roots).
|
|
16
|
+
|
|
17
|
+
Per this project's own POLA rule, removing or thinning either file would
|
|
18
|
+
change `cw list`'s output bytes — that is a breaking change, only doable
|
|
19
|
+
behind a major-version break, not a routine cleanup.
|