cool-workflow 0.1.89 → 0.1.91
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 +42 -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 +151 -9
- package/dist/capability-registry.js +6 -0
- package/dist/cli/command-surface.js +143 -6
- package/dist/cli.js +26 -1
- package/dist/clones.js +162 -0
- package/dist/drive.js +37 -2
- 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 +15 -4
- package/dist/remote-source.js +444 -0
- package/dist/reporter.js +67 -0
- package/dist/term.js +127 -9
- package/dist/version.js +1 -1
- package/docs/agent-delegation-drive.7.md +41 -22
- package/docs/cli-mcp-parity.7.md +9 -2
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
- package/docs/multi-agent-eval-replay-harness.7.md +4 -0
- package/docs/multi-agent-operator-ux.7.md +4 -0
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/project-index.md +15 -4
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +4 -0
- package/docs/remote-source-review.7.md +88 -0
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +4 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +1 -1
- package/package.json +1 -1
- package/scripts/agents/agent-adapter-core.js +137 -3
- package/scripts/agents/claude-p-agent.js +24 -15
- package/scripts/agents/codex-agent.js +11 -8
- package/scripts/agents/gemini-agent.js +11 -8
- package/scripts/agents/opencode-agent.js +11 -8
- package/scripts/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.91",
|
|
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.91",
|
|
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,7 @@ 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
|
|
515
|
+
|
|
516
|
+
v0.1.91
|
|
@@ -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.91",
|
|
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.91",
|
|
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.91",
|
|
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.91",
|
|
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.91",
|
|
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;
|
|
@@ -56,6 +56,7 @@ exports.schedPolicyShow = schedPolicyShow;
|
|
|
56
56
|
exports.schedPolicySet = schedPolicySet;
|
|
57
57
|
exports.runDrivePreview = runDrivePreview;
|
|
58
58
|
exports.runDrive = runDrive;
|
|
59
|
+
exports.collectRunFindings = collectRunFindings;
|
|
59
60
|
exports.quickstart = quickstart;
|
|
60
61
|
exports.backendAgentConfigShow = backendAgentConfigShow;
|
|
61
62
|
exports.backendAgentConfigSet = backendAgentConfigSet;
|
|
@@ -79,9 +80,11 @@ const observability_1 = require("./observability");
|
|
|
79
80
|
const telemetry_ledger_1 = require("./telemetry-ledger");
|
|
80
81
|
const telemetry_attestation_1 = require("./telemetry-attestation");
|
|
81
82
|
const trust_audit_1 = require("./trust-audit");
|
|
83
|
+
const remote_source_1 = require("./remote-source");
|
|
82
84
|
const telemetry_demo_1 = require("./telemetry-demo");
|
|
83
85
|
const state_1 = require("./state");
|
|
84
86
|
const run_export_1 = require("./run-export");
|
|
87
|
+
const result_normalize_1 = require("./result-normalize");
|
|
85
88
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
86
89
|
const node_path_1 = __importDefault(require("node:path"));
|
|
87
90
|
const scheduling_1 = require("./scheduling");
|
|
@@ -497,7 +500,13 @@ const DRIVE_RUNTIME_KEYS = [
|
|
|
497
500
|
"agentTimeoutMs",
|
|
498
501
|
"agent-timeout-ms",
|
|
499
502
|
"resume",
|
|
500
|
-
"incremental"
|
|
503
|
+
"incremental",
|
|
504
|
+
// Remote-source flags (v0.1.91): materialized into a local checkout in the capability
|
|
505
|
+
// layer, never passed to plan as inputs (the resolved sourceUrl/sourceCommit ARE inputs).
|
|
506
|
+
"link",
|
|
507
|
+
"ref",
|
|
508
|
+
"branch",
|
|
509
|
+
"refresh"
|
|
501
510
|
];
|
|
502
511
|
function planInputsFor(args) {
|
|
503
512
|
const copy = withoutRuntimeKeys(args);
|
|
@@ -546,18 +555,72 @@ exports.QUICKSTART_DEFAULT_APP = "architecture-review";
|
|
|
546
555
|
* RED LINE (DIRECTION.md): worker execution still DELEGATES to the operator's own
|
|
547
556
|
* agent backend (claude -p / codex exec / HTTP endpoint). With no agent configured
|
|
548
557
|
* the drive fails closed (status=blocked) and we never fabricate a completion. */
|
|
558
|
+
/** Collect a COMPACT findings list for the end-of-run summary by re-parsing each completed
|
|
559
|
+
* worker's `result.md` `cw:result` block (the schema source of truth — `normalizeResultEnvelope`).
|
|
560
|
+
* Stderr/human-side ONLY: this NEVER touches the byte-exact stdout payload, so it cannot perturb
|
|
561
|
+
* `--json` or the `cw:result` fence. `baseDir` anchors to the run's OWN repo (quickstart may run
|
|
562
|
+
* cross-directory). Best-effort: a missing/garbled result.md or an unloadable run is skipped, not
|
|
563
|
+
* fatal — the full prose still lives in report.md + each worker transcript. */
|
|
564
|
+
function collectRunFindings(runner, runId, baseDir) {
|
|
565
|
+
const rows = [];
|
|
566
|
+
try {
|
|
567
|
+
const run = runner.withBaseDir(baseDir).loadRun(runId);
|
|
568
|
+
for (const task of run.tasks) {
|
|
569
|
+
if (task.status !== "completed" || !task.resultPath || !node_fs_1.default.existsSync(task.resultPath))
|
|
570
|
+
continue;
|
|
571
|
+
try {
|
|
572
|
+
const envelope = (0, result_normalize_1.normalizeResultEnvelope)(node_fs_1.default.readFileSync(task.resultPath, "utf8"));
|
|
573
|
+
for (const f of envelope.findings) {
|
|
574
|
+
rows.push({ id: f.id, severity: f.severity || "none", classification: f.classification || "unknown" });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
/* skip a garbled result.md — the transcript still holds the full record */
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
/* run not loadable on this host — the summary degrades to no findings table */
|
|
584
|
+
}
|
|
585
|
+
return rows;
|
|
586
|
+
}
|
|
549
587
|
function quickstart(runner, args) {
|
|
550
588
|
const appId = String(args.appId || args.app || args.workflowId || exports.QUICKSTART_DEFAULT_APP);
|
|
589
|
+
// Remote source (v0.1.91): a `--link <url>` — or a URL passed to `--repo`/`-dir` — is
|
|
590
|
+
// materialized to a LOCAL checkout in the capability layer (below). Cloning is
|
|
591
|
+
// non-deterministic network I/O and must never enter the replay-deterministic core, so we
|
|
592
|
+
// rewrite `args.repo` to the local path here; everything downstream is a normal local run.
|
|
593
|
+
const linkArg = optionalString(args.link);
|
|
594
|
+
const repoArgRaw = optionalString(args.repo);
|
|
595
|
+
const remoteCandidate = linkArg || (repoArgRaw && (0, remote_source_1.isRemoteUrl)(repoArgRaw) ? repoArgRaw : undefined);
|
|
551
596
|
// 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)) {
|
|
597
|
+
// --repo/--cwd/--link is given. A remote candidate is materialized below, so it must NOT
|
|
598
|
+
// fall through to the cwd default here.
|
|
599
|
+
if (!remoteCandidate && !optionalString(args.repo) && !optionalString(args.cwd)) {
|
|
556
600
|
args.repo = invocationCwd(args);
|
|
557
601
|
}
|
|
558
602
|
const agentConfigured = Boolean((0, agent_config_1.resolveAgentConfig)(args).command || (0, agent_config_1.resolveAgentConfig)(args).endpoint);
|
|
559
|
-
if (isTrue(args.check))
|
|
560
|
-
return
|
|
603
|
+
if (isTrue(args.check)) {
|
|
604
|
+
return remoteCandidate
|
|
605
|
+
? remoteQuickstartCheck(runner, appId, args, agentConfigured, remoteCandidate)
|
|
606
|
+
: quickstartCheck(runner, appId, args, agentConfigured);
|
|
607
|
+
}
|
|
608
|
+
// Materialize the remote NOW — after `--check` (which never fetches) and before any
|
|
609
|
+
// plan/preview/drive — so the orchestrator only ever sees the local checkout. Fails closed:
|
|
610
|
+
// a bad URL / blocked scheme / network / auth failure throws here, before any run is planned.
|
|
611
|
+
let remoteSource;
|
|
612
|
+
if (remoteCandidate) {
|
|
613
|
+
remoteSource = (0, remote_source_1.materializeRemote)(remoteCandidate, {
|
|
614
|
+
ref: optionalString(args.ref || args.branch),
|
|
615
|
+
refresh: isTrue(args.refresh)
|
|
616
|
+
});
|
|
617
|
+
args.repo = remoteSource.localPath;
|
|
618
|
+
// Record the origin as plan INPUTS so it rides into run.inputs → the report header.
|
|
619
|
+
args.sourceUrl = remoteSource.url;
|
|
620
|
+
args.sourceCommit = remoteSource.commit;
|
|
621
|
+
if (remoteSource.ref)
|
|
622
|
+
args.sourceRef = remoteSource.ref;
|
|
623
|
+
}
|
|
561
624
|
// `--resume`: a discoverability flag over the existing continuation. With no
|
|
562
625
|
// `--run`, advance exactly ONE step (reuse the `--once` path) and print a
|
|
563
626
|
// copy-pasteable continue line; with `--run <id>`, continue that run to
|
|
@@ -596,6 +659,25 @@ function quickstart(runner, args) {
|
|
|
596
659
|
const runRepoCwd = node_path_1.default.resolve(node_path_1.default.dirname(result.statePath), "..", "..", "..");
|
|
597
660
|
const reportTarget = node_fs_1.default.existsSync(runRepoCwd) ? runRepoCwd : undefined;
|
|
598
661
|
const reportPath = runner.withBaseDir(reportTarget).report(result.runId).path;
|
|
662
|
+
// Tamper-evident provenance: bind the remote origin (url@sha) into the run's hash-chained
|
|
663
|
+
// trust-audit log so `cw audit verify` re-proves where the code came from. metadata is
|
|
664
|
+
// auto-scrubbed of credentials by recordTrustAuditEvent → scrubMetadata. Best-effort: the
|
|
665
|
+
// origin is already in run.inputs/report/the result, so a recording hiccup never fails the
|
|
666
|
+
// review (additive trust evidence, not a gate).
|
|
667
|
+
if (remoteSource) {
|
|
668
|
+
try {
|
|
669
|
+
const provRun = runner.withBaseDir(reportTarget).loadRun(result.runId);
|
|
670
|
+
(0, trust_audit_1.recordTrustAuditEvent)(provRun, {
|
|
671
|
+
kind: remoteSource.kind === "archive" ? "source.download" : "source.clone",
|
|
672
|
+
decision: "recorded",
|
|
673
|
+
source: "operator-recorded",
|
|
674
|
+
metadata: { url: remoteSource.url, commit: remoteSource.commit, ref: remoteSource.ref || null, kind: remoteSource.kind, depth: 1 }
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
/* provenance is additive; never fail a completed review over an audit-log hiccup */
|
|
679
|
+
}
|
|
680
|
+
}
|
|
599
681
|
// --bundle: after a COMPLETE drive, seal the run into a portable, self-verified
|
|
600
682
|
// bundle so the one command yields a client-verifiable artifact. Pure composition
|
|
601
683
|
// of reportBundle() (export sealed + offline self-verify); spawns nothing. Gated on
|
|
@@ -665,7 +747,12 @@ function quickstart(runner, args) {
|
|
|
665
747
|
...(resumeRunId ? { resumedFrom: resumeRunId } : {}),
|
|
666
748
|
// Same conditional-spread discipline: `bundle` is present only when --bundle ran
|
|
667
749
|
// on a completed drive, so the default (no --bundle) payload is byte-identical.
|
|
668
|
-
...(bundle ? { bundle } : {})
|
|
750
|
+
...(bundle ? { bundle } : {}),
|
|
751
|
+
// `remote` is present only when the review targeted a --link/URL source, so a local-repo
|
|
752
|
+
// run stays byte-identical. Carries the sanitized origin + resolved commit for provenance.
|
|
753
|
+
...(remoteSource
|
|
754
|
+
? { remote: { url: remoteSource.url, commit: remoteSource.commit, kind: remoteSource.kind, cached: remoteSource.cached, ...(remoteSource.ref ? { ref: remoteSource.ref } : {}) } }
|
|
755
|
+
: {})
|
|
669
756
|
};
|
|
670
757
|
}
|
|
671
758
|
function quickstartCheck(runner, appId, args, agentConfigured) {
|
|
@@ -772,6 +859,55 @@ function quickstartCheck(runner, appId, args, agentConfigured) {
|
|
|
772
859
|
nextCommand: quickstartNextCommand(appId, repo, args)
|
|
773
860
|
};
|
|
774
861
|
}
|
|
862
|
+
/** Preflight for a `--link`/URL review: validate the URL SHAPE + tooling WITHOUT fetching
|
|
863
|
+
* (a clone is heavy and side-effecting; --check stays read-only). Mirrors quickstartCheck's
|
|
864
|
+
* app/question/agent sub-checks but swaps the local-repo readability checks for link+tooling.
|
|
865
|
+
* `repo` carries the sanitized URL so the result reports what would be reviewed. */
|
|
866
|
+
function remoteQuickstartCheck(runner, appId, args, agentConfigured, candidate) {
|
|
867
|
+
const validation = (0, remote_source_1.validateRemoteUrl)(candidate);
|
|
868
|
+
const checks = [];
|
|
869
|
+
try {
|
|
870
|
+
runner.showApp(appId);
|
|
871
|
+
checks.push({ name: "app", status: "ok", detail: `Workflow app ${appId} is available.` });
|
|
872
|
+
}
|
|
873
|
+
catch (error) {
|
|
874
|
+
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." });
|
|
875
|
+
}
|
|
876
|
+
if (validation.ok) {
|
|
877
|
+
checks.push({ name: "link", status: "ok", detail: `Remote source is a valid ${validation.kind} URL (${validation.url}).` });
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
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)." });
|
|
881
|
+
}
|
|
882
|
+
if ((0, remote_source_1.gitAvailable)()) {
|
|
883
|
+
checks.push({ name: "tooling", status: "ok", detail: "git is available to clone the remote." });
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
checks.push({ name: "tooling", status: "blocked", detail: "git was not found on PATH.", fix: "Install git, then re-run." });
|
|
887
|
+
}
|
|
888
|
+
if (optionalString(args.question)) {
|
|
889
|
+
checks.push({ name: "question", status: "ok", detail: "Question is set." });
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
checks.push({ name: "question", status: "blocked", detail: "Question is missing.", fix: "Pass --question TEXT." });
|
|
893
|
+
}
|
|
894
|
+
if (agentConfigured) {
|
|
895
|
+
checks.push({ name: "agent", status: "ok", detail: "Agent backend is configured." });
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
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." });
|
|
899
|
+
}
|
|
900
|
+
const ok = checks.every((check) => check.status !== "blocked");
|
|
901
|
+
return {
|
|
902
|
+
schemaVersion: 1,
|
|
903
|
+
mode: "check",
|
|
904
|
+
ok,
|
|
905
|
+
appId,
|
|
906
|
+
repo: validation.url,
|
|
907
|
+
checks,
|
|
908
|
+
nextCommand: `cw quickstart ${shellWord(appId)} --link ${shellWord(validation.url)}${optionalString(args.question) ? ` --question ${shellWord(String(args.question))}` : ""}`
|
|
909
|
+
};
|
|
910
|
+
}
|
|
775
911
|
function quickstartNextCommand(appId, repo, args) {
|
|
776
912
|
const parts = ["cw", "quickstart", shellWord(appId), "--repo", shellWord(repo)];
|
|
777
913
|
const question = optionalString(args.question);
|
|
@@ -845,6 +981,12 @@ function gcRun(reg, runId, args) {
|
|
|
845
981
|
function gcVerify(reg, runId, args) {
|
|
846
982
|
return reg.gcVerify(runId, { scope: scopeOf(args, "home") });
|
|
847
983
|
}
|
|
984
|
+
// Remote-source clone cache (v0.1.91): list/reclaim the `~/.local/state/cool-workflow/clones`
|
|
985
|
+
// checkouts that `--link`/URL reviews populate. Pure filesystem work; both CLI and MCP route
|
|
986
|
+
// here so `cw clones …` and `cw_clones_…` are byte-identical.
|
|
987
|
+
var clones_1 = require("./clones");
|
|
988
|
+
Object.defineProperty(exports, "listClones", { enumerable: true, get: function () { return clones_1.listClones; } });
|
|
989
|
+
Object.defineProperty(exports, "gcClones", { enumerable: true, get: function () { return clones_1.gcClones; } });
|
|
848
990
|
function runHistory(reg, args) {
|
|
849
991
|
return reg.history({
|
|
850
992
|
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",
|
|
@@ -58,7 +58,7 @@ const state_explosion_1 = require("../state-explosion");
|
|
|
58
58
|
const evidence_reasoning_1 = require("../evidence-reasoning");
|
|
59
59
|
const doctor_1 = require("../doctor");
|
|
60
60
|
const orchestrator_2 = require("../orchestrator");
|
|
61
|
-
const
|
|
61
|
+
const reporter_1 = require("../reporter");
|
|
62
62
|
const version_1 = require("../version");
|
|
63
63
|
async function runCli(argv = process.argv.slice(2)) {
|
|
64
64
|
const args = (0, orchestrator_1.parseArgv)(argv);
|
|
@@ -80,6 +80,22 @@ 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;
|
|
87
|
+
// Presentation flags — set BEFORE any drive spawn so the out-of-process agent wrapper
|
|
88
|
+
// inherits them via process.env (presentation-only; stdout/the cw:result fence are untouched):
|
|
89
|
+
// --verbose full agent narration inline (default is compact: current action + summary)
|
|
90
|
+
// --no-color disable ANSI everywhere (CW_NO_COLOR is honored by term.colorEnabled AND the
|
|
91
|
+
// wrapper); complements NO_COLOR/FORCE_COLOR
|
|
92
|
+
// --full also stream full narration AND print the report inline at run end
|
|
93
|
+
if (args.options.verbose)
|
|
94
|
+
process.env.CW_VERBOSE = "1";
|
|
95
|
+
if (args.options["no-color"])
|
|
96
|
+
process.env.CW_NO_COLOR = "1";
|
|
97
|
+
if (args.options.full)
|
|
98
|
+
process.env.CW_OUTPUT = "full";
|
|
83
99
|
// Bare -q / --question -> redirect to quickstart (auto-detect repo/agent/app).
|
|
84
100
|
// CONSUME the positional (shift) so the question never survives as positionals[0]
|
|
85
101
|
// — otherwise the quickstart handler reads it as the app id ("Workflow app not found").
|
|
@@ -236,12 +252,20 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
236
252
|
const qs = (0, capability_core_1.quickstart)(runner, { ...args.options, ...(appId ? { appId } : {}), ...(runId ? { runId } : {}) });
|
|
237
253
|
printJson(qs);
|
|
238
254
|
const qr = qs;
|
|
239
|
-
|
|
240
|
-
|
|
255
|
+
// Clean human summary on stderr (TTY-gated, inside the reporter). Suppressed under --json so
|
|
256
|
+
// machine mode emits ONLY the stdout payload — no stderr chrome to parse around. The type
|
|
257
|
+
// guard also skips --check/--preview results (no reportPath of their own). The summary is the
|
|
258
|
+
// COMPACT findings table (re-parsed from each completed worker's cw:result), the report path,
|
|
259
|
+
// and where the per-worker transcripts live — NOT the full prose (that's report.md/--full).
|
|
260
|
+
if (!wantsJson(args.options) && typeof qr.runId === "string" && typeof qr.reportPath === "string") {
|
|
261
|
+
emitRunSummary(runner, args.options, {
|
|
241
262
|
runId: qr.runId,
|
|
242
263
|
reportPath: qr.reportPath,
|
|
243
264
|
status: String(qr.status || ""),
|
|
244
|
-
|
|
265
|
+
statePath: typeof qr.statePath === "string" ? qr.statePath : undefined,
|
|
266
|
+
completedWorkers: typeof qr.completedWorkers === "number" ? qr.completedWorkers : undefined,
|
|
267
|
+
plannedWorkers: typeof qr.plannedWorkers === "number" ? qr.plannedWorkers : undefined,
|
|
268
|
+
agentConfigured: typeof qr.agentConfigured === "boolean" ? qr.agentConfigured : undefined
|
|
245
269
|
});
|
|
246
270
|
}
|
|
247
271
|
if (qs.mode === "check" && qs.ok === false) {
|
|
@@ -1207,7 +1231,17 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
1207
1231
|
driveArgs.appId = target;
|
|
1208
1232
|
const dr = (0, capability_core_1.runDrive)(runner, driveArgs);
|
|
1209
1233
|
printJson(dr);
|
|
1210
|
-
(
|
|
1234
|
+
if (!wantsJson(args.options)) {
|
|
1235
|
+
emitRunSummary(runner, args.options, {
|
|
1236
|
+
runId: dr.runId,
|
|
1237
|
+
reportPath: dr.reportPath,
|
|
1238
|
+
status: dr.status,
|
|
1239
|
+
statePath: dr.statePath,
|
|
1240
|
+
completedWorkers: dr.completedWorkers,
|
|
1241
|
+
plannedWorkers: dr.plannedWorkers,
|
|
1242
|
+
agentConfigured: dr.agentConfigured
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1211
1245
|
return;
|
|
1212
1246
|
}
|
|
1213
1247
|
const registry = (0, capability_core_1.runRegistryFor)(args.options, runner);
|
|
@@ -1221,7 +1255,17 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
1221
1255
|
driveArgs.runId = id;
|
|
1222
1256
|
const dr = (0, capability_core_1.runDrive)(runner, driveArgs);
|
|
1223
1257
|
printJson(dr);
|
|
1224
|
-
(
|
|
1258
|
+
if (!wantsJson(args.options)) {
|
|
1259
|
+
emitRunSummary(runner, args.options, {
|
|
1260
|
+
runId: dr.runId,
|
|
1261
|
+
reportPath: dr.reportPath,
|
|
1262
|
+
status: dr.status,
|
|
1263
|
+
statePath: dr.statePath,
|
|
1264
|
+
completedWorkers: dr.completedWorkers,
|
|
1265
|
+
plannedWorkers: dr.plannedWorkers,
|
|
1266
|
+
agentConfigured: dr.agentConfigured
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1225
1269
|
return;
|
|
1226
1270
|
}
|
|
1227
1271
|
printJson((0, capability_core_1.runDrivePreview)(runner, { ...args.options, runId: required(id, "run id") }));
|
|
@@ -1354,6 +1398,32 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
1354
1398
|
throw new Error("Usage: cw.js sched plan|lease|release|complete|reclaim|reset|policy [show|set] [id] [--maxConcurrent N --maxAttempts N ...]");
|
|
1355
1399
|
}
|
|
1356
1400
|
}
|
|
1401
|
+
case "clones": {
|
|
1402
|
+
// Remote-source clone cache (v0.1.91): `list` inspects the ~/.local/state/cool-workflow
|
|
1403
|
+
// /clones checkouts that `--link`/URL reviews populate; `gc` reclaims them (a TTL sweep,
|
|
1404
|
+
// or --all). Pure filesystem work — no network, no run registry.
|
|
1405
|
+
const [subcommand] = args.positionals;
|
|
1406
|
+
switch (subcommand) {
|
|
1407
|
+
case "list": {
|
|
1408
|
+
const result = (0, capability_core_1.listClones)(args.options);
|
|
1409
|
+
if (wantsJson(args.options))
|
|
1410
|
+
printJson(result);
|
|
1411
|
+
else
|
|
1412
|
+
process.stdout.write(`${formatClonesList(result)}\n`);
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
case "gc": {
|
|
1416
|
+
const result = (0, capability_core_1.gcClones)(args.options);
|
|
1417
|
+
if (wantsJson(args.options))
|
|
1418
|
+
printJson(result);
|
|
1419
|
+
else
|
|
1420
|
+
process.stdout.write(`${formatClonesGc(result)}\n`);
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
default:
|
|
1424
|
+
throw new Error("Usage: cw.js clones list [--json] | clones gc [--older-than-days N] [--all] [--json]");
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1357
1427
|
case "gc": {
|
|
1358
1428
|
// Run Retention & Provable Reclamation (v0.1.39). `plan` is a pure dry-run
|
|
1359
1429
|
// (frees nothing); `run` executes the write-ahead reclamation transaction;
|
|
@@ -1502,9 +1572,76 @@ function required(value, label) {
|
|
|
1502
1572
|
function optionalArg(value) {
|
|
1503
1573
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1504
1574
|
}
|
|
1575
|
+
/** Emit the calm end-of-run summary (stderr, TTY-gated inside the reporter): the COMPACT findings
|
|
1576
|
+
* table re-parsed from each completed worker's `cw:result`, the report path, where the per-worker
|
|
1577
|
+
* transcripts live, and — under `--full` — the report inline. Stderr/human-side ONLY: stdout (the
|
|
1578
|
+
* `--json` payload printed just before this) stays byte-exact. Shared by the quickstart and the
|
|
1579
|
+
* two `run --drive` paths so all three render an identical summary. */
|
|
1580
|
+
function emitRunSummary(runner, options, fields) {
|
|
1581
|
+
// Anchor run reads to the run's OWN repo (a drive/quickstart may run cross-directory): the run
|
|
1582
|
+
// dir is <repo>/.cw/runs/<id>/, holding each worker's transcript.md next to its result.md.
|
|
1583
|
+
const runDir = typeof fields.statePath === "string" ? node_path_1.default.dirname(fields.statePath) : undefined;
|
|
1584
|
+
const baseDir = runDir ? node_path_1.default.resolve(runDir, "..", "..", "..") : undefined;
|
|
1585
|
+
const findings = (0, capability_core_1.collectRunFindings)(runner, fields.runId, baseDir);
|
|
1586
|
+
// --full ALSO prints the report inline at run end (the compact table stays the default summary).
|
|
1587
|
+
let fullReport;
|
|
1588
|
+
if (options.full && fields.reportPath && node_fs_1.default.existsSync(fields.reportPath)) {
|
|
1589
|
+
try {
|
|
1590
|
+
fullReport = node_fs_1.default.readFileSync(fields.reportPath, "utf8");
|
|
1591
|
+
}
|
|
1592
|
+
catch { /* best-effort inline */ }
|
|
1593
|
+
}
|
|
1594
|
+
reporter_1.reporter.runSummary({
|
|
1595
|
+
runId: fields.runId,
|
|
1596
|
+
reportPath: fields.reportPath,
|
|
1597
|
+
status: fields.status,
|
|
1598
|
+
completedWorkers: fields.completedWorkers,
|
|
1599
|
+
plannedWorkers: fields.plannedWorkers,
|
|
1600
|
+
agentConfigured: fields.agentConfigured,
|
|
1601
|
+
findings,
|
|
1602
|
+
runDir,
|
|
1603
|
+
fullReport
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1505
1606
|
function printJson(value) {
|
|
1506
1607
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
1507
1608
|
}
|
|
1609
|
+
function humanBytes(n) {
|
|
1610
|
+
if (n < 1024)
|
|
1611
|
+
return `${n}B`;
|
|
1612
|
+
const units = ["KiB", "MiB", "GiB"];
|
|
1613
|
+
let v = n / 1024;
|
|
1614
|
+
let i = 0;
|
|
1615
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
1616
|
+
v /= 1024;
|
|
1617
|
+
i += 1;
|
|
1618
|
+
}
|
|
1619
|
+
return `${v.toFixed(1)}${units[i]}`;
|
|
1620
|
+
}
|
|
1621
|
+
function formatClonesList(result) {
|
|
1622
|
+
if (result.count === 0)
|
|
1623
|
+
return `No cached remote checkouts in ${result.clonesDir}.`;
|
|
1624
|
+
const rows = result.entries.map((e) => {
|
|
1625
|
+
const when = e.fetchedAt ? e.fetchedAt.replace("T", " ").replace(/\..*$/, "Z") : "unknown";
|
|
1626
|
+
return ` ${e.kind.padEnd(7)} ${humanBytes(e.bytes).padStart(8)} ${when} ${e.url}${e.ref ? `@${e.ref}` : ""}`;
|
|
1627
|
+
});
|
|
1628
|
+
return [
|
|
1629
|
+
`${result.count} cached checkout${result.count === 1 ? "" : "s"} — ${humanBytes(result.totalBytes)} in ${result.clonesDir}`,
|
|
1630
|
+
" KIND SIZE FETCHED SOURCE",
|
|
1631
|
+
...rows,
|
|
1632
|
+
`\nReclaim with: cw clones gc --older-than-days 30 (or --all)`
|
|
1633
|
+
].join("\n");
|
|
1634
|
+
}
|
|
1635
|
+
function formatClonesGc(result) {
|
|
1636
|
+
const scope = result.all ? "all entries" : `entries older than ${result.olderThanDays} day(s)`;
|
|
1637
|
+
if (result.removed.length === 0)
|
|
1638
|
+
return `Nothing to reclaim (${scope}); ${result.keptCount} kept in ${result.clonesDir}.`;
|
|
1639
|
+
const rows = result.removed.map((r) => ` ${humanBytes(r.bytes).padStart(8)} ${r.url}`);
|
|
1640
|
+
return [
|
|
1641
|
+
`Reclaimed ${result.removed.length} checkout${result.removed.length === 1 ? "" : "s"} (${scope}) — freed ${humanBytes(result.freedBytes)}; ${result.keptCount} kept`,
|
|
1642
|
+
...rows
|
|
1643
|
+
].join("\n");
|
|
1644
|
+
}
|
|
1508
1645
|
function wantsJson(options) {
|
|
1509
1646
|
return Boolean(options.json || options.format === "json");
|
|
1510
1647
|
}
|
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
|
+
}
|