🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

cool-workflow

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cool-workflow - npm Package Compare versions

Comparing version
0.1.96
to
0.1.97
+80
docs/demo.7.md
# DEMO(7)
## NAME
`cw demo` — prove CW trust guarantees with one command
## SYNOPSIS
```text
node dist/cli.js demo tamper [--json]
node dist/cli.js demo bundle [--json]
```
## DESCRIPTION
`cw demo` is a self-contained proof of CW's central trust claims. It works
without an agent and without a network connection. Every run is hermetic
(fully self-contained) — it builds its own state, tampers with it in known
ways, and checks that the tampering is caught. Nothing is read from or
written to the real file system outside a short-term temp directory.
No agent is needed; both demos work when the setup has no agent at all.
## DEMO TAMPER
`cw demo tamper` proves that CW catches forged records offline — with only a
public key, no server. It:
1. Builds a signed telemetry ledger with three hops.
2. Tampers with it in three layers:
- **Hashes**: Changes a record's data and recomputes the record hash to hide
it. The hash chain breaks — the next record's `previousHash` does not
match, so the chain is no longer valid.
- **Signatures**: Inflates token counts and keeps the old signature. The
signature does not match the new data — the verifier catches it.
- **Findings**: Edits a signed finding (severity HIGH → LOW) after it was
signed by the agent. The signature check on the ed25519 envelope fails
because the signed bytes changed.
3. Verifies each tampered ledger with only the public key.
If all three forgeries are caught, the proof holds and the demo exits 0.
If any tamper goes undetected, the demo exits 1 — this is a regression in
the integrity guarantee.
## DEMO BUNDLE
`cw demo bundle` proves that exported report bundles are verifiable offline. It:
1. Builds a full telemetry chain, signs it, and exports a sealed portable
bundle (archive bytes + telemetry chain + trust-audit chain + embedded
public key).
2. Tampers with the bundle in two ways:
- **Telemetry chain**: Forges a record in the chain. The archive's file
digests stay valid (the archive was built from the tampered bytes), but
`report verify-bundle` re-checks the chain and catches it.
- **Signature + usage**: Inflates token counts and reseals. The signature
check and hash chain both break.
3. Verifies each tampered bundle with `report verify-bundle`.
If all forgeries are caught with only the bundle's own public key, the proof
holds. No repo, no server, no key handed over.
## EXIT CODES
| Exit | Meaning |
| --- | --- |
| 0 | All tampering was caught — trust guarantees hold |
| 1 | A tamper went undetected — integrity guarantee regression |
## FILES
```text
src/telemetry-demo.ts
```
## SEE ALSO
report-verifiable-bundle.7.md — offline bundle verification in detail
trust-model.md — the trust model and its limits
security-trust-hardening.7.md — security and trust hardening
# DOCTOR(7)
## NAME
`cw doctor` — check the setup and name all problems with their fixes
## SYNOPSIS
```text
node dist/cli.js doctor
node dist/cli.js doctor --json
node dist/cli.js doctor --fix
node dist/cli.js doctor --onramp
node dist/cli.js doctor --onramp --changed-from origin/main
```
## DESCRIPTION
`cw doctor` is a read-only check of your CW setup, based on `brew doctor`. It
probes your machine and says what is wrong and what to do about it — before a
run fails with a strange error.
The command never makes any file; it only reads. Running it changes nothing on
disk.
It gives back a report with one line for every check. Each check has a status
(`ok`, `warn`, or `fail`) and a clear note. Checks that are not `ok` carry a
`fix` line with the right command or step to put things right.
If any check has status `fail`, the command exits with code 1 (non-zero). A
`warn` (for example, no agent yet — demos and previews still work) does not
make the exit fail.
## CHECKS
The command runs six checks in order:
**node**
: The Node.js version. CW needs v18 or higher. A `fail` here stops everything.
**agent**
: The AI agent backend. CW can auto-detect agents (Claude, Codex, Gemini,
OpenCode) or take one from `CW_AGENT_COMMAND` / `--agent-command`. Without one,
real runs report `status: blocked`, but `demo` and `--preview` still work.
**agent-binary**
: When the agent is set by a command name (not auto or HTTP), this check sees if
the binary is on `$PATH`. Missing here gives a `warn` — the run will get a clear
error later, but CW will not guess at a different agent.
**git**
: The `git` command. CW uses it for commit place of origin. A `warn` here means
commit roots will be recorded as absent; no other part of a run needs git.
**home-registry**
: The cross-repo run index at `$CW_HOME` (default `$HOME/.local/state/cool-workflow`).
This location must be writable. A `fail` here blocks discovery across repos.
**repo-state**
: The per-repo run store under `<cwd>/.cw`. Must be writable. A `warn` here
means runs stay in-memory only — you can use `--cwd PATH` to point at another
writable root.
## OPTIONS
`--json`
: Give back the full report as a stable JSON object. Good for scripts.
`--fix`
: Give back only the fix commands for every non-ok check. Same as running `cw fix`
by itself.
`--onramp`
: Add a quick-start guide to the human output, with recommended checks and a
three-step path to your first report.
`--changed-from <ref>`
: When used with `--onramp`, make the quick-start checks cover only files changed
since `<ref>` (a Git branch, tag, or commit). Good for CI and code reading.
## FILES
```text
src/doctor.ts
dist/doctor.js
```
## EXIT CODES
| Exit | Meaning |
| --- | --- |
| 0 | All checks ok (may have warnings) |
| 1 | One or more checks have status `fail` |
## SEE ALSO
cw fix — the same checks, but gives back only the fix commands
# FIX(7)
## NAME
`cw fix` — give back the fix commands for all setup problems
## SYNOPSIS
```text
node dist/cli.js fix
node dist/cli.js fix --json
```
## DESCRIPTION
`cw fix` runs the same setup checks as `cw doctor`, but gives back only the
fix commands — one numbered step for every check that has a problem. No
running check detail, no status glyphs; just the directions you need to put
things right.
When the output is empty ("No fixes needed."), the setup is clean and nothing
needs doing.
Like `cw doctor`, the command only reads — it never makes a file or does a
fix on its own. You are meant to run the fix commands yourself.
If any check has status `fail`, the command exits with code 1.
## OPTIONS
`--json`
: Give back the full doctor report as a stable JSON object, with the same shape
as `cw doctor --json`. The `checks` array carries every fix string.
## EXIT CODES
| Exit | Meaning |
| --- | --- |
| 0 | No fixes needed — all checks ok or only warnings |
| 1 | One or more checks have status `fail` |
## SEE ALSO
cw doctor — the full setup check with detail for every check
# INIT(7)
## NAME
`cw init` — scaffold a new workflow definition from nothing
## SYNOPSIS
```text
node dist/cli.js init <workflow-id> [--title TITLE] [--output PATH] [--force]
```
## DESCRIPTION
`cw init` makes a new workflow definition file — a `.workflow.js` file filled
with a simple template. The template has a basic run shape: one step with a
sandbox profile, one evidence gate, and the hooks you need to add your own
steps.
This is how you start a new workflow app from zero. After `init`, you have a
real file you can edit to make your own run shape.
The workflow id you give is turned into a safe file name (spaces become dashes,
special signs are taken out). By default, the file is written to the current
working directory, but you can point it somewhere else with `--output`.
If a file of that name is already there, the command refuses to overwrite it
unless you pass `--force`.
## OPTIONS
`--title TITLE`
: A human name for the workflow. If not given, a title is made from the id.
`--output PATH`
: Where to write the workflow file. Default is `<id>.workflow.js` in the
current directory.
`--force`
: Overwrite an existing file. Without this flag, the command fails if the
file already exists.
## EXIT CODES
| Exit | Meaning |
| --- | --- |
| 0 | Workflow file written |
| 1 | Missing workflow id, invalid id, or file exists without `--force` |
## FILES
```text
src/orchestrator.ts (init method)
src/workflow-app-framework.ts (template renderer)
```
## SEE ALSO
cw list — see all workflow apps you have
cw info <id> — read the shape of a workflow app
workflow-app-framework.7.md — the full framework for writing workflow apps
pipeline-verbs.7.md — plan, dispatch, result (the pipeline engine)
# PIPELINE-VERBS(7)
## NAME
`cw plan`, `cw dispatch`, `cw result` — the three core pipeline engine verbs
## SYNOPSIS
```text
node dist/cli.js plan <workflow-id> [--question Q] [--repo PATH] [--sandbox PROFILE]
node dist/cli.js dispatch <run-id> [--sandbox PROFILE]
node dist/cli.js result <run-id> <task-id> <result-file>
```
## DESCRIPTION
These three verbs are the engine that drives every CW run. A run goes through
three stages: plan (get ready), dispatch (hand out work), and result (take work
back). Together they make the CW pipeline loop — a worker gets a task, does it,
and hands in a result file; CW checks the result and moves the run forward.
None of these verbs starts or stops the agent host. They give the control-plane
data that the host reads and acts on. The host keeps its own loop: call
`dispatch`, give the task to an agent, get back a result file, call `result`.
## PLAN
`cw plan <workflow-id>` makes a new run and gives back its canonical plan
summary in JSON. The plan has the run id, the first task (or tasks) to do,
the sandbox profile, and the state of the run.
The workflow id names a workflow app that gives the run its shape: inputs,
steps, evidence gates, and sandbox policy. Use `cw list` to see the workflow
apps you have.
The plan output is stable JSON, good for scripts and the agent host.
Options:
: `--question`, `--repo`, `--sandbox` — the same inputs the workflow app
expects. Different apps take different inputs; see `cw info <workflow-id>`
for the list.
## DISPATCH
`cw dispatch <run-id>` makes the next task ready for a worker. It gives back
a dispatch manifest in JSON: the task id, the prompt, the sandbox profile, and
the input and output paths the worker should use.
The dispatch picks the next runnable task in the pipeline. If no task is ready
— for example, all tasks are done or waiting on evidence — the dispatch payload
says so, and the host should wait or check the run status.
Options:
: `--sandbox PROFILE` — pick a sandbox profile for the worker. The default is
the one the workflow app asked for.
## RESULT
`cw result <run-id> <task-id> <result-file>` records a worker's result against
a task. The result file is a Markdown file the agent wrote — it must have a
`cw:result` JSON fence with the agent's `findings` and `evidence`.
CW accepts the result, checks it, and advances the run pipeline. If the result
is bad (missing, broken, or the evidence does not check out), CW rejects it and
gives back an error feedback record. The host can then try again or give the
task a different agent.
After `result`, the run may be done or have more tasks waiting. Check with
`cw status <run-id>` or `cw next <run-id>`.
## FILES
```text
.cw/runs/<run-id>/state.json
.cw/runs/<run-id>/dispatches/<dispatch-id>.json
.cw/runs/<run-id>/tasks/<task-id>.json
.cw/runs/<run-id>/results/<task-id>.md
.cw/runs/<run-id>/workers/<worker-id>/worker.json
```
## PIPELINE FLOW
```text
plan -> dispatch -> [agent does work] -> result -> [dispatch...] -> done
└─ rejected -> feedback -> retry
```
## SEE ALSO
cw init — make a new workflow definition from nothing
cw status — see the current state of a run
cw next — find the next action for a run
pipeline-runner.7.md — the full pipeline engine detail
# ROUTINE(7)
## NAME
`cw routine` — make and manage trigger-based workflow routines
## SYNOPSIS
```text
node dist/cli.js routine create --kind api|github --prompt PROMPT [--match JSON]
node dist/cli.js routine list [--kind KIND]
node dist/cli.js routine delete <trigger-id>
node dist/cli.js routine fire <kind> <payload-file>
node dist/cli.js routine events [<trigger-id>]
```
## DESCRIPTION
`cw routine` is the local trigger bridge for CW. It lets you make named
triggers that fire when something happens — an API event, a GitHub webhook, or
another outside signal. Each trigger carries a prompt template; when fired,
the prompt gets filled with the event data and handed to an agent host.
CW keeps routine data in:
```text
.cw/routines/triggers.json
.cw/routines/payloads/
```
CW itself does not run a web server or listen for webhooks. The routine bridge
is a local data store that can be joined to GitHub Actions, webhooks, cron, or
a small HTTP adapter.
## COMMANDS
**create**
: Make a new trigger. `--kind` is `api` or `github`. `--prompt` is the prompt
template the agent will see. `--match` is an optional JSON object that filters
events (for example, `{"action":"opened"}` for GitHub pull requests).
**list**
: List all triggers, or filter by kind with `--kind`.
**delete**
: Remove a trigger by its id.
**fire**
: Record an event against a trigger. Give the trigger kind and a path to a
JSON payload file. CW matches the payload against the trigger's match rules
and fills out the prompt.
**events**
: List the events that have been recorded for a trigger.
## FILES
```text
.cw/routines/triggers.json
.cw/routines/payloads/<event-id>.json
```
## EXIT CODES
| Exit | Meaning |
| --- | --- |
| 0 | Command done |
| 1 | Error (bad arguments, missing trigger, etc.) |
## SEE ALSO
cw sched — durable run-queue scheduling for workflow runs
control-plane-scheduling.7.md — the full scheduling and run management design
+1
-1
{
"name": "cool-workflow",
"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.",
"version": "0.1.96",
"version": "0.1.97",
"author": {

@@ -6,0 +6,0 @@ "name": "COOLWHITE LLC"

{
"name": "cool-workflow",
"version": "0.1.96",
"version": "0.1.97",
"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,0 +5,0 @@ "author": {

@@ -6,3 +6,3 @@ {

"summary": "Run a shorter architecture review with parallel map and assess phases for faster first results.",
"version": "0.1.96",
"version": "0.1.97",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -6,3 +6,3 @@ {

"summary": "Map a repository architecture, assess risks, verify important findings, and synthesize an evidence-backed verdict.",
"version": "0.1.96",
"version": "0.1.97",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -6,3 +6,3 @@ {

"summary": "Deterministic one-worker workflow app for proving the CW integration chain.",
"version": "0.1.96",
"version": "0.1.97",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -6,3 +6,3 @@ {

"summary": "Review a pull request or branch, inspect CI failures, diagnose actionable issues, optionally patch, verify, and summarize with evidence.",
"version": "0.1.96",
"version": "0.1.97",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -6,3 +6,3 @@ {

"summary": "Prepare a release with checklist discipline: version checks, changelog, tests, packaging, release notes, and final verification.",
"version": "0.1.96",
"version": "0.1.97",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -6,3 +6,3 @@ {

"summary": "Split a research question into claims, investigate sources, cross-check evidence, verify claims, and synthesize a concise answer.",
"version": "0.1.96",
"version": "0.1.97",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -118,3 +118,3 @@ "use strict";

// forged candidate.json must throw here rather than flow into the run.
const candidate = (0, validation_1.validateCandidateRecord)(JSON.parse(node_fs_1.default.readFileSync(file, "utf8")));
const candidate = (0, validation_1.validateCandidateRecord)((0, state_1.readJson)(file));
upsertCandidate(run, candidate);

@@ -541,3 +541,3 @@ return candidate;

// throws rather than entering the candidate set as a trusted cast.
.map((file) => (0, validation_1.validateCandidateRecord)(JSON.parse(node_fs_1.default.readFileSync(file, "utf8"))));
.map((file) => (0, validation_1.validateCandidateRecord)((0, state_1.readJson)(file)));
}

@@ -555,3 +555,3 @@ function readScores(run, candidateId) {

// throw, not silently widen the normalized/verdict surface the gate reads.
.map((file) => (0, validation_1.validateCandidateScore)(JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(dir, file), "utf8"))));
.map((file) => (0, validation_1.validateCandidateScore)((0, state_1.readJson)(node_path_1.default.join(dir, file))));
}

@@ -558,0 +558,0 @@ function candidateArtifacts(run, candidate) {

@@ -291,3 +291,8 @@ "use strict";

const trustPublicKey = optionalString(args["with-trust-key"] || args.withTrustKey || args.trustKey || args.pubkey) || process.env.CW_AGENT_ATTEST_PUBKEY;
return (0, run_export_1.exportRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId), node_path_1.default.resolve(base, output), { trustPublicKey });
const resolvedOutput = node_path_1.default.resolve(base, output);
const sysDirs = /^\/(etc|bin|sbin|usr|Library|System|Applications|boot|dev|proc|sys|root|var\/log|var\/run)\//;
if (sysDirs.test(resolvedOutput)) {
throw new Error(`Refusing to write archive to a system directory: ${output}`);
}
return (0, run_export_1.exportRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId), resolvedOutput, { trustPublicKey });
}

@@ -294,0 +299,0 @@ function runImportArchive(runner, args) {

@@ -80,3 +80,9 @@ "use strict";

const kind = (0, io_1.required)(idOrKind, "trigger kind");
const payload = payloadPath ? JSON.parse(node_fs_1.default.readFileSync(payloadPath, "utf8")) : args.options;
let payload;
try {
payload = payloadPath ? JSON.parse(node_fs_1.default.readFileSync(payloadPath, "utf8")) : args.options;
}
catch (e) {
throw new Error(`Failed to parse payload${payloadPath ? ` file "${payloadPath}"` : ""}: ${String(e && e.message || e)}`);
}
(0, io_1.printJson)(triggers.fire(kind, payload));

@@ -83,0 +89,0 @@ return;

@@ -478,2 +478,12 @@ "use strict";

if (job) {
const sandboxPolicy = manifest.sandboxPolicy;
if (sandboxPolicy) {
const filteredEnv = (0, execution_backend_1.buildChildEnv)(sandboxPolicy);
for (const key of Object.keys(process.env)) {
if (/^(CW_|ANTHROPIC_|OPENAI_|GEMINI_|DEEPSEEK_|CODEX_|GOOGLE_|COHERE_|MISTRAL_|OLLAMA_|AZURE_|AWS_)/i.test(key)) {
filteredEnv[key] = process.env[key];
}
}
job.env = filteredEnv;
}
jobs.push(job);

@@ -480,0 +490,0 @@ jobTaskIds.push(taskId);

@@ -42,2 +42,3 @@ "use strict";

exports.delegateChildScript = delegateChildScript;
exports.shouldStreamAgentStderr = shouldStreamAgentStderr;
exports.createExecutionBackend = createExecutionBackend;

@@ -414,3 +415,3 @@ exports.backendListPayload = backendListPayload;

const shellArg = [command, ...args].join(" ").replace(/\{\{[a-zA-Z0-9_.-]+\}\}/g, "");
if (/[;&|`$(){}<>!\n\r]/.test(shellArg)) {
if (/[;&|`$(){}<>!\n\r#*?~]/.test(shellArg)) {
throw new Error(`Shell backend refused: args contain shell control characters. ` +

@@ -680,2 +681,21 @@ `Use the node, bun, or agent backend instead for untrusted inputs.`);

// stateful runners below build the refusal/delegated envelopes and stay here.
/** Decide whether cw FORWARDS the agent wrapper's live stderr view (stdio
* "inherit") or captures it ("pipe"). Default follows isTTY — interactive shows
* the live view, a pipe/CI stays silent (Rule of Silence). The two env knobs are
* explicit opt-out/opt-in, honored regardless of isTTY so a piped/CI run can still
* opt into the wrapper's plain append-only trace, exactly as the man page promises
* (agent-delegation-drive.7.md "Live output"):
* CW_NO_STREAM=1 — master off (wins over everything)
* CW_AGENT_STREAM=0 — off
* CW_AGENT_STREAM=1 — on, even without a TTY
* With no env set this returns isTTY — byte-identical to the prior inline gate (POLA). */
function shouldStreamAgentStderr(env, isTTY) {
if (env.CW_NO_STREAM === "1")
return false;
if (env.CW_AGENT_STREAM === "0")
return false;
if (env.CW_AGENT_STREAM === "1")
return true;
return isTTY;
}
function runAgentProcess(descriptor, policy, request, label, handle, attestation) {

@@ -698,6 +718,6 @@ const resolved = (0, agent_1.resolveAgentInvocation)(request);

else {
// Live output on by default when stderr is a TTY. stdout is always
// captured as data. CI/pipes stay silent. CW_AGENT_STREAM=0 or
// CW_NO_STREAM=1 forces off; CW_AGENT_STREAM=1 forces on.
const streamStderr = process.env.CW_AGENT_STREAM !== "0" && Boolean(process.stderr.isTTY) && process.env.CW_NO_STREAM !== "1";
// Live output on by default when stderr is a TTY. stdout is always captured
// as data. CI/pipes stay silent unless CW_AGENT_STREAM=1 opts them into the
// wrapper's plain append-only trace; CW_AGENT_STREAM=0 / CW_NO_STREAM=1 force off.
const streamStderr = shouldStreamAgentStderr(process.env, Boolean(process.stderr.isTTY));
// Build child env from sandbox policy as baseline (respects env.inherit/expose/deny),

@@ -704,0 +724,0 @@ // then re-allow CW_* + well-known API key env vars the agent needs.

@@ -279,3 +279,3 @@ "use strict";

encoding: "utf8",
maxBuffer: 33 * 1024 * 1024 * jobs.length,
maxBuffer: Math.min(33 * 1024 * 1024 * jobs.length, 512 * 1024 * 1024),
timeout: maxTimeout + 30000

@@ -282,0 +282,0 @@ });

@@ -38,2 +38,6 @@ #!/usr/bin/env node

}
if (message === null || typeof message !== "object" || Array.isArray(message)) {
sendError(null, -32600, "Invalid Request: not a JSON-RPC object");
return;
}
try {

@@ -40,0 +44,0 @@ if (message.method === "initialize") {

@@ -389,2 +389,4 @@ "use strict";

function verifyRef(root, ref) {
if (ref.startsWith("-"))
throw new Error(`Invalid onramp base ref (must not start with '-'): ${ref}`);
const resolved = gitOne(root, ["rev-parse", "--verify", `${ref}^{commit}`]);

@@ -391,0 +393,0 @@ if (!resolved)

@@ -172,2 +172,8 @@ "use strict";

const destinationDir = resolveFromBase(String(options.directory || options.output || node_path_1.default.join(appsDir, id)));
// Reject writes to system-owned directories. The operator may provide any
// output path, but writing to /etc, /bin, /usr etc. is never valid.
const sysDirs = /^\/(etc|bin|sbin|usr|Library|System|Applications|boot|dev|proc|sys|root|var\/log|var\/run)\//;
if (sysDirs.test(node_path_1.default.resolve(destinationDir))) {
throw new Error(`Refusing to create app in a system directory: ${destinationDir}`);
}
const manifestPath = node_path_1.default.join(destinationDir, "app.json");

@@ -174,0 +180,0 @@ const entrypointPath = node_path_1.default.join(destinationDir, "workflow.js");

@@ -115,4 +115,10 @@ "use strict";

return raw;
if (typeof raw === "string")
return JSON.parse(raw);
if (typeof raw === "string") {
try {
return JSON.parse(raw);
}
catch {
throw new Error(`Invalid JSON in --metadata: ${String(raw).slice(0, 80)}`);
}
}
return undefined;

@@ -119,0 +125,0 @@ }

@@ -231,2 +231,5 @@ "use strict";

const absoluteResultPath = node_path_1.default.resolve(resultPath);
if (/^\/(etc|bin|sbin|usr|Library|System|Applications|boot|dev|proc|sys|root|var\/log|var\/run)\//.test(absoluteResultPath)) {
throw new Error(`Result path must not be a system directory: ${resultPath}`);
}
if (!node_fs_1.default.existsSync(absoluteResultPath)) {

@@ -233,0 +236,0 @@ throw new Error(`Result file does not exist: ${absoluteResultPath}`);

@@ -43,3 +43,3 @@ "use strict";

throw new Error(`Migration target not found: ${target}`);
return { snapshot: JSON.parse(node_fs_1.default.readFileSync(file, "utf8")), contract, dir: node_path_1.default.dirname(file) };
return { snapshot: (0, state_1.readJson)(file), contract, dir: node_path_1.default.dirname(file) };
}

@@ -456,3 +456,12 @@ "use strict";

reportExtractedTo = node_path_1.default.resolve(options.extractReportTo);
node_fs_1.default.writeFileSync(reportExtractedTo, reportContent);
if (options.cwd) {
const baseCwd = node_path_1.default.resolve(options.cwd);
if (!(0, state_1.isContainedPath)(reportExtractedTo, baseCwd)) {
failedChecks.push({ name: "extract-report", code: "path-outside-working-directory" });
reportExtractedTo = undefined;
}
}
if (reportExtractedTo) {
node_fs_1.default.writeFileSync(reportExtractedTo, reportContent);
}
}

@@ -459,0 +468,0 @@ }

@@ -135,3 +135,8 @@ "use strict";

// A non-bundled, non-file id still fails closed via showBundledSandboxProfile.
const absolute = node_path_1.default.resolve(requested);
const absolute = node_path_1.default.resolve(context.cwd, requested);
if (!absolute.startsWith(node_path_1.default.resolve(context.cwd) + node_path_1.default.sep) && absolute !== node_path_1.default.resolve(context.cwd)) {
throw new SandboxProfileError("sandbox-profile-path-escape", `Custom profile path traversal denied: ${requested}`, {
details: { requested }
});
}
if (node_fs_1.default.existsSync(absolute) && node_fs_1.default.statSync(absolute).isFile()) {

@@ -138,0 +143,0 @@ const result = validateSandboxProfileFile(requested, context);

@@ -150,3 +150,9 @@ "use strict";

return value;
const parsed = JSON.parse(String(value));
let parsed;
try {
parsed = JSON.parse(String(value));
}
catch {
throw new Error("Expected a JSON object, got invalid JSON");
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {

@@ -153,0 +159,0 @@ throw new Error("Expected JSON object");

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MIN_SUPPORTED_RUN_STATE_SCHEMA_VERSION = exports.LEGACY_RUN_STATE_SCHEMA_VERSION = exports.CURRENT_RUN_STATE_SCHEMA_VERSION = exports.WORKFLOW_APP_SCHEMA_VERSION = exports.CURRENT_COOL_WORKFLOW_VERSION = void 0;
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.96";
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.97";
exports.WORKFLOW_APP_SCHEMA_VERSION = 1;

@@ -6,0 +6,0 @@ exports.CURRENT_RUN_STATE_SCHEMA_VERSION = 1;

@@ -27,2 +27,3 @@ "use strict";

const node_path_1 = __importDefault(require("node:path"));
const node_crypto_1 = __importDefault(require("node:crypto"));
const workbench_1 = require("./workbench");

@@ -97,3 +98,8 @@ const ALLOWED_HOSTNAMES = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);

const queryToken = url.searchParams.get("token") || "";
if (bearer !== requiredToken && queryToken !== requiredToken) {
const tokenBuf = Buffer.from(requiredToken);
const bearerBuf = Buffer.from(bearer);
const queryBuf = Buffer.from(queryToken);
const tokenOk = bearerBuf.length === tokenBuf.length && node_crypto_1.default.timingSafeEqual(bearerBuf, tokenBuf);
const queryOk = queryBuf.length === tokenBuf.length && node_crypto_1.default.timingSafeEqual(queryBuf, tokenBuf);
if (!tokenOk && !queryOk) {
return this.send(res, 401, { error: "unauthorized: token mismatch" });

@@ -100,0 +106,0 @@ }

@@ -412,1 +412,3 @@ # Agent Delegation Drive

0.1.96
0.1.97

@@ -554,1 +554,3 @@ # CLI ↔ MCP Parity

0.1.96
0.1.97

@@ -172,1 +172,3 @@ # Contract Migration Tooling

0.1.96
0.1.97

@@ -156,1 +156,3 @@ # Control-Plane Scheduling

0.1.96
0.1.97

@@ -155,1 +155,3 @@ # Durable State & Locking

0.1.96
0.1.97

@@ -316,1 +316,3 @@ # Evidence Adoption Reasoning Chain

0.1.96
0.1.97

@@ -346,1 +346,3 @@ # EXECUTION-BACKENDS(7)

0.1.96
0.1.97

@@ -322,1 +322,3 @@ # Multi-Agent CLI + MCP Surface

0.1.96
0.1.97

@@ -348,1 +348,3 @@ # Multi-Agent Eval & Replay Harness

0.1.96
0.1.97

@@ -360,1 +360,3 @@ # Multi-Agent Operator UX

0.1.96
0.1.97

@@ -181,1 +181,3 @@ # Node Snapshot / Diff / Replay

0.1.96
0.1.97

@@ -240,1 +240,3 @@ # Observability + Cost Accounting

0.1.96
0.1.97
# Cool Workflow Project Index
Generated from the current repository code on 2026-06-28 by `npm run sync:project-index`.
Generated from the current repository code on 2026-06-30 by `npm run sync:project-index`.

@@ -8,7 +8,7 @@ ## Snapshot

- Package: `cool-workflow`
- Version: `0.1.96`
- Version: `0.1.97`
- Source modules: `68`
- Workflow apps: `8`
- Docs: `53`
- Smoke tests: `158`
- Docs: `59`
- Smoke tests: `164`
- Repository: https://github.com/coo1white/cool-workflow

@@ -149,2 +149,4 @@

- [Coordinator / Blackboard](coordinator-blackboard.7.md)
- [DEMO(7)](demo.7.md)
- [DOCTOR(7)](doctor.7.md)
- [Dogfood One Real Repo](dogfood-one-real-repo.7.md)

@@ -156,4 +158,6 @@ - [Durable State & Locking](durable-state-and-locking.7.md)

- [EXECUTION-BACKENDS(7)](execution-backends.7.md)
- [FIX(7)](fix.7.md)
- [Getting Started](getting-started.md)
- [Cool Workflow Docs](index.md)
- [INIT(7)](init.7.md)
- [MCP App Surface](mcp-app-surface.7.md)

@@ -170,2 +174,3 @@ - [Multi-Agent CLI + MCP Surface](multi-agent-cli-mcp-surface.7.md)

- [PIPELINE-RUNNER(7)](pipeline-runner.7.md)
- [PIPELINE-VERBS(7)](pipeline-verbs.7.md)
- [Cool Workflow Project Index](project-index.md)

@@ -179,2 +184,3 @@ - [Cool Workflow](readme-v0.1.87-full.md)

- [Verifiable Report Bundle](report-verifiable-bundle.7.md)
- [ROUTINE(7)](routine.7.md)
- [Routines](routines.md)

@@ -204,2 +210,3 @@ - [Run Registry / Control Plane](run-registry-control-plane.7.md)

- [agent-delegation-drive-smoke.js](../test/agent-delegation-drive-smoke.js)
- [agent-stream-gate-smoke.js](../test/agent-stream-gate-smoke.js)
- [append-run-node-no-realloc-smoke.js](../test/append-run-node-no-realloc-smoke.js)

@@ -291,2 +298,5 @@ - [architecture-review-fast-automation-smoke.js](../test/architecture-review-fast-automation-smoke.js)

- [parity-doc-sync-smoke.js](../test/parity-doc-sync-smoke.js)
- [parse-guard-smoke.js](../test/parse-guard-smoke.js)
- [parse-hardening-round2-smoke.js](../test/parse-hardening-round2-smoke.js)
- [path-containment-smoke.js](../test/path-containment-smoke.js)
- [pdca-blackboard-loop-smoke.js](../test/pdca-blackboard-loop-smoke.js)

@@ -310,2 +320,3 @@ - [pii-redaction-smoke.js](../test/pii-redaction-smoke.js)

- [release-gate-smoke.js](../test/release-gate-smoke.js)
- [release-pipeline-hygiene-smoke.js](../test/release-pipeline-hygiene-smoke.js)
- [release-tooling-smoke.js](../test/release-tooling-smoke.js)

@@ -334,2 +345,3 @@ - [remote-link-archive-smoke.js](../test/remote-link-archive-smoke.js)

- [sample-determinism-smoke.js](../test/sample-determinism-smoke.js)
- [sandbox-env-batch-hardening-smoke.js](../test/sandbox-env-batch-hardening-smoke.js)
- [sandbox-profile-smoke.js](../test/sandbox-profile-smoke.js)

@@ -336,0 +348,0 @@ - [sched-policy-validation-smoke.js](../test/sched-policy-validation-smoke.js)

@@ -188,1 +188,3 @@ # Real Execution Backend Integrations

0.1.96
0.1.97

@@ -328,1 +328,3 @@ # Release And Migration Discipline

0.1.96
0.1.97

@@ -291,1 +291,3 @@ # Release Tooling

0.1.96
0.1.97

@@ -471,1 +471,3 @@ # Run Registry / Control Plane

0.1.96
0.1.97

@@ -239,1 +239,3 @@ # Run Retention & Provable Reclamation

0.1.96
0.1.97

@@ -317,1 +317,3 @@ # State Explosion Management

0.1.96
0.1.97

@@ -253,1 +253,3 @@ # Team Collaboration

0.1.96
0.1.97

@@ -261,1 +261,3 @@ # Web / Desktop Workbench

0.1.96
0.1.97

@@ -5,3 +5,3 @@ {

"name": "cool-workflow",
"version": "0.1.96",
"version": "0.1.97",
"license": "BSD-2-Clause",

@@ -8,0 +8,0 @@ "homepage": "https://github.com/coo1white/cool-workflow",

# Vendor Manifest Source of Truth
Every agent host scans a different, hard-coded manifest directory
(`.claude-plugin/`, `.codex-plugin/`, `.agents/`) with a different JSON shape.
You cannot unify the directory or the schema — so we do not try. Instead, all
vendor manifests are **generated** from one neutral source and point at the same
shared runtime (`skills/`, `dist/`, `apps/`, the MCP server). No vendor forks the
logic; each manifest is a thin adapter.
(`.claude-plugin/`, `.codex-plugin/`, `.agents/`, `.gemini-plugin/`,
`.opencode-plugin/`) with a different JSON shape. You cannot unify the directory
or the schema — so we do not try. Instead, all vendor manifests are **generated**
from one neutral source and point at the same shared runtime (`skills/`, `dist/`,
`apps/`, the MCP server). No vendor forks the logic; each manifest is a thin
adapter.

@@ -28,9 +29,14 @@ This is the mechanism/policy split: shared assets are mechanism, per-vendor

Five vendors are generated today. Paths are repo-root-relative.
| Vendor | Marketplace | Plugin manifest | MCP config | MCP path var |
| --- | --- | --- | --- | --- |
| Claude Code | `../../../.claude-plugin/marketplace.json` | `../.claude-plugin/plugin.json` | `../.mcp.json` (auto-discovered) | `${CLAUDE_PLUGIN_ROOT}/` |
| Codex / `.agents` | `../../../.agents/plugins/marketplace.json` | `../.codex-plugin/plugin.json` | `../.codex-plugin/mcp.json` | `./` |
| Claude Code | `.claude-plugin/marketplace.json` | `plugins/cool-workflow/.claude-plugin/plugin.json` | `plugins/cool-workflow/.mcp.json` (auto-discovered) | `${CLAUDE_PLUGIN_ROOT}/` |
| Codex | `.agents/plugins/marketplace.json` | `plugins/cool-workflow/.codex-plugin/plugin.json` | `plugins/cool-workflow/.codex-plugin/mcp.json` | `./` |
| `.agents` | — | `.agents/plugins/cool-workflow/plugin.json` | `.agents/plugins/cool-workflow/mcp.json` | `./` |
| Gemini | — | `plugins/cool-workflow/.gemini-plugin/plugin.json` | `plugins/cool-workflow/.gemini-plugin/mcp.json` | `./` |
| OpenCode | — | `plugins/cool-workflow/.opencode-plugin/plugin.json` | `plugins/cool-workflow/.opencode-plugin/mcp.json` | `./` |
The two vendors read **different** MCP files, so the plugin-root path variable
never collides.
Each vendor reads its **own** MCP file, so the plugin-root path variable never
collides.

@@ -37,0 +43,0 @@ ## Adding a new vendor (Cursor, Windsurf, …)

{
"name": "cool-workflow",
"version": "0.1.96",
"version": "0.1.97",
"bin": {

@@ -5,0 +5,0 @@ "cool-workflow": "scripts/cw.js",

@@ -460,5 +460,8 @@ #!/usr/bin/env node

function persistStderr(resultPath, text) {
const t = String(text || "").trim();
let t = String(text || "").trim();
if (!t || !resultPath) return;
try {
t = String(t).replace(/\b(sk-[A-Za-z0-9_-]{20,}|ghp_[A-Za-z0-9]{20,}|xox[bprs]-[A-Za-z0-9-]{20,}|Bearer\s+\S+|Authorization:\s*\S+|api[_-]?key[=:]\s*\S+|token[=:]\s*\S+)/gi, (match) => match.slice(0, 4) + "***[REDACTED]");
const cap = 4096;
if (t.length > cap) t = t.slice(0, cap) + `\n [truncated from ${t.length} bytes]`;
const dir = path.join(path.dirname(resultPath), "logs");

@@ -465,0 +468,0 @@ fs.mkdirSync(dir, { recursive: true });

@@ -86,3 +86,3 @@ #!/usr/bin/env node

"--scope",
"Cool Workflow v0.1.96",
"Cool Workflow v0.1.97",
"--freshness",

@@ -121,3 +121,3 @@ "as of release preparation"

assert.equal(summary.legacy, false);
assert.equal(summary.version, "0.1.96");
assert.equal(summary.version, "0.1.97");

@@ -129,3 +129,3 @@ const validation = runJson(["app", "validate", manifestPath]);

assert.equal(shown.app.id, app.id);
assert.equal(shown.app.version, "0.1.96");
assert.equal(shown.app.version, "0.1.97");
assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);

@@ -141,3 +141,3 @@ assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);

assert.equal(state.workflow.app.id, app.id);
assert.equal(state.workflow.app.version, "0.1.96");
assert.equal(state.workflow.app.version, "0.1.97");
assert.equal(state.workflow.app.metadata.canonical, true);

@@ -144,0 +144,0 @@ assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);

@@ -21,6 +21,13 @@ #!/usr/bin/env node

let raw = "";
const MAX_STDIN_BYTES = 32 * 1024 * 1024;
process.stdin.setEncoding("utf8");
process.stdin.on("data", (d) => (raw += d));
process.stdin.on("data", (d) => { if (raw.length < MAX_STDIN_BYTES) raw += d; });
process.stdin.on("end", () => {
const jobs = JSON.parse(raw);
let jobs;
try {
jobs = JSON.parse(raw);
} catch (e) {
process.stdout.write(JSON.stringify([{ spawnError: `invalid stdin JSON: ${String(e && e.message || e)}`, exitCode: null, stdout: "" }]));
return;
}
if (!jobs.length) { process.stdout.write("[]"); return; }

@@ -41,3 +48,3 @@ const out = new Array(jobs.length);

try {
child = spawn(job.binary, job.args, { cwd: job.cwd, env: process.env, shell: false });
child = spawn(job.binary, job.args, { cwd: job.cwd, env: job.env || process.env, shell: false });
} catch (error) {

@@ -44,0 +51,0 @@ settle({ spawnError: String((error && error.message) || error), exitCode: null, stdout: "" });

@@ -18,3 +18,4 @@ #!/usr/bin/env node

(async () => {
const read = () => new Promise((res) => { let b = ""; process.stdin.on("data", (c) => (b += c)); process.stdin.on("end", () => res(b)); });
const MAX_STDIN_BYTES = 32 * 1024 * 1024;
const read = () => new Promise((res) => { let b = ""; process.stdin.on("data", (c) => { if (b.length < MAX_STDIN_BYTES) b += c; }); process.stdin.on("end", () => res(b)); });
try {

@@ -21,0 +22,0 @@ const job = JSON.parse((await read()) || "{}");

@@ -9,3 +9,3 @@ #!/usr/bin/env node

const TARGET_VERSION = "0.1.96";
const TARGET_VERSION = "0.1.97";
const PREVIOUS_VERSION = "0.1.31";

@@ -12,0 +12,0 @@ const pluginRoot = path.resolve(__dirname, "..");

@@ -36,3 +36,3 @@ #!/usr/bin/env node

assert.equal(appValidation.summary.id, "end-to-end-golden-path");
assert.equal(appValidation.summary.version, "0.1.96");
assert.equal(appValidation.summary.version, "0.1.97");

@@ -46,3 +46,3 @@ const plan = runJson(

"--question",
"Prove the deterministic v0.1.96 end-to-end golden path."
"Prove the deterministic v0.1.97 end-to-end golden path."
],

@@ -57,3 +57,3 @@ pluginRoot

assert.equal(state.workflow.app.id, "end-to-end-golden-path");
assert.equal(state.workflow.app.version, "0.1.96");
assert.equal(state.workflow.app.version, "0.1.97");
assert.equal(state.loopStage, "interpret");

@@ -201,3 +201,3 @@

const report = fs.readFileSync(reportPath, "utf8");
assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.96/);
assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.97/);
assert.match(report, /## Candidates/);

@@ -204,0 +204,0 @@ assert.match(report, /## Trust Audit/);

@@ -157,2 +157,7 @@ #!/usr/bin/env node

// ---- 2. independent reviewer, delegated to the configured agent -------------
// Default reviewer deadline. The zero-trust reviewer re-runs release-gate.sh,
// whose sequential test suite alone is ~12 min, then reads + reasons over the
// diff — so a 10-min default guaranteed a timeout on a real release. 30 min gives
// headroom; override with CW_AGENT_TIMEOUT_MS (or --agent-timeout-ms).
const REVIEWER_TIMEOUT_MS = 1800000;
function reviewerPromptBody() {

@@ -284,3 +289,3 @@ // Reuse the committed reviewer spec as the prompt; strip YAML frontmatter.

encoding: "utf8",
timeout: cfg.timeoutMs || 600000,
timeout: cfg.timeoutMs || REVIEWER_TIMEOUT_MS,
shell: false,

@@ -315,3 +320,3 @@ stdio: ["ignore", "pipe", "inherit"],

const lib = cfg.endpoint.startsWith("https:") ? https : http;
const text = postSync(lib, cfg.endpoint, body, cfg.timeoutMs || 600000);
const text = postSync(lib, cfg.endpoint, body, cfg.timeoutMs || REVIEWER_TIMEOUT_MS);
if (text === null) die("reviewer endpoint call failed — no verdict trusted.");

@@ -508,14 +513,14 @@ fs.writeFileSync(resultPath, text.endsWith("\n") ? text : `${text}\n`);

// Regenerate the gated project index after the version bump (PR #87 gate).
spawnSync("npm", ["run", "sync:project-index", "--", "--repo-only"], { cwd: pluginRoot, stdio: "inherit" });
// Belt-and-suspenders: the reviewer agent writes a narration transcript into
// .cw-release/ that may carry local paths (the operator's home dir). It is
// .gitignored, but `git add -A` would still stage it if it were ever tracked,
// and a tracked transcript leaks PII into the immutable tag commit (it tripped
// pii-redaction-smoke and red-failed release-gate for v0.1.96). Remove any
// transcript before staging so it can never ride into the tag.
const releaseDir = path.join(repoRoot, ".cw-release");
for (const f of fs.existsSync(releaseDir) ? fs.readdirSync(releaseDir) : []) {
if (/^transcript.*\.md$/.test(f)) fs.rmSync(path.join(releaseDir, f), { force: true });
}
git(["add", "-A"]);
// Fail closed: a failed regen must not bake a stale index into the immutable tag.
const sync = spawnSync("npm", ["run", "sync:project-index", "--", "--repo-only"], { cwd: pluginRoot, encoding: "utf8", stdio: "inherit" });
if (sync.status !== 0) die("sync:project-index failed — refusing to cut with a stale project index");
// Stage ONLY tracked-file modifications (the bump surfaces, project-index, dist)
// plus the ONE intended new file: the reviewer verdict. NEVER `git add -A` — an
// untracked stray (e.g. the reviewer's narration transcript, which carries the
// operator's local home path) must never ride into the immutable tag commit
// (that tripped pii-redaction-smoke and red-failed release-gate for v0.1.96).
// `git add -u` touches tracked files only, so no untracked file can be swept in;
// the verdict is the single new path the cut is allowed to add.
git(["add", "-u"]);
git(["add", "--", path.relative(repoRoot, resultPath)]);
const commit = git(["commit", "-m", `chore(release): record APPROVED reviewer verdict for v${cutVersion}`]);

@@ -526,4 +531,7 @@ if (commit.code !== 0) die("verdict commit failed", commit.err);

if (PUSH) {
git(["push", "origin", "HEAD"]);
git(["push", "origin", `v${cutVersion}`]);
// Atomic: the verdict commit on HEAD and the tag land together or not at all.
// A non-atomic two-push could leave main advanced with no tag, so CI's
// release-gate (which fires on the tag) never runs and the release silently stalls.
const push = git(["push", "--atomic", "origin", "HEAD", `v${cutVersion}`]);
if (push.code !== 0) die("atomic push of HEAD + tag failed (nothing partially pushed)", push.err);
}

@@ -530,0 +538,0 @@ say(`tagged v${cutVersion}${PUSH ? " and pushed" : " (local only; push when ready)"}`);