cool-workflow 0.1.88 → 0.1.90

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.
Files changed (51) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +44 -1
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +1 -1
  6. package/apps/end-to-end-golden-path/app.json +1 -1
  7. package/apps/pr-review-fix-ci/app.json +1 -1
  8. package/apps/release-cut/app.json +1 -1
  9. package/apps/research-synthesis/app.json +1 -1
  10. package/dist/capability-core.js +123 -5
  11. package/dist/capability-registry.js +6 -0
  12. package/dist/cli/command-surface.js +99 -7
  13. package/dist/cli.js +27 -1
  14. package/dist/clones.js +162 -0
  15. package/dist/drive.js +35 -1
  16. package/dist/mcp/tool-call.js +4 -0
  17. package/dist/mcp/tool-definitions.js +5 -0
  18. package/dist/orchestrator/report.js +6 -0
  19. package/dist/orchestrator.js +37 -6
  20. package/dist/remote-source.js +444 -0
  21. package/dist/term.js +54 -8
  22. package/dist/version.js +1 -1
  23. package/docs/agent-delegation-drive.7.md +6 -0
  24. package/docs/cli-mcp-parity.7.md +11 -2
  25. package/docs/contract-migration-tooling.7.md +6 -0
  26. package/docs/control-plane-scheduling.7.md +6 -0
  27. package/docs/durable-state-and-locking.7.md +6 -0
  28. package/docs/evidence-adoption-reasoning-chain.7.md +6 -0
  29. package/docs/execution-backends.7.md +6 -0
  30. package/docs/multi-agent-cli-mcp-surface.7.md +6 -0
  31. package/docs/multi-agent-eval-replay-harness.7.md +6 -0
  32. package/docs/multi-agent-operator-ux.7.md +6 -0
  33. package/docs/node-snapshot-diff-replay.7.md +6 -0
  34. package/docs/observability-cost-accounting.7.md +6 -0
  35. package/docs/project-index.md +16 -5
  36. package/docs/real-execution-backends.7.md +6 -0
  37. package/docs/release-and-migration.7.md +6 -0
  38. package/docs/release-tooling.7.md +6 -0
  39. package/docs/remote-source-review.7.md +88 -0
  40. package/docs/run-registry-control-plane.7.md +6 -0
  41. package/docs/run-retention-reclamation.7.md +6 -0
  42. package/docs/state-explosion-management.7.md +6 -0
  43. package/docs/team-collaboration.7.md +6 -0
  44. package/docs/web-desktop-workbench.7.md +6 -0
  45. package/manifest/plugin.manifest.json +1 -1
  46. package/manifest/source-context-profiles.json +1 -1
  47. package/package.json +1 -1
  48. package/scripts/canonical-apps.js +4 -4
  49. package/scripts/dogfood-release.js +1 -1
  50. package/scripts/golden-path.js +4 -4
  51. package/scripts/release-gate.sh +11 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
3
  "description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
