cool-workflow 0.1.90 → 0.1.92
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 +78 -374
- 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/apps/research-synthesis/workflow.js +1 -1
- package/dist/capability-core.js +31 -0
- package/dist/cli/command-surface.js +55 -8
- package/dist/doctor.js +1 -0
- package/dist/drive.js +2 -1
- package/dist/orchestrator/report.js +6 -1
- package/dist/orchestrator.js +3 -0
- package/dist/reporter.js +67 -0
- package/dist/term.js +73 -1
- package/dist/version.js +1 -1
- package/docs/agent-delegation-drive.7.md +41 -22
- package/docs/canonical-workflow-apps.7.md +12 -0
- package/docs/cli-mcp-parity.7.md +4 -0
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/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/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 +7 -3
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +13 -0
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +4 -0
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +3 -1
- package/scripts/agents/agent-adapter-core.js +289 -4
- package/scripts/agents/claude-p-agent.js +45 -19
- package/scripts/agents/codex-agent.js +11 -8
- package/scripts/agents/gemini-agent.js +11 -8
- package/scripts/agents/opencode-agent.js +11 -8
- package/scripts/bump-version.js +4 -1
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/release-check.js +5 -1
- package/scripts/sync-readme.js +123 -0
- package/scripts/version-sync-check.js +5 -1
package/dist/capability-core.js
CHANGED
|
@@ -56,6 +56,7 @@ exports.schedPolicyShow = schedPolicyShow;
|
|
|
56
56
|
exports.schedPolicySet = schedPolicySet;
|
|
57
57
|
exports.runDrivePreview = runDrivePreview;
|
|
58
58
|
exports.runDrive = runDrive;
|
|
59
|
+
exports.collectRunFindings = collectRunFindings;
|
|
59
60
|
exports.quickstart = quickstart;
|
|
60
61
|
exports.backendAgentConfigShow = backendAgentConfigShow;
|
|
61
62
|
exports.backendAgentConfigSet = backendAgentConfigSet;
|
|
@@ -83,6 +84,7 @@ const remote_source_1 = require("./remote-source");
|
|
|
83
84
|
const telemetry_demo_1 = require("./telemetry-demo");
|
|
84
85
|
const state_1 = require("./state");
|
|
85
86
|
const run_export_1 = require("./run-export");
|
|
87
|
+
const result_normalize_1 = require("./result-normalize");
|
|
86
88
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
87
89
|
const node_path_1 = __importDefault(require("node:path"));
|
|
88
90
|
const scheduling_1 = require("./scheduling");
|
|
@@ -553,6 +555,35 @@ exports.QUICKSTART_DEFAULT_APP = "architecture-review";
|
|
|
553
555
|
* RED LINE (DIRECTION.md): worker execution still DELEGATES to the operator's own
|
|
554
556
|
* agent backend (claude -p / codex exec / HTTP endpoint). With no agent configured
|
|
555
557
|
* the drive fails closed (status=blocked) and we never fabricate a completion. */
|
|
558
|
+
/** Collect a COMPACT findings list for the end-of-run summary by re-parsing each completed
|
|
559
|
+
* worker's `result.md` `cw:result` block (the schema source of truth — `normalizeResultEnvelope`).
|
|
560
|
+
* Stderr/human-side ONLY: this NEVER touches the byte-exact stdout payload, so it cannot perturb
|
|
561
|
+
* `--json` or the `cw:result` fence. `baseDir` anchors to the run's OWN repo (quickstart may run
|
|
562
|
+
* cross-directory). Best-effort: a missing/garbled result.md or an unloadable run is skipped, not
|
|
563
|
+
* fatal — the full prose still lives in report.md + each worker transcript. */
|
|
564
|
+
function collectRunFindings(runner, runId, baseDir) {
|
|
565
|
+
const rows = [];
|
|
566
|
+
try {
|
|
567
|
+
const run = runner.withBaseDir(baseDir).loadRun(runId);
|
|
568
|
+
for (const task of run.tasks) {
|
|
569
|
+
if (task.status !== "completed" || !task.resultPath || !node_fs_1.default.existsSync(task.resultPath))
|
|
570
|
+
continue;
|
|
571
|
+
try {
|
|
572
|
+
const envelope = (0, result_normalize_1.normalizeResultEnvelope)(node_fs_1.default.readFileSync(task.resultPath, "utf8"));
|
|
573
|
+
for (const f of envelope.findings) {
|
|
574
|
+
rows.push({ id: f.id, severity: f.severity || "none", classification: f.classification || "unknown" });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
/* skip a garbled result.md — the transcript still holds the full record */
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
/* run not loadable on this host — the summary degrades to no findings table */
|
|
584
|
+
}
|
|
585
|
+
return rows;
|
|
586
|
+
}
|
|
556
587
|
function quickstart(runner, args) {
|
|
557
588
|
const appId = String(args.appId || args.app || args.workflowId || exports.QUICKSTART_DEFAULT_APP);
|
|
558
589
|
// Remote source (v0.1.91): a `--link <url>` — or a URL passed to `--repo`/`-dir` — is
|
|
@@ -58,7 +58,7 @@ const state_explosion_1 = require("../state-explosion");
|
|
|
58
58
|
const evidence_reasoning_1 = require("../evidence-reasoning");
|
|
59
59
|
const doctor_1 = require("../doctor");
|
|
60
60
|
const orchestrator_2 = require("../orchestrator");
|
|
61
|
-
const
|
|
61
|
+
const reporter_1 = require("../reporter");
|
|
62
62
|
const version_1 = require("../version");
|
|
63
63
|
async function runCli(argv = process.argv.slice(2)) {
|
|
64
64
|
const args = (0, orchestrator_1.parseArgv)(argv);
|
|
@@ -84,6 +84,18 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
84
84
|
// so `cw -q "…" -dir /path` works from any directory (no cd). Explicit --repo wins.
|
|
85
85
|
if (!args.options.repo && args.options.dir)
|
|
86
86
|
args.options.repo = args.options.dir;
|
|
87
|
+
// Presentation flags — set BEFORE any drive spawn so the out-of-process agent wrapper
|
|
88
|
+
// inherits them via process.env (presentation-only; stdout/the cw:result fence are untouched):
|
|
89
|
+
// --verbose full agent narration inline (default is compact: current action + summary)
|
|
90
|
+
// --no-color disable ANSI everywhere (CW_NO_COLOR is honored by term.colorEnabled AND the
|
|
91
|
+
// wrapper); complements NO_COLOR/FORCE_COLOR
|
|
92
|
+
// --full also stream full narration AND print the report inline at run end
|
|
93
|
+
if (args.options.verbose)
|
|
94
|
+
process.env.CW_VERBOSE = "1";
|
|
95
|
+
if (args.options["no-color"])
|
|
96
|
+
process.env.CW_NO_COLOR = "1";
|
|
97
|
+
if (args.options.full)
|
|
98
|
+
process.env.CW_OUTPUT = "full";
|
|
87
99
|
// Bare -q / --question -> redirect to quickstart (auto-detect repo/agent/app).
|
|
88
100
|
// CONSUME the positional (shift) so the question never survives as positionals[0]
|
|
89
101
|
// — otherwise the quickstart handler reads it as the app id ("Workflow app not found").
|
|
@@ -240,15 +252,17 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
240
252
|
const qs = (0, capability_core_1.quickstart)(runner, { ...args.options, ...(appId ? { appId } : {}), ...(runId ? { runId } : {}) });
|
|
241
253
|
printJson(qs);
|
|
242
254
|
const qr = qs;
|
|
243
|
-
// Clean human summary on stderr (TTY-gated). Suppressed under --json so
|
|
244
|
-
// mode emits ONLY the stdout payload — no stderr chrome to parse around. The
|
|
245
|
-
//
|
|
255
|
+
// Clean human summary on stderr (TTY-gated, inside the reporter). Suppressed under --json so
|
|
256
|
+
// machine mode emits ONLY the stdout payload — no stderr chrome to parse around. The type
|
|
257
|
+
// guard also skips --check/--preview results (no reportPath of their own). The summary is the
|
|
258
|
+
// COMPACT findings table (re-parsed from each completed worker's cw:result), the report path,
|
|
259
|
+
// and where the per-worker transcripts live — NOT the full prose (that's report.md/--full).
|
|
246
260
|
if (!wantsJson(args.options) && typeof qr.runId === "string" && typeof qr.reportPath === "string") {
|
|
247
|
-
(
|
|
261
|
+
emitRunSummary(runner, args.options, {
|
|
248
262
|
runId: qr.runId,
|
|
249
263
|
reportPath: qr.reportPath,
|
|
250
264
|
status: String(qr.status || ""),
|
|
251
|
-
|
|
265
|
+
statePath: typeof qr.statePath === "string" ? qr.statePath : undefined,
|
|
252
266
|
completedWorkers: typeof qr.completedWorkers === "number" ? qr.completedWorkers : undefined,
|
|
253
267
|
plannedWorkers: typeof qr.plannedWorkers === "number" ? qr.plannedWorkers : undefined,
|
|
254
268
|
agentConfigured: typeof qr.agentConfigured === "boolean" ? qr.agentConfigured : undefined
|
|
@@ -1218,10 +1232,11 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
1218
1232
|
const dr = (0, capability_core_1.runDrive)(runner, driveArgs);
|
|
1219
1233
|
printJson(dr);
|
|
1220
1234
|
if (!wantsJson(args.options)) {
|
|
1221
|
-
(
|
|
1235
|
+
emitRunSummary(runner, args.options, {
|
|
1222
1236
|
runId: dr.runId,
|
|
1223
1237
|
reportPath: dr.reportPath,
|
|
1224
1238
|
status: dr.status,
|
|
1239
|
+
statePath: dr.statePath,
|
|
1225
1240
|
completedWorkers: dr.completedWorkers,
|
|
1226
1241
|
plannedWorkers: dr.plannedWorkers,
|
|
1227
1242
|
agentConfigured: dr.agentConfigured
|
|
@@ -1241,10 +1256,11 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
1241
1256
|
const dr = (0, capability_core_1.runDrive)(runner, driveArgs);
|
|
1242
1257
|
printJson(dr);
|
|
1243
1258
|
if (!wantsJson(args.options)) {
|
|
1244
|
-
(
|
|
1259
|
+
emitRunSummary(runner, args.options, {
|
|
1245
1260
|
runId: dr.runId,
|
|
1246
1261
|
reportPath: dr.reportPath,
|
|
1247
1262
|
status: dr.status,
|
|
1263
|
+
statePath: dr.statePath,
|
|
1248
1264
|
completedWorkers: dr.completedWorkers,
|
|
1249
1265
|
plannedWorkers: dr.plannedWorkers,
|
|
1250
1266
|
agentConfigured: dr.agentConfigured
|
|
@@ -1556,6 +1572,37 @@ function required(value, label) {
|
|
|
1556
1572
|
function optionalArg(value) {
|
|
1557
1573
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1558
1574
|
}
|
|
1575
|
+
/** Emit the calm end-of-run summary (stderr, TTY-gated inside the reporter): the COMPACT findings
|
|
1576
|
+
* table re-parsed from each completed worker's `cw:result`, the report path, where the per-worker
|
|
1577
|
+
* transcripts live, and — under `--full` — the report inline. Stderr/human-side ONLY: stdout (the
|
|
1578
|
+
* `--json` payload printed just before this) stays byte-exact. Shared by the quickstart and the
|
|
1579
|
+
* two `run --drive` paths so all three render an identical summary. */
|
|
1580
|
+
function emitRunSummary(runner, options, fields) {
|
|
1581
|
+
// Anchor run reads to the run's OWN repo (a drive/quickstart may run cross-directory): the run
|
|
1582
|
+
// dir is <repo>/.cw/runs/<id>/, holding each worker's transcript.md next to its result.md.
|
|
1583
|
+
const runDir = typeof fields.statePath === "string" ? node_path_1.default.dirname(fields.statePath) : undefined;
|
|
1584
|
+
const baseDir = runDir ? node_path_1.default.resolve(runDir, "..", "..", "..") : undefined;
|
|
1585
|
+
const findings = (0, capability_core_1.collectRunFindings)(runner, fields.runId, baseDir);
|
|
1586
|
+
// --full ALSO prints the report inline at run end (the compact table stays the default summary).
|
|
1587
|
+
let fullReport;
|
|
1588
|
+
if (options.full && fields.reportPath && node_fs_1.default.existsSync(fields.reportPath)) {
|
|
1589
|
+
try {
|
|
1590
|
+
fullReport = node_fs_1.default.readFileSync(fields.reportPath, "utf8");
|
|
1591
|
+
}
|
|
1592
|
+
catch { /* best-effort inline */ }
|
|
1593
|
+
}
|
|
1594
|
+
reporter_1.reporter.runSummary({
|
|
1595
|
+
runId: fields.runId,
|
|
1596
|
+
reportPath: fields.reportPath,
|
|
1597
|
+
status: fields.status,
|
|
1598
|
+
completedWorkers: fields.completedWorkers,
|
|
1599
|
+
plannedWorkers: fields.plannedWorkers,
|
|
1600
|
+
agentConfigured: fields.agentConfigured,
|
|
1601
|
+
findings,
|
|
1602
|
+
runDir,
|
|
1603
|
+
fullReport
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1559
1606
|
function printJson(value) {
|
|
1560
1607
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
1561
1608
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -174,6 +174,7 @@ function formatDoctorReport(report) {
|
|
|
174
174
|
lines.push(" 1. cw demo tamper — prove trust checks work (30s)");
|
|
175
175
|
lines.push(" 2. cw demo bundle — prove portable bundles (30s)");
|
|
176
176
|
lines.push(' 3. cw -q "what risks?" — your first real report (needs an agent)');
|
|
177
|
+
lines.push(' cw quickstart research-synthesis --repo <folder> --question "..." — cited report over a docs/papers folder, not only code');
|
|
177
178
|
lines.push("");
|
|
178
179
|
lines.push("Onramp");
|
|
179
180
|
lines.push(` ${report.onramp.summary}`);
|
package/dist/drive.js
CHANGED
|
@@ -33,6 +33,7 @@ exports.drivePreview = drivePreview;
|
|
|
33
33
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
34
34
|
const node_path_1 = __importDefault(require("node:path"));
|
|
35
35
|
const term_1 = require("./term");
|
|
36
|
+
const reporter_1 = require("./reporter");
|
|
36
37
|
const dispatch_1 = require("./dispatch");
|
|
37
38
|
const execution_backend_1 = require("./execution-backend");
|
|
38
39
|
const worker_isolation_1 = require("./worker-isolation");
|
|
@@ -83,7 +84,7 @@ function emitProgress(message) {
|
|
|
83
84
|
const forcedOff = process.env.CW_DRIVE_PROGRESS === "0";
|
|
84
85
|
const forcedOn = process.env.CW_DRIVE_PROGRESS === "1";
|
|
85
86
|
if ((Boolean(process.stderr.isTTY) && !forcedOff) || forcedOn)
|
|
86
|
-
|
|
87
|
+
reporter_1.reporter.progress(`[drive] ${message}`);
|
|
87
88
|
}
|
|
88
89
|
/** Advance exactly ONE deterministic step. Pure-ish: all mutation is through the
|
|
89
90
|
* existing runner verbs + runBackend. */
|
|
@@ -25,6 +25,11 @@ function writeReport(run) {
|
|
|
25
25
|
(0, dispatch_1.updatePhaseStatuses)(run);
|
|
26
26
|
const workerSummary = (0, worker_isolation_1.summarizeWorkers)(run);
|
|
27
27
|
const candidateSummary = (0, candidate_scoring_1.summarizeCandidates)(run);
|
|
28
|
+
// A research run reads a local folder of files, not a code repo — label its source line
|
|
29
|
+
// "Source". Skip the relabel when the run ALSO carries a remote-provenance "- Source: <url>"
|
|
30
|
+
// line below (run.inputs.sourceUrl set by a --link/URL), so a report never shows two
|
|
31
|
+
// "- Source:" lines. Every other app keeps the byte-identical "Repository:" (POLA).
|
|
32
|
+
const sourceLabel = run.workflow.app?.metadata?.domain === "research" && !run.inputs.sourceUrl ? "Source" : "Repository";
|
|
28
33
|
const report = [
|
|
29
34
|
`# ${run.workflow.title}`,
|
|
30
35
|
"",
|
|
@@ -38,7 +43,7 @@ function writeReport(run) {
|
|
|
38
43
|
: []),
|
|
39
44
|
`- Created: ${run.createdAt}`,
|
|
40
45
|
`- Updated: ${run.updatedAt}`,
|
|
41
|
-
`-
|
|
46
|
+
`- ${sourceLabel}: ${String(run.inputs.repo || run.cwd)}`,
|
|
42
47
|
// Remote provenance (v0.1.91): when the repo was materialized from a --link/URL, record
|
|
43
48
|
// the sanitized origin + resolved commit so the report itself says where the code came
|
|
44
49
|
// from. Conditional — absent for a local-repo run, so existing reports stay byte-identical.
|
package/dist/orchestrator.js
CHANGED
|
@@ -968,6 +968,9 @@ function formatHelp() {
|
|
|
968
968
|
" -claude Use Claude agent",
|
|
969
969
|
" -codex Use Codex agent",
|
|
970
970
|
" -deepseek Use DeepSeek (via opencode)",
|
|
971
|
+
" --verbose Show full agent narration live (default: compact)",
|
|
972
|
+
" --full Verbose, plus the report printed inline at the end",
|
|
973
|
+
" --no-color Disable ANSI color (also honors NO_COLOR / FORCE_COLOR)",
|
|
971
974
|
"",
|
|
972
975
|
(0, term_1.bold)("More commands", out),
|
|
973
976
|
...wrapped
|
package/dist/reporter.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/reporter.ts — the cw-side run reporter.
|
|
3
|
+
//
|
|
4
|
+
// Rendering lives behind a small interface so the drive/command-surface stay logic-only: the
|
|
5
|
+
// orchestrator emits events (progress lines, the end-of-run summary) and the Reporter decides how
|
|
6
|
+
// to draw them. The live AGENT view (spinner / streamed tokens / folding tool lines) is rendered
|
|
7
|
+
// by the async agent wrapper — cw is blocked in spawnSync during a run, so cw only renders the
|
|
8
|
+
// calm orchestration BETWEEN agents plus the final summary. Everything here goes to STDERR; the
|
|
9
|
+
// MACHINE payloads on stdout (the cw:result fence + cw's --json via printJson) carry NO term
|
|
10
|
+
// styling, so they stay byte-exact under any color env. Color is TTY-gated and honors
|
|
11
|
+
// NO_COLOR/CW_NO_COLOR(--no-color)/FORCE_COLOR via term — FORCE_COLOR may color human-readable
|
|
12
|
+
// output (its purpose) but never the machine payloads, which use no styling at all.
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.reporter = void 0;
|
|
15
|
+
exports.createReporter = createReporter;
|
|
16
|
+
const term_1 = require("./term");
|
|
17
|
+
function isTTY(stream) {
|
|
18
|
+
return Boolean(stream.isTTY);
|
|
19
|
+
}
|
|
20
|
+
class StderrReporter {
|
|
21
|
+
s;
|
|
22
|
+
constructor(s) {
|
|
23
|
+
this.s = s;
|
|
24
|
+
}
|
|
25
|
+
progress(line) {
|
|
26
|
+
// The caller already decided WHETHER to emit (drive's CW_DRIVE_PROGRESS/TTY gate) and has
|
|
27
|
+
// already styled the line (phaseProgressLine etc.) — the reporter just centralizes the write
|
|
28
|
+
// so all orchestration output flows through one interface. No extra styling (avoids ANSI nesting).
|
|
29
|
+
this.s.write(`${line}\n`);
|
|
30
|
+
}
|
|
31
|
+
runSummary(f) {
|
|
32
|
+
if (!isTTY(this.s))
|
|
33
|
+
return; // summary is human chrome; piped/--json stdout already carries the data
|
|
34
|
+
const s = this.s;
|
|
35
|
+
const counts = (typeof f.completedWorkers === "number" && typeof f.plannedWorkers === "number")
|
|
36
|
+
? ` — ${f.completedWorkers}/${f.plannedWorkers}` : "";
|
|
37
|
+
s.write("\n");
|
|
38
|
+
if (f.findings && f.findings.length)
|
|
39
|
+
s.write(`${(0, term_1.formatFindingsSummary)(f.findings, s)}\n\n`);
|
|
40
|
+
s.write(`${(0, term_1.green)("✓", s)} Report: ${f.reportPath}\n`);
|
|
41
|
+
if (f.status === "complete") {
|
|
42
|
+
s.write(` ${(0, term_1.green)("✓", s)} Status: complete${counts}\n`);
|
|
43
|
+
if (f.runDir)
|
|
44
|
+
s.write(` ${(0, term_1.dim)(`Transcript: ${f.runDir}`, s)}\n`);
|
|
45
|
+
s.write(` ${(0, term_1.nextHint)(`cw report ${f.runId} --show`, s)}\n`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
s.write(` ${(0, term_1.yellow)("!", s)} Status: ${f.status}${counts}\n`);
|
|
49
|
+
if (f.agentConfigured === false)
|
|
50
|
+
s.write(` ${(0, term_1.tryHint)("cw doctor", s)}\n`);
|
|
51
|
+
else
|
|
52
|
+
s.write(` ${(0, term_1.nextHint)(`cw status ${f.runId}`, s)}\n`);
|
|
53
|
+
}
|
|
54
|
+
if (typeof f.fullReport === "string" && f.fullReport.trim()) {
|
|
55
|
+
s.write(`\n${(0, term_1.dim)("──── full report ────", s)}\n${f.fullReport.trim()}\n`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Build a reporter over an explicit stream. Used by the default singleton and by tests, which
|
|
60
|
+
* drive a fake `{ isTTY, write }` stream to assert the TTY path renders (findings table + report
|
|
61
|
+
* path + hints) and the non-TTY path stays silent. */
|
|
62
|
+
function createReporter(stream) {
|
|
63
|
+
return new StderrReporter(stream);
|
|
64
|
+
}
|
|
65
|
+
/** The default reporter writes the orchestration view to stderr. (A future non-TTY-specific
|
|
66
|
+
* reporter could differ; today the single impl gates internally on isTTY, matching emitProgress.) */
|
|
67
|
+
exports.reporter = createReporter(process.stderr);
|
package/dist/term.js
CHANGED
|
@@ -22,9 +22,23 @@ exports.sectionHeader = sectionHeader;
|
|
|
22
22
|
exports.phaseProgressLine = phaseProgressLine;
|
|
23
23
|
exports.formatDuration = formatDuration;
|
|
24
24
|
exports.printSuccessSummary = printSuccessSummary;
|
|
25
|
+
exports.stripAnsi = stripAnsi;
|
|
26
|
+
exports.visibleWidth = visibleWidth;
|
|
27
|
+
exports.truncate = truncate;
|
|
28
|
+
exports.formatFindingsSummary = formatFindingsSummary;
|
|
25
29
|
function isTTY(stream = process.stderr) {
|
|
26
30
|
return Boolean(stream.isTTY);
|
|
27
31
|
}
|
|
32
|
+
/** Whether to emit ANSI color on a stream, honoring the de-facto env standards:
|
|
33
|
+
* NO_COLOR / CW_NO_COLOR (any non-empty value) disable; FORCE_COLOR (non-"0") forces on
|
|
34
|
+
* even when piped; otherwise fall back to isTTY. The `--no-color` flag sets CW_NO_COLOR. */
|
|
35
|
+
function colorEnabled(stream, env = process.env) {
|
|
36
|
+
if ((env.NO_COLOR ?? "") !== "" || (env.CW_NO_COLOR ?? "") !== "")
|
|
37
|
+
return false;
|
|
38
|
+
if (env.FORCE_COLOR !== undefined && env.FORCE_COLOR !== "" && env.FORCE_COLOR !== "0")
|
|
39
|
+
return true;
|
|
40
|
+
return isTTY(stream);
|
|
41
|
+
}
|
|
28
42
|
// ---- ansi codes ----
|
|
29
43
|
const ansi = {
|
|
30
44
|
reset: "\x1b[0m",
|
|
@@ -37,7 +51,7 @@ const ansi = {
|
|
|
37
51
|
};
|
|
38
52
|
// ---- styled text ----
|
|
39
53
|
function style(code, text, stream) {
|
|
40
|
-
if (!
|
|
54
|
+
if (!colorEnabled(stream))
|
|
41
55
|
return text;
|
|
42
56
|
return `${code}${text}${ansi.reset}`;
|
|
43
57
|
}
|
|
@@ -137,3 +151,61 @@ function printSuccessSummary(fields, stream) {
|
|
|
137
151
|
s.write(` ${nextHint(`cw status ${fields.runId}`, s)}\n`);
|
|
138
152
|
}
|
|
139
153
|
}
|
|
154
|
+
// ---- width-aware truncation (zero-dep) ----
|
|
155
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
156
|
+
/** Strip ANSI SGR codes (for measuring visible width). */
|
|
157
|
+
function stripAnsi(text) {
|
|
158
|
+
return text.replace(ANSI_RE, "");
|
|
159
|
+
}
|
|
160
|
+
/** Visible width of a string, ignoring ANSI. Counts each code point as width 1 — a known
|
|
161
|
+
* minor caveat for wide (CJK/emoji) glyphs, acceptable for one-line status truncation. */
|
|
162
|
+
function visibleWidth(text) {
|
|
163
|
+
return [...stripAnsi(text)].length;
|
|
164
|
+
}
|
|
165
|
+
/** Truncate a (possibly styled) string to `maxWidth` visible columns, appending `…` when cut.
|
|
166
|
+
* Operates on the PLAIN text (callers truncate before styling), so no ANSI is split. */
|
|
167
|
+
function truncate(text, maxWidth) {
|
|
168
|
+
if (maxWidth <= 0)
|
|
169
|
+
return "";
|
|
170
|
+
const chars = [...stripAnsi(text)];
|
|
171
|
+
if (chars.length <= maxWidth)
|
|
172
|
+
return text;
|
|
173
|
+
if (maxWidth === 1)
|
|
174
|
+
return "…";
|
|
175
|
+
return `${chars.slice(0, maxWidth - 1).join("")}…`;
|
|
176
|
+
}
|
|
177
|
+
// ---- findings summary table (end-of-run, compact) ----
|
|
178
|
+
/** Severity order for sorting + counting (most severe first). */
|
|
179
|
+
const SEVERITY_ORDER = ["P0", "P1", "P2", "P3", "none"];
|
|
180
|
+
/** Render a compact findings summary assembled from the run's `cw:result` blocks: a one-line
|
|
181
|
+
* count headline (e.g. `Findings: 3 — 2×P1, 1×P2`) plus a small id/severity/class table. This
|
|
182
|
+
* is the end-of-run summary — NOT the full prose (that stays in report.md + the transcript).
|
|
183
|
+
* Returns "" when there are no findings (caller prints nothing). */
|
|
184
|
+
function formatFindingsSummary(findings, stream) {
|
|
185
|
+
if (!findings.length)
|
|
186
|
+
return "";
|
|
187
|
+
const bySev = new Map();
|
|
188
|
+
for (const f of findings)
|
|
189
|
+
bySev.set(f.severity || "none", (bySev.get(f.severity || "none") || 0) + 1);
|
|
190
|
+
const order = (s) => {
|
|
191
|
+
const i = SEVERITY_ORDER.indexOf(s);
|
|
192
|
+
return i === -1 ? SEVERITY_ORDER.length : i;
|
|
193
|
+
};
|
|
194
|
+
const counts = [...bySev.entries()]
|
|
195
|
+
.sort((a, b) => order(a[0]) - order(b[0]))
|
|
196
|
+
.map(([sev, n]) => `${n}×${sev}`)
|
|
197
|
+
.join(", ");
|
|
198
|
+
const rows = [...findings].sort((a, b) => order(a.severity) - order(b.severity));
|
|
199
|
+
const sevW = Math.max(8, ...rows.map((r) => (r.severity || "none").length));
|
|
200
|
+
const clsW = Math.max(5, ...rows.map((r) => (r.classification || "unknown").length));
|
|
201
|
+
const lines = [
|
|
202
|
+
`${bold("Findings:", stream)} ${findings.length} — ${counts}`,
|
|
203
|
+
dim(` ${"SEVERITY".padEnd(sevW)} ${"CLASS".padEnd(clsW)} ID`, stream)
|
|
204
|
+
];
|
|
205
|
+
for (const r of rows) {
|
|
206
|
+
const sev = (r.severity || "none").padEnd(sevW);
|
|
207
|
+
const cls = (r.classification || "unknown").padEnd(clsW);
|
|
208
|
+
lines.push(` ${sev} ${cls} ${truncate(r.id || "(unnamed)", 60)}`);
|
|
209
|
+
}
|
|
210
|
+
return lines.join("\n");
|
|
211
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MIN_SUPPORTED_RUN_STATE_SCHEMA_VERSION = exports.LEGACY_RUN_STATE_SCHEMA_VERSION = exports.CURRENT_RUN_STATE_SCHEMA_VERSION = exports.WORKFLOW_APP_SCHEMA_VERSION = exports.CURRENT_COOL_WORKFLOW_VERSION = void 0;
|
|
4
|
-
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.
|
|
4
|
+
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.92";
|
|
5
5
|
exports.WORKFLOW_APP_SCHEMA_VERSION = 1;
|
|
6
6
|
exports.CURRENT_RUN_STATE_SCHEMA_VERSION = 1;
|
|
7
7
|
exports.LEGACY_RUN_STATE_SCHEMA_VERSION = 0;
|
|
@@ -198,28 +198,43 @@ string). Each verb is declared once in `capability-registry.ts`, so `cw <cmd>
|
|
|
198
198
|
--json` is byte-identical to the matching `cw_<tool>` MCP tool for the read-only
|
|
199
199
|
preview/config-show verbs.
|
|
200
200
|
|
|
201
|
-
## Live output —
|
|
202
|
-
|
|
203
|
-
A drive
|
|
204
|
-
contract
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
the
|
|
201
|
+
## Live output — a calm live view, on stderr, Unix-clean
|
|
202
|
+
|
|
203
|
+
A drive shows the agent's activity live without ever touching the evidence
|
|
204
|
+
contract. The async per-vendor wrapper owns the live region (it parses
|
|
205
|
+
`--output-format stream-json` and its event loop is free); cw renders the calm
|
|
206
|
+
orchestration between agents plus the end-of-run summary. **Everything goes to
|
|
207
|
+
stderr — stdout stays the byte-exact data channel** (the `cw:result` fence, the
|
|
208
|
+
wrapper's `{model, usage, result}` JSON, cw's `--json` payload), so a pipe is
|
|
209
|
+
never polluted.
|
|
210
|
+
|
|
211
|
+
- **Interactive (TTY).** The wrapper renders ONE in-place status line: a Braille
|
|
212
|
+
spinner + the current action + elapsed (e.g. `⠹ Read app.js 1.2s`). Tool calls
|
|
213
|
+
fold to a single line each — spinning while running, then resolving to
|
|
214
|
+
`✓ Read app.js (0.3s)` / `✗ Bash (1.1s)` (dimmed, args width-truncated). The
|
|
215
|
+
cursor is hidden while the spinner runs and **ALWAYS restored** on exit /
|
|
216
|
+
Ctrl-C / SIGTERM (Ctrl-C exits non-zero, leaving a clean terminal).
|
|
217
|
+
- **Non-TTY stays SILENT by default** (the Rule of Silence). `CW_AGENT_STREAM=1`
|
|
218
|
+
opts a CI/piped run into a **plain append-only** trace (`→ …` / `✓ … (Xs)`
|
|
219
|
+
lines, zero ANSI/cursor bytes) for debuggability — mirroring `CW_DRIVE_PROGRESS=1`.
|
|
220
|
+
- **Verbosity.** Default is compact: the current action + folded tool lines, with
|
|
221
|
+
the model's narration/reasoning HIDDEN. `--verbose` (sets `CW_VERBOSE=1`)
|
|
222
|
+
surfaces the full narration inline; `--full` (sets `CW_OUTPUT=full`) implies
|
|
223
|
+
verbose AND prints the report inline at run end.
|
|
224
|
+
- **Transcript always on disk.** Regardless of verbosity — even when the screen
|
|
225
|
+
view is silent or compact — the wrapper writes the COMPLETE narration + tool I/O
|
|
226
|
+
to `transcript.md` next to that worker's `result.md`. The end-of-run summary
|
|
227
|
+
prints the run dir where the transcripts live, so nothing is ever lost to a
|
|
228
|
+
compact view.
|
|
229
|
+
- **Color control.** `NO_COLOR` / `CW_NO_COLOR` (the `--no-color` flag sets the
|
|
230
|
+
latter) disable ANSI; `FORCE_COLOR` forces it even when piped; otherwise color
|
|
231
|
+
follows isTTY. Honored identically by cw (`term.ts`) and every wrapper.
|
|
232
|
+
- **End-of-run summary (cw side).** A COMPACT findings table — id / severity /
|
|
233
|
+
classification + counts, re-parsed from each completed worker's `cw:result` —
|
|
234
|
+
plus the report path, status, and run dir. NOT the full prose (that stays in
|
|
235
|
+
`report.md` + the transcript); `--full` also prints the report inline.
|
|
236
|
+
- **Determinism intact.** The backend evidence triple hashes stdout only, so the
|
|
237
|
+
live stderr view never changes recorded evidence or replay.
|
|
223
238
|
|
|
224
239
|
The built-in templates are:
|
|
225
240
|
|
|
@@ -319,3 +334,7 @@ Orchestration-parity for the agent drive: `run --drive --incremental` step-level
|
|
|
319
334
|
The one-command `cw -q` headline now routes the question and defaults the repo to the caller cwd before driving the agent; the delegation contract, drive, and accept path are unchanged.
|
|
320
335
|
|
|
321
336
|
0.1.90
|
|
337
|
+
|
|
338
|
+
0.1.91
|
|
339
|
+
|
|
340
|
+
0.1.92
|
|
@@ -98,6 +98,11 @@ node scripts/cw.js plan release-cut \
|
|
|
98
98
|
Break a research question into claims, look into sources, cross-check
|
|
99
99
|
the evidence, check the claims, and put together a short answer.
|
|
100
100
|
|
|
101
|
+
When you point it at a folder, it reads the local files there as primary
|
|
102
|
+
sources. So this app works over any corpus — your own docs, notes, or
|
|
103
|
+
papers — not only a git code repo. The working directory is the corpus,
|
|
104
|
+
so the agent can back its answer with your own files.
|
|
105
|
+
|
|
101
106
|
```bash
|
|
102
107
|
node scripts/cw.js plan research-synthesis \
|
|
103
108
|
--cwd /tmp/research-run \
|
|
@@ -107,6 +112,13 @@ node scripts/cw.js plan research-synthesis \
|
|
|
107
112
|
--freshness "as of today"
|
|
108
113
|
```
|
|
109
114
|
|
|
115
|
+
Over a local corpus folder, point `--repo` at it:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
cw quickstart research-synthesis --repo /path/to/papers \
|
|
119
|
+
--question "What do these papers conclude?"
|
|
120
|
+
```
|
|
121
|
+
|
|
110
122
|
## Validation Matrix
|
|
111
123
|
|
|
112
124
|
Run the canonical app matrix from the plugin root directory:
|
package/docs/cli-mcp-parity.7.md
CHANGED
|
@@ -526,3 +526,7 @@ CLI surface simplified to 6 commands with agent stderr streaming on by default a
|
|
|
526
526
|
CLI golden-path fixes: `cw -q "…"` routes the question (was read as an app id → "Workflow app not found"), auto-detects the cwd as the repo (run anywhere, no `--repo`), and `cw help` wraps its command list with a trailing newline; the CLI↔MCP parity contract and the help-token parser are unchanged.
|
|
527
527
|
|
|
528
528
|
0.1.90
|
|
529
|
+
|
|
530
|
+
0.1.91
|
|
531
|
+
|
|
532
|
+
0.1.92
|
|
@@ -301,3 +301,7 @@ The host-facing surface tracks the CLI simplification to 6 commands (streaming o
|
|
|
301
301
|
The host-facing surface tracks the CLI golden-path fixes (`cw -q` routing + repo auto-detect + clean help); the multi-agent verbs and their MCP-tool mirrors are unchanged.
|
|
302
302
|
|
|
303
303
|
0.1.90
|
|
304
|
+
|
|
305
|
+
0.1.91
|
|
306
|
+
|
|
307
|
+
0.1.92
|