cool-workflow 0.2.4 → 0.2.6
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 +204 -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 +32 -4
- package/dist/cli/entry.js +11 -19
- package/dist/cli/global-flags.js +113 -0
- package/dist/cli/parseargv.js +7 -1
- package/dist/core/capability-data.js +337 -8
- package/dist/core/format/help.js +73 -3
- 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/state/run-paths.js +3 -30
- package/dist/core/trust/evidence-grounding.js +13 -1
- package/dist/core/util/cli-args.js +22 -0
- package/dist/core/version.js +1 -1
- package/dist/mcp/dispatch.js +22 -2
- package/dist/mcp/server.js +215 -36
- package/dist/mcp/tool-process.js +181 -0
- package/dist/mcp-server.js +28 -1
- package/dist/shell/agent-config.js +11 -2
- package/dist/shell/audit-cli.js +1 -1
- package/dist/shell/commit.js +8 -2
- package/dist/shell/coordinator-io.js +73 -1
- package/dist/shell/drive.js +195 -128
- package/dist/shell/error-feedback-io.js +6 -0
- package/dist/shell/execution-backend/agent.js +201 -26
- package/dist/shell/execution-backend/container.js +44 -10
- package/dist/shell/execution-backend/local.js +33 -0
- package/dist/shell/fs-atomic.js +96 -12
- package/dist/shell/ledger-cli.js +9 -2
- package/dist/shell/metrics-cli.js +1 -1
- package/dist/shell/multi-agent-cli.js +9 -2
- package/dist/shell/multi-agent-operator-ux.js +1 -1
- package/dist/shell/observability.js +7 -4
- package/dist/shell/onramp.js +48 -5
- package/dist/shell/operator-ux.js +1 -1
- package/dist/shell/perf-trace.js +136 -0
- package/dist/shell/pipeline.js +3 -2
- package/dist/shell/reclamation-io.js +10 -9
- package/dist/shell/report-view-cli.js +2 -0
- package/dist/shell/run-export-cli.js +5 -2
- package/dist/shell/run-export.js +293 -31
- package/dist/shell/run-registry-io.js +8 -0
- package/dist/shell/run-store.js +183 -3
- package/dist/shell/scheduler-io.js +101 -10
- package/dist/shell/state-explosion-cli.js +4 -1
- package/dist/shell/telemetry-demo.js +1 -1
- package/dist/shell/telemetry-ledger-io.js +36 -24
- package/dist/shell/trust-audit.js +178 -36
- package/dist/shell/workbench-host.js +121 -10
- package/dist/shell/workbench.js +83 -6
- package/dist/shell/worker-isolation.js +1 -1
- package/dist/wiring/capability-table/basics.js +5 -0
- package/dist/wiring/capability-table/exec-backend.js +40 -22
- package/dist/wiring/capability-table/parity.js +10 -5
- package/dist/wiring/capability-table/pipeline.js +32 -0
- package/dist/wiring/capability-table/registry-core.js +26 -3
- package/dist/wiring/capability-table/reporting.js +7 -1
- package/dist/wiring/capability-table/scheduling-registry.js +8 -2
- package/dist/wiring/capability-table/trust-ledger.js +54 -12
- package/docs/agent-delegation-drive.7.md +17 -0
- package/docs/cli-mcp-parity.7.md +46 -5
- 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 +27 -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 +53 -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 +12 -0
- package/docs/routine.7.md +22 -0
- package/docs/run-registry-control-plane.7.md +37 -9
- package/docs/run-retention-reclamation.7.md +12 -4
- 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 +72 -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 +6 -3
- 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/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/gen-manifests.js +32 -61
- package/scripts/golden-path.js +4 -4
- package/scripts/lang-policy-check.js +82 -0
- package/scripts/parity-check.js +42 -23
- package/scripts/purity-baseline.json +0 -3
- package/scripts/purity-gate.js +16 -3
- package/scripts/release-check.js +2 -1
- package/scripts/release-flow.js +169 -41
- package/scripts/release-gate.js +208 -0
- package/scripts/release-oneclick.js +38 -7
- package/scripts/release-tags.js +47 -0
- package/scripts/schema-version-inventory.json +31 -0
- package/scripts/validate-run-state-schema.js +95 -4
- package/scripts/verdict-keygen.js +2 -2
- package/scripts/verify-bump-reproduction.js +193 -0
- package/scripts/verify-release-verdict.js +139 -0
- package/scripts/verify-verdict-signature.js +1 -1
- package/ui/workbench/app.css +50 -10
- package/ui/workbench/app.js +269 -34
- package/ui/workbench/index.html +4 -2
- package/ui/workbench/inspection.js +51 -0
- package/ui/workbench/navigation.js +44 -0
- 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 -75
- package/scripts/release-gate.sh +0 -94
- package/scripts/verify-bump-reproduction.sh +0 -148
- package/scripts/verify-container-selfref.js +0 -64
package/dist/core/format/help.js
CHANGED
|
@@ -30,6 +30,7 @@ exports.formatHelp = formatHelp;
|
|
|
30
30
|
exports.formatCommandHelp = formatCommandHelp;
|
|
31
31
|
exports.formatInfo = formatInfo;
|
|
32
32
|
exports.formatSearchResults = formatSearchResults;
|
|
33
|
+
exports.formatWorkflowList = formatWorkflowList;
|
|
33
34
|
const capability_table_1 = require("../capability-table");
|
|
34
35
|
/** src/orchestrator.ts:934-951 — the "More commands" token set, in the old
|
|
35
36
|
* build's order (space-joined in the source, pipe-joined for display).
|
|
@@ -252,6 +253,7 @@ function formatHelp() {
|
|
|
252
253
|
" -claude Use Claude agent",
|
|
253
254
|
" -codex Use Codex agent",
|
|
254
255
|
" -gemini Use Gemini (via opencode)",
|
|
256
|
+
" -opencode Use OpenCode agent",
|
|
255
257
|
" -deepseek Use DeepSeek (via opencode)",
|
|
256
258
|
" --verbose Show full agent narration live (default: compact)",
|
|
257
259
|
" --full Verbose, plus the report printed inline at the end",
|
|
@@ -276,16 +278,44 @@ function formatHelp() {
|
|
|
276
278
|
function cliCommandHelpRows(verb) {
|
|
277
279
|
return (0, capability_table_1.cliCapabilities)()
|
|
278
280
|
.filter((row) => row.cli.path[0] === verb && !row.cli.hiddenFromHelp)
|
|
279
|
-
.map((row) => ({
|
|
281
|
+
.map((row) => ({
|
|
282
|
+
command: `cw ${(row.cli.helpPath ?? row.cli.path).join(" ")}`,
|
|
283
|
+
summary: row.summary,
|
|
284
|
+
...(row.cli.flags ? { flags: row.cli.flags } : {}),
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
/** A verb that is only a `caseTokens` alias of another verb's row (e.g.
|
|
288
|
+
* `audit-run` -> `quickstart`) has NO row whose `cli.path[0]` matches it,
|
|
289
|
+
* so `cliCommandHelpRows` above finds nothing for it. This looks the
|
|
290
|
+
* alias up the same way the dispatcher does (registry-core.ts's
|
|
291
|
+
* findCapabilityByCliPath alias branch: a row whose `caseTokens` holds
|
|
292
|
+
* the token) and returns the verb the alias dispatches to. */
|
|
293
|
+
function aliasTargetVerb(verb) {
|
|
294
|
+
for (const row of (0, capability_table_1.cliCapabilities)()) {
|
|
295
|
+
if (row.cli.caseTokens && row.cli.caseTokens.includes(verb) && row.cli.path[0] !== verb) {
|
|
296
|
+
return row.cli.path[0];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return undefined;
|
|
280
300
|
}
|
|
281
301
|
/** src/orchestrator.ts:988-1007 — `formatCommandHelp(verb)`. Unknown verb
|
|
282
302
|
* gives a SOFT text (never a throw); known verb lists its registry rows,
|
|
283
303
|
* sorted by command string, padded to the longest command column + 2
|
|
284
304
|
* (capped at 40, per the old registry's own cap; none of the milestone-1
|
|
285
|
-
* fixture rows exceed it).
|
|
305
|
+
* fixture rows exceed it). An alias verb (a `caseTokens` token like
|
|
306
|
+
* `audit-run`) gets an alias header line plus its target verb's rows —
|
|
307
|
+
* before this fix it fell into the unknown-verb text and, worse, the
|
|
308
|
+
* Did-you-mean line suggested the very verb the user just typed. */
|
|
286
309
|
function formatCommandHelp(verb, suggestCommand) {
|
|
287
310
|
const rows = [...(COMMAND_HELP_ROWS[verb] ?? []), ...cliCommandHelpRows(verb)];
|
|
288
311
|
if (rows.length === 0) {
|
|
312
|
+
const target = aliasTargetVerb(verb);
|
|
313
|
+
if (target) {
|
|
314
|
+
const targetRows = [...(COMMAND_HELP_ROWS[target] ?? []), ...cliCommandHelpRows(target)];
|
|
315
|
+
if (targetRows.length > 0) {
|
|
316
|
+
return renderCommandHelpRows(`cw ${verb} — alias of cw ${target}`, targetRows);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
289
319
|
const hint = suggestCommand(verb);
|
|
290
320
|
const lines = [`Unknown command: ${verb}`];
|
|
291
321
|
if (hint)
|
|
@@ -293,13 +323,40 @@ function formatCommandHelp(verb, suggestCommand) {
|
|
|
293
323
|
lines.push(" Try: cw help (list all commands)");
|
|
294
324
|
return `${lines.join("\n")}\n`;
|
|
295
325
|
}
|
|
326
|
+
return renderCommandHelpRows(`cw ${verb}`, rows);
|
|
327
|
+
}
|
|
328
|
+
/** The row-list rendering `formatCommandHelp` has always done, extracted
|
|
329
|
+
* so the alias page above can render its target's rows under its own
|
|
330
|
+
* header line without a second copy of the sort/pad/Flags logic. */
|
|
331
|
+
function renderCommandHelpRows(header, rows) {
|
|
296
332
|
const sorted = [...rows].sort((a, b) => (a.command < b.command ? -1 : a.command > b.command ? 1 : 0));
|
|
297
333
|
const longest = Math.min(40, Math.max(...sorted.map((row) => row.command.length)));
|
|
298
|
-
const lines = [
|
|
334
|
+
const lines = [header, ""];
|
|
299
335
|
for (const row of sorted) {
|
|
300
336
|
const padded = row.command.padEnd(longest, " ");
|
|
301
337
|
lines.push(` ${padded} ${row.summary}`);
|
|
302
338
|
}
|
|
339
|
+
// Flags block(s): only for rows that declare at least one flag (most
|
|
340
|
+
// rows do not — see CliBinding.flags). Uses the SAME 4-space indent as
|
|
341
|
+
// formatHelp's own "Run cw help <command> ..." note above, on purpose:
|
|
342
|
+
// the CLI/MCP parity help-token parser reads only 2-space lines as
|
|
343
|
+
// command tokens, so any wider indent is a line it already knows to
|
|
344
|
+
// skip. One row's command name is only spelled out in the block's own
|
|
345
|
+
// header when more than one row on this page declares flags (e.g.
|
|
346
|
+
// "cw ledger" lists both propose and review); a single-row page (e.g.
|
|
347
|
+
// "cw doctor") just says "Flags".
|
|
348
|
+
const flaggedRows = sorted.filter((row) => row.flags && row.flags.length > 0);
|
|
349
|
+
if (flaggedRows.length > 0) {
|
|
350
|
+
const oneFlaggedRow = flaggedRows.length === 1;
|
|
351
|
+
for (const row of flaggedRows) {
|
|
352
|
+
const flags = row.flags;
|
|
353
|
+
const flagWidth = Math.min(40, Math.max(...flags.map((flag) => flag.name.length)));
|
|
354
|
+
lines.push("", oneFlaggedRow ? " Flags" : ` Flags (${row.command})`);
|
|
355
|
+
for (const flag of flags) {
|
|
356
|
+
lines.push(` ${flag.name.padEnd(flagWidth, " ")} ${flag.summary}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
303
360
|
return `${lines.join("\n")}\n`;
|
|
304
361
|
}
|
|
305
362
|
/** `cw info <id>` human card — MILESTONE 12. Byte-exact in spirit to
|
|
@@ -360,3 +417,16 @@ function formatSearchResults(keyword, results) {
|
|
|
360
417
|
lines.push("Use cw info <id> for full details.");
|
|
361
418
|
return lines.join("\n");
|
|
362
419
|
}
|
|
420
|
+
/** `cw list`'s TTY-only human text (the `list` row's `humanRender`, see
|
|
421
|
+
* core/capability-data.ts's CliBinding). Modeled on formatSearchResults
|
|
422
|
+
* above: one "<id> — <title>" line per workflow, then the same short
|
|
423
|
+
* next-step footer. NEVER printed to a pipe — the row's canonical JSON
|
|
424
|
+
* stays the only piped output (SPEC/cli-surface.md's jsonMode contract:
|
|
425
|
+
* "default" verbs are always JSON on a non-TTY stream). */
|
|
426
|
+
function formatWorkflowList(workflows) {
|
|
427
|
+
const rows = (Array.isArray(workflows) ? workflows : []);
|
|
428
|
+
const lines = rows.map((w) => `${w.id} — ${w.title}`);
|
|
429
|
+
lines.push("");
|
|
430
|
+
lines.push("Use cw info <id> for full details.");
|
|
431
|
+
return lines.join("\n");
|
|
432
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// core/format/recovery-hint.ts — recoveryHint, a pure content-based lookup
|
|
3
|
+
// from a thrown error's message to ONE copy-pasteable follow-up command.
|
|
4
|
+
//
|
|
5
|
+
// src/cli.ts:18-29 in the old build. Moved here (out of cli/entry.ts) so
|
|
6
|
+
// mcp/server.ts can use the same lookup for its own error text without
|
|
7
|
+
// crossing the mcp/-may-never-import-cli/ layer rule that
|
|
8
|
+
// scripts/purity-gate.js enforces — core/ may be read by both cli/ and
|
|
9
|
+
// mcp/. cli/entry.ts now just re-exports this function; its own call
|
|
10
|
+
// site and behavior are unchanged.
|
|
11
|
+
//
|
|
12
|
+
// Content-based (reads the message text, not the call site), so it stays
|
|
13
|
+
// correct no matter which command or tool threw. Returns `undefined`
|
|
14
|
+
// rather than a wrong guess when no pattern matches.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.recoveryHint = recoveryHint;
|
|
17
|
+
function recoveryHint(message) {
|
|
18
|
+
const m = message.toLowerCase();
|
|
19
|
+
if (m.startsWith("unknown command"))
|
|
20
|
+
return "cw help";
|
|
21
|
+
if (m.includes("not configured") || m.includes("agent backend"))
|
|
22
|
+
return "cw doctor";
|
|
23
|
+
if (m.includes("missing") && m.includes("repo"))
|
|
24
|
+
return 'cw -q "<question>" -dir <project-folder>';
|
|
25
|
+
if (m.includes("app") && (m.includes("not found") || m.includes("not available")))
|
|
26
|
+
return "cw app list";
|
|
27
|
+
if (m.includes("run id") || m.includes("run not found"))
|
|
28
|
+
return "cw run list";
|
|
29
|
+
if (m.includes("missing required input") && m.includes("question"))
|
|
30
|
+
return 'cw -q "<question>"';
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
@@ -36,6 +36,7 @@ exports.listComments = listComments;
|
|
|
36
36
|
exports.distinctTargets = distinctTargets;
|
|
37
37
|
exports.formatReviewStatus = formatReviewStatus;
|
|
38
38
|
exports.formatCommentList = formatCommentList;
|
|
39
|
+
const cli_args_1 = require("../util/cli-args");
|
|
39
40
|
const collate_1 = require("../util/collate");
|
|
40
41
|
exports.COLLABORATION_SCHEMA_VERSION = 1;
|
|
41
42
|
/** The single, honest stand-in for an absent identity. */
|
|
@@ -166,10 +167,10 @@ function buildHandoff(input, handoffCount, runId, now, auditEventId) {
|
|
|
166
167
|
auditEventIds: [auditEventId],
|
|
167
168
|
});
|
|
168
169
|
}
|
|
169
|
-
/**
|
|
170
|
+
/** Parse a defined tri-state flag; leave `undefined` alone so the
|
|
170
171
|
* caller's `?? existing ?? default` chain still governs an unset flag. */
|
|
171
|
-
function coerceFlag(value) {
|
|
172
|
-
return
|
|
172
|
+
function coerceFlag(value, label) {
|
|
173
|
+
return (0, cli_args_1.parseBoolFlag)(value, label);
|
|
173
174
|
}
|
|
174
175
|
function toNumber(value, fallback) {
|
|
175
176
|
if (value === undefined || value === null || value === "" || value === true)
|
|
@@ -201,8 +202,8 @@ function buildReviewPolicy(input, existing, now) {
|
|
|
201
202
|
id: existing?.id || createCollabId("policy", 0),
|
|
202
203
|
requiredApprovals: Math.max(0, Math.floor(toNumber(input.requiredApprovals, existing?.requiredApprovals ?? 0))),
|
|
203
204
|
authorizedRoles: toStringList(input.authorizedRoles, existing?.authorizedRoles ?? ["*"]),
|
|
204
|
-
allowSelfApproval: coerceFlag(input.allowSelfApproval) ?? existing?.allowSelfApproval ?? false,
|
|
205
|
-
requireAttestedActor: coerceFlag(input.requireAttestedActor) ?? existing?.requireAttestedActor ?? false,
|
|
205
|
+
allowSelfApproval: coerceFlag(input.allowSelfApproval, "allowSelfApproval") ?? existing?.allowSelfApproval ?? false,
|
|
206
|
+
requireAttestedActor: coerceFlag(input.requireAttestedActor, "requireAttestedActor") ?? existing?.requireAttestedActor ?? false,
|
|
206
207
|
appliesTo: toTargetKindList(input.appliesTo, existing?.appliesTo ?? ["commit"]),
|
|
207
208
|
updatedAt: now,
|
|
208
209
|
};
|
|
@@ -235,6 +236,17 @@ function roleAuthorized(roleId, authorizedRoles) {
|
|
|
235
236
|
return false;
|
|
236
237
|
return authorizedRoles.includes(roleId);
|
|
237
238
|
}
|
|
239
|
+
/** Whether `record` itself counts for anything at all (same eligibility a
|
|
240
|
+
* record needs to be counted as an approval or a veto in the main loop
|
|
241
|
+
* below). A record that fails this can't void another record either —
|
|
242
|
+
* otherwise a disqualified self-approval could still cancel someone
|
|
243
|
+
* else's veto via `supersedes`. */
|
|
244
|
+
function recordCountsAtAll(record, policy, selfIds) {
|
|
245
|
+
const reason = disqualify(record, policy, selfIds);
|
|
246
|
+
if (record.decision === "reject")
|
|
247
|
+
return !reason || reason === "self-approval";
|
|
248
|
+
return !reason;
|
|
249
|
+
}
|
|
238
250
|
function deriveStatus(gated, required, recorded, rejectionCount, disqualified) {
|
|
239
251
|
if (!gated)
|
|
240
252
|
return "approved";
|
|
@@ -284,7 +296,24 @@ function deriveReviewState(runId, approvalsAll, target, options = {}) {
|
|
|
284
296
|
const related = (options.relatedTargets && options.relatedTargets.length ? options.relatedTargets : [normalized]).map(normalizeTarget);
|
|
285
297
|
const selfIds = new Set((options.selfActorIds || []).filter(Boolean));
|
|
286
298
|
const approvals = approvalsAll.filter((record) => matchesAnyTarget(record.target, related));
|
|
287
|
-
|
|
299
|
+
// A record's `supersedes` only takes effect when the target is that SAME
|
|
300
|
+
// actor's own prior record (an actor may only supersede their own
|
|
301
|
+
// record, never someone else's) and the superseding record itself is
|
|
302
|
+
// eligible to count for something. Otherwise a disqualified record (e.g.
|
|
303
|
+
// a self-approval a policy forbids) could still void another actor's
|
|
304
|
+
// veto just by naming it in `supersedes`.
|
|
305
|
+
const byId = new Map(approvals.map((record) => [record.id, record]));
|
|
306
|
+
const supersededIds = new Set();
|
|
307
|
+
for (const record of approvals) {
|
|
308
|
+
if (!record.supersedes)
|
|
309
|
+
continue;
|
|
310
|
+
const target = byId.get(record.supersedes);
|
|
311
|
+
if (!target || target.actor.id !== record.actor.id)
|
|
312
|
+
continue;
|
|
313
|
+
if (!recordCountsAtAll(record, policy, selfIds))
|
|
314
|
+
continue;
|
|
315
|
+
supersededIds.add(record.supersedes);
|
|
316
|
+
}
|
|
288
317
|
const gated = Boolean(policy && policy.requiredApprovals > 0 && policy.appliesTo.includes(normalized.kind));
|
|
289
318
|
const required = gated ? policy.requiredApprovals : 0;
|
|
290
319
|
const counted = [];
|
|
@@ -651,6 +651,13 @@ function collectAgentFanin(run, input, now) {
|
|
|
651
651
|
...missingRoleIds.map((roleId) => `required role ${roleId} has no membership`),
|
|
652
652
|
...missingMembershipIds.map((membershipId) => `membership ${membershipId} has not reported required evidence`),
|
|
653
653
|
];
|
|
654
|
+
// An aggregation gate that observed zero members must not report itself
|
|
655
|
+
// ready: with no required roles and no memberships every per-item check
|
|
656
|
+
// above is vacuously empty, which used to yield verifierReady:true for a
|
|
657
|
+
// fan-in over nothing (fail-open).
|
|
658
|
+
if (!requiredRoleIds.length && !scopedMemberships.length) {
|
|
659
|
+
blockedReasons.push("fan-in has no memberships and no required roles — nothing to aggregate");
|
|
660
|
+
}
|
|
654
661
|
const requiredMemberships = scopedMemberships.filter((membership) => requiredRoleIds.includes(membership.roleId));
|
|
655
662
|
const blackboardId = input.blackboardId || group.blackboardId || multiAgentRun.blackboardId;
|
|
656
663
|
const requiresBlackboardEvidence = Boolean(blackboardId || requiredMemberships.some((membership) => membership.blackboardId));
|
|
@@ -168,9 +168,15 @@ function evaluatePolicy(policy, operation, topicId, evidenceRefs) {
|
|
|
168
168
|
}
|
|
169
169
|
const missing = missingEvidence(policy, operation, evidenceRefs);
|
|
170
170
|
if (missing.length)
|
|
171
|
-
return `operation ${operation} requires evidence refs: ${missing.join(", ")}`;
|
|
171
|
+
return `operation ${operation} requires evidence refs (at least one; expected kinds: ${missing.join(", ")})`;
|
|
172
172
|
return undefined;
|
|
173
173
|
}
|
|
174
|
+
/** The requiredEvidenceFor entries are prose descriptions ("judge
|
|
175
|
+
* messages", "score evidence"), not machine-matchable ids — so this can
|
|
176
|
+
* only check that SOME evidence ref was supplied, not that each named
|
|
177
|
+
* kind is present. The denial message above says exactly that ("at least
|
|
178
|
+
* one; expected kinds: ...") rather than promising a per-item match this
|
|
179
|
+
* check cannot do. */
|
|
174
180
|
function missingEvidence(policy, operation, evidenceRefs) {
|
|
175
181
|
if (!policy)
|
|
176
182
|
return [];
|
|
@@ -71,6 +71,13 @@ function createDefaultPipelineContract() {
|
|
|
71
71
|
},
|
|
72
72
|
],
|
|
73
73
|
artifactPolicy: { root: ".cw/runs/<run-id>", requireReadablePaths: true },
|
|
74
|
+
// highPriorityRequiresEvidence is carried for byte-compat with the old
|
|
75
|
+
// build's default contract (pinned by SPEC/pipeline-run.md) but is NOT
|
|
76
|
+
// enforced anywhere — no gate reads it, and core has no task-priority
|
|
77
|
+
// concept to key it on. The enforced contract-wide flag is
|
|
78
|
+
// `requireEvidence` (state-node.ts assertRequiredEvidence, runner.ts
|
|
79
|
+
// evidenceSatisfied). The per-stage `requiredEvidence` lists are what
|
|
80
|
+
// actually gate evidence in the default contract.
|
|
74
81
|
evidencePolicy: { highPriorityRequiresEvidence: true },
|
|
75
82
|
failurePolicy: { preserveFailureNodes: true, retryableByDefault: false },
|
|
76
83
|
commitPolicy: { requiresVerifierGate: true, acceptedVerifierStatuses: ["verified"] },
|
|
@@ -85,10 +85,10 @@ function sourceFor(classification) {
|
|
|
85
85
|
return "contract";
|
|
86
86
|
return "manual";
|
|
87
87
|
}
|
|
88
|
-
/** Feedback dedup key: joined with
|
|
88
|
+
/** Feedback dedup key: joined with the ASCII unit separator "\u001f" (runId, code, message, nodeId,
|
|
89
89
|
* stageId, contractId, path). */
|
|
90
90
|
function feedbackKey(value) {
|
|
91
|
-
return [value.runId || "", value.code || "", value.message || "", value.nodeId || "", value.stageId || "", value.contractId || "", value.path || ""].join("
|
|
91
|
+
return [value.runId || "", value.code || "", value.message || "", value.nodeId || "", value.stageId || "", value.contractId || "", value.path || ""].join("\u001f");
|
|
92
92
|
}
|
|
93
93
|
function compactMetadata(metadata) {
|
|
94
94
|
const compacted = {};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// core/state/run-paths.ts —
|
|
2
|
+
// core/state/run-paths.ts — pure RunPaths construction.
|
|
3
3
|
//
|
|
4
4
|
// MILESTONE 3. Byte-exact port of the old build's src/state.ts:10-50.
|
|
5
|
-
// `createRunPaths` is pure path math
|
|
6
|
-
//
|
|
7
|
-
// only place that actually creates a run directory tree.
|
|
5
|
+
// `createRunPaths` is pure path math. The directory write mechanism lives in
|
|
6
|
+
// shell/run-store.ts.
|
|
8
7
|
//
|
|
9
8
|
// Evidence: SPEC/state-core.md "src/state.ts — persistence kernel" and "The
|
|
10
9
|
// full .cw/runs/<run-id>/ layout".
|
|
@@ -43,8 +42,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
43
42
|
})();
|
|
44
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
44
|
exports.createRunPaths = createRunPaths;
|
|
46
|
-
exports.ensureRunDirs = ensureRunDirs;
|
|
47
|
-
const fs = __importStar(require("node:fs"));
|
|
48
45
|
const path = __importStar(require("node:path"));
|
|
49
46
|
/** The 16-key RunPaths object, all joined under `runDir`. */
|
|
50
47
|
function createRunPaths(runDir) {
|
|
@@ -67,27 +64,3 @@ function createRunPaths(runDir) {
|
|
|
67
64
|
topologiesDir: path.join(runDir, "topologies"),
|
|
68
65
|
};
|
|
69
66
|
}
|
|
70
|
-
/** `mkdirSync` (recursive) every dir this run needs. Missing optional dir
|
|
71
|
-
* fields fall back to `path.join(runDir, "<name>")`, matching the old
|
|
72
|
-
* build's defensive default (a RunPaths loaded from an old/partial
|
|
73
|
-
* state.json may be missing an optional key). */
|
|
74
|
-
function ensureRunDirs(paths) {
|
|
75
|
-
const dirs = [
|
|
76
|
-
paths.runDir,
|
|
77
|
-
paths.tasksDir,
|
|
78
|
-
paths.resultsDir,
|
|
79
|
-
paths.dispatchesDir,
|
|
80
|
-
paths.artifactsDir,
|
|
81
|
-
paths.commitsDir,
|
|
82
|
-
paths.stateNodesDir,
|
|
83
|
-
paths.feedbackDir,
|
|
84
|
-
paths.auditDir || path.join(paths.runDir, "audit"),
|
|
85
|
-
paths.workersDir || path.join(paths.runDir, "workers"),
|
|
86
|
-
paths.candidatesDir || path.join(paths.runDir, "candidates"),
|
|
87
|
-
paths.multiAgentDir || path.join(paths.runDir, "multi-agent"),
|
|
88
|
-
paths.blackboardDir || path.join(paths.runDir, "blackboard"),
|
|
89
|
-
paths.topologiesDir || path.join(paths.runDir, "topologies"),
|
|
90
|
-
];
|
|
91
|
-
for (const dir of dirs)
|
|
92
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
93
|
-
}
|
|
@@ -116,8 +116,13 @@ function extractEvidenceContent(locator, baseDirs, ops, readFile) {
|
|
|
116
116
|
const shape = classify(locator);
|
|
117
117
|
if (shape.kind !== "file" || !shape.pathPart)
|
|
118
118
|
return undefined;
|
|
119
|
-
|
|
119
|
+
// Match the same shapes LINE_SUFFIX_RE (classify) accepts: ":<n>" and
|
|
120
|
+
// ":<n>-<m>". A range that only matched classify used to fall through to
|
|
121
|
+
// the head-of-file slice below — returning bytes that had nothing to do
|
|
122
|
+
// with the cited lines.
|
|
123
|
+
const lineMatch = locator.match(/:(\d+)(?:-(\d+))?$/);
|
|
120
124
|
const lineNum = lineMatch ? Number(lineMatch[1]) : undefined;
|
|
125
|
+
const lineEnd = lineMatch && lineMatch[2] ? Number(lineMatch[2]) : undefined;
|
|
121
126
|
const candidatePath = ops.isAbsolute(shape.pathPart)
|
|
122
127
|
? shape.pathPart
|
|
123
128
|
: baseDirs.filter(Boolean).map((base) => ops.resolve(base, shape.pathPart)).find((p) => ops.exists(p));
|
|
@@ -128,6 +133,13 @@ function extractEvidenceContent(locator, baseDirs, ops, readFile) {
|
|
|
128
133
|
return undefined;
|
|
129
134
|
if (lineNum && lineNum > 0) {
|
|
130
135
|
const lines = content.split("\n");
|
|
136
|
+
if (lineEnd !== undefined) {
|
|
137
|
+
// Range: return exactly the cited lines; a backwards or out-of-range
|
|
138
|
+
// span returns undefined, never fabricated content.
|
|
139
|
+
if (lineEnd < lineNum || lineNum > lines.length)
|
|
140
|
+
return undefined;
|
|
141
|
+
return lines.slice(lineNum - 1, Math.min(lineEnd, lines.length)).join("\n") || undefined;
|
|
142
|
+
}
|
|
131
143
|
return lines[lineNum - 1] || undefined;
|
|
132
144
|
}
|
|
133
145
|
return content.slice(0, 200);
|
|
@@ -16,6 +16,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
16
16
|
exports.required = required;
|
|
17
17
|
exports.optionalArg = optionalArg;
|
|
18
18
|
exports.wantsJson = wantsJson;
|
|
19
|
+
exports.parseBoolFlag = parseBoolFlag;
|
|
19
20
|
/** Require a positional/option value or fail with a copy-pasteable recovery tip. */
|
|
20
21
|
function required(value, label) {
|
|
21
22
|
if (!value) {
|
|
@@ -31,3 +32,24 @@ function optionalArg(value) {
|
|
|
31
32
|
function wantsJson(options) {
|
|
32
33
|
return Boolean(options.json || options.format === "json");
|
|
33
34
|
}
|
|
35
|
+
/** Parse a boolean flag value that may arrive as a real boolean (bare
|
|
36
|
+
* `--flag`) or as a CLI/MCP string (`--flag false`). `Boolean("false")` is
|
|
37
|
+
* `true` in JS, so a plain Boolean() coercion silently ENABLES a flag the
|
|
38
|
+
* operator asked to turn off — on a gate-policy flag like
|
|
39
|
+
* `--allow-self-approval false` that is a fail-open. Recognized strings
|
|
40
|
+
* (case-insensitive, trimmed): true/1/yes/on and false/0/no/off/"".
|
|
41
|
+
* Anything else throws — fail closed, never guess. `undefined` and JSON
|
|
42
|
+
* `null` (an MCP caller's "unset") stay `undefined` so a caller's
|
|
43
|
+
* `?? existing ?? default` chain still governs an unset flag. */
|
|
44
|
+
function parseBoolFlag(value, label) {
|
|
45
|
+
if (value === undefined || value === null)
|
|
46
|
+
return undefined;
|
|
47
|
+
if (typeof value === "boolean")
|
|
48
|
+
return value;
|
|
49
|
+
const text = String(value).trim().toLowerCase();
|
|
50
|
+
if (text === "true" || text === "1" || text === "yes" || text === "on")
|
|
51
|
+
return true;
|
|
52
|
+
if (text === "false" || text === "0" || text === "no" || text === "off" || text === "")
|
|
53
|
+
return false;
|
|
54
|
+
throw new Error(`Invalid boolean value for ${label}: "${String(value)}" (use true or false)`);
|
|
55
|
+
}
|
package/dist/core/version.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.MIN_SUPPORTED_RUN_STATE_SCHEMA_VERSION = exports.LEGACY_RUN_STATE_SCHEMA
|
|
|
7
7
|
// `cw version` output does not change bytes just because the code under it
|
|
8
8
|
// was rebuilt. See SPEC/cli-surface.md "Exact outputs > Version" and
|
|
9
9
|
// conformance/cases/version-basic.case.js (regex `/^\d+\.\d+\.\d+\n$/`).
|
|
10
|
-
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.2.
|
|
10
|
+
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.2.6";
|
|
11
11
|
// State-kernel schema version constants (SPEC/state-core.md "Version
|
|
12
12
|
// constants"). Pinned to the old build's src/version.ts byte-for-byte.
|
|
13
13
|
exports.WORKFLOW_APP_SCHEMA_VERSION = 1;
|
package/dist/mcp/dispatch.js
CHANGED
|
@@ -81,6 +81,22 @@ function requiredToolArguments(name, value) {
|
|
|
81
81
|
}
|
|
82
82
|
return args;
|
|
83
83
|
}
|
|
84
|
+
/** MCP clients send JSON, so an argument the CLI would receive as a string
|
|
85
|
+
* can arrive as a number or boolean (e.g. `{"runId": 5}`). Every capability
|
|
86
|
+
* handler coerces its args through optionalString/numberArg/boolArg, all of
|
|
87
|
+
* which expect the CLI's string form — optionalString silently DROPS a
|
|
88
|
+
* number, so `{"runId": 5}` used to look like "no runId given" and returned
|
|
89
|
+
* a success-shaped WRONG answer (the "create a run first" payload). Coerce
|
|
90
|
+
* top-level scalars to their string form so an MCP arg behaves exactly like
|
|
91
|
+
* the CLI's argv, which is always strings. Arrays/objects (e.g.
|
|
92
|
+
* authorizedRoles) pass through untouched; null/undefined stay absent. */
|
|
93
|
+
function coerceScalarArgs(args) {
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const [key, value] of Object.entries(args)) {
|
|
96
|
+
out[key] = typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" ? String(value) : value;
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
84
100
|
/** `callTool(name, args)` — resolves `cwd` (SPEC/mcp.md invariant 7),
|
|
85
101
|
* checks required args, then calls the row's `mcp.handler`. Throws
|
|
86
102
|
* `Unknown tool: <name>` for a name with no row (src/mcp/tool-call.ts:513
|
|
@@ -90,12 +106,16 @@ function callTool(name, rawArgs) {
|
|
|
90
106
|
if (!row || !row.mcp) {
|
|
91
107
|
throw new Error(`Unknown tool: ${name}`);
|
|
92
108
|
}
|
|
93
|
-
const args = requiredToolArguments(name, rawArgs);
|
|
109
|
+
const args = coerceScalarArgs(requiredToolArguments(name, rawArgs));
|
|
94
110
|
const resolvedArgs = { ...args };
|
|
95
111
|
const cwdInput = args.cwd;
|
|
96
112
|
if (typeof cwdInput === "string" && cwdInput.length > 0) {
|
|
97
113
|
const resolved = path.resolve(cwdInput);
|
|
98
|
-
|
|
114
|
+
// throwIfNoEntry:false so a MISSING cwd yields the same crafted
|
|
115
|
+
// "not a directory" message as a non-dir path — not a raw ENOENT that
|
|
116
|
+
// gives the recovery-hint matcher nothing to key on.
|
|
117
|
+
const stat = fs.statSync(resolved, { throwIfNoEntry: false });
|
|
118
|
+
if (!stat || !stat.isDirectory()) {
|
|
99
119
|
throw new Error(`MCP cwd is not a directory: ${resolved}`);
|
|
100
120
|
}
|
|
101
121
|
resolvedArgs.cwd = resolved;
|