cool-workflow 0.1.89 → 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 (50) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +40 -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 +120 -9
  11. package/dist/capability-registry.js +6 -0
  12. package/dist/cli/command-surface.js +94 -4
  13. package/dist/cli.js +26 -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 +12 -4
  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 +2 -0
  24. package/docs/cli-mcp-parity.7.md +7 -2
  25. package/docs/contract-migration-tooling.7.md +2 -0
  26. package/docs/control-plane-scheduling.7.md +2 -0
  27. package/docs/durable-state-and-locking.7.md +2 -0
  28. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  29. package/docs/execution-backends.7.md +2 -0
  30. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  31. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  32. package/docs/multi-agent-operator-ux.7.md +2 -0
  33. package/docs/node-snapshot-diff-replay.7.md +2 -0
  34. package/docs/observability-cost-accounting.7.md +2 -0
  35. package/docs/project-index.md +13 -4
  36. package/docs/real-execution-backends.7.md +2 -0
  37. package/docs/release-and-migration.7.md +2 -0
  38. package/docs/release-tooling.7.md +2 -0
  39. package/docs/remote-source-review.7.md +88 -0
  40. package/docs/run-registry-control-plane.7.md +2 -0
  41. package/docs/run-retention-reclamation.7.md +2 -0
  42. package/docs/state-explosion-management.7.md +2 -0
  43. package/docs/team-collaboration.7.md +2 -0
  44. package/docs/web-desktop-workbench.7.md +2 -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
@@ -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.89",
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.89",
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
 
@@ -473,3 +510,5 @@ Orchestration-parity for the agent drive — inline `subWorkflow()` nesting, bou
473
510
  ## 0.1.87 (v0.1.87)
474
511
 
475
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.89",
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.89",
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.89",
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.89",
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.89",
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.89",
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,16 +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);
551
565
  // Run anywhere (like brew): default the repo-under-review to the caller's cwd when no
552
- // --repo/--cwd is given, mirroring quickstartCheck so `cw -q "…"` works from any
553
- // project directory without a flag, instead of failing validateInputs with "Missing
554
- // required input --repo".
555
- if (!optionalString(args.repo) && !optionalString(args.cwd)) {
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)) {
556
569
  args.repo = invocationCwd(args);
557
570
  }
558
571
  const agentConfigured = Boolean((0, agent_config_1.resolveAgentConfig)(args).command || (0, agent_config_1.resolveAgentConfig)(args).endpoint);
559
- if (isTrue(args.check))
560
- 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
+ }
561
593
  // `--resume`: a discoverability flag over the existing continuation. With no
562
594
  // `--run`, advance exactly ONE step (reuse the `--once` path) and print a
563
595
  // copy-pasteable continue line; with `--run <id>`, continue that run to
@@ -596,6 +628,25 @@ function quickstart(runner, args) {
596
628
  const runRepoCwd = node_path_1.default.resolve(node_path_1.default.dirname(result.statePath), "..", "..", "..");
597
629
  const reportTarget = node_fs_1.default.existsSync(runRepoCwd) ? runRepoCwd : undefined;
598
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
+ }
599
650
  // --bundle: after a COMPLETE drive, seal the run into a portable, self-verified
600
651
  // bundle so the one command yields a client-verifiable artifact. Pure composition
601
652
  // of reportBundle() (export sealed + offline self-verify); spawns nothing. Gated on
@@ -665,7 +716,12 @@ function quickstart(runner, args) {
665
716
  ...(resumeRunId ? { resumedFrom: resumeRunId } : {}),
666
717
  // Same conditional-spread discipline: `bundle` is present only when --bundle ran
667
718
  // on a completed drive, so the default (no --bundle) payload is byte-identical.
668
- ...(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
+ : {})
669
725
  };
670
726
  }
671
727
  function quickstartCheck(runner, appId, args, agentConfigured) {
@@ -772,6 +828,55 @@ function quickstartCheck(runner, appId, args, agentConfigured) {
772
828
  nextCommand: quickstartNextCommand(appId, repo, args)
773
829
  };
774
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
+ }
775
880
  function quickstartNextCommand(appId, repo, args) {
776
881
  const parts = ["cw", "quickstart", shellWord(appId), "--repo", shellWord(repo)];
777
882
  const question = optionalString(args.question);
@@ -845,6 +950,12 @@ function gcRun(reg, runId, args) {
845
950
  function gcVerify(reg, runId, args) {
846
951
  return reg.gcVerify(runId, { scope: scopeOf(args, "home") });
847
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; } });
848
959
  function runHistory(reg, args) {
849
960
  return reg.history({
850
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",
@@ -80,6 +80,10 @@ 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).
84
88
  // CONSUME the positional (shift) so the question never survives as positionals[0]
85
89
  // — otherwise the quickstart handler reads it as the app id ("Workflow app not found").
@@ -236,12 +240,18 @@ async function runCli(argv = process.argv.slice(2)) {
236
240
  const qs = (0, capability_core_1.quickstart)(runner, { ...args.options, ...(appId ? { appId } : {}), ...(runId ? { runId } : {}) });
237
241
  printJson(qs);
238
242
  const qr = qs;
239
- 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") {
240
247
  (0, term_1.printSuccessSummary)({
241
248
  runId: qr.runId,
242
249
  reportPath: qr.reportPath,
243
250
  status: String(qr.status || ""),
244
- 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
245
255
  });
246
256
  }
247
257
  if (qs.mode === "check" && qs.ok === false) {
@@ -1207,7 +1217,16 @@ async function runCli(argv = process.argv.slice(2)) {
1207
1217
  driveArgs.appId = target;
1208
1218
  const dr = (0, capability_core_1.runDrive)(runner, driveArgs);
1209
1219
  printJson(dr);
1210
- (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
+ }
1211
1230
  return;
1212
1231
  }
1213
1232
  const registry = (0, capability_core_1.runRegistryFor)(args.options, runner);
@@ -1221,7 +1240,16 @@ async function runCli(argv = process.argv.slice(2)) {
1221
1240
  driveArgs.runId = id;
1222
1241
  const dr = (0, capability_core_1.runDrive)(runner, driveArgs);
1223
1242
  printJson(dr);
1224
- (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
+ }
1225
1253
  return;
1226
1254
  }
1227
1255
  printJson((0, capability_core_1.runDrivePreview)(runner, { ...args.options, runId: required(id, "run id") }));
@@ -1354,6 +1382,32 @@ async function runCli(argv = process.argv.slice(2)) {
1354
1382
  throw new Error("Usage: cw.js sched plan|lease|release|complete|reclaim|reset|policy [show|set] [id] [--maxConcurrent N --maxAttempts N ...]");
1355
1383
  }
1356
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
+ }
1357
1411
  case "gc": {
1358
1412
  // Run Retention & Provable Reclamation (v0.1.39). `plan` is a pure dry-run
1359
1413
  // (frees nothing); `run` executes the write-ahead reclamation transaction;
@@ -1505,6 +1559,42 @@ function optionalArg(value) {
1505
1559
  function printJson(value) {
1506
1560
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
1507
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
+ }
1508
1598
  function wantsJson(options) {
1509
1599
  return Boolean(options.json || options.format === "json");
1510
1600
  }
package/dist/cli.js CHANGED
@@ -1,11 +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);
9
+ const err = process.stderr;
8
10
  // Errors go to stderr → color must key off stderr (not the term default).
9
- process.stderr.write(`${(0, term_1.bold)("cw:", process.stderr)} ${(0, term_1.red)(message, process.stderr)}\n`);
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`);
10
18
  process.exitCode = 1;
11
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
+ }