cool-workflow 0.1.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/.claude-plugin/plugin.json +20 -0
  2. package/.codex-plugin/mcp.json +10 -0
  3. package/.codex-plugin/plugin.json +38 -0
  4. package/.mcp.json +10 -0
  5. package/LICENSE +24 -0
  6. package/README.md +638 -0
  7. package/apps/architecture-review/app.json +51 -0
  8. package/apps/architecture-review/workflow.js +116 -0
  9. package/apps/end-to-end-golden-path/app.json +30 -0
  10. package/apps/end-to-end-golden-path/workflow.js +33 -0
  11. package/apps/pr-review-fix-ci/app.json +59 -0
  12. package/apps/pr-review-fix-ci/workflow.js +90 -0
  13. package/apps/release-cut/app.json +54 -0
  14. package/apps/release-cut/workflow.js +82 -0
  15. package/apps/research-synthesis/app.json +50 -0
  16. package/apps/research-synthesis/workflow.js +76 -0
  17. package/apps/workflow-app-framework-demo/app.json +29 -0
  18. package/apps/workflow-app-framework-demo/workflow.js +44 -0
  19. package/dist/agent-config.js +223 -0
  20. package/dist/candidate-scoring.js +715 -0
  21. package/dist/capability-core.js +630 -0
  22. package/dist/capability-dispatcher.js +86 -0
  23. package/dist/capability-registry.js +523 -0
  24. package/dist/cli.js +1276 -0
  25. package/dist/collaboration.js +727 -0
  26. package/dist/commit.js +570 -0
  27. package/dist/contract-migration.js +234 -0
  28. package/dist/coordinator.js +1163 -0
  29. package/dist/daemon.js +44 -0
  30. package/dist/dispatch.js +201 -0
  31. package/dist/drive.js +503 -0
  32. package/dist/error-feedback.js +415 -0
  33. package/dist/evidence-grounding.js +179 -0
  34. package/dist/evidence-reasoning.js +733 -0
  35. package/dist/execution-backend.js +1279 -0
  36. package/dist/harness.js +61 -0
  37. package/dist/mcp-server.js +1615 -0
  38. package/dist/multi-agent-eval.js +857 -0
  39. package/dist/multi-agent-host.js +764 -0
  40. package/dist/multi-agent-operator-ux.js +537 -0
  41. package/dist/multi-agent-trust.js +366 -0
  42. package/dist/multi-agent.js +1173 -0
  43. package/dist/node-snapshot.js +270 -0
  44. package/dist/observability.js +922 -0
  45. package/dist/operator-ux.js +971 -0
  46. package/dist/orchestrator/audit-operations.js +182 -0
  47. package/dist/orchestrator/candidate-operations.js +117 -0
  48. package/dist/orchestrator/cli-options.js +288 -0
  49. package/dist/orchestrator/collaboration-operations.js +86 -0
  50. package/dist/orchestrator/feedback-operations.js +81 -0
  51. package/dist/orchestrator/host-operations.js +78 -0
  52. package/dist/orchestrator/lifecycle-operations.js +462 -0
  53. package/dist/orchestrator/migration-operations.js +44 -0
  54. package/dist/orchestrator/multi-agent-operations.js +362 -0
  55. package/dist/orchestrator/report.js +369 -0
  56. package/dist/orchestrator/topology-operations.js +84 -0
  57. package/dist/orchestrator.js +874 -0
  58. package/dist/pipeline-contract.js +92 -0
  59. package/dist/pipeline-runner.js +285 -0
  60. package/dist/reclamation.js +882 -0
  61. package/dist/result-normalize.js +194 -0
  62. package/dist/run-export.js +64 -0
  63. package/dist/run-registry.js +1347 -0
  64. package/dist/run-state-schema.js +67 -0
  65. package/dist/sandbox-profile.js +471 -0
  66. package/dist/scheduler.js +266 -0
  67. package/dist/scheduling.js +184 -0
  68. package/dist/schema-validate.js +98 -0
  69. package/dist/state-explosion.js +1213 -0
  70. package/dist/state-migrations.js +463 -0
  71. package/dist/state-node.js +301 -0
  72. package/dist/state.js +308 -0
  73. package/dist/telemetry-attestation.js +156 -0
  74. package/dist/telemetry-ledger.js +145 -0
  75. package/dist/topology.js +527 -0
  76. package/dist/triggers.js +159 -0
  77. package/dist/trust-audit.js +475 -0
  78. package/dist/types/blackboard.js +2 -0
  79. package/dist/types/boundary.js +29 -0
  80. package/dist/types/candidate.js +2 -0
  81. package/dist/types/collaboration.js +2 -0
  82. package/dist/types/core.js +2 -0
  83. package/dist/types/drive.js +10 -0
  84. package/dist/types/error-feedback.js +2 -0
  85. package/dist/types/evidence-reasoning.js +2 -0
  86. package/dist/types/execution-backend.js +2 -0
  87. package/dist/types/multi-agent.js +2 -0
  88. package/dist/types/observability.js +2 -0
  89. package/dist/types/pipeline.js +2 -0
  90. package/dist/types/reclamation.js +8 -0
  91. package/dist/types/result.js +2 -0
  92. package/dist/types/run-registry.js +2 -0
  93. package/dist/types/run.js +2 -0
  94. package/dist/types/sandbox.js +2 -0
  95. package/dist/types/schedule.js +2 -0
  96. package/dist/types/state-node.js +2 -0
  97. package/dist/types/topology.js +2 -0
  98. package/dist/types/trust.js +2 -0
  99. package/dist/types/workbench.js +2 -0
  100. package/dist/types/worker.js +2 -0
  101. package/dist/types/workflow-app.js +2 -0
  102. package/dist/types.js +43 -0
  103. package/dist/verifier-registry.js +46 -0
  104. package/dist/verifier.js +78 -0
  105. package/dist/version.js +8 -0
  106. package/dist/workbench-host.js +172 -0
  107. package/dist/workbench.js +190 -0
  108. package/dist/worker-isolation.js +1028 -0
  109. package/dist/workflow-api.js +98 -0
  110. package/dist/workflow-app-framework.js +626 -0
  111. package/docs/agent-delegation-drive.7.md +190 -0
  112. package/docs/agent-framework.md +176 -0
  113. package/docs/candidate-scoring.7.md +106 -0
  114. package/docs/canonical-workflow-apps.7.md +137 -0
  115. package/docs/capability-topology-registry.7.md +168 -0
  116. package/docs/cli-mcp-parity.7.md +373 -0
  117. package/docs/contract-migration-tooling.7.md +123 -0
  118. package/docs/control-plane-scheduling.7.md +110 -0
  119. package/docs/coordinator-blackboard.7.md +183 -0
  120. package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
  121. package/docs/dogfood-one-real-repo.7.md +168 -0
  122. package/docs/durable-state-and-locking.7.md +107 -0
  123. package/docs/end-to-end-golden-path.7.md +117 -0
  124. package/docs/error-feedback.7.md +153 -0
  125. package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
  126. package/docs/execution-backends.7.md +300 -0
  127. package/docs/getting-started.md +99 -0
  128. package/docs/index.md +41 -0
  129. package/docs/mcp-app-surface.7.md +235 -0
  130. package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
  131. package/docs/multi-agent-eval-replay-harness.7.md +302 -0
  132. package/docs/multi-agent-operator-ux.7.md +314 -0
  133. package/docs/multi-agent-runtime-core.7.md +231 -0
  134. package/docs/multi-agent-topologies.7.md +103 -0
  135. package/docs/multi-agent-trust-policy-audit.7.md +154 -0
  136. package/docs/node-snapshot-diff-replay.7.md +135 -0
  137. package/docs/observability-cost-accounting.7.md +194 -0
  138. package/docs/operator-ux.7.md +180 -0
  139. package/docs/pipeline-runner.7.md +136 -0
  140. package/docs/project-index.md +261 -0
  141. package/docs/real-execution-backends.7.md +142 -0
  142. package/docs/release-and-migration.7.md +280 -0
  143. package/docs/release-tooling.7.md +159 -0
  144. package/docs/routines.md +48 -0
  145. package/docs/run-registry-control-plane.7.md +312 -0
  146. package/docs/run-retention-reclamation.7.md +191 -0
  147. package/docs/sandbox-profiles.7.md +137 -0
  148. package/docs/scheduled-tasks.md +80 -0
  149. package/docs/security-trust-hardening.7.md +117 -0
  150. package/docs/state-explosion-management.7.md +264 -0
  151. package/docs/state-node.7.md +96 -0
  152. package/docs/team-collaboration.7.md +207 -0
  153. package/docs/unix-principles.md +192 -0
  154. package/docs/verifier-gated-commit.7.md +140 -0
  155. package/docs/web-desktop-workbench.7.md +215 -0
  156. package/docs/worker-isolation.7.md +167 -0
  157. package/docs/workflow-app-framework.7.md +274 -0
  158. package/manifest/README.md +43 -0
  159. package/manifest/plugin.manifest.json +316 -0
  160. package/manifest/pricing.policy.json +14 -0
  161. package/package.json +79 -0
  162. package/scripts/agents/claude-p-agent.js +104 -0
  163. package/scripts/agents/claude-p-agent.sh +9 -0
  164. package/scripts/agents/cw-attest-keygen.js +55 -0
  165. package/scripts/agents/cw-attest-wrap.js +143 -0
  166. package/scripts/block-unapproved-tag.sh +39 -0
  167. package/scripts/bump-version.js +249 -0
  168. package/scripts/canonical-apps.js +171 -0
  169. package/scripts/cw.js +4 -0
  170. package/scripts/dist-drift-check.js +79 -0
  171. package/scripts/dogfood-architecture-review.js +237 -0
  172. package/scripts/dogfood-release.js +624 -0
  173. package/scripts/forward-ref-docs.js +73 -0
  174. package/scripts/gen-manifests.js +232 -0
  175. package/scripts/golden-path.js +300 -0
  176. package/scripts/mcp-server.js +4 -0
  177. package/scripts/new-feature.js +121 -0
  178. package/scripts/parity-check.js +213 -0
  179. package/scripts/release-check.js +118 -0
  180. package/scripts/release-flow.js +272 -0
  181. package/scripts/release-gate.sh +85 -0
  182. package/scripts/sync-project-index.js +387 -0
  183. package/scripts/validate-run-state-schema.js +126 -0
  184. package/scripts/verify-container-selfref.js +64 -0
  185. package/scripts/version-sync-check.js +237 -0
  186. package/skills/cool-workflow/SKILL.md +162 -0
  187. package/skills/cool-workflow/references/commands.md +282 -0
  188. package/tsconfig.json +16 -0
  189. package/ui/workbench/app.css +76 -0
  190. package/ui/workbench/app.js +159 -0
  191. package/ui/workbench/index.html +32 -0
  192. package/workflows/architecture-review.workflow.js +84 -0
  193. package/workflows/research-synthesis.workflow.js +47 -0
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // Generate every vendor plugin manifest from a single source of truth.
5
+ //
6
+ // node scripts/gen-manifests.js # write generated manifests
7
+ // node scripts/gen-manifests.js --check # fail (exit 1) if any drift
8
+ //
9
+ // Mechanism vs policy (BSD discipline): the shared assets (skills/, dist/,
10
+ // apps/, the MCP server) live once; each vendor manifest is a thin, generated
11
+ // adapter. Edit manifest/plugin.manifest.json, never the generated files.
12
+ //
13
+ // v0.1.47 — VENDOR ADAPTER REGISTRY: vendor outputs are now declarative
14
+ // templates in plugin.manifest.json's `vendors` section. Adding a new AI
15
+ // platform is just data — no gen-manifests.js changes needed.
16
+ //
17
+ // Supported template markers:
18
+ // {{path.to.field}} — resolve a dot-path in the source object
19
+ // {{path.to.field|lowercase}}— apply transformer (only |lowercase)
20
+ // {{pluginRootVar}} — resolve to the vendor's pluginRootVar
21
+ // Bare strings without {{ }} are literal values
22
+
23
+ const assert = require("node:assert/strict");
24
+ const fs = require("node:fs");
25
+ const path = require("node:path");
26
+
27
+ const pluginRoot = path.resolve(__dirname, "..");
28
+ const repoRoot = path.resolve(pluginRoot, "..", "..");
29
+ const SOURCE = path.join(pluginRoot, "manifest", "plugin.manifest.json");
30
+
31
+ function loadSource() {
32
+ assert.ok(fs.existsSync(SOURCE), `source of truth missing: ${rel(SOURCE)}`);
33
+ return JSON.parse(fs.readFileSync(SOURCE, "utf8"));
34
+ }
35
+
36
+ // ---- Template engine (BSD: mechanism — one pure resolver, policy is data) --
37
+
38
+ /** Resolve a dot-path string in the source object. Returns undefined if not found. */
39
+ function _resolvePath(obj, pathStr) {
40
+ const parts = pathStr.split(".");
41
+ let current = obj;
42
+ for (const part of parts) {
43
+ if (current === null || typeof current !== "object") return undefined;
44
+ if (!(part in current)) return undefined;
45
+ current = current[part];
46
+ }
47
+ return current;
48
+ }
49
+
50
+ /** Interpolate {{path.to.field}} or {{path.to.field|transformer}} markers.
51
+ * Bare strings without markers are returned as-is. */
52
+ function _interpolate(template, src, pluginRootVar) {
53
+ if (typeof template !== "string") return template;
54
+ // Whole-string interpolation: if the entire value is a single marker, resolve
55
+ // to the actual JS value (object, array, string) rather than a string.
56
+ const wholeMatch = template.match(/^\{\{([^}]+)\}\}$/);
57
+ if (wholeMatch) {
58
+ const expr = wholeMatch[1];
59
+ // Check for transformer: {{path|lowercase}}
60
+ const pipeIdx = expr.lastIndexOf("|");
61
+ const pathStr = pipeIdx >= 0 ? expr.slice(0, pipeIdx) : expr;
62
+ const transformer = pipeIdx >= 0 ? expr.slice(pipeIdx + 1).trim() : null;
63
+ let value;
64
+ if (pathStr === "pluginRootVar") {
65
+ value = pluginRootVar;
66
+ } else {
67
+ value = _resolvePath(src, pathStr);
68
+ }
69
+ if (transformer === "lowercase" && typeof value === "string") return value.toLowerCase();
70
+ return value !== undefined ? value : `{{${expr}}}`;
71
+ }
72
+ // Partial interpolation: replace {{...}} in the string
73
+ return template.replace(/\{\{([^}]+)\}\}/g, (_match, expr) => {
74
+ const pipeIdx = expr.lastIndexOf("|");
75
+ const pathStr = pipeIdx >= 0 ? expr.slice(0, pipeIdx) : expr;
76
+ const transformer = pipeIdx >= 0 ? expr.slice(pipeIdx + 1).trim() : null;
77
+ let value;
78
+ if (pathStr === "pluginRootVar") {
79
+ value = pluginRootVar;
80
+ } else {
81
+ value = _resolvePath(src, pathStr);
82
+ }
83
+ if (value === undefined) return `{{${expr}}}`;
84
+ if (transformer === "lowercase" && typeof value === "string") return value.toLowerCase();
85
+ return String(value);
86
+ });
87
+ }
88
+
89
+ /** Recursively resolve a template (object/array/string) against the source.
90
+ * Object keys containing {{...}} are resolved; bare keys pass through.
91
+ * Values that are arrays/objects are recursed; strings are interpolated. */
92
+ function _resolveTemplate(template, src, pluginRootVar) {
93
+ if (template === null || template === undefined) return template;
94
+ if (typeof template === "string") return _interpolate(template, src, pluginRootVar);
95
+ if (Array.isArray(template)) return template.map(item => _resolveTemplate(item, src, pluginRootVar));
96
+ if (typeof template === "object") {
97
+ const result = {};
98
+ for (const [key, value] of Object.entries(template)) {
99
+ // Resolve key: {{mcp.serverName}} -> "cool-workflow"
100
+ const resolvedKey = _interpolate(key, src, pluginRootVar);
101
+ const keyStr = typeof resolvedKey === "string" ? resolvedKey : key;
102
+ result[keyStr] = _resolveTemplate(value, src, pluginRootVar);
103
+ }
104
+ return result;
105
+ }
106
+ return template;
107
+ }
108
+
109
+ // ---- Build from vendor adapter registry -----------------------------------
110
+
111
+ function build(src) {
112
+ const { targets, vendors } = src;
113
+ // Backward compat: if no `vendors` key, build from legacy targets
114
+ if (!vendors || typeof vendors !== "object" || Object.keys(vendors).length === 0) {
115
+ return buildLegacy(src);
116
+ }
117
+ const outputs = [];
118
+ for (const [vendorId, vendorDef] of Object.entries(vendors)) {
119
+ const targetConfig = targets && targets[vendorId] ? targets[vendorId] : {};
120
+ const pluginRootVar = targetConfig.pluginRootVar || "./";
121
+ const vendorOutputs = vendorDef.outputs || [];
122
+ for (const output of vendorOutputs) {
123
+ const resolved = _resolveTemplate(output, src, pluginRootVar);
124
+ outputs.push({
125
+ path: typeof resolved.path === "string" ? resolved.path : `vendor-${vendorId}-${outputs.length}`,
126
+ json: resolved.json || {}
127
+ });
128
+ }
129
+ }
130
+ return outputs;
131
+ }
132
+
133
+ /** Legacy path: build from hardcoded shapes (backward compat if no `vendors` key). */
134
+ function buildLegacy(src) {
135
+ const { identity, descriptions, interface: ui, layout, mcp, targets } = src;
136
+ const outputs = [];
137
+
138
+ if (targets && targets.claude) {
139
+ outputs.push({
140
+ path: targets.claude.marketplace,
141
+ json: { name: identity.name, owner: identity.author, metadata: { description: descriptions.standard, version: identity.version }, plugins: [{ name: identity.name, source: layout.pluginPathFromRepoRoot, description: descriptions.standard, category: ui.category.toLowerCase() }] }
142
+ });
143
+ outputs.push({
144
+ path: targets.claude.plugin,
145
+ json: { name: identity.name, description: descriptions.standard, version: identity.version, author: identity.author, homepage: identity.homepage, license: identity.license, keywords: identity.keywords }
146
+ });
147
+ outputs.push({
148
+ path: targets.claude.mcp,
149
+ json: legacyMcpConfig(mcp, layout, targets.claude.pluginRootVar)
150
+ });
151
+ }
152
+ if (targets && targets.codex) {
153
+ outputs.push({
154
+ path: targets.codex.marketplace,
155
+ json: { name: identity.name, interface: { displayName: ui.displayName }, plugins: [{ name: identity.name, source: { source: "local", path: layout.pluginPathFromRepoRoot }, policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" }, category: ui.category }] }
156
+ });
157
+ outputs.push({
158
+ path: targets.codex.plugin,
159
+ json: { name: identity.name, version: identity.version, description: descriptions.standard, author: identity.author, keywords: identity.keywords, skills: layout.skillsDir, mcpServers: "./.codex-plugin/mcp.json", interface: { displayName: ui.displayName, shortDescription: descriptions.short, longDescription: descriptions.long, developerName: identity.author.name, category: ui.category, capabilities: ui.capabilities, brandColor: ui.brandColor, defaultPrompt: ui.defaultPrompt } }
160
+ });
161
+ outputs.push({
162
+ path: targets.codex.mcp,
163
+ json: legacyMcpConfig(mcp, layout, targets.codex.pluginRootVar)
164
+ });
165
+ }
166
+ if (targets && targets.agents) {
167
+ outputs.push({
168
+ path: targets.agents.plugin,
169
+ json: { name: identity.name, description: descriptions.standard, version: identity.version, author: identity.author, homepage: identity.homepage, license: identity.license, keywords: identity.keywords, skills: layout.skillsDir, mcpServers: "./.codex-plugin/mcp.json", interface: { displayName: ui.displayName, shortDescription: descriptions.short, longDescription: descriptions.long, developerName: identity.author.name, category: ui.category, capabilities: ui.capabilities, brandColor: ui.brandColor, defaultPrompt: ui.defaultPrompt } }
170
+ });
171
+ outputs.push({
172
+ path: targets.agents.mcp,
173
+ json: legacyMcpConfig(mcp, layout, targets.agents.pluginRootVar)
174
+ });
175
+ }
176
+
177
+ return outputs;
178
+ }
179
+
180
+ function legacyMcpConfig(mcp, layout, pluginRootVar) {
181
+ return {
182
+ mcpServers: {
183
+ [mcp.serverName]: {
184
+ command: mcp.command,
185
+ args: [`${pluginRootVar}${layout.mcpServerScript}`]
186
+ }
187
+ }
188
+ };
189
+ }
190
+
191
+ function serialize(json) {
192
+ return `${JSON.stringify(json, null, 2)}\n`;
193
+ }
194
+
195
+ function rel(absolute) {
196
+ return path.relative(repoRoot, absolute);
197
+ }
198
+
199
+ function main() {
200
+ const check = process.argv.includes("--check");
201
+ const outputs = build(loadSource());
202
+ const results = [];
203
+ const drift = [];
204
+
205
+ for (const output of outputs) {
206
+ const absolute = path.join(repoRoot, output.path);
207
+ const next = serialize(output.json);
208
+ if (check) {
209
+ const current = fs.existsSync(absolute) ? fs.readFileSync(absolute, "utf8") : null;
210
+ const ok = current === next;
211
+ if (!ok) drift.push(output.path);
212
+ results.push({ path: output.path, ok, status: current === null ? "missing" : ok ? "in-sync" : "drift" });
213
+ } else {
214
+ fs.mkdirSync(path.dirname(absolute), { recursive: true });
215
+ fs.writeFileSync(absolute, next);
216
+ results.push({ path: output.path, status: "written" });
217
+ }
218
+ }
219
+
220
+ process.stdout.write(`${JSON.stringify({ ok: drift.length === 0, mode: check ? "check" : "write", results }, null, 2)}\n`);
221
+
222
+ if (check && drift.length > 0) {
223
+ process.stderr.write(
224
+ `\n${drift.length} generated manifest(s) drifted from manifest/plugin.manifest.json:\n` +
225
+ drift.map((p) => ` - ${p}`).join("\n") +
226
+ `\nRun \`npm run gen:manifests\` and commit the result.\n`
227
+ );
228
+ process.exitCode = 1;
229
+ }
230
+ }
231
+
232
+ main();
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const assert = require("node:assert/strict");
5
+ const { execFileSync } = require("node:child_process");
6
+ const fs = require("node:fs");
7
+ const os = require("node:os");
8
+ const path = require("node:path");
9
+
10
+ const pluginRoot = path.resolve(__dirname, "..");
11
+ const cli = path.join(pluginRoot, "scripts/cw.js");
12
+ const node = process.execPath;
13
+ const args = new Set(process.argv.slice(2));
14
+ const jsonOutput = args.has("--json");
15
+ const quiet = args.has("--quiet") || jsonOutput;
16
+ const cleanup = args.has("--cleanup");
17
+
18
+ function main() {
19
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "cw-e2e-golden-path-"));
20
+ const evidencePath = path.join(tmp, "golden-evidence.md");
21
+ fs.writeFileSync(
22
+ evidencePath,
23
+ [
24
+ "# Golden Evidence",
25
+ "The golden path evidence file proves the worker result and candidate score are backed by a stable file:line locator.",
26
+ ""
27
+ ].join("\n"),
28
+ "utf8"
29
+ );
30
+ const evidenceLocator = `${evidencePath}:2`;
31
+
32
+ try {
33
+ const appValidation = runJson(["app", "validate", "end-to-end-golden-path"], pluginRoot);
34
+ assert.equal(appValidation.valid, true);
35
+ assert.equal(appValidation.summary.id, "end-to-end-golden-path");
36
+ assert.equal(appValidation.summary.version, "0.1.78");
37
+
38
+ const plan = runJson(
39
+ [
40
+ "plan",
41
+ "end-to-end-golden-path",
42
+ "--repo",
43
+ tmp,
44
+ "--question",
45
+ "Prove the deterministic v0.1.78 end-to-end golden path."
46
+ ],
47
+ pluginRoot
48
+ );
49
+ assert.equal(plan.workflowId, "end-to-end-golden-path");
50
+ assert.equal(plan.pendingTasks, 1);
51
+ assert.ok(fs.existsSync(plan.statePath));
52
+
53
+ let state = readJson(plan.statePath);
54
+ assert.equal(state.workflow.app.id, "end-to-end-golden-path");
55
+ assert.equal(state.workflow.app.version, "0.1.78");
56
+ assert.equal(state.loopStage, "interpret");
57
+
58
+ const dispatch = runJson(["dispatch", plan.runId, "--limit", "1", "--sandbox", "readonly"], tmp);
59
+ assert.ok(dispatch.dispatchId);
60
+ assert.equal(dispatch.tasks.length, 1);
61
+ assert.equal(dispatch.sandboxProfileId, "readonly");
62
+ assert.equal(dispatch.tasks[0].sandboxProfileId, "readonly");
63
+ assert.ok(dispatch.tasks[0].workerId);
64
+ assert.ok(dispatch.tasks[0].workerManifestPath);
65
+ assert.ok(dispatch.tasks[0].workerResultPath);
66
+ assert.ok(fs.existsSync(dispatch.manifestPath));
67
+
68
+ const dispatchState = readJson(plan.statePath);
69
+ const dispatchRecord = dispatchState.dispatches.find((entry) => entry.id === dispatch.dispatchId);
70
+ assert.ok(dispatchRecord);
71
+ assert.equal(dispatchRecord.sandboxProfileId, "readonly");
72
+ assert.deepEqual(dispatchRecord.workerIds, [dispatch.tasks[0].workerId]);
73
+
74
+ const workerId = dispatch.tasks[0].workerId;
75
+ const workerManifest = runJson(["worker", "manifest", plan.runId, workerId], tmp);
76
+ assert.equal(workerManifest.id, workerId);
77
+ assert.equal(workerManifest.sandboxProfileId, "readonly");
78
+ assert.equal(workerManifest.sandbox.profileId, "readonly");
79
+ assert.equal(workerManifest.sandbox.policy.network.mode, "none");
80
+ assert.ok(workerManifest.sandbox.policy.enforcement.enforcedByCW.length > 0);
81
+ assert.ok(workerManifest.sandbox.policy.enforcement.hostRequired.length > 0);
82
+ assert.equal(workerManifest.resultPath, dispatch.tasks[0].workerResultPath);
83
+
84
+ const manifestFile = readJson(dispatch.tasks[0].workerManifestPath);
85
+ assert.equal(manifestFile.id, workerId);
86
+ assert.equal(manifestFile.sandboxPolicy.id, "readonly");
87
+
88
+ writeWorkerResult(workerManifest.resultPath, evidenceLocator);
89
+ assert.ok(fs.existsSync(workerManifest.resultPath));
90
+
91
+ const workerSummary = runJson(["worker", "output", plan.runId, workerId, workerManifest.resultPath], tmp);
92
+ assert.equal(workerSummary.tasks.completed, 1);
93
+ assert.equal(workerSummary.workers.byStatus.verified, 1);
94
+ assert.equal(workerSummary.loopStage, "observe");
95
+
96
+ state = readJson(plan.statePath);
97
+ const worker = state.workers.find((entry) => entry.id === workerId);
98
+ assert.equal(worker.status, "verified");
99
+ assert.equal(worker.sandboxProfileId, "readonly");
100
+ assert.ok(worker.output.stateNodeId);
101
+ assert.ok(worker.output.verifierNodeId);
102
+
103
+ const task = state.tasks.find((entry) => entry.workerId === workerId);
104
+ assert.equal(task.status, "completed");
105
+ assert.equal(task.requiresEvidence, true);
106
+ assert.equal(task.sandboxProfileId, "readonly");
107
+
108
+ const resultNode = state.nodes.find((entry) => entry.id === task.resultNodeId);
109
+ const verifierNode = state.nodes.find((entry) => entry.id === task.verifierNodeId);
110
+ assert.equal(resultNode.kind, "result");
111
+ assert.equal(resultNode.status, "completed");
112
+ assert.ok(resultNode.evidence.some((entry) => entry.locator === evidenceLocator));
113
+ assert.equal(verifierNode.kind, "verifier");
114
+ assert.equal(verifierNode.status, "verified");
115
+ assert.ok(verifierNode.evidence.some((entry) => entry.locator === evidenceLocator));
116
+
117
+ const candidate = runJson(
118
+ ["candidate", "register", plan.runId, "--worker", workerId, "--id", "golden-candidate"],
119
+ tmp
120
+ );
121
+ assert.equal(candidate.id, "golden-candidate");
122
+ assert.equal(candidate.workerId, workerId);
123
+ assert.equal(candidate.status, "registered");
124
+ assert.equal(candidate.resultNodeId, resultNode.id);
125
+ assert.equal(candidate.verifierNodeId, verifierNode.id);
126
+ assert.ok(fs.existsSync(path.join(tmp, ".cw", "runs", plan.runId, "candidates", "golden-candidate", "candidate.json")));
127
+
128
+ const score = runJson(
129
+ [
130
+ "candidate",
131
+ "score",
132
+ plan.runId,
133
+ "golden-candidate",
134
+ "--criterion",
135
+ "correctness=4",
136
+ "--criterion",
137
+ "evidence=4",
138
+ "--criterion",
139
+ "fit=2",
140
+ "--maxTotal",
141
+ "10",
142
+ "--evidence",
143
+ evidenceLocator
144
+ ],
145
+ tmp
146
+ );
147
+ assert.equal(score.candidateId, "golden-candidate");
148
+ assert.equal(score.total, 10);
149
+ assert.equal(score.normalized, 1);
150
+ assert.equal(score.verdict, "pass");
151
+ assert.ok(
152
+ fs.existsSync(path.join(tmp, ".cw", "runs", plan.runId, "candidates", "golden-candidate", "scores", `${score.id}.json`))
153
+ );
154
+
155
+ const ranking = runJson(["candidate", "rank", plan.runId], tmp);
156
+ assert.equal(ranking.candidates[0].candidateId, "golden-candidate");
157
+ assert.equal(ranking.candidates[0].rank, 1);
158
+ const rankingPath = path.join(tmp, ".cw", "runs", plan.runId, "candidates", "ranking.json");
159
+ assert.ok(fs.existsSync(rankingPath));
160
+
161
+ const selection = runJson(
162
+ ["candidate", "select", plan.runId, "golden-candidate", "--reason", "golden path verified"],
163
+ tmp
164
+ );
165
+ assert.equal(selection.candidateId, "golden-candidate");
166
+ assert.equal(selection.verifierNodeId, verifierNode.id);
167
+ assert.equal(selection.scoreId, score.id);
168
+ assert.ok(selection.evidence.length > 0);
169
+
170
+ state = readJson(plan.statePath);
171
+ const verifiedCandidate = state.candidates.find((entry) => entry.id === "golden-candidate");
172
+ assert.equal(verifiedCandidate.status, "verified");
173
+
174
+ const commitResult = runJson(
175
+ [
176
+ "commit",
177
+ plan.runId,
178
+ "--selection",
179
+ selection.id,
180
+ "--reason",
181
+ "golden path verifier-gated commit"
182
+ ],
183
+ tmp
184
+ );
185
+ assert.equal(commitResult.runId, plan.runId);
186
+ assert.equal(commitResult.commit.verifierGated, true);
187
+ assert.equal(commitResult.commit.checkpoint, false);
188
+ assert.equal(commitResult.commit.selectionId, selection.id);
189
+ assert.equal(commitResult.commit.candidateId, "golden-candidate");
190
+ assert.equal(commitResult.commit.verifierNodeId, verifierNode.id);
191
+ assert.ok(commitResult.commit.evidence.some((entry) => entry.locator === evidenceLocator));
192
+ assert.ok(fs.existsSync(commitResult.commit.snapshotPath));
193
+
194
+ const reportPath = runText(["report", plan.runId], tmp).trim();
195
+ assert.equal(reportPath, plan.reportPath);
196
+ assert.ok(fs.existsSync(reportPath));
197
+ const report = fs.readFileSync(reportPath, "utf8");
198
+ assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.78/);
199
+ assert.match(report, /## Candidates/);
200
+ assert.match(report, /## Trust Audit/);
201
+ assert.match(report, /## Acceptance Rationale/);
202
+ assert.match(report, /golden-candidate/);
203
+ assert.match(report, /verifier-gated commit/);
204
+ assert.match(report, /golden path verifier-gated commit/);
205
+
206
+ state = readJson(plan.statePath);
207
+ const finalCommit = state.commits[state.commits.length - 1];
208
+ assert.equal(finalCommit.id, commitResult.commit.id);
209
+ assert.equal(finalCommit.verifierGated, true);
210
+ assert.equal(finalCommit.checkpoint, false);
211
+ assert.equal(finalCommit.selectionId, selection.id);
212
+ assert.equal(finalCommit.candidateId, "golden-candidate");
213
+ assert.equal(finalCommit.verifierNodeId, verifierNode.id);
214
+ assert.ok(finalCommit.evidence.some((entry) => entry.locator === evidenceLocator));
215
+ assert.equal((state.feedback || []).length, 0);
216
+
217
+ const summary = {
218
+ ok: true,
219
+ runId: plan.runId,
220
+ workspace: tmp,
221
+ statePath: plan.statePath,
222
+ reportPath,
223
+ workerId,
224
+ candidateId: "golden-candidate",
225
+ selectionId: selection.id,
226
+ commitId: finalCommit.id,
227
+ evidence: [evidenceLocator, reportPath, finalCommit.snapshotPath]
228
+ };
229
+ if (jsonOutput) {
230
+ process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
231
+ } else if (!quiet) {
232
+ process.stdout.write(
233
+ [
234
+ "golden-path: ok",
235
+ `run: ${summary.runId}`,
236
+ `report: ${summary.reportPath}`,
237
+ `commit: ${summary.commitId}`,
238
+ ""
239
+ ].join("\n")
240
+ );
241
+ }
242
+ if (cleanup) fs.rmSync(tmp, { recursive: true, force: true });
243
+ return summary;
244
+ } catch (error) {
245
+ if (cleanup) fs.rmSync(tmp, { recursive: true, force: true });
246
+ throw error;
247
+ }
248
+ }
249
+
250
+ function writeWorkerResult(resultPath, evidenceLocator) {
251
+ fs.writeFileSync(
252
+ resultPath,
253
+ [
254
+ "# Golden Worker Result",
255
+ "",
256
+ "The isolated worker produced deterministic evidence for the end-to-end golden path.",
257
+ "",
258
+ "```cw:result",
259
+ JSON.stringify({
260
+ summary: "Golden path worker result accepted with durable evidence.",
261
+ findings: [],
262
+ evidence: [evidenceLocator]
263
+ }),
264
+ "```",
265
+ ""
266
+ ].join("\n"),
267
+ "utf8"
268
+ );
269
+ }
270
+
271
+ function runJson(commandArgs, cwd) {
272
+ return JSON.parse(runText(commandArgs, cwd));
273
+ }
274
+
275
+ function runText(commandArgs, cwd) {
276
+ try {
277
+ return execFileSync(node, [cli, ...commandArgs], {
278
+ cwd,
279
+ encoding: "utf8",
280
+ stdio: ["ignore", "pipe", "pipe"]
281
+ });
282
+ } catch (error) {
283
+ const rendered = [
284
+ `Command failed: node ${path.relative(pluginRoot, cli)} ${commandArgs.join(" ")}`,
285
+ `cwd: ${cwd}`,
286
+ error.stdout ? `stdout:\n${String(error.stdout)}` : "",
287
+ error.stderr ? `stderr:\n${String(error.stderr)}` : ""
288
+ ]
289
+ .filter(Boolean)
290
+ .join("\n");
291
+ error.message = `${error.message}\n${rendered}`;
292
+ throw error;
293
+ }
294
+ }
295
+
296
+ function readJson(file) {
297
+ return JSON.parse(fs.readFileSync(file, "utf8"));
298
+ }
299
+
300
+ main();
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ require("../dist/mcp-server.js");
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // Scaffold the per-tag boilerplate for a new feature so the agent spends tokens
5
+ // on novel logic, not on recreating the same file shapes every release.
6
+ //
7
+ // node scripts/new-feature.js <slug> "<Title>" ["one-line summary"]
8
+ // npm run new:feature -- team-x "Team X" "what it does"
9
+ //
10
+ // Generates the man-page doc, the smoke-test skeleton, and a CHANGELOG entry,
11
+ // then PRINTS the exact gate-file edits to make by hand (capability registry,
12
+ // version:sync assertions, release-check docs-presence, npm test chain) — those
13
+ // are intentionally not auto-edited so a scaffold can never break a gate file.
14
+
15
+ const fs = require("node:fs");
16
+ const path = require("node:path");
17
+
18
+ const pluginRoot = path.resolve(__dirname, "..");
19
+
20
+ function fail(msg) {
21
+ process.stderr.write(`new:feature error: ${msg}\n`);
22
+ process.exit(1);
23
+ }
24
+
25
+ function writeNew(absPath, content) {
26
+ if (fs.existsSync(absPath)) fail(`${path.relative(pluginRoot, absPath)} already exists`);
27
+ fs.writeFileSync(absPath, content);
28
+ }
29
+
30
+ function main() {
31
+ const slug = process.argv[2];
32
+ const title = process.argv[3];
33
+ const summary = process.argv[4] || `${title}: TODO one-line summary.`;
34
+ if (!slug || !title) fail('usage: node scripts/new-feature.js <slug> "<Title>" ["summary"]');
35
+ if (!/^[a-z][a-z0-9-]+$/.test(slug)) fail("slug must be kebab-case, e.g. team-collaboration");
36
+
37
+ const version = JSON.parse(fs.readFileSync(path.join(pluginRoot, "package.json"), "utf8")).version;
38
+ const docRel = `docs/${slug}.7.md`;
39
+ const testRel = `test/${slug}-smoke.js`;
40
+
41
+ // 1. docs/<slug>.7.md — title + version line satisfy version:sync checkIncludes.
42
+ writeNew(path.join(pluginRoot, docRel), `# ${title}
43
+
44
+ CW v${version} adds ${title}: TODO one-paragraph description of the feature, the
45
+ problem it solves, and what existed before it. Keep the base-system voice:
46
+ explicit data flow, no hidden state.
47
+
48
+ ## Design Discipline
49
+
50
+ - TODO: mechanism vs policy — what is the kernel mechanism, what is configurable policy.
51
+ - TODO: fail-closed rule — what ambiguous/missing state surfaces as, never a guess.
52
+ - TODO: reuse — which existing records/types this builds on without forking.
53
+ - TODO: parity — the verbs are declared in capability-registry.ts so CLI === MCP.
54
+
55
+ ## CLI
56
+
57
+ \`\`\`text
58
+ TODO node dist/cli.js <verb> ... [--json]
59
+ \`\`\`
60
+
61
+ ## Compatibility
62
+
63
+ ${title} is introduced in CW v${version}. Fields are additive and optional; older
64
+ run state loads unchanged.
65
+
66
+ ## See Also
67
+
68
+ cli-mcp-parity(7), web-desktop-workbench(7), security-trust-hardening(7)
69
+ `);
70
+
71
+ // 2. test/<slug>-smoke.js — runnable stub; the "<slug>-smoke" marker satisfies
72
+ // version:sync; replace the stub assertion with real coverage.
73
+ writeNew(path.join(pluginRoot, testRel), `"use strict";
74
+ // ${slug}-smoke (v${version}). TODO prove, end to end:
75
+ // 1. the happy path produces the expected durable records;
76
+ // 2. the fail-closed rule actually refuses ambiguous/missing input;
77
+ // 3. cw <cmd> --json === cw_<cmd> (CLI <-> MCP parity).
78
+
79
+ const assert = require("node:assert/strict");
80
+
81
+ function main() {
82
+ // TODO: build a run, exercise the feature, assert on durable state.
83
+ assert.ok(true, "${slug}-smoke: replace this stub with real coverage");
84
+ process.stdout.write("${slug}-smoke: ok (STUB — implement real assertions)\\n");
85
+ }
86
+
87
+ main();
88
+ `);
89
+
90
+ // 3. CHANGELOG.md — ensure a section for this version, add a bullet.
91
+ const changelogPath = path.join(pluginRoot, "..", "..", "CHANGELOG.md");
92
+ let changelog = fs.readFileSync(changelogPath, "utf8");
93
+ const bullet = `- Added ${title}: ${summary}\n`;
94
+ if (changelog.includes(`## ${version}`)) {
95
+ changelog = changelog.replace(`## ${version}\n`, `## ${version}\n\n${bullet}`);
96
+ } else {
97
+ changelog = changelog.replace(/^# Changelog\n/, `# Changelog\n\n## ${version}\n\n${bullet}`);
98
+ }
99
+ fs.writeFileSync(changelogPath, changelog);
100
+
101
+ process.stdout.write(`new:feature "${title}" (v${version})\n`);
102
+ process.stdout.write(` created ${docRel}\n`);
103
+ process.stdout.write(` created ${testRel}\n`);
104
+ process.stdout.write(` updated CHANGELOG.md\n\n`);
105
+
106
+ // 4. Gate-file edits to make by hand (printed, never auto-applied).
107
+ process.stdout.write(`Remaining wiring (edit by hand — kept manual so a scaffold can't break a gate):\n\n`);
108
+ process.stdout.write(` docs/index.md\n add: N. [${title}](${slug}.7.md) - ${summary}\n\n`);
109
+ process.stdout.write(` package.json "test" chain\n add: && node ${testRel}\n\n`);
110
+ process.stdout.write(` scripts/release-check.js "docs presence" list\n add: "${docRel}",\n\n`);
111
+ process.stdout.write(` scripts/version-sync-check.js (assert the new surfaces carry the version)\n`);
112
+ process.stdout.write(` checkIncludes("plugins/cool-workflow/${docRel}", "${title}", checks);\n`);
113
+ process.stdout.write(` checkIncludes("plugins/cool-workflow/${docRel}", VERSION, checks);\n`);
114
+ process.stdout.write(` checkIncludes("plugins/cool-workflow/docs/index.md", "${slug}.7.md", checks);\n`);
115
+ process.stdout.write(` checkIncludes("plugins/cool-workflow/${testRel}", "${slug}-smoke", checks);\n\n`);
116
+ process.stdout.write(` src/capability-registry.ts — declare the new verb(s) so CLI === MCP (parity gate).\n`);
117
+ process.stdout.write(` sibling docs/*.7.md — append a "## ${title} (v${version})" forward-reference section\n`);
118
+ process.stdout.write(` to the docs version:sync checks for VERSION (that's the repo's per-release pattern).\n`);
119
+ }
120
+
121
+ main();