4
- "version": "0.1.88",
4
+ "version": "0.1.90",
5
5
  "author": {
6
6
  "name": "COOLWHITE LLC"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
- "version": "0.1.88",
3
+ "version": "0.1.90",
4
4
  "description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
5
5
  "author": {
6
6
  "name": "COOLWHITE LLC"
package/README.md CHANGED
@@ -53,7 +53,44 @@ cw -q "What are the security risks?" -codex
53
53
  cw -q "What are the security risks?" -deepseek
54
54
  ```
55
55
 
56
- You will see live streaming output as the agent worksno env vars needed.
56
+ Review a project **from any directory** no `cd` neededby pointing at its folder:
57
+
58
+ ```bash
59
+ cw -q "What are the risks?" -dir /path/to/project # -d / --dir / --repo are equivalent
60
+ ```
61
+
62
+ Or review a **remote repo by URL** — CW clones it for you, then reviews the checkout
63
+ (`-l` / `--link`, or just pass the URL to `-dir`/`--repo`):
64
+
65
+ ```bash
66
+ cw -q "What are the risks?" --link https://github.com/owner/repo
67
+ cw -q "What are the risks?" --link git@gitlab.com:owner/repo.git --ref v1.2.0
68
+ ```
69
+
70
+ Works with GitHub, GitLab, Bitbucket, self-hosted git, `ssh://`, and `file://` — **and with
71
+ downloadable archives** (`.tar.gz`/`.tgz`/`.tar`/`.zip`, e.g. a GitHub "Download ZIP" link),
72
+ which are fetched, extracted, and snapshotted into a local repo before review. The clone is
73
+ cached under `~/.local/state/cool-workflow/clones/` and reused on the next question (`--refresh`
74
+ re-fetches; manage with `cw clones list` / `cw clones gc`). The report records the exact origin —
75
+ `Source: <url>@<commit>` — and a tamper-evident `source.clone` event you can re-prove with
76
+ `cw audit verify`. A private repo with no credentials **fails closed** (it never hangs on a
77
+ prompt). Validate without fetching: `cw -q "…" --link <url> --check`.
78
+
79
+ As it runs you see CW's own phases tick by, then a clean summary — no env vars needed:
80
+
81
+ ```text
82
+ ==> Map ✓ (6/6)
83
+ ==> Assess ✓ (6/6)
84
+ ==> Verdict ✓
85
+
86
+ ✓ Report: /path/to/project/.cw/runs/<run-id>/report.md
87
+ ✓ Status: complete — 14/14
88
+ Next: cw report <run-id> --show
89
+ ```
90
+
91
+ If something is off, the error hands you the next move — e.g. `Try: cw doctor` when no
92
+ agent is configured. Pipe-clean: with `--json`, stdout is pure data and all of this chrome
93
+ goes to stderr (TTY only).
57
94
 
58
95
  ### 3. Open the report
59
96
 
@@ -462,6 +499,10 @@ Verification and synthesis tasks need a structured result block:
462
499
  ```
463
500
  ````
464
501
 
502
+ ## 0.1.89 (v0.1.89)
503
+
504
+ Headline golden-path fixes: `cw -q "question"` asks the question (was read as an app id), auto-detects the cwd as the repo (run anywhere, no `--repo`/`cd`), and `cw help` renders a clean wrapped command list with a trailing newline; plus a vendor-agnostic UI/UX prevention gate (a headline-command smoke + a real `npm install -g` run-from-anywhere smoke) so a CLI-surface regression can't ship invisibly again.
505
+
465
506
  ## 0.1.88 (v0.1.88)
466
507
 
467
508
  Orchestration-parity for the agent drive — inline `subWorkflow()` nesting, bounded dynamic `loop()` phases (a `predicate` or a `budget-target` token `until`), and `cw run --drive --incremental` step-level resume; the agent now signs its findings (result-bound ed25519) and `cw report verify-bundle --require-signatures` proves offline that every signed finding is in the report unaltered; CLI simplified to 6 commands with agent streaming on by default; path-traversal run ids refused on archive import.
@@ -469,3 +510,5 @@ Orchestration-parity for the agent drive — inline `subWorkflow()` nesting, bou
469
510
  ## 0.1.87 (v0.1.87)
470
511
 
471
512
  npm test parallel, 4-vendor wrappers (Claude/Codex/Gemini/OpenCode), Homebrew-style CLI UX (colors/did-you-mean/categorized help/error tips/cw info/cw search/cw man/doctor --fix), post-success summaries, agent execution timing
513
+
514
+ v0.1.90
@@ -3,7 +3,7 @@
3
3
  "id": "architecture-review",
4
4
  "title": "Architecture Review",
5
5
  "summary": "Map a repository architecture, assess risks, verify important findings, and synthesize an evidence-backed verdict.",
6
- "version": "0.1.88",
6
+ "version": "0.1.90",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "architecture-review-fast",
4
4
  "title": "Architecture Review Fast",
5
5
  "summary": "Run a shorter architecture review with parallel map and assess phases for faster first results.",
6
- "version": "0.1.88",
6
+ "version": "0.1.90",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "end-to-end-golden-path",
4
4
  "title": "End-to-End Golden Path",
5
5
  "summary": "Deterministic one-worker workflow app for proving the CW integration chain.",
6
- "version": "0.1.88",
6
+ "version": "0.1.90",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "pr-review-fix-ci",
4
4
  "title": "PR Review Fix CI",
5
5
  "summary": "Review a pull request or branch, inspect CI failures, diagnose actionable issues, optionally patch, verify, and summarize with evidence.",
6
- "version": "0.1.88",
6
+ "version": "0.1.90",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "release-cut",
4
4
  "title": "Release Cut",
5
5
  "summary": "Prepare a release with checklist discipline: version checks, changelog, tests, packaging, release notes, and final verification.",
6
- "version": "0.1.88",
6
+ "version": "0.1.90",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "research-synthesis",
4
4
  "title": "Research Synthesis",
5
5
  "summary": "Split a research question into claims, investigate sources, cross-check evidence, verify claims, and synthesize a concise answer.",
6
- "version": "0.1.88",
6
+ "version": "0.1.90",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -21,7 +21,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
21
21
  return (mod && mod.__esModule) ? mod : { "default": mod };
22
22
  };
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.QUICKSTART_DEFAULT_APP = void 0;
24
+ exports.gcClones = exports.listClones = exports.QUICKSTART_DEFAULT_APP = void 0;
25
25
  exports.planSummary = planSummary;
26
26
  exports.appRun = appRun;
27
27
  exports.sandboxChoose = sandboxChoose;
@@ -79,6 +79,7 @@ const observability_1 = require("./observability");
79
79
  const telemetry_ledger_1 = require("./telemetry-ledger");
80
80
  const telemetry_attestation_1 = require("./telemetry-attestation");
81
81
  const trust_audit_1 = require("./trust-audit");
82
+ const remote_source_1 = require("./remote-source");
82
83
  const telemetry_demo_1 = require("./telemetry-demo");
83
84
  const state_1 = require("./state");
84
85
  const run_export_1 = require("./run-export");
@@ -497,7 +498,13 @@ const DRIVE_RUNTIME_KEYS = [
497
498
  "agentTimeoutMs",
498
499
  "agent-timeout-ms",
499
500
  "resume",
500
- "incremental"
501
+ "incremental",
502
+ // Remote-source flags (v0.1.91): materialized into a local checkout in the capability
503
+ // layer, never passed to plan as inputs (the resolved sourceUrl/sourceCommit ARE inputs).
504
+ "link",
505
+ "ref",
506
+ "branch",
507
+ "refresh"
501
508
  ];
502
509
  function planInputsFor(args) {
503
510
  const copy = withoutRuntimeKeys(args);
@@ -548,9 +555,41 @@ exports.QUICKSTART_DEFAULT_APP = "architecture-review";
548
555
  * the drive fails closed (status=blocked) and we never fabricate a completion. */
549
556
  function quickstart(runner, args) {
550
557
  const appId = String(args.appId || args.app || args.workflowId || exports.QUICKSTART_DEFAULT_APP);
558
+ // Remote source (v0.1.91): a `--link <url>` — or a URL passed to `--repo`/`-dir` — is
559
+ // materialized to a LOCAL checkout in the capability layer (below). Cloning is
560
+ // non-deterministic network I/O and must never enter the replay-deterministic core, so we
561
+ // rewrite `args.repo` to the local path here; everything downstream is a normal local run.
562
+ const linkArg = optionalString(args.link);
563
+ const repoArgRaw = optionalString(args.repo);
564
+ const remoteCandidate = linkArg || (repoArgRaw && (0, remote_source_1.isRemoteUrl)(repoArgRaw) ? repoArgRaw : undefined);
565
+ // Run anywhere (like brew): default the repo-under-review to the caller's cwd when no
566
+ // --repo/--cwd/--link is given. A remote candidate is materialized below, so it must NOT
567
+ // fall through to the cwd default here.
568
+ if (!remoteCandidate && !optionalString(args.repo) && !optionalString(args.cwd)) {
569
+ args.repo = invocationCwd(args);
570
+ }
551
571
  const agentConfigured = Boolean((0, agent_config_1.resolveAgentConfig)(args).command || (0, agent_config_1.resolveAgentConfig)(args).endpoint);
552
- if (isTrue(args.check))
553
- return quickstartCheck(runner, appId, args, agentConfigured);
572
+ if (isTrue(args.check)) {
573
+ return remoteCandidate
574
+ ? remoteQuickstartCheck(runner, appId, args, agentConfigured, remoteCandidate)
575
+ : quickstartCheck(runner, appId, args, agentConfigured);
576
+ }
577
+ // Materialize the remote NOW — after `--check` (which never fetches) and before any
578
+ // plan/preview/drive — so the orchestrator only ever sees the local checkout. Fails closed:
579
+ // a bad URL / blocked scheme / network / auth failure throws here, before any run is planned.
580
+ let remoteSource;
581
+ if (remoteCandidate) {
582
+ remoteSource = (0, remote_source_1.materializeRemote)(remoteCandidate, {
583
+ ref: optionalString(args.ref || args.branch),
584
+ refresh: isTrue(args.refresh)
585
+ });
586
+ args.repo = remoteSource.localPath;
587
+ // Record the origin as plan INPUTS so it rides into run.inputs → the report header.
588
+ args.sourceUrl = remoteSource.url;
589
+ args.sourceCommit = remoteSource.commit;
590
+ if (remoteSource.ref)
591
+ args.sourceRef = remoteSource.ref;
592
+ }
554
593
  // `--resume`: a discoverability flag over the existing continuation. With no
555
594
  // `--run`, advance exactly ONE step (reuse the `--once` path) and print a
556
595
  // copy-pasteable continue line; with `--run <id>`, continue that run to
@@ -589,6 +628,25 @@ function quickstart(runner, args) {
589
628
  const runRepoCwd = node_path_1.default.resolve(node_path_1.default.dirname(result.statePath), "..", "..", "..");
590
629
  const reportTarget = node_fs_1.default.existsSync(runRepoCwd) ? runRepoCwd : undefined;
591
630
  const reportPath = runner.withBaseDir(reportTarget).report(result.runId).path;
631
+ // Tamper-evident provenance: bind the remote origin (url@sha) into the run's hash-chained
632
+ // trust-audit log so `cw audit verify` re-proves where the code came from. metadata is
633
+ // auto-scrubbed of credentials by recordTrustAuditEvent → scrubMetadata. Best-effort: the
634
+ // origin is already in run.inputs/report/the result, so a recording hiccup never fails the
635
+ // review (additive trust evidence, not a gate).
636
+ if (remoteSource) {
637
+ try {
638
+ const provRun = runner.withBaseDir(reportTarget).loadRun(result.runId);
639
+ (0, trust_audit_1.recordTrustAuditEvent)(provRun, {
640
+ kind: remoteSource.kind === "archive" ? "source.download" : "source.clone",
641
+ decision: "recorded",
642
+ source: "operator-recorded",
643
+ metadata: { url: remoteSource.url, commit: remoteSource.commit, ref: remoteSource.ref || null, kind: remoteSource.kind, depth: 1 }
644
+ });
645
+ }
646
+ catch {
647
+ /* provenance is additive; never fail a completed review over an audit-log hiccup */
648
+ }
649
+ }
592
650
  // --bundle: after a COMPLETE drive, seal the run into a portable, self-verified
593
651
  // bundle so the one command yields a client-verifiable artifact. Pure composition
594
652
  // of reportBundle() (export sealed + offline self-verify); spawns nothing. Gated on
@@ -658,7 +716,12 @@ function quickstart(runner, args) {
658
716
  ...(resumeRunId ? { resumedFrom: resumeRunId } : {}),
659
717
  // Same conditional-spread discipline: `bundle` is present only when --bundle ran
660
718
  // on a completed drive, so the default (no --bundle) payload is byte-identical.
661
- ...(bundle ? { bundle } : {})
719
+ ...(bundle ? { bundle } : {}),
720
+ // `remote` is present only when the review targeted a --link/URL source, so a local-repo
721
+ // run stays byte-identical. Carries the sanitized origin + resolved commit for provenance.
722
+ ...(remoteSource
723
+ ? { remote: { url: remoteSource.url, commit: remoteSource.commit, kind: remoteSource.kind, cached: remoteSource.cached, ...(remoteSource.ref ? { ref: remoteSource.ref } : {}) } }
724
+ : {})
662
725
  };
663
726
  }
664
727
  function quickstartCheck(runner, appId, args, agentConfigured) {
@@ -765,6 +828,55 @@ function quickstartCheck(runner, appId, args, agentConfigured) {
765
828
  nextCommand: quickstartNextCommand(appId, repo, args)
766
829
  };
767
830
  }
831
+ /** Preflight for a `--link`/URL review: validate the URL SHAPE + tooling WITHOUT fetching
832
+ * (a clone is heavy and side-effecting; --check stays read-only). Mirrors quickstartCheck's
833
+ * app/question/agent sub-checks but swaps the local-repo readability checks for link+tooling.
834
+ * `repo` carries the sanitized URL so the result reports what would be reviewed. */
835
+ function remoteQuickstartCheck(runner, appId, args, agentConfigured, candidate) {
836
+ const validation = (0, remote_source_1.validateRemoteUrl)(candidate);
837
+ const checks = [];
838
+ try {
839
+ runner.showApp(appId);
840
+ checks.push({ name: "app", status: "ok", detail: `Workflow app ${appId} is available.` });
841
+ }
842
+ catch (error) {
843
+ checks.push({ name: "app", status: "blocked", detail: `Workflow app ${appId} is not available.`, fix: "Run `cw app list` and choose one of the listed app ids." });
844
+ }
845
+ if (validation.ok) {
846
+ checks.push({ name: "link", status: "ok", detail: `Remote source is a valid ${validation.kind} URL (${validation.url}).` });
847
+ }
848
+ else {
849
+ checks.push({ name: "link", status: "blocked", detail: `Remote source is not usable: ${validation.reason}.`, fix: "Pass a git URL (https/ssh/git/file or git@host:repo)." });
850
+ }
851
+ if ((0, remote_source_1.gitAvailable)()) {
852
+ checks.push({ name: "tooling", status: "ok", detail: "git is available to clone the remote." });
853
+ }
854
+ else {
855
+ checks.push({ name: "tooling", status: "blocked", detail: "git was not found on PATH.", fix: "Install git, then re-run." });
856
+ }
857
+ if (optionalString(args.question)) {
858
+ checks.push({ name: "question", status: "ok", detail: "Question is set." });
859
+ }
860
+ else {
861
+ checks.push({ name: "question", status: "blocked", detail: "Question is missing.", fix: "Pass --question TEXT." });
862
+ }
863
+ if (agentConfigured) {
864
+ checks.push({ name: "agent", status: "ok", detail: "Agent backend is configured." });
865
+ }
866
+ else {
867
+ checks.push({ name: "agent", status: "blocked", detail: "No agent backend is configured.", fix: "Pass --agent-command \"claude -p\", set $CW_AGENT_COMMAND, or use --agent-command builtin:claude." });
868
+ }
869
+ const ok = checks.every((check) => check.status !== "blocked");
870
+ return {
871
+ schemaVersion: 1,
872
+ mode: "check",
873
+ ok,
874
+ appId,
875
+ repo: validation.url,
876
+ checks,
877
+ nextCommand: `cw quickstart ${shellWord(appId)} --link ${shellWord(validation.url)}${optionalString(args.question) ? ` --question ${shellWord(String(args.question))}` : ""}`
878
+ };
879
+ }
768
880
  function quickstartNextCommand(appId, repo, args) {
769
881
  const parts = ["cw", "quickstart", shellWord(appId), "--repo", shellWord(repo)];
770
882
  const question = optionalString(args.question);
@@ -838,6 +950,12 @@ function gcRun(reg, runId, args) {
838
950
  function gcVerify(reg, runId, args) {
839
951
  return reg.gcVerify(runId, { scope: scopeOf(args, "home") });
840
952
  }
953
+ // Remote-source clone cache (v0.1.91): list/reclaim the `~/.local/state/cool-workflow/clones`
954
+ // checkouts that `--link`/URL reviews populate. Pure filesystem work; both CLI and MCP route
955
+ // here so `cw clones …` and `cw_clones_…` are byte-identical.
956
+ var clones_1 = require("./clones");
957
+ Object.defineProperty(exports, "listClones", { enumerable: true, get: function () { return clones_1.listClones; } });
958
+ Object.defineProperty(exports, "gcClones", { enumerable: true, get: function () { return clones_1.gcClones; } });
841
959
  function runHistory(reg, args) {
842
960
  return reg.history({
843
961
  scope: scopeOf(args, "home"),
@@ -427,6 +427,8 @@ const BUILTIN_CAPABILITIES = [
427
427
  { capability: "gc.plan", summary: "Dry-run plan of run reclamation (per-kind bytes + capability downgrade); frees nothing.", entry: "gcPlan", surface: "both", cli: { path: ["gc", "plan"], caseTokens: ["gc", "plan"], jsonMode: "flag" }, mcp: { tool: "cw_gc_plan" } },
428
428
  { capability: "gc.run", summary: "Execute the write-ahead reclamation transaction (skeleton -> tombstone -> fsync -> free).", entry: "gcRun", surface: "both", cli: { path: ["gc", "run"], caseTokens: ["gc", "run"], jsonMode: "flag" }, mcp: { tool: "cw_gc_run" }, payloadIdentical: false, reason: "Mutating: frees disk and appends a tombstone; both surfaces perform the identical transaction but the payload reports now-derived bytesFreed/tombstone." },
429
429
  { capability: "gc.verify", summary: "Re-prove a reclaimed run: skeleton-complete, tombstone chain untampered, artifacts reconstructable.", entry: "gcVerify", surface: "both", cli: { path: ["gc", "verify"], caseTokens: ["gc", "verify"], jsonMode: "flag" }, mcp: { tool: "cw_gc_verify", requiredArgs: ["runId"] } },
430
+ { capability: "clones.list", summary: "List the cached remote-source checkouts that --link/URL reviews populate (origin URL, kind, commit, age, bytes). Read-only.", entry: "listClones", surface: "both", cli: { path: ["clones", "list"], caseTokens: ["clones", "list"], jsonMode: "flag" }, mcp: { tool: "cw_clones_list" } },
431
+ { capability: "clones.gc", summary: "Reclaim cached remote-source checkouts: a TTL sweep (--older-than-days, default 30) or --all. Deletes only inside the clones cache.", entry: "gcClones", surface: "both", cli: { path: ["clones", "gc"], caseTokens: ["clones", "gc"], jsonMode: "flag" }, mcp: { tool: "cw_clones_gc" }, payloadIdentical: false, reason: "Mutating: removes cache directories and reports now-derived freedBytes/removed; both surfaces perform the identical reclamation." },
430
432
  { capability: "telemetry.verify", summary: "Re-prove a run's telemetry attestation ledger offline: chain linkage + independent hash recompute, and (with --pubkey / CW_AGENT_ATTEST_PUBKEY) re-verify each attested hop's ed25519 signature against the public key.", entry: "telemetryVerify", surface: "both", cli: { path: ["telemetry", "verify"], caseTokens: ["telemetry"], jsonMode: "flag" }, mcp: { tool: "cw_telemetry_verify", requiredArgs: ["runId"] } },
431
433
  { capability: "demo.tamper", summary: "Prove tamper-evidence: build a signed telemetry ledger, forge it, watch verification fail offline.", entry: "demoTamper", surface: "cli-only", cli: { path: ["demo", "tamper"], caseTokens: ["demo", "tamper"], jsonMode: "flag" }, reason: "Human-facing demonstration (operator/newcomer onboarding); the underlying integrity check is exposed programmatically as the both-surface telemetry.verify. No agent or MCP client needs to invoke a demo." },
432
434
  { capability: "demo.bundle", summary: "Prove portable-bundle verification: export a sealed report bundle, forge it two ways, watch report verify-bundle catch both offline with only the embedded public key.", entry: "demoBundle", surface: "cli-only", cli: { path: ["demo", "bundle"], caseTokens: ["demo", "bundle"], jsonMode: "flag" }, reason: "Human-facing demonstration (operator/newcomer onboarding); the underlying integrity check is exposed programmatically as the both-surface report.verify-bundle. No agent or MCP client needs to invoke a demo." },
@@ -560,6 +562,10 @@ const PAYLOAD_PROBE_DEFERRED_GROUPS = [
560
562
  "init",
561
563
  "dispatch",
562
564
  "result",
565
+ // clones.list is payload-identical by construction (both surfaces call listClones), but it
566
+ // reads the external filesystem clone cache (absolute paths + per-entry bytes/timestamps),
567
+ // so it is not byte-probed by the deterministic bootstrap.
568
+ "clones.list",
563
569
  "app.init",
564
570
  "app.run",
565
571
  "migration.list",
@@ -69,7 +69,7 @@ async function runCli(argv = process.argv.slice(2)) {
69
69
  return;
70
70
  }
71
71
  if (!args.command || args.command === "--help" || args.command === "-h" || args.options.h || args.options.help) {
72
- process.stdout.write((0, orchestrator_1.formatHelp)());
72
+ process.stdout.write((0, orchestrator_1.formatHelp)() + "\n");
73
73
  return;
74
74
  }
75
75
  }
@@ -80,10 +80,16 @@ async function runCli(argv = process.argv.slice(2)) {
80
80
  args.options["agent-command"] = "builtin:codex";
81
81
  if (args.options.deepseek)
82
82
  args.options["agent-command"] = "builtin:deepseek";
83
+ // -dir / --dir / -d : an intuitive alias for --repo — the project folder to review,
84
+ // so `cw -q "…" -dir /path` works from any directory (no cd). Explicit --repo wins.
85
+ if (!args.options.repo && args.options.dir)
86
+ args.options.repo = args.options.dir;
83
87
  // Bare -q / --question -> redirect to quickstart (auto-detect repo/agent/app).
88
+ // CONSUME the positional (shift) so the question never survives as positionals[0]
89
+ // — otherwise the quickstart handler reads it as the app id ("Workflow app not found").
84
90
  if (args.command === "-q" || args.command === "--question") {
85
91
  if (!args.options.question && args.positionals[0])
86
- args.options.question = args.positionals[0];
92
+ args.options.question = args.positionals.shift();
87
93
  args.command = "quickstart";
88
94
  }
89
95
  else if (!args.command && typeof args.options.question === "string") {
@@ -97,7 +103,7 @@ async function runCli(argv = process.argv.slice(2)) {
97
103
  switch (args.command) {
98
104
  case "help":
99
105
  case undefined:
100
- process.stdout.write((0, orchestrator_1.formatHelp)());
106
+ process.stdout.write((0, orchestrator_1.formatHelp)() + "\n");
101
107
  return;
102
108
  case "version":
103
109
  process.stdout.write(`${version_1.CURRENT_COOL_WORKFLOW_VERSION}\n`);
@@ -234,12 +240,18 @@ async function runCli(argv = process.argv.slice(2)) {
234
240
  const qs = (0, capability_core_1.quickstart)(runner, { ...args.options, ...(appId ? { appId } : {}), ...(runId ? { runId } : {}) });
235
241
  printJson(qs);
236
242
  const qr = qs;
237
- if (typeof qr.runId === "string" && typeof qr.reportPath === "string") {
243
+ // Clean human summary on stderr (TTY-gated). Suppressed under --json so machine
244
+ // mode emits ONLY the stdout payload — no stderr chrome to parse around. The
245
+ // type guard also skips --check/--preview results (no reportPath of their own).
246
+ if (!wantsJson(args.options) && typeof qr.runId === "string" && typeof qr.reportPath === "string") {
238
247
  (0, term_1.printSuccessSummary)({
239
248
  runId: qr.runId,
240
249
  reportPath: qr.reportPath,
241
250
  status: String(qr.status || ""),
242
- bundle: Boolean(args.options.bundle)
251
+ bundle: Boolean(args.options.bundle),
252
+ completedWorkers: typeof qr.completedWorkers === "number" ? qr.completedWorkers : undefined,
253
+ plannedWorkers: typeof qr.plannedWorkers === "number" ? qr.plannedWorkers : undefined,
254
+ agentConfigured: typeof qr.agentConfigured === "boolean" ? qr.agentConfigured : undefined
243
255
  });
244
256
  }
245
257
  if (qs.mode === "check" && qs.ok === false) {
@@ -1205,7 +1217,16 @@ async function runCli(argv = process.argv.slice(2)) {
1205
1217
  driveArgs.appId = target;
1206
1218
  const dr = (0, capability_core_1.runDrive)(runner, driveArgs);
1207
1219
  printJson(dr);
1208
- (0, term_1.printSuccessSummary)({ runId: dr.runId, reportPath: dr.reportPath, status: dr.status });
1220
+ if (!wantsJson(args.options)) {
1221
+ (0, term_1.printSuccessSummary)({
1222
+ runId: dr.runId,
1223
+ reportPath: dr.reportPath,
1224
+ status: dr.status,
1225
+ completedWorkers: dr.completedWorkers,
1226
+ plannedWorkers: dr.plannedWorkers,
1227
+ agentConfigured: dr.agentConfigured
1228
+ });
1229
+ }
1209
1230
  return;
1210
1231
  }
1211
1232
  const registry = (0, capability_core_1.runRegistryFor)(args.options, runner);
@@ -1219,7 +1240,16 @@ async function runCli(argv = process.argv.slice(2)) {
1219
1240
  driveArgs.runId = id;
1220
1241
  const dr = (0, capability_core_1.runDrive)(runner, driveArgs);
1221
1242
  printJson(dr);
1222
- (0, term_1.printSuccessSummary)({ runId: dr.runId, reportPath: dr.reportPath, status: dr.status });
1243
+ if (!wantsJson(args.options)) {
1244
+ (0, term_1.printSuccessSummary)({
1245
+ runId: dr.runId,
1246
+ reportPath: dr.reportPath,
1247
+ status: dr.status,
1248
+ completedWorkers: dr.completedWorkers,
1249
+ plannedWorkers: dr.plannedWorkers,
1250
+ agentConfigured: dr.agentConfigured
1251
+ });
1252
+ }
1223
1253
  return;
1224
1254
  }
1225
1255
  printJson((0, capability_core_1.runDrivePreview)(runner, { ...args.options, runId: required(id, "run id") }));
@@ -1352,6 +1382,32 @@ async function runCli(argv = process.argv.slice(2)) {
1352
1382
  throw new Error("Usage: cw.js sched plan|lease|release|complete|reclaim|reset|policy [show|set] [id] [--maxConcurrent N --maxAttempts N ...]");
1353
1383
  }
1354
1384
  }
1385
+ case "clones": {
1386
+ // Remote-source clone cache (v0.1.91): `list` inspects the ~/.local/state/cool-workflow
1387
+ // /clones checkouts that `--link`/URL reviews populate; `gc` reclaims them (a TTL sweep,
1388
+ // or --all). Pure filesystem work — no network, no run registry.
1389
+ const [subcommand] = args.positionals;
1390
+ switch (subcommand) {
1391
+ case "list": {
1392
+ const result = (0, capability_core_1.listClones)(args.options);
1393
+ if (wantsJson(args.options))
1394
+ printJson(result);
1395
+ else
1396
+ process.stdout.write(`${formatClonesList(result)}\n`);
1397
+ return;
1398
+ }
1399
+ case "gc": {
1400
+ const result = (0, capability_core_1.gcClones)(args.options);
1401
+ if (wantsJson(args.options))
1402
+ printJson(result);
1403
+ else
1404
+ process.stdout.write(`${formatClonesGc(result)}\n`);
1405
+ return;
1406
+ }
1407
+ default:
1408
+ throw new Error("Usage: cw.js clones list [--json] | clones gc [--older-than-days N] [--all] [--json]");
1409
+ }
1410
+ }
1355
1411
  case "gc": {
1356
1412
  // Run Retention & Provable Reclamation (v0.1.39). `plan` is a pure dry-run
1357
1413
  // (frees nothing); `run` executes the write-ahead reclamation transaction;
@@ -1503,6 +1559,42 @@ function optionalArg(value) {
1503
1559
  function printJson(value) {
1504
1560
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
1505
1561
  }
1562
+ function humanBytes(n) {
1563
+ if (n < 1024)
1564
+ return `${n}B`;
1565
+ const units = ["KiB", "MiB", "GiB"];
1566
+ let v = n / 1024;
1567
+ let i = 0;
1568
+ while (v >= 1024 && i < units.length - 1) {
1569
+ v /= 1024;
1570
+ i += 1;
1571
+ }
1572
+ return `${v.toFixed(1)}${units[i]}`;
1573
+ }
1574
+ function formatClonesList(result) {
1575
+ if (result.count === 0)
1576
+ return `No cached remote checkouts in ${result.clonesDir}.`;
1577
+ const rows = result.entries.map((e) => {
1578
+ const when = e.fetchedAt ? e.fetchedAt.replace("T", " ").replace(/\..*$/, "Z") : "unknown";
1579
+ return ` ${e.kind.padEnd(7)} ${humanBytes(e.bytes).padStart(8)} ${when} ${e.url}${e.ref ? `@${e.ref}` : ""}`;
1580
+ });
1581
+ return [
1582
+ `${result.count} cached checkout${result.count === 1 ? "" : "s"} — ${humanBytes(result.totalBytes)} in ${result.clonesDir}`,
1583
+ " KIND SIZE FETCHED SOURCE",
1584
+ ...rows,
1585
+ `\nReclaim with: cw clones gc --older-than-days 30 (or --all)`
1586
+ ].join("\n");
1587
+ }
1588
+ function formatClonesGc(result) {
1589
+ const scope = result.all ? "all entries" : `entries older than ${result.olderThanDays} day(s)`;
1590
+ if (result.removed.length === 0)
1591
+ return `Nothing to reclaim (${scope}); ${result.keptCount} kept in ${result.clonesDir}.`;
1592
+ const rows = result.removed.map((r) => ` ${humanBytes(r.bytes).padStart(8)} ${r.url}`);
1593
+ return [
1594
+ `Reclaimed ${result.removed.length} checkout${result.removed.length === 1 ? "" : "s"} (${scope}) — freed ${humanBytes(result.freedBytes)}; ${result.keptCount} kept`,
1595
+ ...rows
1596
+ ].join("\n");
1597
+ }
1506
1598
  function wantsJson(options) {
1507
1599
  return Boolean(options.json || options.format === "json");
1508
1600
  }
package/dist/cli.js CHANGED
@@ -1,10 +1,36 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.recoveryHint = recoveryHint;
4
5
  const command_surface_1 = require("./cli/command-surface");
5
6
  const term_1 = require("./term");
6
7
  (0, command_surface_1.runCli)(process.argv.slice(2)).catch((error) => {
7
8
  const message = error instanceof Error ? error.message : String(error);
8
- process.stderr.write(`${(0, term_1.bold)("cw:")} ${(0, term_1.red)(message)}\n`);
9
+ const err = process.stderr;
10
+ // Errors go to stderr → color must key off stderr (not the term default).
11
+ err.write(`${(0, term_1.bold)("cw:", err)} ${(0, term_1.red)(message, err)}\n`);
12
+ // Brew-style recovery: a failed command should suggest a concrete next move. The hint
13
+ // is TTY-gated (tryHint dims only on a TTY) and goes to stderr, so piped stdout stays
14
+ // clean. It points at CW's OWN diagnose/discovery verbs (vendor-neutral) — never a model.
15
+ const hint = recoveryHint(message);
16
+ if (hint)
17
+ err.write(` ${(0, term_1.tryHint)(hint, err)}\n`);
9
18
  process.exitCode = 1;
10
19
  });
20
+ /** Map a top-level error message to ONE copy-pasteable recovery command (brew's `Try:`).
21
+ * Content-based so it stays correct for any vendor; returns undefined rather than a
22
+ * wrong guess when nothing matches (no hint beats a misleading one). */
23
+ function recoveryHint(message) {
24
+ const m = message.toLowerCase();
25
+ if (m.startsWith("unknown command"))
26
+ return "cw help";
27
+ if (m.includes("not configured") || m.includes("agent backend"))
28
+ return "cw doctor";
29
+ if (m.includes("missing") && m.includes("repo"))
30
+ return 'cw -q "<question>" -dir <project-folder>';
31
+ if (m.includes("app") && (m.includes("not found") || m.includes("not available")))
32
+ return "cw app list";
33
+ if (m.includes("run id") || m.includes("run not found"))
34
+ return "cw run list";
35
+ return undefined;
36
+ }