wicked-crew 0.6.0 → 0.7.1
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/README.md +103 -0
- package/dist/api/audit.d.ts +13 -0
- package/dist/api/audit.d.ts.map +1 -1
- package/dist/api/audit.js +18 -2
- package/dist/api/audit.js.map +1 -1
- package/dist/api/endpoint-manifest-live.d.ts +29 -0
- package/dist/api/endpoint-manifest-live.d.ts.map +1 -0
- package/dist/api/endpoint-manifest-live.js +72 -0
- package/dist/api/endpoint-manifest-live.js.map +1 -0
- package/dist/api/endpoint-manifest.d.ts +107 -0
- package/dist/api/endpoint-manifest.d.ts.map +1 -0
- package/dist/api/endpoint-manifest.js +108 -0
- package/dist/api/endpoint-manifest.js.map +1 -0
- package/dist/api/guidance-index.d.ts +39 -0
- package/dist/api/guidance-index.d.ts.map +1 -0
- package/dist/api/guidance-index.js +67 -0
- package/dist/api/guidance-index.js.map +1 -0
- package/dist/api/open-path.d.ts +16 -0
- package/dist/api/open-path.d.ts.map +1 -1
- package/dist/api/open-path.js +22 -0
- package/dist/api/open-path.js.map +1 -1
- package/dist/api/requirements.d.ts +7 -0
- package/dist/api/requirements.d.ts.map +1 -1
- package/dist/api/requirements.js +23 -2
- package/dist/api/requirements.js.map +1 -1
- package/dist/api/retry-index.d.ts +30 -0
- package/dist/api/retry-index.d.ts.map +1 -0
- package/dist/api/retry-index.js +45 -0
- package/dist/api/retry-index.js.map +1 -0
- package/dist/api/routes.d.ts +53 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +399 -30
- package/dist/api/routes.js.map +1 -1
- package/dist/api/run-files.d.ts +63 -0
- package/dist/api/run-files.d.ts.map +1 -0
- package/dist/api/run-files.js +271 -0
- package/dist/api/run-files.js.map +1 -0
- package/dist/api/server.d.ts +79 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +183 -8
- package/dist/api/server.js.map +1 -1
- package/dist/api/stall-watchdog.d.ts +62 -0
- package/dist/api/stall-watchdog.d.ts.map +1 -0
- package/dist/api/stall-watchdog.js +138 -0
- package/dist/api/stall-watchdog.js.map +1 -0
- package/dist/cli/index.js +89 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/core/adapter.d.ts +36 -10
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +245 -31
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/bridge-reaper.d.ts +134 -0
- package/dist/core/bridge-reaper.d.ts.map +1 -0
- package/dist/core/bridge-reaper.js +286 -0
- package/dist/core/bridge-reaper.js.map +1 -0
- package/dist/core/deliver.d.ts +118 -0
- package/dist/core/deliver.d.ts.map +1 -0
- package/dist/core/deliver.js +241 -0
- package/dist/core/deliver.js.map +1 -0
- package/dist/core/deliverable-floor.d.ts +155 -0
- package/dist/core/deliverable-floor.d.ts.map +1 -0
- package/dist/core/deliverable-floor.js +248 -0
- package/dist/core/deliverable-floor.js.map +1 -0
- package/dist/core/exec.d.ts +2 -0
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js.map +1 -1
- package/dist/core/types.d.ts +79 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -1
- package/dist/interactive/bridge-pool.d.ts +52 -0
- package/dist/interactive/bridge-pool.d.ts.map +1 -1
- package/dist/interactive/bridge-pool.js +93 -12
- package/dist/interactive/bridge-pool.js.map +1 -1
- package/dist/interactive/chat-events.d.ts +207 -0
- package/dist/interactive/chat-events.d.ts.map +1 -0
- package/dist/interactive/chat-events.js +769 -0
- package/dist/interactive/chat-events.js.map +1 -0
- package/dist/interactive/demo-events.d.ts +283 -0
- package/dist/interactive/demo-events.d.ts.map +1 -0
- package/dist/interactive/demo-events.js +889 -0
- package/dist/interactive/demo-events.js.map +1 -0
- package/dist/interactive/draft-events.d.ts +87 -7
- package/dist/interactive/draft-events.d.ts.map +1 -1
- package/dist/interactive/draft-events.js +352 -49
- package/dist/interactive/draft-events.js.map +1 -1
- package/dist/interactive/edit-events.d.ts +25 -2
- package/dist/interactive/edit-events.d.ts.map +1 -1
- package/dist/interactive/edit-events.js +88 -9
- package/dist/interactive/edit-events.js.map +1 -1
- package/dist/interactive/repo-snapshot.d.ts +100 -0
- package/dist/interactive/repo-snapshot.d.ts.map +1 -0
- package/dist/interactive/repo-snapshot.js +289 -0
- package/dist/interactive/repo-snapshot.js.map +1 -0
- package/dist/projects/graph-paths.d.ts +122 -0
- package/dist/projects/graph-paths.d.ts.map +1 -0
- package/dist/projects/graph-paths.js +175 -0
- package/dist/projects/graph-paths.js.map +1 -0
- package/dist/projects/graph.d.ts +191 -0
- package/dist/projects/graph.d.ts.map +1 -0
- package/dist/projects/graph.js +829 -0
- package/dist/projects/graph.js.map +1 -0
- package/dist/projects/routes.d.ts +17 -0
- package/dist/projects/routes.d.ts.map +1 -1
- package/dist/projects/routes.js +139 -0
- package/dist/projects/routes.js.map +1 -1
- package/dist/qe/ledger.d.ts +3 -2
- package/dist/qe/ledger.d.ts.map +1 -1
- package/dist/qe/ledger.js +5 -4
- package/dist/qe/ledger.js.map +1 -1
- package/dist/studio/assets/index-D-BFUYnY.js +530 -0
- package/dist/studio/assets/index-D6S9zUtO.css +32 -0
- package/dist/studio/index.html +5 -3
- package/endpoint-manifest.json +624 -0
- package/package.json +8 -5
- package/dist/studio/assets/index-CCwXa1cn.js +0 -428
- package/dist/studio/assets/index-HWxo0h41.css +0 -32
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# wicked-crew
|
|
2
|
+
|
|
3
|
+
**The harness for your agent harnesses** — run the coding-agent CLIs you already use as governed
|
|
4
|
+
workers through durable, multi-agent workflows. Intent in, verified work out: the evaluator is
|
|
5
|
+
structurally not the creator, gates are deny-dominates, and "done" is re-derived from evidence
|
|
6
|
+
instead of asserted.
|
|
7
|
+
|
|
8
|
+
Local-first: loopback only, no accounts, no billing, no model reselling. Crew drives the agents
|
|
9
|
+
*you* already pay for, on *your* auth and *your* plan.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
Requires Node.js ≥ 22 and at least one coding-agent CLI on your machine (e.g. Claude Code, Codex).
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g wicked-crew
|
|
17
|
+
wicked-crew serve
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or without installing:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx wicked-crew serve
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or use the family installer — [`npx wicked-installer`](https://www.npmjs.com/package/wicked-installer)
|
|
27
|
+
installs/updates the whole wicked-\* family, crew included.
|
|
28
|
+
|
|
29
|
+
`serve` starts the daemon on `http://127.0.0.1:7701` (override with `--port` or `CREW_PORT`) and
|
|
30
|
+
serves the bundled **wicked-studio** browser console same-origin — open the URL and you have the
|
|
31
|
+
control room: launch and steer runs, answer human gates, browse projects and evidence, watch live
|
|
32
|
+
engine events. Durable state (runs, evidence, event log) lives in `~/.wicked-crew/` (`--db`
|
|
33
|
+
overrides).
|
|
34
|
+
|
|
35
|
+
## Quickstart
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# 1. Start the daemon (leave it running)
|
|
39
|
+
wicked-crew serve
|
|
40
|
+
|
|
41
|
+
# 2. Launch a governed run — from the console at http://127.0.0.1:7701,
|
|
42
|
+
# or headless over the API:
|
|
43
|
+
curl -X POST http://127.0.0.1:7701/api/v1/runs \
|
|
44
|
+
-H 'content-type: application/json' \
|
|
45
|
+
-d '{"problem": "Add input validation to the signup form", "workflow": "feature"}'
|
|
46
|
+
|
|
47
|
+
# 3. Watch it move through the workflow's phases; approve or reject at the gates.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
One run token moves through the workflow's phases with a gate between every one — e.g. for
|
|
51
|
+
`feature`: clarify → design → build → adversarial-review → test → review. Each phase is executed
|
|
52
|
+
by a versioned skill on an assigned CLI worker; the gates decide, and every decision is audited.
|
|
53
|
+
|
|
54
|
+
## The governed-run model
|
|
55
|
+
|
|
56
|
+
- **You own the workflow; the agent owns the work.** The orchestrator sequences phases and holds
|
|
57
|
+
the gates; the agent does the coding inside a phase.
|
|
58
|
+
- **Workflows are data.** `feature` / `bug` / `migration` ship built-in; new ones are drop-in JSON
|
|
59
|
+
files, not code.
|
|
60
|
+
- **Gates are real, not self-graded.** Phase transitions resolve **deny-dominates** on a
|
|
61
|
+
deterministic structural floor, with an independent evaluator seat that reads cold evidence
|
|
62
|
+
only — a model may fail a gate, never solely approve one.
|
|
63
|
+
- **Evidence, not assertion.** "Done" is re-derived from evidence at the gate, never claimed by
|
|
64
|
+
the agent that did the work.
|
|
65
|
+
|
|
66
|
+
The engine underneath is [wicked-core](https://github.com/mikeparcewski/wicked-core) (Rust,
|
|
67
|
+
single-writer), embedded via the `wicked-core-ts` napi bridge — the daemon is a thin REST+WS
|
|
68
|
+
layer over it.
|
|
69
|
+
|
|
70
|
+
## The acceptance gate
|
|
71
|
+
|
|
72
|
+
`GET /api/v1/runs/:id/acceptance` answers "does the QE evidence ledger accept this run's work?" —
|
|
73
|
+
crew's machine gate, absorbed from the retired wicked-testing product. <!-- historical --> It reads the repo's
|
|
74
|
+
evidence ledger (a wicked-ledger store at `<repo>/.wicked-qe/`, written by wicked-garden's QE
|
|
75
|
+
skills; legacy `.wicked-testing/` ledgers are still read) and resolves the workflow's acceptance
|
|
76
|
+
requirement **deny-dominates**: only a `PASS` verdict satisfies it. `FAIL`, `CONDITIONAL`,
|
|
77
|
+
`PARTIAL`, `INCONCLUSIVE`, a missing ledger, or a missing verdict each deny with their own named
|
|
78
|
+
reason — no evidence is never a pass. The route always returns 200 for a known run ("no verdict"
|
|
79
|
+
is a real answer about the gate, not an error); `?qeRun=<id>` pins the read to one QE run.
|
|
80
|
+
|
|
81
|
+
## CLI
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
wicked-crew serve|start|resume|gate|status|mcp
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| command | what it does |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `serve` | run the daemon: REST `/api/v1` + WS `/ws` + the bundled studio console |
|
|
90
|
+
| `start` | boot and launch a run headless (`--problem`, `--workflow`, `--repo`) |
|
|
91
|
+
| `resume` | resume a persisted run (`--session <id>`) |
|
|
92
|
+
| `gate` | answer a pending human gate from the terminal |
|
|
93
|
+
| `status` | inspect run state |
|
|
94
|
+
| `mcp` | stdio MCP server — crew-as-a-tool for coding agents |
|
|
95
|
+
|
|
96
|
+
## Links
|
|
97
|
+
|
|
98
|
+
- Repo: <https://github.com/mikeparcewski/wicked-crew>
|
|
99
|
+
- Site & docs: <https://wc.wickedagile.com>
|
|
100
|
+
- Wire contract (every `/api/v1` + `/ws` shape, types-only): [`wicked-crew-api-types`](https://www.npmjs.com/package/wicked-crew-api-types)
|
|
101
|
+
- Engine: [wicked-core](https://github.com/mikeparcewski/wicked-core) · Console: [wicked-studio](https://github.com/mikeparcewski/wicked-studio)
|
|
102
|
+
|
|
103
|
+
MIT
|
package/dist/api/audit.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface AuditReadFilter {
|
|
|
21
21
|
/** Max entries returned (newest first). Default 200, capped at 1000. */
|
|
22
22
|
limit?: number;
|
|
23
23
|
}
|
|
24
|
+
/** `readAll`'s filter: no limit — the whole point is that nothing gets trimmed. */
|
|
25
|
+
export type AuditScanFilter = Omit<AuditReadFilter, 'limit'>;
|
|
24
26
|
export declare class AuditLog {
|
|
25
27
|
readonly path: string;
|
|
26
28
|
private readonly warn;
|
|
@@ -53,5 +55,16 @@ export declare class AuditLog {
|
|
|
53
55
|
* still answers.
|
|
54
56
|
*/
|
|
55
57
|
read(filter?: AuditReadFilter): Promise<AuditEntry[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Read the trail EXHAUSTIVELY, newest first — no read cap. The hydrate path
|
|
60
|
+
* for the boot-time indexes (`RetryIndex`, `GuidanceIndex`): durable facts
|
|
61
|
+
* like retry lineage must not vanish because 1000+ newer launches landed on
|
|
62
|
+
* top of them (BRIEF-UX-002 C5). Same cost class as `read` — that already
|
|
63
|
+
* parses the whole file and only trims the returned slice — so this is a
|
|
64
|
+
* one-time full-file scan at boot, measured at ~19ms on a 19k-line live trail.
|
|
65
|
+
*/
|
|
66
|
+
readAll(filter?: AuditScanFilter): Promise<AuditEntry[]>;
|
|
67
|
+
/** The shared full-file parse: every matching entry, newest first, uncapped. */
|
|
68
|
+
private scan;
|
|
56
69
|
}
|
|
57
70
|
//# sourceMappingURL=audit.d.ts.map
|
package/dist/api/audit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/api/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE1D,qDAAqD;AACrD,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE7E;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,QAAQ;IAMjB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IANvB,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;gBAGd,IAAI,GAAE,MAA2B,EACzB,IAAI,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAA6B;IAGvE;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,IAAI,QAAQ;IAMvB;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,IAAI;IAyBzG,wCAAwC;IACxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB;;;;OAIG;IACG,IAAI,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/api/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE1D,qDAAqD;AACrD,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE7E;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,mFAAmF;AACnF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AAE7D,qBAAa,QAAQ;IAMjB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IANvB,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;gBAGd,IAAI,GAAE,MAA2B,EACzB,IAAI,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAA6B;IAGvE;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,IAAI,QAAQ;IAMvB;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,IAAI;IAyBzG,wCAAwC;IACxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB;;;;OAIG;IACG,IAAI,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAM3D;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAIxD,gFAAgF;YAClE,IAAI;CA0BnB"}
|
package/dist/api/audit.js
CHANGED
|
@@ -77,6 +77,23 @@ export class AuditLog {
|
|
|
77
77
|
* still answers.
|
|
78
78
|
*/
|
|
79
79
|
async read(filter) {
|
|
80
|
+
const entries = await this.scan(filter);
|
|
81
|
+
const limit = Math.min(Math.max(filter?.limit ?? 200, 1), 1000);
|
|
82
|
+
return entries.slice(0, limit);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Read the trail EXHAUSTIVELY, newest first — no read cap. The hydrate path
|
|
86
|
+
* for the boot-time indexes (`RetryIndex`, `GuidanceIndex`): durable facts
|
|
87
|
+
* like retry lineage must not vanish because 1000+ newer launches landed on
|
|
88
|
+
* top of them (BRIEF-UX-002 C5). Same cost class as `read` — that already
|
|
89
|
+
* parses the whole file and only trims the returned slice — so this is a
|
|
90
|
+
* one-time full-file scan at boot, measured at ~19ms on a 19k-line live trail.
|
|
91
|
+
*/
|
|
92
|
+
readAll(filter) {
|
|
93
|
+
return this.scan(filter);
|
|
94
|
+
}
|
|
95
|
+
/** The shared full-file parse: every matching entry, newest first, uncapped. */
|
|
96
|
+
async scan(filter) {
|
|
80
97
|
if (this.disabled)
|
|
81
98
|
return [];
|
|
82
99
|
await this.flush();
|
|
@@ -108,8 +125,7 @@ export class AuditLog {
|
|
|
108
125
|
}
|
|
109
126
|
}
|
|
110
127
|
entries.reverse(); // file order is append order; the API answers newest first
|
|
111
|
-
|
|
112
|
-
return entries.slice(0, limit);
|
|
128
|
+
return entries;
|
|
113
129
|
}
|
|
114
130
|
}
|
|
115
131
|
//# sourceMappingURL=audit.js.map
|
package/dist/api/audit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/api/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG1C,qDAAqD;AACrD,MAAM,UAAU,gBAAgB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACnE,OAAO,GAAG,CAAC,uBAAuB,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC;AACtF,CAAC;
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/api/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the LIVE endpoint manifest by booting the real server assembly (TH-11).
|
|
3
|
+
*
|
|
4
|
+
* A separate module from endpoint-manifest.ts on purpose: this imports `createServer` while
|
|
5
|
+
* server.ts imports the hook installer — folding both into one file would make the cycle
|
|
6
|
+
* structural instead of avoiding it.
|
|
7
|
+
*
|
|
8
|
+
* # Why boot `createServer` rather than re-list routes by hand
|
|
9
|
+
*
|
|
10
|
+
* The manifest's whole value is that it cannot drift from the daemon: it is read from the same
|
|
11
|
+
* `onRoute` hook, over the same registration calls (`registerRoutes`, project routes, terminal
|
|
12
|
+
* WS, `/ws`, interactive event routes), that the served daemon runs. A hand-maintained list would
|
|
13
|
+
* be a second spelling of the route table — the exact thing the manifest exists to end.
|
|
14
|
+
*
|
|
15
|
+
* # What is faked, and why it is safe
|
|
16
|
+
*
|
|
17
|
+
* Route REGISTRATION never calls the engine — only handlers do, and no request is ever injected
|
|
18
|
+
* here. The adapter stub answers the three things `createServer` itself asks at boot:
|
|
19
|
+
* `getSettings()` (worker-config root export), `projectsSupported()` → false (skips membership
|
|
20
|
+
* hydration), and `stub: true` (any answering seam that were ever armed would refuse rather than
|
|
21
|
+
* subscribe). Every optional seam is disabled explicitly, the audit trail goes to a temp file
|
|
22
|
+
* (never `~/.wicked-crew/audit.log`), and `studioRoot` points at a nonexistent directory so the
|
|
23
|
+
* server boots HEADLESS — deliberately: the static wildcard route depends on whether a studio
|
|
24
|
+
* bundle is installed on the generating machine, and a committed manifest must not vary by
|
|
25
|
+
* install state. The manifest documents the API + WS surface.
|
|
26
|
+
*/
|
|
27
|
+
import { type EndpointManifest } from './endpoint-manifest.js';
|
|
28
|
+
export declare function collectLiveEndpointManifest(): Promise<EndpointManifest>;
|
|
29
|
+
//# sourceMappingURL=endpoint-manifest-live.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-manifest-live.d.ts","sourceRoot":"","sources":["../../src/api/endpoint-manifest-live.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAQH,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAEtF,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAmC7E"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the LIVE endpoint manifest by booting the real server assembly (TH-11).
|
|
3
|
+
*
|
|
4
|
+
* A separate module from endpoint-manifest.ts on purpose: this imports `createServer` while
|
|
5
|
+
* server.ts imports the hook installer — folding both into one file would make the cycle
|
|
6
|
+
* structural instead of avoiding it.
|
|
7
|
+
*
|
|
8
|
+
* # Why boot `createServer` rather than re-list routes by hand
|
|
9
|
+
*
|
|
10
|
+
* The manifest's whole value is that it cannot drift from the daemon: it is read from the same
|
|
11
|
+
* `onRoute` hook, over the same registration calls (`registerRoutes`, project routes, terminal
|
|
12
|
+
* WS, `/ws`, interactive event routes), that the served daemon runs. A hand-maintained list would
|
|
13
|
+
* be a second spelling of the route table — the exact thing the manifest exists to end.
|
|
14
|
+
*
|
|
15
|
+
* # What is faked, and why it is safe
|
|
16
|
+
*
|
|
17
|
+
* Route REGISTRATION never calls the engine — only handlers do, and no request is ever injected
|
|
18
|
+
* here. The adapter stub answers the three things `createServer` itself asks at boot:
|
|
19
|
+
* `getSettings()` (worker-config root export), `projectsSupported()` → false (skips membership
|
|
20
|
+
* hydration), and `stub: true` (any answering seam that were ever armed would refuse rather than
|
|
21
|
+
* subscribe). Every optional seam is disabled explicitly, the audit trail goes to a temp file
|
|
22
|
+
* (never `~/.wicked-crew/audit.log`), and `studioRoot` points at a nonexistent directory so the
|
|
23
|
+
* server boots HEADLESS — deliberately: the static wildcard route depends on whether a studio
|
|
24
|
+
* bundle is installed on the generating machine, and a committed manifest must not vary by
|
|
25
|
+
* install state. The manifest documents the API + WS surface.
|
|
26
|
+
*/
|
|
27
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
28
|
+
import { tmpdir } from 'node:os';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
import { createServer } from './server.js';
|
|
31
|
+
import { buildEndpointManifest } from './endpoint-manifest.js';
|
|
32
|
+
export async function collectLiveEndpointManifest() {
|
|
33
|
+
// Deterministic boot: auth mode is pinned OFF via options (no env/file resolution), and the
|
|
34
|
+
// logger is silenced for the duration — this runs inside test output and CI logs.
|
|
35
|
+
const savedLogLevel = process.env['LOG_LEVEL'];
|
|
36
|
+
process.env['LOG_LEVEL'] = 'silent';
|
|
37
|
+
const scratch = mkdtempSync(join(tmpdir(), 'crew-endpoint-manifest-'));
|
|
38
|
+
const adapter = {
|
|
39
|
+
stub: true,
|
|
40
|
+
projectsSupported: () => false,
|
|
41
|
+
getSettings: async () => ({}),
|
|
42
|
+
// The daemon's single CoreEvent fan-out subscribes at boot; no event ever arrives here.
|
|
43
|
+
onEvent: () => () => { },
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
const app = await createServer(adapter, {
|
|
47
|
+
auth: { mode: 'off' },
|
|
48
|
+
auditPath: join(scratch, 'audit.log'),
|
|
49
|
+
projectEvents: { disabled: true },
|
|
50
|
+
interactiveWsRelay: { disabled: true },
|
|
51
|
+
seatHealthProbe: { enabled: false },
|
|
52
|
+
stallWatchdog: { enabled: false },
|
|
53
|
+
// Nonexistent on purpose — headless boot; see the module header.
|
|
54
|
+
studioRoot: join(scratch, 'no-studio-bundle'),
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
await app.ready();
|
|
58
|
+
return buildEndpointManifest(app.endpointManifest ?? []);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await app.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
if (savedLogLevel === undefined)
|
|
66
|
+
delete process.env['LOG_LEVEL'];
|
|
67
|
+
else
|
|
68
|
+
process.env['LOG_LEVEL'] = savedLogLevel;
|
|
69
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=endpoint-manifest-live.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-manifest-live.js","sourceRoot":"","sources":["../../src/api/endpoint-manifest-live.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,qBAAqB,EAAyB,MAAM,wBAAwB,CAAC;AAEtF,MAAM,CAAC,KAAK,UAAU,2BAA2B;IAC/C,4FAA4F;IAC5F,kFAAkF;IAClF,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,QAAQ,CAAC;IACpC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,yBAAyB,CAAC,CAAC,CAAC;IACvE,MAAM,OAAO,GAAG;QACd,IAAI,EAAE,IAAI;QACV,iBAAiB,EAAE,GAAG,EAAE,CAAC,KAAK;QAC9B,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;QAC7B,wFAAwF;QACxF,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;KACE,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE;YACtC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;YACrB,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC;YACrC,aAAa,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YACjC,kBAAkB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YACtC,eAAe,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;YACnC,aAAa,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;YACjC,iEAAiE;YACjE,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;YAClB,OAAO,qBAAqB,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;gBAAS,CAAC;YACT,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,aAAa,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;;YAC5D,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,aAAa,CAAC;QAC9C,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The endpoint manifest (TH-11) — crew's route table as a MACHINE-READABLE build artifact.
|
|
3
|
+
*
|
|
4
|
+
* # Why this exists
|
|
5
|
+
*
|
|
6
|
+
* routes.ts declares zero fastify schemas (verified: grep `schema:` = 0), no OpenAPI spec exists
|
|
7
|
+
* anywhere in crew, and `wicked-crew-api-types` is types-only with no endpoint-to-type binding —
|
|
8
|
+
* so nothing machine-readable said which methods+paths this daemon serves, what a request body is
|
|
9
|
+
* called, or which status codes a route can answer. Request validation is NOT a vacuum (13
|
|
10
|
+
* `.strict()` zod objects guard the bodies), but zod objects are route-internal: a test harness,
|
|
11
|
+
* a drift check, or an API-test generator had nothing to read. With no fastify schemas declared,
|
|
12
|
+
* `@fastify/swagger` has nothing to generate from either — the `onRoute` hook IS the cheap path.
|
|
13
|
+
*
|
|
14
|
+
* # How it works
|
|
15
|
+
*
|
|
16
|
+
* {@link installEndpointManifestHook} registers a fastify `onRoute` hook (added by `createServer`
|
|
17
|
+
* BEFORE any route registration, so every route the daemon serves is seen) that accumulates one
|
|
18
|
+
* {@link EndpointEntry} per method+path. Two declaration channels feed the type/status fields:
|
|
19
|
+
*
|
|
20
|
+
* 1. `config.manifest` on the route options ({@link ManifestRouteConfig}) — the explicit
|
|
21
|
+
* channel. Names should reference `wicked-crew-api-types` exports where one exists
|
|
22
|
+
* (`LaunchRunBody`, `GateDecision`, …) so the manifest binds endpoints to the published
|
|
23
|
+
* wire contract; a structural spelling (`'{ runId: string }'`) is the honest fallback for
|
|
24
|
+
* wire shapes the contract has no name for.
|
|
25
|
+
* 2. `schema.response` keys, when fastify schemas start existing (the longer-term
|
|
26
|
+
* zod-to-json-schema path) — status codes are folded in automatically, so migrating a route
|
|
27
|
+
* to real fastify schemas enriches the manifest without touching this module.
|
|
28
|
+
*
|
|
29
|
+
* Routes that declare neither still land in the manifest with `requestType`/`responseType` null
|
|
30
|
+
* and `statusCodes: []` — the manifest records "where declared", never invents.
|
|
31
|
+
*
|
|
32
|
+
* # What consumes it
|
|
33
|
+
*
|
|
34
|
+
* - `scripts/generate-endpoint-manifest.ts` (npm run manifest:endpoints) writes the committed
|
|
35
|
+
* `endpoint-manifest.json` at the package root — the build artifact.
|
|
36
|
+
* - `tests/endpoint-manifest.test.ts` rebuilds the live route table and fails on ANY drift from
|
|
37
|
+
* the committed file, so an added/removed/renamed endpoint fails CI until the manifest is
|
|
38
|
+
* regenerated and the diff reviewed. The manifest diff IS the API regression trigger.
|
|
39
|
+
* - `scripts/generate-api-tests.ts` derives positive + 400/404/409 negative API tests from the
|
|
40
|
+
* committed manifest (see `tests/generated/`).
|
|
41
|
+
*
|
|
42
|
+
* HEAD entries are dropped: fastify v5 auto-exposes a HEAD twin for every GET
|
|
43
|
+
* (`exposeHeadRoutes`), which would double the GET surface with rows nobody declared.
|
|
44
|
+
*/
|
|
45
|
+
import type { FastifyInstance } from 'fastify';
|
|
46
|
+
/** The explicit per-route declaration channel — `{ config: { manifest: {...} } }` on a route. */
|
|
47
|
+
export interface ManifestRouteConfig {
|
|
48
|
+
/** Request-body type name — a `wicked-crew-api-types` export where one exists. */
|
|
49
|
+
requestType?: string;
|
|
50
|
+
/** Success-response type name, or a structural spelling when the contract has no name for it. */
|
|
51
|
+
responseType?: string;
|
|
52
|
+
/** Every status code this route answers on purpose (success + named error codes). */
|
|
53
|
+
statusCodes?: number[];
|
|
54
|
+
}
|
|
55
|
+
declare module 'fastify' {
|
|
56
|
+
interface FastifyContextConfig {
|
|
57
|
+
/** TH-11 — this route's row in the committed endpoint manifest. */
|
|
58
|
+
manifest?: ManifestRouteConfig;
|
|
59
|
+
}
|
|
60
|
+
interface FastifyInstance {
|
|
61
|
+
/** Accumulated by `createServer`'s onRoute hook; read after `app.ready()`. */
|
|
62
|
+
endpointManifest?: EndpointEntry[];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** One method+path the daemon serves. */
|
|
66
|
+
export interface EndpointEntry {
|
|
67
|
+
method: string;
|
|
68
|
+
path: string;
|
|
69
|
+
/** Declared request-body type name, or null — "where declared", never invented. */
|
|
70
|
+
requestType: string | null;
|
|
71
|
+
/** Declared response type name, or null. */
|
|
72
|
+
responseType: string | null;
|
|
73
|
+
/** Declared status codes (config.manifest + fastify schema.response keys), sorted. */
|
|
74
|
+
statusCodes: number[];
|
|
75
|
+
/** Present (true) only on websocket routes (`/ws`, `/ws/terminals/:id`). */
|
|
76
|
+
websocket?: boolean;
|
|
77
|
+
}
|
|
78
|
+
/** The committed artifact's shape (endpoint-manifest.json). */
|
|
79
|
+
export interface EndpointManifest {
|
|
80
|
+
version: 1;
|
|
81
|
+
/**
|
|
82
|
+
* The `wicked-crew-api-types` version this route table was generated against — the published
|
|
83
|
+
* wire contract the type names bind to. Evidence manifests that cite endpoints should carry
|
|
84
|
+
* this (recon R8/R11: api-types drift is live; studio pinned ^0.8.0 against 0.10.0).
|
|
85
|
+
*/
|
|
86
|
+
apiTypesVersion: string;
|
|
87
|
+
/** Sorted by path, then method — a stable order so drift diffs are readable. */
|
|
88
|
+
endpoints: EndpointEntry[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Register the accumulating `onRoute` hook. MUST run before any route is registered — fastify
|
|
92
|
+
* only replays `onRoute` for routes added after the hook exists. Returns the live array the hook
|
|
93
|
+
* appends to; `createServer` exposes it as `app.endpointManifest`.
|
|
94
|
+
*/
|
|
95
|
+
export declare function installEndpointManifestHook(app: FastifyInstance): EndpointEntry[];
|
|
96
|
+
/**
|
|
97
|
+
* The published wire contract's version, read from the actually-installed dependency.
|
|
98
|
+
*
|
|
99
|
+
* NOT `require('wicked-crew-api-types/package.json')`: that package's exports map exposes only
|
|
100
|
+
* `.` (types-only, no runtime condition), so both the subpath and a bare resolve are refused.
|
|
101
|
+
* Walking the resolver's candidate directories and reading the file with fs sidesteps the
|
|
102
|
+
* exports map while still honoring the real resolution order (workspace link included).
|
|
103
|
+
*/
|
|
104
|
+
export declare function apiTypesVersion(): string;
|
|
105
|
+
/** Entries → the committed artifact: stable order, contract version stamped. */
|
|
106
|
+
export declare function buildEndpointManifest(entries: EndpointEntry[]): EndpointManifest;
|
|
107
|
+
//# sourceMappingURL=endpoint-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-manifest.d.ts","sourceRoot":"","sources":["../../src/api/endpoint-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAC;AAE7D,iGAAiG;AACjG,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iGAAiG;IACjG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,oBAAoB;QAC5B,mEAAmE;QACnE,QAAQ,CAAC,EAAE,mBAAmB,CAAC;KAChC;IACD,UAAU,eAAe;QACvB,8EAA8E;QAC9E,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC;KACpC;CACF;AAED,yCAAyC;AACzC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,4CAA4C;IAC5C,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,sFAAsF;IACtF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,+DAA+D;AAC/D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,CAAC,CAAC;IACX;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,eAAe,GAAG,aAAa,EAAE,CAgCjF;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAWxC;AAED,gFAAgF;AAChF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,gBAAgB,CAKhF"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The endpoint manifest (TH-11) — crew's route table as a MACHINE-READABLE build artifact.
|
|
3
|
+
*
|
|
4
|
+
* # Why this exists
|
|
5
|
+
*
|
|
6
|
+
* routes.ts declares zero fastify schemas (verified: grep `schema:` = 0), no OpenAPI spec exists
|
|
7
|
+
* anywhere in crew, and `wicked-crew-api-types` is types-only with no endpoint-to-type binding —
|
|
8
|
+
* so nothing machine-readable said which methods+paths this daemon serves, what a request body is
|
|
9
|
+
* called, or which status codes a route can answer. Request validation is NOT a vacuum (13
|
|
10
|
+
* `.strict()` zod objects guard the bodies), but zod objects are route-internal: a test harness,
|
|
11
|
+
* a drift check, or an API-test generator had nothing to read. With no fastify schemas declared,
|
|
12
|
+
* `@fastify/swagger` has nothing to generate from either — the `onRoute` hook IS the cheap path.
|
|
13
|
+
*
|
|
14
|
+
* # How it works
|
|
15
|
+
*
|
|
16
|
+
* {@link installEndpointManifestHook} registers a fastify `onRoute` hook (added by `createServer`
|
|
17
|
+
* BEFORE any route registration, so every route the daemon serves is seen) that accumulates one
|
|
18
|
+
* {@link EndpointEntry} per method+path. Two declaration channels feed the type/status fields:
|
|
19
|
+
*
|
|
20
|
+
* 1. `config.manifest` on the route options ({@link ManifestRouteConfig}) — the explicit
|
|
21
|
+
* channel. Names should reference `wicked-crew-api-types` exports where one exists
|
|
22
|
+
* (`LaunchRunBody`, `GateDecision`, …) so the manifest binds endpoints to the published
|
|
23
|
+
* wire contract; a structural spelling (`'{ runId: string }'`) is the honest fallback for
|
|
24
|
+
* wire shapes the contract has no name for.
|
|
25
|
+
* 2. `schema.response` keys, when fastify schemas start existing (the longer-term
|
|
26
|
+
* zod-to-json-schema path) — status codes are folded in automatically, so migrating a route
|
|
27
|
+
* to real fastify schemas enriches the manifest without touching this module.
|
|
28
|
+
*
|
|
29
|
+
* Routes that declare neither still land in the manifest with `requestType`/`responseType` null
|
|
30
|
+
* and `statusCodes: []` — the manifest records "where declared", never invents.
|
|
31
|
+
*
|
|
32
|
+
* # What consumes it
|
|
33
|
+
*
|
|
34
|
+
* - `scripts/generate-endpoint-manifest.ts` (npm run manifest:endpoints) writes the committed
|
|
35
|
+
* `endpoint-manifest.json` at the package root — the build artifact.
|
|
36
|
+
* - `tests/endpoint-manifest.test.ts` rebuilds the live route table and fails on ANY drift from
|
|
37
|
+
* the committed file, so an added/removed/renamed endpoint fails CI until the manifest is
|
|
38
|
+
* regenerated and the diff reviewed. The manifest diff IS the API regression trigger.
|
|
39
|
+
* - `scripts/generate-api-tests.ts` derives positive + 400/404/409 negative API tests from the
|
|
40
|
+
* committed manifest (see `tests/generated/`).
|
|
41
|
+
*
|
|
42
|
+
* HEAD entries are dropped: fastify v5 auto-exposes a HEAD twin for every GET
|
|
43
|
+
* (`exposeHeadRoutes`), which would double the GET surface with rows nobody declared.
|
|
44
|
+
*/
|
|
45
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
46
|
+
import { createRequire } from 'node:module';
|
|
47
|
+
import { join } from 'node:path';
|
|
48
|
+
/**
|
|
49
|
+
* Register the accumulating `onRoute` hook. MUST run before any route is registered — fastify
|
|
50
|
+
* only replays `onRoute` for routes added after the hook exists. Returns the live array the hook
|
|
51
|
+
* appends to; `createServer` exposes it as `app.endpointManifest`.
|
|
52
|
+
*/
|
|
53
|
+
export function installEndpointManifestHook(app) {
|
|
54
|
+
const entries = [];
|
|
55
|
+
app.addHook('onRoute', (route) => {
|
|
56
|
+
const methods = Array.isArray(route.method) ? route.method : [route.method];
|
|
57
|
+
const declared = route.config?.manifest;
|
|
58
|
+
// The future-proofing channel: fastify response schemas keyed by status code. None exist
|
|
59
|
+
// today (routes.ts declares zero), but the fold means the zod→fastify-schema migration
|
|
60
|
+
// enriches the manifest for free.
|
|
61
|
+
const schemaResponse = route.schema
|
|
62
|
+
?.response;
|
|
63
|
+
const schemaCodes = Object.keys(schemaResponse ?? {})
|
|
64
|
+
.map(Number)
|
|
65
|
+
.filter((n) => Number.isInteger(n) && n >= 100 && n <= 599);
|
|
66
|
+
const statusCodes = [...new Set([...(declared?.statusCodes ?? []), ...schemaCodes])].sort((a, b) => a - b);
|
|
67
|
+
const websocket = route.websocket === true;
|
|
68
|
+
for (const method of methods) {
|
|
69
|
+
// Fastify v5 auto-exposes a HEAD twin per GET; recording it would double the GET surface
|
|
70
|
+
// with rows nobody declared and no client calls on purpose.
|
|
71
|
+
if (method === 'HEAD')
|
|
72
|
+
continue;
|
|
73
|
+
entries.push({
|
|
74
|
+
method,
|
|
75
|
+
path: route.url,
|
|
76
|
+
requestType: declared?.requestType ?? null,
|
|
77
|
+
responseType: declared?.responseType ?? null,
|
|
78
|
+
statusCodes,
|
|
79
|
+
...(websocket ? { websocket: true } : {}),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return entries;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The published wire contract's version, read from the actually-installed dependency.
|
|
87
|
+
*
|
|
88
|
+
* NOT `require('wicked-crew-api-types/package.json')`: that package's exports map exposes only
|
|
89
|
+
* `.` (types-only, no runtime condition), so both the subpath and a bare resolve are refused.
|
|
90
|
+
* Walking the resolver's candidate directories and reading the file with fs sidesteps the
|
|
91
|
+
* exports map while still honoring the real resolution order (workspace link included).
|
|
92
|
+
*/
|
|
93
|
+
export function apiTypesVersion() {
|
|
94
|
+
const require = createRequire(import.meta.url);
|
|
95
|
+
for (const dir of require.resolve.paths('wicked-crew-api-types') ?? []) {
|
|
96
|
+
const pkgPath = join(dir, 'wicked-crew-api-types', 'package.json');
|
|
97
|
+
if (existsSync(pkgPath)) {
|
|
98
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8')).version;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw new Error('wicked-crew-api-types is not resolvable from packages/crew — the manifest cannot stamp the wire-contract version');
|
|
102
|
+
}
|
|
103
|
+
/** Entries → the committed artifact: stable order, contract version stamped. */
|
|
104
|
+
export function buildEndpointManifest(entries) {
|
|
105
|
+
const endpoints = [...entries].sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
|
|
106
|
+
return { version: 1, apiTypesVersion: apiTypesVersion(), endpoints };
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=endpoint-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-manifest.js","sourceRoot":"","sources":["../../src/api/endpoint-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAmDjC;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,GAAoB;IAC9D,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,KAAwD,EAAE,EAAE;QAClF,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5E,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC;QACxC,yFAAyF;QACzF,uFAAuF;QACvF,kCAAkC;QAClC,MAAM,cAAc,GAAI,KAAK,CAAC,MAA6D;YACzF,EAAE,QAAQ,CAAC;QACb,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC;aAClD,GAAG,CAAC,MAAM,CAAC;aACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAC9D,MAAM,WAAW,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,IAAI,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CACvF,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAChB,CAAC;QACF,MAAM,SAAS,GAAI,KAAiC,CAAC,SAAS,KAAK,IAAI,CAAC;QACxE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,yFAAyF;YACzF,4DAA4D;YAC5D,IAAI,MAAM,KAAK,MAAM;gBAAE,SAAS;YAChC,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM;gBACN,IAAI,EAAE,KAAK,CAAC,GAAG;gBACf,WAAW,EAAE,QAAQ,EAAE,WAAW,IAAI,IAAI;gBAC1C,YAAY,EAAE,QAAQ,EAAE,YAAY,IAAI,IAAI;gBAC5C,WAAW;gBACX,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnD,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,IAAI,EAAE,EAAE,CAAC;QACvE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,uBAAuB,EAAE,cAAc,CAAC,CAAC;QACnE,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACxB,OAAQ,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAyB,CAAC,OAAO,CAAC;QACpF,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CACb,kHAAkH,CACnH,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,qBAAqB,CAAC,OAAwB;IAC5D,MAAM,SAAS,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CACjC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAC3E,CAAC;IACF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,eAAe,EAAE,eAAe,EAAE,EAAE,SAAS,EAAE,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run→operator-guidance index (DES-UX-002 §7.2, CREW-UX-7).
|
|
3
|
+
*
|
|
4
|
+
* NAMING NOTE: DES-UX-002 §7.2 labels this slice CREW-UX-4, but that id was already spent on an
|
|
5
|
+
* unrelated merged slice (crew#308) — this implementation is CREW-UX-7; the doc's §7.2 spec is
|
|
6
|
+
* what it implements.
|
|
7
|
+
*
|
|
8
|
+
* A durable pre-gate annotation: ONE operator guidance note per run, upserted via
|
|
9
|
+
* `PUT /runs/:id/guidance` and echoed as `AgentSession.guidance` on the run DTOs. The engine's
|
|
10
|
+
* run record carries no such field (wicked-core-ts 0.6.x), so — exactly like retry lineage
|
|
11
|
+
* (CREW-UX-3) and the actor trail (task #88) — the durable record is the audit trail: every
|
|
12
|
+
* write lands as a `guidance.set` entry whose `detail.text` is the full note (empty string =
|
|
13
|
+
* cleared), and this map is the read-side latency layer that lets the run DTOs echo it without
|
|
14
|
+
* a trail scan per request.
|
|
15
|
+
*
|
|
16
|
+
* Mirrors `RetryIndex`'s posture: hydrated once at server start (best-effort — a missing or
|
|
17
|
+
* unreadable trail leaves guidance blank, the pre-CREW-UX-7 behavior, not an error) and updated
|
|
18
|
+
* at the same post-commit point that writes the audit entry, so the map can only lag by a
|
|
19
|
+
* failed hydrate, never diverge silently.
|
|
20
|
+
*/
|
|
21
|
+
import type { AuditLog } from './audit.js';
|
|
22
|
+
export declare class GuidanceIndex {
|
|
23
|
+
private readonly runToGuidance;
|
|
24
|
+
/**
|
|
25
|
+
* Load guidance from EVERY `guidance.set` entry in the trail — exhaustively, not capped
|
|
26
|
+
* (BRIEF-UX-002 C5, same defect as `RetryIndex.hydrate`: a durable note must not vanish
|
|
27
|
+
* because 1000+ newer writes landed on top of it). The trail answers newest first, so the
|
|
28
|
+
* FIRST entry seen per run is the current note — later (older) entries for the same run
|
|
29
|
+
* are superseded writes and are skipped, which is also what keeps a cleared note (newest
|
|
30
|
+
* entry has `text: ''`) from being resurrected by an older non-empty one. Still best-effort,
|
|
31
|
+
* like `RetryIndex.hydrate`; cost is one full-file scan at boot.
|
|
32
|
+
*/
|
|
33
|
+
hydrate(audit: AuditLog, log?: (msg: string) => void): Promise<void>;
|
|
34
|
+
/** Upsert the note; the empty string CLEARS it (the DTO field goes back to absent). */
|
|
35
|
+
set(runId: string, text: string): void;
|
|
36
|
+
/** The note for this run, or `undefined` (the DTO spells that as an ABSENT field). */
|
|
37
|
+
guidanceFor(runId: string): string | undefined;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=guidance-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guidance-index.d.ts","sourceRoot":"","sources":["../../src/api/guidance-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAE3D;;;;;;;;OAQG;IACG,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB1E,uFAAuF;IACvF,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKtC,sFAAsF;IACtF,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAG/C"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run→operator-guidance index (DES-UX-002 §7.2, CREW-UX-7).
|
|
3
|
+
*
|
|
4
|
+
* NAMING NOTE: DES-UX-002 §7.2 labels this slice CREW-UX-4, but that id was already spent on an
|
|
5
|
+
* unrelated merged slice (crew#308) — this implementation is CREW-UX-7; the doc's §7.2 spec is
|
|
6
|
+
* what it implements.
|
|
7
|
+
*
|
|
8
|
+
* A durable pre-gate annotation: ONE operator guidance note per run, upserted via
|
|
9
|
+
* `PUT /runs/:id/guidance` and echoed as `AgentSession.guidance` on the run DTOs. The engine's
|
|
10
|
+
* run record carries no such field (wicked-core-ts 0.6.x), so — exactly like retry lineage
|
|
11
|
+
* (CREW-UX-3) and the actor trail (task #88) — the durable record is the audit trail: every
|
|
12
|
+
* write lands as a `guidance.set` entry whose `detail.text` is the full note (empty string =
|
|
13
|
+
* cleared), and this map is the read-side latency layer that lets the run DTOs echo it without
|
|
14
|
+
* a trail scan per request.
|
|
15
|
+
*
|
|
16
|
+
* Mirrors `RetryIndex`'s posture: hydrated once at server start (best-effort — a missing or
|
|
17
|
+
* unreadable trail leaves guidance blank, the pre-CREW-UX-7 behavior, not an error) and updated
|
|
18
|
+
* at the same post-commit point that writes the audit entry, so the map can only lag by a
|
|
19
|
+
* failed hydrate, never diverge silently.
|
|
20
|
+
*/
|
|
21
|
+
export class GuidanceIndex {
|
|
22
|
+
runToGuidance = new Map();
|
|
23
|
+
/**
|
|
24
|
+
* Load guidance from EVERY `guidance.set` entry in the trail — exhaustively, not capped
|
|
25
|
+
* (BRIEF-UX-002 C5, same defect as `RetryIndex.hydrate`: a durable note must not vanish
|
|
26
|
+
* because 1000+ newer writes landed on top of it). The trail answers newest first, so the
|
|
27
|
+
* FIRST entry seen per run is the current note — later (older) entries for the same run
|
|
28
|
+
* are superseded writes and are skipped, which is also what keeps a cleared note (newest
|
|
29
|
+
* entry has `text: ''`) from being resurrected by an older non-empty one. Still best-effort,
|
|
30
|
+
* like `RetryIndex.hydrate`; cost is one full-file scan at boot.
|
|
31
|
+
*/
|
|
32
|
+
async hydrate(audit, log) {
|
|
33
|
+
try {
|
|
34
|
+
const superseded = new Set();
|
|
35
|
+
for (const entry of await audit.readAll({ action: 'guidance.set' })) {
|
|
36
|
+
if (typeof entry.runId !== 'string')
|
|
37
|
+
continue;
|
|
38
|
+
if (superseded.has(entry.runId))
|
|
39
|
+
continue;
|
|
40
|
+
// The NEWEST entry for a run decides — even when its text is malformed. Marking the
|
|
41
|
+
// run seen BEFORE the text check keeps a corrupt newest write from resurrecting an
|
|
42
|
+
// older superseded note (Copilot, #312): unknowable current note = no note.
|
|
43
|
+
superseded.add(entry.runId);
|
|
44
|
+
const text = entry.detail?.['text'];
|
|
45
|
+
if (typeof text !== 'string')
|
|
46
|
+
continue;
|
|
47
|
+
if (text !== '')
|
|
48
|
+
this.runToGuidance.set(entry.runId, text);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
log?.(`[runs] guidance-index hydrate failed (prior runs read as no-guidance until restart): ${err instanceof Error ? err.message : String(err)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Upsert the note; the empty string CLEARS it (the DTO field goes back to absent). */
|
|
56
|
+
set(runId, text) {
|
|
57
|
+
if (text === '')
|
|
58
|
+
this.runToGuidance.delete(runId);
|
|
59
|
+
else
|
|
60
|
+
this.runToGuidance.set(runId, text);
|
|
61
|
+
}
|
|
62
|
+
/** The note for this run, or `undefined` (the DTO spells that as an ABSENT field). */
|
|
63
|
+
guidanceFor(runId) {
|
|
64
|
+
return this.runToGuidance.get(runId);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=guidance-index.js.map
|