cool-workflow 0.1.79 → 0.1.81
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 +51 -3
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +64 -0
- package/apps/architecture-review-fast/workflow.js +153 -0
- 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 +21 -7
- package/dist/candidate-scoring.js +42 -22
- package/dist/capability-core.js +132 -17
- package/dist/capability-registry.js +138 -168
- package/dist/cli.js +97 -98
- package/dist/collaboration.js +5 -6
- package/dist/commit.js +20 -6
- package/dist/compare.js +18 -0
- package/dist/coordinator/classify.js +45 -0
- package/dist/coordinator/paths.js +42 -0
- package/dist/coordinator/util.js +129 -0
- package/dist/coordinator.js +127 -300
- package/dist/dispatch.js +35 -0
- package/dist/drive.js +79 -6
- package/dist/error-feedback.js +8 -4
- package/dist/evidence-reasoning.js +3 -3
- package/dist/execution-backend/agent.js +331 -0
- package/dist/execution-backend/probes.js +96 -0
- package/dist/execution-backend/util.js +47 -0
- package/dist/execution-backend.js +73 -421
- package/dist/mcp-server.js +79 -183
- package/dist/multi-agent/graph.js +84 -0
- package/dist/multi-agent/helpers.js +145 -0
- package/dist/multi-agent/paths.js +22 -0
- package/dist/multi-agent-eval/format.js +194 -0
- package/dist/multi-agent-eval/normalize.js +51 -0
- package/dist/multi-agent-eval.js +39 -244
- package/dist/multi-agent-host.js +0 -19
- package/dist/multi-agent.js +125 -314
- package/dist/node-snapshot.js +3 -3
- package/dist/observability/format.js +61 -0
- package/dist/observability/intake.js +98 -0
- package/dist/observability.js +14 -160
- package/dist/operator-ux/format.js +364 -0
- package/dist/operator-ux.js +22 -363
- package/dist/orchestrator/lifecycle-operations.js +2 -1
- package/dist/orchestrator/report.js +8 -0
- package/dist/orchestrator.js +26 -9
- package/dist/reclamation.js +26 -21
- package/dist/run-export.js +494 -25
- package/dist/run-registry/derive.js +172 -0
- package/dist/run-registry/format.js +124 -0
- package/dist/run-registry/gc.js +251 -0
- package/dist/run-registry/policy.js +16 -0
- package/dist/run-registry/queue.js +116 -0
- package/dist/run-registry.js +89 -597
- package/dist/run-state-schema.js +1 -0
- package/dist/sandbox-profile.js +43 -2
- package/dist/state-explosion/format.js +159 -0
- package/dist/state-explosion/helpers.js +82 -0
- package/dist/state-explosion.js +165 -304
- package/dist/state-node.js +19 -4
- package/dist/telemetry-attestation.js +55 -0
- package/dist/telemetry-demo.js +15 -3
- package/dist/telemetry-ledger.js +60 -15
- package/dist/topology.js +25 -8
- package/dist/triggers.js +33 -14
- package/dist/trust-audit.js +145 -33
- package/dist/version.js +1 -1
- package/dist/worker-isolation/helpers.js +51 -0
- package/dist/worker-isolation/paths.js +46 -0
- package/dist/worker-isolation.js +39 -115
- package/docs/agent-delegation-drive.7.md +71 -0
- package/docs/canonical-workflow-apps.7.md +37 -0
- package/docs/cli-mcp-parity.7.md +16 -0
- package/docs/contract-migration-tooling.7.md +6 -0
- package/docs/control-plane-scheduling.7.md +6 -0
- package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +40 -0
- package/docs/durable-state-and-locking.7.md +8 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +6 -0
- package/docs/execution-backends.7.md +6 -0
- package/docs/index.md +2 -0
- package/docs/launch/demo.tape +28 -0
- package/docs/launch/launch-kit.md +96 -17
- package/docs/launch/pre-launch-checklist.md +53 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +8 -0
- package/docs/multi-agent-eval-replay-harness.7.md +6 -0
- package/docs/multi-agent-operator-ux.7.md +6 -0
- package/docs/multi-agent-trust-policy-audit.7.md +27 -0
- package/docs/node-snapshot-diff-replay.7.md +6 -0
- package/docs/observability-cost-accounting.7.md +6 -0
- package/docs/project-index.md +27 -6
- package/docs/real-execution-backends.7.md +6 -0
- package/docs/release-and-migration.7.md +8 -0
- package/docs/release-tooling.7.md +6 -0
- package/docs/routines.md +23 -0
- package/docs/run-registry-control-plane.7.md +89 -2
- package/docs/run-retention-reclamation.7.md +8 -0
- package/docs/source-context-profiles.7.md +119 -0
- package/docs/state-explosion-management.7.md +13 -0
- package/docs/team-collaboration.7.md +6 -0
- package/docs/trust-model.md +267 -0
- package/docs/unix-principles.md +49 -1
- package/docs/vendor-manifest-loadability.7.md +43 -0
- package/docs/web-desktop-workbench.7.md +6 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +142 -0
- package/package.json +4 -1
- package/scripts/agents/builtin-templates.json +7 -0
- package/scripts/agents/claude-p-agent.js +129 -43
- package/scripts/architecture-review-fast.js +362 -0
- package/scripts/bump-version.js +5 -10
- package/scripts/canonical-apps-list.js +64 -0
- package/scripts/canonical-apps.js +36 -4
- package/scripts/coverage-gate.js +211 -0
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/parity-check.js +5 -0
- package/scripts/release-check.js +5 -1
- package/scripts/source-context.js +291 -0
- package/scripts/version-sync-check.js +5 -7
- package/skills/ci-triage/SKILL.md +50 -0
- package/skills/ci-triage/agents/openai.yaml +4 -0
- package/skills/cool-workflow/SKILL.md +4 -1
- package/skills/deploy-check/SKILL.md +55 -0
- package/skills/deploy-check/agents/openai.yaml +4 -0
- package/skills/design-qa/SKILL.md +49 -0
- package/skills/design-qa/agents/openai.yaml +4 -0
- package/skills/pr-review/SKILL.md +45 -0
- package/skills/pr-review/agents/openai.yaml +4 -0
- package/dist/capability-dispatcher.js +0 -86
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// architecture-review-fast — userland accelerator wrapper.
|
|
5
|
+
//
|
|
6
|
+
// Mechanism only: prepare one cached JSONL source context, pass its digest into
|
|
7
|
+
// the opt-in fast app, then optionally create a background schedule for the full
|
|
8
|
+
// architecture-review app. The model still runs only through CW's external agent
|
|
9
|
+
// backend; this script imports no model SDK and holds no key.
|
|
10
|
+
|
|
11
|
+
const crypto = require("node:crypto");
|
|
12
|
+
const fs = require("node:fs");
|
|
13
|
+
const path = require("node:path");
|
|
14
|
+
const { spawnSync } = require("node:child_process");
|
|
15
|
+
|
|
16
|
+
const pluginRoot = path.resolve(__dirname, "..");
|
|
17
|
+
const repoRoot = path.resolve(pluginRoot, "..", "..");
|
|
18
|
+
const node = process.execPath;
|
|
19
|
+
const cw = path.join(pluginRoot, "scripts", "cw.js");
|
|
20
|
+
const sourceContext = path.join(pluginRoot, "scripts", "source-context.js");
|
|
21
|
+
|
|
22
|
+
function main() {
|
|
23
|
+
const started = nowNs();
|
|
24
|
+
const args = parseArgs(process.argv.slice(2));
|
|
25
|
+
if (args.help) return usage(0);
|
|
26
|
+
|
|
27
|
+
const repo = path.resolve(required(args.repo, "repo"));
|
|
28
|
+
const question = required(args.question, "question");
|
|
29
|
+
const requestedProfile = stringArg(args.profile);
|
|
30
|
+
const ref = stringArg(args.ref) || "HEAD";
|
|
31
|
+
const requestedProfileFile = stringArg(args.profileFile || args["profile-file"]);
|
|
32
|
+
const defaultExternalProfile = !requestedProfile && !requestedProfileFile && repo !== repoRoot;
|
|
33
|
+
const profile = defaultExternalProfile ? "repo" : requestedProfile || "core";
|
|
34
|
+
const cacheDir = path.resolve(stringArg(args.cacheDir || args["cache-dir"]) || path.join(repo, ".cw", "cache", "source-context"));
|
|
35
|
+
const contextOut = path.resolve(stringArg(args.contextOut || args["context-out"]) || path.join(repo, ".cw", "context", `${profile}-source.jsonl`));
|
|
36
|
+
const profileFile = defaultExternalProfile ? writeDefaultRepoProfile(repo, contextOut) : requestedProfileFile;
|
|
37
|
+
const includeMetrics = truthy(args.metrics);
|
|
38
|
+
const fastModel = stringArg(args.fastModel || args["fast-model"]);
|
|
39
|
+
const strongModel = stringArg(args.strongModel || args["strong-model"]);
|
|
40
|
+
const modelEnv = modelPolicyEnv(fastModel, strongModel);
|
|
41
|
+
|
|
42
|
+
const contextExport = timed(() => exportSourceContext({
|
|
43
|
+
repo,
|
|
44
|
+
profile,
|
|
45
|
+
ref,
|
|
46
|
+
profileFile,
|
|
47
|
+
cacheDir
|
|
48
|
+
}));
|
|
49
|
+
const contextText = contextExport.value;
|
|
50
|
+
assertNonEmptySourceContext(contextText, profile, repo);
|
|
51
|
+
fs.mkdirSync(path.dirname(contextOut), { recursive: true });
|
|
52
|
+
fs.writeFileSync(contextOut, contextText, "utf8");
|
|
53
|
+
const digest = `sha256:${crypto.createHash("sha256").update(contextText, "utf8").digest("hex")}`;
|
|
54
|
+
|
|
55
|
+
const reviewArgs = [
|
|
56
|
+
"quickstart",
|
|
57
|
+
"architecture-review-fast",
|
|
58
|
+
"--repo",
|
|
59
|
+
repo,
|
|
60
|
+
"--question",
|
|
61
|
+
question,
|
|
62
|
+
"--sourceContext",
|
|
63
|
+
contextOut,
|
|
64
|
+
"--sourceContextDigest",
|
|
65
|
+
digest
|
|
66
|
+
];
|
|
67
|
+
appendRepeated(reviewArgs, "--invariant", args.invariant);
|
|
68
|
+
appendOption(reviewArgs, "--focus", args.focus);
|
|
69
|
+
appendPassThrough(reviewArgs, args, [
|
|
70
|
+
"agent-command",
|
|
71
|
+
"agentCommand",
|
|
72
|
+
"agent-endpoint",
|
|
73
|
+
"agentEndpoint",
|
|
74
|
+
"agent-model",
|
|
75
|
+
"agentModel",
|
|
76
|
+
"agent-timeout-ms",
|
|
77
|
+
"agentTimeoutMs",
|
|
78
|
+
"once",
|
|
79
|
+
"preview",
|
|
80
|
+
"now"
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const fastReviewRun = timed(() => runCwJson(reviewArgs, repo, modelEnv));
|
|
84
|
+
const fastReview = fastReviewRun.value;
|
|
85
|
+
const sourceContextMeta = {
|
|
86
|
+
path: contextOut,
|
|
87
|
+
digest,
|
|
88
|
+
profile,
|
|
89
|
+
ref,
|
|
90
|
+
cacheDir
|
|
91
|
+
};
|
|
92
|
+
const fullReviewScheduleRun = truthy(args.scheduleFull || args["schedule-full"])
|
|
93
|
+
? timed(() => scheduleFullReview(repo, question, args, fastReview, sourceContextMeta))
|
|
94
|
+
: undefined;
|
|
95
|
+
const fullReviewSchedule = fullReviewScheduleRun?.value;
|
|
96
|
+
|
|
97
|
+
writeJson({
|
|
98
|
+
schemaVersion: 1,
|
|
99
|
+
appId: "architecture-review-fast",
|
|
100
|
+
sourceContext: sourceContextMeta,
|
|
101
|
+
fastReview,
|
|
102
|
+
...(fastModel || strongModel ? { modelPolicy: { ...(fastModel ? { fastModel } : {}), ...(strongModel ? { strongModel } : {}) } } : {}),
|
|
103
|
+
...(fullReviewSchedule ? { fullReviewSchedule } : {}),
|
|
104
|
+
...(includeMetrics ? { metrics: buildMetrics(started, contextText, contextExport.elapsedMs, fastReview, fastReviewRun.elapsedMs, fullReviewScheduleRun?.elapsedMs) } : {})
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function exportSourceContext(options) {
|
|
109
|
+
const argv = [
|
|
110
|
+
sourceContext,
|
|
111
|
+
"export",
|
|
112
|
+
"--profile",
|
|
113
|
+
options.profile,
|
|
114
|
+
"--ref",
|
|
115
|
+
options.ref,
|
|
116
|
+
"--repo-root",
|
|
117
|
+
options.repo,
|
|
118
|
+
"--cache-dir",
|
|
119
|
+
options.cacheDir
|
|
120
|
+
];
|
|
121
|
+
if (options.profileFile) argv.push("--profile-file", path.resolve(options.profileFile));
|
|
122
|
+
const result = spawnSync(node, argv, {
|
|
123
|
+
cwd: repoRoot,
|
|
124
|
+
encoding: "utf8",
|
|
125
|
+
maxBuffer: 1024 * 1024 * 128
|
|
126
|
+
});
|
|
127
|
+
if (result.status !== 0) die(result.stderr || result.stdout || "source context export failed");
|
|
128
|
+
return result.stdout;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function writeDefaultRepoProfile(repo, contextOut) {
|
|
132
|
+
const file = path.join(path.dirname(contextOut), "repo-source-profile.json");
|
|
133
|
+
const profile = {
|
|
134
|
+
schemaVersion: 1,
|
|
135
|
+
profiles: {
|
|
136
|
+
repo: {
|
|
137
|
+
description: "Default external repository profile for architecture-review-fast.",
|
|
138
|
+
maxLines: 50000,
|
|
139
|
+
include: [
|
|
140
|
+
"README.md",
|
|
141
|
+
"README.*",
|
|
142
|
+
"package.json",
|
|
143
|
+
"tsconfig.json",
|
|
144
|
+
"pyproject.toml",
|
|
145
|
+
"Cargo.toml",
|
|
146
|
+
"go.mod",
|
|
147
|
+
".github/**",
|
|
148
|
+
"src/**",
|
|
149
|
+
"lib/**",
|
|
150
|
+
"app/**",
|
|
151
|
+
"apps/**",
|
|
152
|
+
"bin/**",
|
|
153
|
+
"scripts/**",
|
|
154
|
+
"docs/**",
|
|
155
|
+
"test/**",
|
|
156
|
+
"tests/**"
|
|
157
|
+
],
|
|
158
|
+
exclude: [
|
|
159
|
+
".cw/**",
|
|
160
|
+
"dist/**",
|
|
161
|
+
"build/**",
|
|
162
|
+
"coverage/**",
|
|
163
|
+
"node_modules/**",
|
|
164
|
+
"vendor/**",
|
|
165
|
+
"target/**",
|
|
166
|
+
"docs/assets/**",
|
|
167
|
+
"assets/**",
|
|
168
|
+
"public/**"
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
174
|
+
fs.writeFileSync(file, `${JSON.stringify(profile, null, 2)}\n`, "utf8");
|
|
175
|
+
return file;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function assertNonEmptySourceContext(text, profile, repo) {
|
|
179
|
+
const records = text.trim() ? text.trim().split(/\n/).filter(Boolean).length : 0;
|
|
180
|
+
if (records > 0) return;
|
|
181
|
+
die([
|
|
182
|
+
`source context profile "${profile}" exported zero records for ${repo}`,
|
|
183
|
+
"pass --profile-file with include rules for this repository, or choose a profile that matches tracked text files"
|
|
184
|
+
].join("; "));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function scheduleFullReview(repo, question, args, fastReview, sourceContextMeta) {
|
|
188
|
+
const delayMinutes = stringArg(args.fullDelayMinutes || args["full-delay-minutes"]) || "1";
|
|
189
|
+
const prompt = [
|
|
190
|
+
`Run full architecture-review for ${repo}.`,
|
|
191
|
+
`Question: ${question}`,
|
|
192
|
+
args.focus ? `Focus: ${args.focus}` : "",
|
|
193
|
+
`Fast review run: ${fastReview?.runId || "unknown"}.`,
|
|
194
|
+
fastReview?.reportPath ? `Fast review report: ${fastReview.reportPath}.` : "",
|
|
195
|
+
`Fast review status: ${fastReview?.status || "unknown"} (${fastReview?.completedWorkers || 0}/${fastReview?.plannedWorkers || 0} workers completed).`,
|
|
196
|
+
`Source context: ${sourceContextMeta.path} (${sourceContextMeta.digest}, profile ${sourceContextMeta.profile}, ref ${sourceContextMeta.ref}).`,
|
|
197
|
+
"Use the completed architecture-review-fast report as the foreground triage result; write the full review report path and digest when the background review finishes."
|
|
198
|
+
].filter(Boolean).join(" ");
|
|
199
|
+
return runCwJson([
|
|
200
|
+
"schedule",
|
|
201
|
+
"create",
|
|
202
|
+
"--cwd",
|
|
203
|
+
repo,
|
|
204
|
+
"--kind",
|
|
205
|
+
"reminder",
|
|
206
|
+
"--delayMinutes",
|
|
207
|
+
delayMinutes,
|
|
208
|
+
"--maxRuns",
|
|
209
|
+
"1",
|
|
210
|
+
"--workflowId",
|
|
211
|
+
"architecture-review",
|
|
212
|
+
"--prompt",
|
|
213
|
+
prompt
|
|
214
|
+
], repo);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function runCwJson(args, cwd, extraEnv = {}) {
|
|
218
|
+
const result = spawnSync(node, [cw, ...args], {
|
|
219
|
+
cwd,
|
|
220
|
+
encoding: "utf8",
|
|
221
|
+
env: { ...process.env, ...extraEnv },
|
|
222
|
+
maxBuffer: 1024 * 1024 * 64
|
|
223
|
+
});
|
|
224
|
+
if (result.status !== 0) die(result.stderr || result.stdout || `cw ${args.join(" ")} failed`);
|
|
225
|
+
try {
|
|
226
|
+
return JSON.parse(result.stdout);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
die(`cw returned non-JSON output: ${error.message}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function parseArgs(argv) {
|
|
233
|
+
const args = {};
|
|
234
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
235
|
+
const token = argv[index];
|
|
236
|
+
if (!token.startsWith("--")) {
|
|
237
|
+
(args._ ||= []).push(token);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const raw = token.slice(2);
|
|
241
|
+
const eq = raw.indexOf("=");
|
|
242
|
+
const key = eq >= 0 ? raw.slice(0, eq) : raw;
|
|
243
|
+
const value = eq >= 0 ? raw.slice(eq + 1) : argv[index + 1] && !argv[index + 1].startsWith("--") ? argv[++index] : true;
|
|
244
|
+
if (args[key] === undefined) args[key] = value;
|
|
245
|
+
else if (Array.isArray(args[key])) args[key].push(value);
|
|
246
|
+
else args[key] = [args[key], value];
|
|
247
|
+
}
|
|
248
|
+
return args;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function appendPassThrough(argv, args, keys) {
|
|
252
|
+
for (const key of keys) {
|
|
253
|
+
if (args[key] === undefined) continue;
|
|
254
|
+
const flag = key.includes("-") ? `--${key}` : `--${key}`;
|
|
255
|
+
if (args[key] === true) argv.push(flag);
|
|
256
|
+
else appendRepeated(argv, flag, args[key]);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function appendRepeated(argv, flag, value) {
|
|
261
|
+
if (value === undefined || value === false) return;
|
|
262
|
+
const values = Array.isArray(value) ? value : [value];
|
|
263
|
+
for (const entry of values) argv.push(flag, String(entry));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function appendOption(argv, flag, value) {
|
|
267
|
+
if (value === undefined || value === false || value === true || value === "") return;
|
|
268
|
+
argv.push(flag, String(value));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function required(value, name) {
|
|
272
|
+
const text = stringArg(value);
|
|
273
|
+
if (!text) die(`missing required --${name}`);
|
|
274
|
+
return text;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function stringArg(value) {
|
|
278
|
+
if (value === undefined || value === null || value === true || value === false) return "";
|
|
279
|
+
return Array.isArray(value) ? String(value[value.length - 1] || "") : String(value);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function truthy(value) {
|
|
283
|
+
return value === true || value === "true" || value === "1" || value === "yes";
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function modelPolicyEnv(fastModel, strongModel) {
|
|
287
|
+
return {
|
|
288
|
+
...(fastModel ? { CW_ARCHITECTURE_REVIEW_FAST_MODEL: fastModel } : {}),
|
|
289
|
+
...(strongModel ? { CW_ARCHITECTURE_REVIEW_STRONG_MODEL: strongModel } : {})
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function nowNs() {
|
|
294
|
+
return process.hrtime.bigint();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function elapsedMs(started) {
|
|
298
|
+
return Number((process.hrtime.bigint() - started) / 1000000n);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function timed(fn) {
|
|
302
|
+
const started = nowNs();
|
|
303
|
+
const value = fn();
|
|
304
|
+
return { value, elapsedMs: elapsedMs(started) };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function buildMetrics(started, contextText, sourceContextElapsedMs, fastReview, fastReviewElapsedMs, fullReviewScheduleElapsedMs) {
|
|
308
|
+
const steps = Array.isArray(fastReview?.steps) ? fastReview.steps : [];
|
|
309
|
+
const handleKinds = countBy(steps.map((step) => step && step.handleKind).filter(Boolean));
|
|
310
|
+
const actions = countBy(steps.map((step) => step && step.action).filter(Boolean));
|
|
311
|
+
return {
|
|
312
|
+
totalElapsedMs: elapsedMs(started),
|
|
313
|
+
sourceContext: {
|
|
314
|
+
elapsedMs: sourceContextElapsedMs,
|
|
315
|
+
bytes: Buffer.byteLength(contextText, "utf8")
|
|
316
|
+
},
|
|
317
|
+
fastReview: {
|
|
318
|
+
elapsedMs: fastReviewElapsedMs,
|
|
319
|
+
status: fastReview?.status,
|
|
320
|
+
plannedWorkers: fastReview?.plannedWorkers,
|
|
321
|
+
completedWorkers: fastReview?.completedWorkers,
|
|
322
|
+
steps: steps.length,
|
|
323
|
+
actions,
|
|
324
|
+
handleKinds,
|
|
325
|
+
resultCacheHits: Number(handleKinds["result-cache"] || 0),
|
|
326
|
+
agentSpawns: steps.filter((step) => step && step.backendId === "agent" && step.handleKind && step.handleKind !== "result-cache").length
|
|
327
|
+
},
|
|
328
|
+
...(fullReviewScheduleElapsedMs === undefined ? {} : { fullReviewSchedule: { elapsedMs: fullReviewScheduleElapsedMs } })
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function countBy(values) {
|
|
333
|
+
const counts = {};
|
|
334
|
+
for (const value of values) counts[String(value)] = (counts[String(value)] || 0) + 1;
|
|
335
|
+
return counts;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function writeJson(value) {
|
|
339
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function usage(code) {
|
|
343
|
+
process.stderr.write([
|
|
344
|
+
"usage:",
|
|
345
|
+
" node scripts/architecture-review-fast.js --repo PATH --question TEXT [--agent-command CMD]",
|
|
346
|
+
"",
|
|
347
|
+
"options:",
|
|
348
|
+
" --profile core --ref HEAD --profile-file PATH --cache-dir DIR --context-out PATH",
|
|
349
|
+
" --fast-model MODEL --strong-model MODEL",
|
|
350
|
+
" --invariant TEXT --focus TEXT --preview --once",
|
|
351
|
+
" --schedule-full [--full-delay-minutes N]",
|
|
352
|
+
" --metrics"
|
|
353
|
+
].join("\n") + "\n");
|
|
354
|
+
process.exitCode = code;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function die(message) {
|
|
358
|
+
process.stderr.write(`architecture-review-fast: ${String(message).trim()}\n`);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
main();
|
package/scripts/bump-version.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
const { spawnSync } = require("node:child_process");
|
|
23
23
|
const fs = require("node:fs");
|
|
24
24
|
const path = require("node:path");
|
|
25
|
+
const { CANONICAL_APP_IDS } = require("./canonical-apps-list.js");
|
|
25
26
|
|
|
26
27
|
const pluginRoot = path.resolve(__dirname, "..");
|
|
27
28
|
const repoRoot = path.resolve(pluginRoot, "..", "..");
|
|
@@ -89,16 +90,10 @@ function main() {
|
|
|
89
90
|
|
|
90
91
|
// 5. canonical apps app.json (top-level version only; never minVersion).
|
|
91
92
|
// ONLY the canonical apps track the runtime version — workflow-app-framework-demo
|
|
92
|
-
// is pinned (e.g. 0.1.0) and must NOT be bumped.
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"end-to-end-golden-path",
|
|
97
|
-
"pr-review-fix-ci",
|
|
98
|
-
"release-cut",
|
|
99
|
-
"research-synthesis"
|
|
100
|
-
];
|
|
101
|
-
for (const appId of CANONICAL_APPS) {
|
|
93
|
+
// is pinned (e.g. 0.1.0) and must NOT be bumped. The list is DERIVED from
|
|
94
|
+
// apps/ (excluding metadata.example demos) by scripts/canonical-apps-list.js,
|
|
95
|
+
// the single source version-sync-check.js asserts against — no hand-copy.
|
|
96
|
+
for (const appId of CANONICAL_APP_IDS) {
|
|
102
97
|
const appJson = path.join(pluginRoot, "apps", appId, "app.json");
|
|
103
98
|
if (fs.existsSync(appJson) && replaceFirstVersionField(appJson, next)) {
|
|
104
99
|
note(`apps/${appId}/app.json`);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// Single source of truth for the CANONICAL app id list.
|
|
5
|
+
//
|
|
6
|
+
// Audit finding M5: this list was hand-copied into three scripts
|
|
7
|
+
// (bump-version.js, version-sync-check.js, canonical-apps.js) with no gate
|
|
8
|
+
// enforcing agreement, so drift between the copies was silent. This module
|
|
9
|
+
// DERIVES the list from the `apps/` directory on disk so the three callers can
|
|
10
|
+
// never disagree — there is nothing left to copy.
|
|
11
|
+
//
|
|
12
|
+
// What counts as canonical: every app directory under `apps/` whose `app.json`
|
|
13
|
+
// is NOT a demo. The real demo marker is `metadata.example === true` (that, NOT
|
|
14
|
+
// `versionPinned`, is how the only non-canonical app — workflow-app-framework-demo,
|
|
15
|
+
// pinned at 0.1.0 — is flagged). Example apps are excluded because they are
|
|
16
|
+
// version-pinned and must not be bumped or version-asserted with the runtime.
|
|
17
|
+
//
|
|
18
|
+
// Portability: node fs/path only, no external tools (CI portability rule).
|
|
19
|
+
|
|
20
|
+
const fs = require("node:fs");
|
|
21
|
+
const path = require("node:path");
|
|
22
|
+
|
|
23
|
+
const pluginRoot = path.resolve(__dirname, "..");
|
|
24
|
+
const appsDir = path.join(pluginRoot, "apps");
|
|
25
|
+
|
|
26
|
+
// The end-to-end golden path is canonical (and version-tracked) but is exercised
|
|
27
|
+
// by its own dedicated harness (scripts/golden-path.js), not by the per-app CLI
|
|
28
|
+
// smoke in canonical-apps.js. Expose its id so that script can express
|
|
29
|
+
// "canonical minus golden-path" without re-introducing a hand-copied list.
|
|
30
|
+
const GOLDEN_PATH_APP_ID = "end-to-end-golden-path";
|
|
31
|
+
|
|
32
|
+
function isExampleApp(appJsonPath) {
|
|
33
|
+
// An app is excluded from the canonical list iff its app.json declares
|
|
34
|
+
// metadata.example === true. Any read/parse failure is treated as
|
|
35
|
+
// "not an example" so a malformed app surfaces in the canonical list (and
|
|
36
|
+
// therefore in the version gate) rather than being silently dropped.
|
|
37
|
+
try {
|
|
38
|
+
const json = JSON.parse(fs.readFileSync(appJsonPath, "utf8"));
|
|
39
|
+
return json && json.metadata && json.metadata.example === true;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function listCanonicalAppIds() {
|
|
46
|
+
return fs
|
|
47
|
+
.readdirSync(appsDir, { withFileTypes: true })
|
|
48
|
+
.filter((entry) => entry.isDirectory())
|
|
49
|
+
.map((entry) => entry.name)
|
|
50
|
+
.filter((id) => {
|
|
51
|
+
const appJson = path.join(appsDir, id, "app.json");
|
|
52
|
+
if (!fs.existsSync(appJson)) return false; // not an app directory
|
|
53
|
+
return !isExampleApp(appJson);
|
|
54
|
+
})
|
|
55
|
+
.sort(); // deterministic order (replay determinism)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const CANONICAL_APP_IDS = listCanonicalAppIds();
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
CANONICAL_APP_IDS,
|
|
62
|
+
listCanonicalAppIds,
|
|
63
|
+
GOLDEN_PATH_APP_ID
|
|
64
|
+
};
|
|
@@ -6,6 +6,7 @@ const { execFileSync } = require("node:child_process");
|
|
|
6
6
|
const fs = require("node:fs");
|
|
7
7
|
const os = require("node:os");
|
|
8
8
|
const path = require("node:path");
|
|
9
|
+
const { CANONICAL_APP_IDS, GOLDEN_PATH_APP_ID } = require("./canonical-apps-list.js");
|
|
9
10
|
|
|
10
11
|
const pluginRoot = path.resolve(__dirname, "..");
|
|
11
12
|
const cli = path.join(pluginRoot, "scripts/cw.js");
|
|
@@ -25,6 +26,23 @@ const canonicalApps = [
|
|
|
25
26
|
"Workflow App framework"
|
|
26
27
|
]
|
|
27
28
|
},
|
|
29
|
+
{
|
|
30
|
+
id: "architecture-review-fast",
|
|
31
|
+
args: (workspace) => [
|
|
32
|
+
"--repo",
|
|
33
|
+
workspace,
|
|
34
|
+
"--question",
|
|
35
|
+
"Can a user get a fast architecture answer?",
|
|
36
|
+
"--invariant",
|
|
37
|
+
"Full architecture-review remains available",
|
|
38
|
+
"--focus",
|
|
39
|
+
"Runtime speed",
|
|
40
|
+
"--sourceContext",
|
|
41
|
+
"",
|
|
42
|
+
"--sourceContextDigest",
|
|
43
|
+
""
|
|
44
|
+
]
|
|
45
|
+
},
|
|
28
46
|
{
|
|
29
47
|
id: "pr-review-fix-ci",
|
|
30
48
|
args: (workspace) => [
|
|
@@ -65,7 +83,7 @@ const canonicalApps = [
|
|
|
65
83
|
"--source",
|
|
66
84
|
"plugins/cool-workflow/docs/workflow-app-framework.7.md",
|
|
67
85
|
"--scope",
|
|
68
|
-
"Cool Workflow v0.1.
|
|
86
|
+
"Cool Workflow v0.1.81",
|
|
69
87
|
"--freshness",
|
|
70
88
|
"as of release preparation"
|
|
71
89
|
]
|
|
@@ -73,6 +91,20 @@ const canonicalApps = [
|
|
|
73
91
|
];
|
|
74
92
|
|
|
75
93
|
function main() {
|
|
94
|
+
// Fail-closed drift gate (audit M5): the per-app CLI smoke below must cover
|
|
95
|
+
// exactly the DERIVED canonical set (apps/ minus metadata.example demos) less
|
|
96
|
+
// the golden-path app, which scripts/golden-path.js owns. If a new canonical
|
|
97
|
+
// app appears (or the demo marker flips) without smoke args here, this fails
|
|
98
|
+
// instead of silently skipping it — there is no second hand-copied list.
|
|
99
|
+
const expectedSmokeIds = CANONICAL_APP_IDS.filter((id) => id !== GOLDEN_PATH_APP_ID).sort();
|
|
100
|
+
const actualSmokeIds = canonicalApps.map((app) => app.id).sort();
|
|
101
|
+
assert.deepEqual(
|
|
102
|
+
actualSmokeIds,
|
|
103
|
+
expectedSmokeIds,
|
|
104
|
+
`canonical-apps smoke set drifted from derived canonical list (apps/ minus example demos, minus ${GOLDEN_PATH_APP_ID}): ` +
|
|
105
|
+
`expected ${JSON.stringify(expectedSmokeIds)}, got ${JSON.stringify(actualSmokeIds)}`
|
|
106
|
+
);
|
|
107
|
+
|
|
76
108
|
const appList = runJson(["app", "list"]);
|
|
77
109
|
const workflowList = runJson(["list"]);
|
|
78
110
|
assertUniqueIds(appList, "app list");
|
|
@@ -85,14 +117,14 @@ function main() {
|
|
|
85
117
|
assert.ok(summary, `${app.id} must appear in app list`);
|
|
86
118
|
assert.equal(summary.sourceKind, "app-directory");
|
|
87
119
|
assert.equal(summary.legacy, false);
|
|
88
|
-
assert.equal(summary.version, "0.1.
|
|
120
|
+
assert.equal(summary.version, "0.1.81");
|
|
89
121
|
|
|
90
122
|
const validation = runJson(["app", "validate", manifestPath]);
|
|
91
123
|
assert.equal(validation.valid, true, `${app.id} manifest must validate`);
|
|
92
124
|
|
|
93
125
|
const shown = runJson(["app", "show", app.id]);
|
|
94
126
|
assert.equal(shown.app.id, app.id);
|
|
95
|
-
assert.equal(shown.app.version, "0.1.
|
|
127
|
+
assert.equal(shown.app.version, "0.1.81");
|
|
96
128
|
assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);
|
|
97
129
|
assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);
|
|
98
130
|
assertTaskIdsUnique(shown);
|
|
@@ -103,7 +135,7 @@ function main() {
|
|
|
103
135
|
const plan = runJson(["plan", app.id, ...app.args(workspace)]);
|
|
104
136
|
const state = JSON.parse(fs.readFileSync(plan.statePath, "utf8"));
|
|
105
137
|
assert.equal(state.workflow.app.id, app.id);
|
|
106
|
-
assert.equal(state.workflow.app.version, "0.1.
|
|
138
|
+
assert.equal(state.workflow.app.version, "0.1.81");
|
|
107
139
|
assert.equal(state.workflow.app.metadata.canonical, true);
|
|
108
140
|
assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);
|
|
109
141
|
assert.ok(state.tasks.every((task) => task.sandboxProfileId), `${app.id} plan must include sandbox hints`);
|