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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +40 -1
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/capability-core.js +120 -9
- package/dist/capability-registry.js +6 -0
- package/dist/cli/command-surface.js +94 -4
- package/dist/cli.js +26 -1
- package/dist/clones.js +162 -0
- package/dist/drive.js +35 -1
- package/dist/mcp/tool-call.js +4 -0
- package/dist/mcp/tool-definitions.js +5 -0
- package/dist/orchestrator/report.js +6 -0
- package/dist/orchestrator.js +12 -4
- package/dist/remote-source.js +444 -0
- package/dist/term.js +54 -8
- package/dist/version.js +1 -1
- package/docs/agent-delegation-drive.7.md +2 -0
- package/docs/cli-mcp-parity.7.md +7 -2
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +2 -0
- package/docs/durable-state-and-locking.7.md +2 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
- package/docs/multi-agent-eval-replay-harness.7.md +2 -0
- package/docs/multi-agent-operator-ux.7.md +2 -0
- package/docs/node-snapshot-diff-replay.7.md +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/project-index.md +13 -4
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +2 -0
- package/docs/release-tooling.7.md +2 -0
- package/docs/remote-source-review.7.md +88 -0
- package/docs/run-registry-control-plane.7.md +2 -0
- package/docs/run-retention-reclamation.7.md +2 -0
- package/docs/state-explosion-management.7.md +2 -0
- package/docs/team-collaboration.7.md +2 -0
- package/docs/web-desktop-workbench.7.md +2 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +1 -1
- package/package.json +1 -1
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- 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.
|
|
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.
|
|
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
|
-
|
|
56
|
+
Review a project **from any directory** — no `cd` needed — by 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
6
|
+
"version": "0.1.90",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
package/dist/capability-core.js
CHANGED
|
@@ -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
|
|
553
|
-
//
|
|
554
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
(
|
|
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
|
-
(
|
|
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
|
-
|
|
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
|
+
}
|