wicked-crew-api-types 0.25.0 → 0.29.0

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 (2) hide show
  1. package/index.d.ts +547 -4
  2. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -1156,8 +1156,15 @@ export interface ConformanceRule {
1156
1156
  excludes?: string[];
1157
1157
  /** Ordering within a severity band + gate priority. Engine default: 1.0. */
1158
1158
  weight?: number;
1159
- /** Enforcement half (from the retired policy model). Absent ⇒ the rule is recall-only. */
1160
- effect?: 'deny' | 'allow_with_conditions' | 'allow';
1159
+ /**
1160
+ * Enforcement half (from the retired policy model). Absent ⇒ the rule is RECALL-ONLY: it is
1161
+ * surfaced to workers but never enters decide()/select(), so it can neither block a gate nor
1162
+ * `catch` an eval sample — which is why a store of effect-less rules evals to "every bad
1163
+ * sample is a gap" (api-types 0.27.0, the #394/#395 companion). `deny` blocks (the only effect
1164
+ * the evals credit as a catch); `warn` surfaces without blocking (the operator-authorable
1165
+ * middle band); `allow_with_conditions` permits with obligations; `allow` permits outright.
1166
+ */
1167
+ effect?: 'deny' | 'warn' | 'allow_with_conditions' | 'allow';
1161
1168
  trigger?: { contains?: string };
1162
1169
  obligations?: string[];
1163
1170
  /** The frozen acceptance-criteria text (becomes a claim's `criteria` when the rule decides a gate). */
@@ -1319,6 +1326,380 @@ export interface SteeringLandingResult {
1319
1326
  error?: string;
1320
1327
  }
1321
1328
 
1329
+ // ── Skills — the daemon-owned garden plugin root, published as immutable snapshots (api-types 0.28.0) ──
1330
+ //
1331
+ // api-types 0.29.0 (design amendment v3.6, crew #490): the installer-managed copy
1332
+ // `<config dir>/plugins/wicked-garden` is a LAST-resort seed source — `SkillSourceKind` gains
1333
+ // `installer-copy`, and `DiagnosticsSkillsFinding.kind` gains the persistent `skills.source` warning
1334
+ // and the fail-closed `skills.manifest` error.
1335
+ //
1336
+ // Skills are files (skills keystone, design v3 + amendments v3.1/v3.2). The daemon owns ONE
1337
+ // effective `wicked-garden`-shaped plugin root — `<state home>/skills/effective/`, the dependency
1338
+ // closure of the installed plugin: `.claude-plugin/{plugin.json,archetypes.json,components.json}`,
1339
+ // `skills/**` (nested layout verbatim), `scripts/**` minus CI + dev tools, `schemas/`,
1340
+ // `docs/examples/`, `pyproject.toml`, `uv.lock` — seeded from the LIVE installed plugin into a
1341
+ // content-addressed `baseline/<contentHash>/`. The operator edits files in place, replaces a
1342
+ // skill, adds one, disables one (manifest state — the files stay), resets one (content from the
1343
+ // baseline; never enablement). Nothing a worker runs is read from `effective/`: PUBLISHING
1344
+ // validates the whole tree and writes an IMMUTABLE, read-only `snapshots/<gen>/` (enabled skills
1345
+ // only, closure included, `snapshot.json`, and the generated delivery views —
1346
+ // `views/copilot/.github/skills/<name>/` holding the enabled PORTABLE skills, part of the content
1347
+ // hash), then flips `current -> snapshots/<gen>`; the engine receives the resolved snapshot path
1348
+ // as `WICKED_SKILLS_SNAPSHOT`. The daemon NEVER writes into the user's own CLI directories
1349
+ // (`~/.codex`, `~/.pi`, `~/.copilot`, `~/.config/opencode`, `~/.claude`): skills reach non-Claude
1350
+ // workers only through per-launch, wicked-owned delivery core performs from the snapshot (v3.2) —
1351
+ // a CLI without a lever (codex today) runs without wicked skills, and a unit on such a seat that
1352
+ // requires one is REFUSED at launch (no proceed-with-disclosure setting exists).
1353
+ // `/skills` is a file manager whose EVERY mutation is CAS-guarded (`expectedRevision`) and
1354
+ // answers 2xx `{verdict, findings[], revision}` — a `blocked` verdict is a normal response,
1355
+ // including a publish refused because one is in flight (`publish-in-flight`) or aborted because the
1356
+ // root changed under it (`root-changed`), neither of which wrote anything. 409 (`{error, revision}`)
1357
+ // is EXCLUSIVELY a stale `expectedRevision` (a CAS conflict).
1358
+
1359
+ /** What a skill IS, from its frontmatter — fork-first: `context: fork` → fork worker (a subagent
1360
+ * body); else `user-invocable: true` → router (an operator-facing entry point); else module (a
1361
+ * nested reference skill routers/workers pull in). */
1362
+ export type SkillKind = 'router' | 'fork-worker' | 'module';
1363
+
1364
+ /** `shipped` = every own file byte-identical to the baseline; `override` = a shipped skill with
1365
+ * edited/replaced files; `user-added` = no baseline (added through the API, or upstream dropped
1366
+ * it while the operator's edits were kept). */
1367
+ export type SkillProvenance = 'shipped' | 'override' | 'user-added';
1368
+
1369
+ /** Where a baseline was captured from. `claude-plugin-cache` is the marketplace cache
1370
+ * (`<config dir>/plugins/cache/wicked-garden/wicked-garden/<version>` — the plugin Claude Code
1371
+ * runs); `installer-copy` is the installer-managed `<config dir>/plugins/wicked-garden` copy
1372
+ * (`npx wicked-installer install wicked-garden`), accepted only as the LAST resort when no cache
1373
+ * exists (design v3.6, api-types 0.29.0) and flagged by the `skills.source` diagnostics finding
1374
+ * while it is the current baseline; `checkout` is a git working tree; `directory` any other
1375
+ * explicit plugin-shaped directory. */
1376
+ export type SkillSourceKind = 'claude-plugin-cache' | 'installer-copy' | 'checkout' | 'directory';
1377
+
1378
+ /** The per-baseline `uv sync` state (`<baseline>/.venv`, provisioned once per content hash — the
1379
+ * publish that needs it AWAITS it — and shared read-only by every snapshot that links it):
1380
+ * `pending` until a successful publish records it, `synced` on success (the snapshot carries a
1381
+ * `.venv` link), `skipped` when the bundle carries no `pyproject.toml` (nothing to provision; no
1382
+ * link). `failed` (uv missing or a sync error) is BLOCKING for the publish (`venv-failed`) and is
1383
+ * therefore never the recorded state of a published baseline — the record keeps its previous
1384
+ * value; a later publish retries. */
1385
+ export type SkillVenvState = 'pending' | 'synced' | 'failed' | 'skipped';
1386
+
1387
+ /** One captured baseline — keyed in `SkillManifest.baselines` by the content hash of its bundle
1388
+ * (sorted relative paths + sha256 digests), never by the version string alone: two installs of
1389
+ * "12.32.0" with different bytes are two baselines. */
1390
+ export interface SkillBaselineRecord {
1391
+ /** `version` from the source's `.claude-plugin/plugin.json`. */
1392
+ plugin_version: string;
1393
+ source: { kind: SkillSourceKind; path: string };
1394
+ /** HEAD sha for a `checkout` source; `null` otherwise (or when git could not answer). */
1395
+ git_sha: string | null;
1396
+ /** ISO-8601 instant the baseline was captured. */
1397
+ captured_at: string;
1398
+ venv: SkillVenvState;
1399
+ }
1400
+
1401
+ export interface SkillEntry {
1402
+ /** Plugin-relative directory, nested layout preserved (`skills/engineering/frontend`). Never
1403
+ * renamed — sibling `../` links depend on it. */
1404
+ dir: string;
1405
+ kind: SkillKind;
1406
+ /** Core-by-reference: in the registered-reference closure — a `skill_ref` of a workflow the
1407
+ * daemon knows (core drop-ins, crew-generated, user-registered) or a skill one of those names in
1408
+ * its SKILL.md (repo-learn → search, mem). Disabling or renaming it is blocking. */
1409
+ core: boolean;
1410
+ /** `false` when the skill's files resolve `${CLAUDE_PLUGIN_ROOT}`, invoke a script relative to
1411
+ * the cwd (`python3 -u scripts/x.py`, `./scripts/x`, …), or link `../` — Claude-only by nature:
1412
+ * excluded from the snapshot's `views/copilot/` and from the per-launch skill lists core builds
1413
+ * for the other CLIs. `portable` is the admission key for every non-Claude view (design v3.2). */
1414
+ portable: boolean;
1415
+ /** Manifest state, orthogonal to content: a disabled skill's files stay in `effective/` and are
1416
+ * excluded from the next published snapshot. Reset never flips it. */
1417
+ enabled: boolean;
1418
+ provenance: SkillProvenance;
1419
+ /** ISO-8601 instant of the last edit/replace/add through the API; `null` when untouched. */
1420
+ editedAt: string | null;
1421
+ /** A refreshed baseline changed a file this skill overrides — the new side is readable as
1422
+ * `?side=baseline` for diffing; the operator's content is kept. */
1423
+ upgradeAvailable: boolean;
1424
+ /** `upgradeAvailable`, or the last refresh found a name collision (upstream now ships a skill
1425
+ * under this user-added name at another dir). Cleared by reset / a later refresh. */
1426
+ conflict: boolean;
1427
+ /** The held-back UPSTREAM skill's directory in the current baseline when the last refresh found a
1428
+ * name collision (upstream ships this name at another dir than the operator's skill); `null`
1429
+ * otherwise. `GET /skills/:name/files/*path?side=baseline` reads THIS directory for such a skill,
1430
+ * so the two sides of the collision are comparable — the answer's `path` names the file actually
1431
+ * read (api-types 0.28.0). Re-derived by every refresh. */
1432
+ upstreamDir: string | null;
1433
+ }
1434
+
1435
+ /** One managed file (plugin-relative path → hashes). `baselineHash` `null` = user-added file;
1436
+ * `effectiveHash` `null` = a baseline file absent from `effective/` (removed by a direct
1437
+ * filesystem edit — reset restores it; the skill reads `override` until then). */
1438
+ export interface SkillFileRecord {
1439
+ baselineHash: string | null;
1440
+ effectiveHash: string | null;
1441
+ /** The hash this file had in the most recent published snapshot; `null` = never published. */
1442
+ lastPublishedHash: string | null;
1443
+ /** The last refresh saw BOTH sides change (kept the effective side). */
1444
+ conflict: boolean;
1445
+ }
1446
+
1447
+ /** The most recent publish. */
1448
+ export interface SkillPublishedRecord {
1449
+ gen: number;
1450
+ /** Hash over the snapshot's files (sorted relative paths + sha256 digests), `snapshot.json` excluded. */
1451
+ contentHash: string;
1452
+ /** ISO-8601 instant. */
1453
+ at: string;
1454
+ /** sha256 of the exact `snapshot.json` bytes publish wrote. `snapshot.json` is excluded from the
1455
+ * content hash, so the crew-owned manifest AUTHENTICATES it: `current` verifies only the
1456
+ * generation this record names, with metadata hashing to this value (api-types 0.28.0). */
1457
+ snapshotHash: string;
1458
+ }
1459
+
1460
+ /** `<skills root>/manifest.json` — the state of the daemon-owned root. (The v3 mirror ledger is
1461
+ * withdrawn with the mirror — design v3.2 §1: the daemon never writes into the user's CLI dirs.) */
1462
+ export interface SkillManifest {
1463
+ version: 2;
1464
+ /** Monotonic; bumped by EVERY mutation. Every mutating request carries it as `expectedRevision`. */
1465
+ revision: number;
1466
+ /** Content hash of the current baseline (`baseline/<hash>/`). */
1467
+ baseline: string;
1468
+ baselines: Record<string, SkillBaselineRecord>;
1469
+ /** Keyed by frontmatter `name` (`wicked-garden-<dir segments joined by '-'>`). */
1470
+ skills: Record<string, SkillEntry>;
1471
+ /** Every managed file under `effective/`, plugin-relative. */
1472
+ files: Record<string, SkillFileRecord>;
1473
+ published: SkillPublishedRecord | null;
1474
+ }
1475
+
1476
+ /** `GET /skills` 200 body. 503 when the root is not seeded (no installed plugin was found) or when
1477
+ * `current` exists but fails verification (realpath outside `snapshots/`, malformed
1478
+ * `snapshot.json`, content hash mismatch) — a corrupt root is a loud error, never an empty catalog. */
1479
+ export interface SkillsManifestResponse {
1480
+ manifest: SkillManifest;
1481
+ revision: number;
1482
+ /** The resolved skills root on the daemon host (`<state home>/skills` by default). */
1483
+ root: string;
1484
+ /** The VERIFIED published snapshot `current` resolves to, or `null` before the first publish.
1485
+ * `path` is the absolute REAL path of `snapshots/<gen>` — byte-identical to the one input the
1486
+ * engine is handed (`WICKED_SKILLS_SNAPSHOT`; design v3.1 §2). */
1487
+ current: { gen: number; path: string } | null;
1488
+ }
1489
+
1490
+ export interface SkillFileEntry {
1491
+ /** POSIX path relative to the skill dir. */
1492
+ path: string;
1493
+ size: number;
1494
+ sha256: string;
1495
+ /** This file's manifest record (`null` for a file present on disk but not yet recorded). */
1496
+ record: SkillFileRecord | null;
1497
+ }
1498
+
1499
+ /** `GET /skills/:name/files` 200 body — the skill's OWN files (a nested skill's files are its own). */
1500
+ export interface SkillFileTree {
1501
+ name: string;
1502
+ dir: string;
1503
+ enabled: boolean;
1504
+ files: SkillFileEntry[];
1505
+ }
1506
+
1507
+ /** `GET /skills/:name/files/*path` and `GET /skills/support/*path` 200 body — a typed, capped read.
1508
+ * Save is disabled on `truncated` or `binary`. `?side=baseline` reads the baseline copy instead
1509
+ * (the "new side" of a refresh conflict). */
1510
+ export interface SkillReadResult {
1511
+ /** Plugin-relative path served. */
1512
+ path: string;
1513
+ /** UTF-8 text — the first 512 KB when `truncated`; `null` when `binary` (there is no text to show). */
1514
+ content: string | null;
1515
+ /** The file's FULL size in bytes. */
1516
+ size: number;
1517
+ truncated: boolean;
1518
+ binary: boolean;
1519
+ }
1520
+
1521
+ export type SkillFindingKind =
1522
+ | 'name-invalid'
1523
+ | 'name-collision'
1524
+ | 'name-mismatch'
1525
+ | 'frontmatter-invalid'
1526
+ | 'missing-skill-md'
1527
+ | 'unregistered-skill'
1528
+ | 'nested-skill-create'
1529
+ | 'core-disable'
1530
+ | 'core-rename'
1531
+ | 'core-missing'
1532
+ | 'support-file-edit'
1533
+ | 'non-portable'
1534
+ /** A `${CLAUDE_PLUGIN_ROOT}/<p>` or `../<p>` reference in an enabled skill's files that does not
1535
+ * resolve inside the would-be snapshot. Severity follows the TARGET (design v3.4 §1): a reference
1536
+ * that ESCAPES the plugin root (`..` climbing out — it reaches whatever lies beside the snapshot on
1537
+ * the worker host) is `blocking`; one whose target is MISSING (it normalizes inside the root but
1538
+ * the snapshot does not carry it — a file the bundle omits, or a skill that is disabled) is a
1539
+ * `warning`: a content bug the skill's author owns, published as found — a publish with only
1540
+ * warnings answers `verdict: 'warnings'` with the findings AND a written snapshot (api-types
1541
+ * 0.28.0). */
1542
+ | 'unresolved-ref'
1543
+ /** A path the store refuses BY NAME rather than reads through: a shape that could leave its root
1544
+ * (`..`, an absolute piece), a component that crosses a symlink, or — under `effective/`, where
1545
+ * every entry is classified — a symlink or a special node (socket, fifo, device) anywhere,
1546
+ * the contents of a pruned directory (`.venv`, `node_modules`, `__pycache__`) INCLUDED: such a
1547
+ * directory is never copied or hashed, but what it holds is still judged, and a link inside one
1548
+ * blocks with the reason (a provisioned environment lives under `baseline/`, not the editable
1549
+ * root). Blocking; `file` names the entry, `skill` its owning skill when it has one. */
1550
+ | 'path-invalid'
1551
+ | 'unknown-skill'
1552
+ | 'no-baseline'
1553
+ | 'fs-drift'
1554
+ | 'refresh-conflict'
1555
+ | 'empty-snapshot'
1556
+ /** `.claude-plugin/plugin.json`, `archetypes.json` or `components.json` is absent from `effective/`
1557
+ * — the plugin manifest + the runtime catalogs are REQUIRED snapshot members (blocking at
1558
+ * publish; api-types 0.28.0). */
1559
+ | 'missing-plugin-manifest'
1560
+ /** The plugin manifest or a runtime catalog is present but not what its reader expects — does
1561
+ * not parse as JSON, is not a JSON object, or `archetypes.json` lacks its `archetypes`
1562
+ * collection (blocking at publish; api-types 0.28.0). A manifest without `name` is
1563
+ * `name-mismatch`. */
1564
+ | 'catalog-invalid'
1565
+ /** The baseline's shared read-only Python env could not be provisioned (uv missing, `uv sync`
1566
+ * failed, or the env could not be locked) while the bundle carries a `pyproject.toml` — the env
1567
+ * is REQUIRED, so the publish is blocked and nothing (the provisioning state included) is
1568
+ * persisted (api-types 0.28.0). */
1569
+ | 'venv-failed'
1570
+ /** A publish was refused because one is already running (one at a time) — nothing was written, so
1571
+ * it is a 2xx `blocked` envelope, not a 409 (409 is only a stale `expectedRevision`; api-types
1572
+ * 0.28.0). Re-read `GET /skills` and retry against the revision it answers. */
1573
+ | 'publish-in-flight'
1574
+ /** A publish was aborted because the skills root changed under it — nothing was written to either
1575
+ * root; a 2xx `blocked` envelope (api-types 0.28.0). Re-read `GET /skills` and retry. */
1576
+ | 'root-changed'
1577
+ /** A path outside the bundle closure — the ONE allowlist of what the seed copies, what the support
1578
+ * API may address, and what a snapshot may carry: the five runtime catalogs under `.claude-plugin`
1579
+ * BY NAME (`plugin.json`, `archetypes.json`, `components.json`, `specialist.json`,
1580
+ * `stack-registry.json` — `marketplace.json`, a publish-time listing, is outside), everything under
1581
+ * `skills`, `schemas` and `docs/examples`, everything under `scripts` except the `ci` and `wg`
1582
+ * directories and any `wg-`-prefixed dev tooling, plus `pyproject.toml` and `uv.lock`. A support
1583
+ * add/PUT there is a 2xx `blocked` envelope (nothing written); a file found there under
1584
+ * `effective/` at publish/analyze (a direct filesystem edit) is a BLOCKING finding naming the path
1585
+ * — a snapshot never ships it (api-types 0.28.0). */
1586
+ | 'outside-closure'
1587
+ /** A content-addressed `baseline/<hash>` whose tree does not hash to its name (a bundle file
1588
+ * modified, planted or removed, or a symlink inside), or a baseline file whose bytes do not match
1589
+ * the hash the manifest recorded for it. Baselines are re-verified before EVERY reuse: a refresh
1590
+ * refuses to reuse it (2xx `blocked`, nothing copied), reset refuses to restore from it (nothing
1591
+ * written), publish/analyze report it blocking (before AND after the env was provisioned in it).
1592
+ * Read-only mode bits on the baseline are a guard, never the integrity boundary — the hash is
1593
+ * (api-types 0.28.0). */
1594
+ | 'baseline-corrupt';
1595
+
1596
+ export type SkillFindingSeverity = 'warning' | 'blocking';
1597
+
1598
+ /** `blocked` = at least one blocking finding (nothing was written / published); `warnings` =
1599
+ * proceeded with warnings; `clear` = nothing to say. */
1600
+ export type SkillVerdict = 'clear' | 'warnings' | 'blocked';
1601
+
1602
+ export interface SkillConflictFinding {
1603
+ kind: SkillFindingKind;
1604
+ severity: SkillFindingSeverity;
1605
+ /** The skill this finding is about, or `null` for a catalog / support-file finding. */
1606
+ skill: string | null;
1607
+ /** Plugin-relative file the finding names, or `null`. */
1608
+ file: string | null;
1609
+ /** 1-based line in `file`, when the finding is anchored to one. */
1610
+ line: number | null;
1611
+ /** The OTHER catalog skill this finding is against (a collision, a core guard), or `null`. */
1612
+ againstSkill: string | null;
1613
+ againstIsCore: boolean;
1614
+ /** The concrete fact (names, paths, the parse error). */
1615
+ evidence: string;
1616
+ /** Why it matters. */
1617
+ explanation: string;
1618
+ }
1619
+
1620
+ /** `POST /skills/analyze` 200 body (a PURE dry run of the publish validation: nothing persisted,
1621
+ * `revision` unchanged — drift it observes is reported, and recorded only by the publish that
1622
+ * ships it) — and the base of every mutation result. */
1623
+ export interface SkillAnalyzeResult {
1624
+ verdict: SkillVerdict;
1625
+ findings: SkillConflictFinding[];
1626
+ revision: number;
1627
+ }
1628
+
1629
+ /** Every `/skills` mutation's 200 body. `blocked` ⇒ nothing was written and `revision` is unchanged. */
1630
+ export interface SkillMutationResult extends SkillAnalyzeResult {
1631
+ skill?: SkillEntry & { name: string };
1632
+ }
1633
+
1634
+ /** `POST /skills/publish` 200 body. `snapshot` is `null` when the verdict is `blocked` — and then
1635
+ * `revision` is unchanged (a blocked publish persists nothing — not the drift it observed, not the
1636
+ * baseline's provisioning state). `path` is the absolute REAL path of the locked, read-only
1637
+ * generation; its `snapshot.json` carries the skill rows and the `views` block (`views.copilot`:
1638
+ * the `views/copilot` dir + the portable skills laid out in it). One publish runs at a time — a
1639
+ * concurrent one wrote nothing and answers a 2xx `blocked` `publish-in-flight` envelope
1640
+ * (`snapshot: null`), not a 409. */
1641
+ export interface SkillPublishResult extends SkillAnalyzeResult {
1642
+ snapshot: { gen: number; path: string; contentHash: string; skills: number } | null;
1643
+ }
1644
+
1645
+ /** `POST /skills/refresh-baseline` 200 body — the three-way merge per FILE (baseline_old /
1646
+ * baseline_new / effective): unchanged → take new; user-modified & upstream-unchanged → keep;
1647
+ * both changed → keep + `conflict` (new side readable as `?side=baseline`); user-DELETED &
1648
+ * upstream-changed → the deletion is kept + `conflict` (a deletion is a modification; reset
1649
+ * restores upstream); upstream-deleted & user-unmodified → remove; upstream-deleted &
1650
+ * user-modified → keep as user-added; a new upstream skill whose path-derived name is a
1651
+ * user-added skill's → conflict. 502 when no plugin is installed. */
1652
+ export interface SkillRefreshResult extends SkillAnalyzeResult {
1653
+ previous_baseline: string;
1654
+ baseline: string;
1655
+ plugin_version: string;
1656
+ /** Skills whose files were replaced from the new baseline. */
1657
+ taken: string[];
1658
+ /** Skills that kept operator content (upstream unchanged, or a flagged conflict). */
1659
+ kept: string[];
1660
+ added: string[];
1661
+ removed: string[];
1662
+ /** Skills flagged `conflict` by this refresh. */
1663
+ conflicts: string[];
1664
+ }
1665
+
1666
+ /** The 409 body of a `/skills` mutation whose `expectedRevision` is stale — a CAS conflict, the
1667
+ * ONLY thing that answers 409. A publish refused because another is in flight, or aborted because
1668
+ * the skills root changed under it, wrote nothing and instead answers a 2xx `blocked` findings
1669
+ * envelope (`publish-in-flight` / `root-changed`; api-types 0.28.0), never a 409. */
1670
+ export interface SkillRevisionConflict {
1671
+ error: string;
1672
+ /** The current revision — re-read `GET /skills` (or use this) and retry. */
1673
+ revision: number;
1674
+ }
1675
+
1676
+ /** The body of `POST /skills/:name/{enable,disable,reset}`, `POST /skills/publish` and
1677
+ * `POST /skills/refresh-baseline`. */
1678
+ export interface SkillRevisionBody {
1679
+ expectedRevision: number;
1680
+ }
1681
+
1682
+ /** `PUT /skills/:name/files/*path` and `PUT /skills/support/*path` body. `content` is UTF-8 text,
1683
+ * at most 512 KB. */
1684
+ export interface PutSkillFileBody {
1685
+ content: string;
1686
+ expectedRevision: number;
1687
+ }
1688
+
1689
+ /** `POST /skills` body — add a user skill. `files` maps skill-relative POSIX paths to UTF-8 text
1690
+ * and must include `SKILL.md`; the skill lands at `skills/<name minus "wicked-garden-">`. */
1691
+ export interface AddSkillBody {
1692
+ name: string;
1693
+ files: Record<string, string>;
1694
+ expectedRevision: number;
1695
+ }
1696
+
1697
+ /** `POST /skills/:name/replace` body — replace the skill's OWN files wholesale (nested skills untouched). */
1698
+ export interface ReplaceSkillBody {
1699
+ files: Record<string, string>;
1700
+ expectedRevision: number;
1701
+ }
1702
+
1322
1703
  // ── Governance wiki management (scoreboard + meta) ─────────────────────────────
1323
1704
 
1324
1705
  /**
@@ -1465,7 +1846,17 @@ export interface GovernanceEvalNearestRule {
1465
1846
  * remediation pointer for "which rule needs sharpening".
1466
1847
  */
1467
1848
  export interface GovernanceEvalResult {
1468
- sample: Pick<GovernanceEvalSample, 'id' | 'description' | 'kind' | 'steering_type'>;
1849
+ sample: Pick<GovernanceEvalSample, 'id' | 'description' | 'kind' | 'steering_type'> & {
1850
+ /**
1851
+ * The sample's PAYLOAD identity — `sha256:` over the canonical JSON of its full payload (id,
1852
+ * description, kind, steering_type, signals). The engine echoes only the four fields above
1853
+ * (input `signals` never ride a result row), so this is stamped by a PRODUCER that held the
1854
+ * samples it staged (the internal-corpus `run`); a row without it cannot be proven to be the
1855
+ * same action as a row with the same id in another run, and the offline comparison reports
1856
+ * such a pair as unverified rather than comparable.
1857
+ */
1858
+ payload_hash?: string;
1859
+ };
1469
1860
  expected: 'deny' | 'allow';
1470
1861
  fired: string[];
1471
1862
  verdict: 'caught' | 'gap' | 'false_positive';
@@ -1480,15 +1871,73 @@ export interface GovernanceEvalSummary {
1480
1871
  false_positives: number;
1481
1872
  }
1482
1873
 
1874
+ /**
1875
+ * One steering rule in the judged store that NO sample of the run exercised — the inverse blind
1876
+ * spot of `summary.gaps` (core #394): a gap is a sample nothing caught, but a rule with zero
1877
+ * exercising samples produces no result row at all and so is invisible to the summary. Each
1878
+ * entry names the rule and the Steering sub-page it belongs to, so the corpus can grow a sample
1879
+ * for it (or the rule can be retired as untestable).
1880
+ */
1881
+ export interface GovernanceEvalUnexercisedRule {
1882
+ rule_id: string;
1883
+ steering_type: SteeringType;
1884
+ }
1885
+
1886
+ /**
1887
+ * One steering type's row of {@link GovernanceEvalRuleCoverage.per_type} (evals.rs `TypeCoverage`):
1888
+ * how many of the run's eligible decide-lane rules OF THAT TYPE fired for at least one sample, and
1889
+ * how many fired for none. `unexercised` here is a COUNT — the ids are the parent's `unexercised`
1890
+ * list, whose rows each carry their `steering_type`, so the two reconcile per type.
1891
+ */
1892
+ export interface GovernanceEvalTypeCoverage {
1893
+ exercised: number;
1894
+ unexercised: number;
1895
+ }
1896
+
1897
+ /**
1898
+ * Rule-side coverage of an eval run (core #394/#395 — evals.rs `RuleCoverage`): the decide-lane
1899
+ * rules ELIGIBLE for the run (every active effect-bearing rule, narrowed to the run's `--type`
1900
+ * slice when one was given) partitioned into `exercised` (fired — blocking or not — for at least
1901
+ * one sample; a COUNT) and `unexercised` (fired for none; the ids, each with its steering type).
1902
+ * Together with `summary` it separates "the corpus lacks a behavior" from "the store lacks a rule"
1903
+ * — the two readings a bare gap count conflates. `recall_only` counts the active rules in the slice
1904
+ * carrying NO effect — outside the partition because the gate never fires them (core #395).
1905
+ * `per_type` is the same partition per steering type — all seven keys, zeros included (the engine
1906
+ * pins the shape).
1907
+ *
1908
+ * Every engine that emits `rule_coverage` at all (core #394 onward) serializes all four fields on
1909
+ * every report (serde, no skip). `recall_only` and `per_type` are declared optional on the CONTRACT
1910
+ * only because the daemon persists a run's coverage VERBATIM and validates none of its fields, so a
1911
+ * stored record is exactly as complete as its producer made it and the contract never promises what
1912
+ * was never checked: a consumer reads an absent `per_type` as "the engine reports no per-type
1913
+ * coverage" (the offline comparison says exactly that), never as zeros. `exercised` and
1914
+ * `unexercised` are required — no engine ever emitted one without the other.
1915
+ *
1916
+ * A run's `results[].fired` is NOT this partition's evidence: `fired` is the BLOCKING subset of the
1917
+ * firings, and under a type filter it may name rules of OTHER types — the filter slices the samples
1918
+ * and this denominator, never the gate (evals.rs `run_evals` / `evaluate_sample`).
1919
+ */
1920
+ export interface GovernanceEvalRuleCoverage {
1921
+ exercised: number;
1922
+ unexercised: GovernanceEvalUnexercisedRule[];
1923
+ recall_only?: number;
1924
+ per_type?: Record<SteeringType, GovernanceEvalTypeCoverage>;
1925
+ }
1926
+
1483
1927
  /**
1484
1928
  * The `POST /testing/evals/run` 200 body — the engine's serde report passed through VERBATIM
1485
1929
  * (snake_case field names, `degraded` spelled `null` when the run was full-fidelity;
1486
- * `'facet-only'` when the embedding side was unavailable and only facet matching ran).
1930
+ * `'facet-only'` when the gap-hint embedder was unavailable and hints fell back to lexical
1931
+ * matching — the verdicts are the same either way, only `nearest_rules` degrades).
1932
+ *
1933
+ * `rule_coverage` is OPTIONAL on purpose: an engine that predates core #394 emits a report
1934
+ * without it, and that report still validates — the daemon never fabricates the field.
1487
1935
  */
1488
1936
  export interface GovernanceEvalReport {
1489
1937
  results: GovernanceEvalResult[];
1490
1938
  summary: GovernanceEvalSummary;
1491
1939
  degraded: 'facet-only' | null;
1940
+ rule_coverage?: GovernanceEvalRuleCoverage;
1492
1941
  }
1493
1942
 
1494
1943
  /** The `POST /testing/corpora/import` request body — a named eval corpus for later runs. */
@@ -1553,6 +2002,12 @@ export interface EvalRunSummary {
1553
2002
  per_type: Partial<Record<SteeringType, EvalRunPerTypeCount>>;
1554
2003
  /** `'facet-only'` when the run degraded to facet matching; null when it ran full-fidelity. */
1555
2004
  degraded: 'facet-only' | null;
2005
+ /**
2006
+ * The report's {@link GovernanceEvalReport.rule_coverage}, persisted VERBATIM (like `degraded`,
2007
+ * never recomputed daemon-side). ABSENT — not null — when the engine that ran the eval predates
2008
+ * core #394 and emitted no coverage, so a history row honestly shows which runs measured it.
2009
+ */
2010
+ rule_coverage?: GovernanceEvalRuleCoverage;
1556
2011
  }
1557
2012
 
1558
2013
  /**
@@ -1963,6 +2418,15 @@ export interface SystemSettings {
1963
2418
  * the engine default `~/.wicked-worker`.
1964
2419
  */
1965
2420
  worker_config_root?: string;
2421
+ /*
2422
+ * There is NO skills setting (skills keystone, api-types 0.28.0). The daemon-owned skills root is
2423
+ * `<state home>/skills` — the directory holding `manifest.json`, `baseline/<hash>/`, `effective/`,
2424
+ * `snapshots/<gen>/` and the `current` symlink — full stop: not configurable (a `skills_root` a
2425
+ * client sends is dropped and named in the audit entry's `ignored`; design v3.1 §1 one storage
2426
+ * root, v3.2 §1 never a user CLI directory), and there is no mirror knob either (the daemon never
2427
+ * writes into the user's own CLI directories). `GET /skills` → `root` and `GET /diagnostics` →
2428
+ * `skills.root` report where it is.
2429
+ */
1966
2430
  /**
1967
2431
  * The repo-scoped launch delivery DEFAULT (crew#393; api-types 0.18.0, additive). What a
1968
2432
  * `POST /runs` with `repoRef` + a CODE-WORK `workflow` (a def with at least one
@@ -2246,6 +2710,32 @@ export interface InteractiveDocDeleteResponse {
2246
2710
  ledger: InteractiveDocDeleteLedgerReport;
2247
2711
  }
2248
2712
 
2713
+ /**
2714
+ * One row of `GET /projects/:projectId/interactive/api/docs` (crew#472) — the bridge's own
2715
+ * `GET /api/docs` row (interactive's `listDocs` shape, relayed field-for-field) stamped with the
2716
+ * project whose mount it was listed under. Docs roots are partitioned per project (the `default`
2717
+ * project keeps the legacy shared root), so `projectId` is the attribution a client rendering
2718
+ * docs from several projects at once cannot otherwise recover.
2719
+ */
2720
+ export interface InteractiveDocSummary {
2721
+ /** The doc name (slug). */
2722
+ name: string;
2723
+ /** Manifest `kind`; a manifest without one lists as `doc`. */
2724
+ kind: 'doc' | 'html' | 'source' | 'demo';
2725
+ /** Head version. */
2726
+ head: number;
2727
+ /** Lineage size. */
2728
+ versions: number;
2729
+ /** ISO-8601 timestamp of the head version, or null when the lineage is empty. */
2730
+ updated_at: string | null;
2731
+ /** Present only on a retired (tombstoned) row, which lists only with `?includeRetired=1`. */
2732
+ retired?: true;
2733
+ /** ISO-8601 retirement timestamp; present with `retired`. */
2734
+ retired_at?: string;
2735
+ /** The crew project this row was listed under — the mount's `:projectId`. */
2736
+ projectId: string;
2737
+ }
2738
+
2249
2739
  /**
2250
2740
  * One normalized entry of the merged project activity feed
2251
2741
  * (`GET /projects/:id/activity`, ADR §5.2): core events of member runs/chats
@@ -2881,6 +3371,59 @@ export interface DiagnosticsResponse {
2881
3371
  /** Bounded tail of the daemon's own error-level log lines, newest first. */
2882
3372
  recentErrors: DiagnosticsRecentError[];
2883
3373
  acp: AcpDiagnostics;
3374
+ /** The skills seam's last outcome (api-types 0.28.0) — see `DiagnosticsSkills`. */
3375
+ skills: DiagnosticsSkills;
3376
+ }
3377
+
3378
+ /** The skills seam's state as `GET /diagnostics` reports it (skills keystone, api-types 0.28.0). */
3379
+ export type DiagnosticsSkillsState = 'published' | 'fallback' | 'blocked' | 'config-error' | 'disabled';
3380
+
3381
+ /** One finding the skills degradation ladder produced (design v3 §3). */
3382
+ export interface DiagnosticsSkillsFinding {
3383
+ /** `skills.fallback` = no wicked-garden installed (engine input left unset / restored to the boot
3384
+ * value — the engine resolves the live cache itself); `skills.blocked` = the first publish is
3385
+ * blocked (engine input points at a non-existent refusal path — launches fail loudly until the
3386
+ * catalog is fixed); `skills.config` = the configured root is corrupt/unusable (same refusal;
3387
+ * `error`), or — as a `warning` on the `published` state — the root lies OUTSIDE the daemon
3388
+ * state home, so core's fence cross-check (`WICKED_CREW_STATE_HOME`) refuses every launch;
3389
+ * `skills.source` (`warning`, api-types 0.29.0, design v3.6) = the CURRENT baseline was seeded
3390
+ * from the installer-managed copy (`SkillSourceKind` `installer-copy`) — the daemon works, but
3391
+ * that copy receives no marketplace updates until the plugin is registered with Claude Code; it
3392
+ * persists (alongside the ladder's own finding, if any) until a refresh from the marketplace
3393
+ * cache re-records the baseline's provenance (byte-identical or not); `skills.manifest`
3394
+ * (`error`, api-types 0.29.0) = `manifest.json` could not be read when diagnostics were taken
3395
+ * (corrupt, unreadable, or the root no longer the one the store bound) — reported as
3396
+ * `config-error` with the cause instead of the stale boot outcome; the exported engine input is
3397
+ * unchanged until the daemon restarts. */
3398
+ kind: 'skills.fallback' | 'skills.blocked' | 'skills.config' | 'skills.source' | 'skills.manifest';
3399
+ severity: 'warning' | 'error';
3400
+ message: string;
3401
+ }
3402
+
3403
+ /**
3404
+ * `GET /diagnostics` → `skills`: whether the engine is being handed a verified snapshot, and if not,
3405
+ * why — the one read-only answer to "why do launches refuse the skills snapshot". `disabled` is a
3406
+ * daemon booted without the seam (the manifest collector, some tests).
3407
+ */
3408
+ export interface DiagnosticsSkills {
3409
+ state: DiagnosticsSkillsState;
3410
+ /** The resolved skills root on the daemon host; `null` when `disabled`. */
3411
+ root: string | null;
3412
+ /** The VERIFIED published snapshot (`current` realpath-contained, `snapshot.json` + content hash
3413
+ * checked), or `null`. `path` is the absolute REAL path of `snapshots/<gen>`. */
3414
+ current: { gen: number; path: string } | null;
3415
+ /** What `WICKED_SKILLS_SNAPSHOT` — the engine's ONE skills input (v3.1 §2, v3.4 §2;
3416
+ * `WICKED_SKILLS_CURRENT` is never set, `WICKED_CREW_STATE_HOME` is not exported) — is exported as
3417
+ * right now: the real snapshot path, a `<root>/refused/…` refusal path, `""` when the process
3418
+ * booted with an explicitly EMPTY value (preserved — a configuration error core refuses, state
3419
+ * `config-error`; design v3.5 §4), or `null` = unset. */
3420
+ engineInput: string | null;
3421
+ /** DIAGNOSTICS-ONLY: the canonical realpath of the daemon state home, reported for humans. It is
3422
+ * NOT an engine input — `WICKED_CREW_STATE_HOME` is retired (v3.4 §2): core reads only
3423
+ * `WICKED_SKILLS_SNAPSHOT` and derives the state home from its `<state home>/skills/snapshots/<gen>`
3424
+ * layout. `null` only when `disabled`. */
3425
+ stateHome: string | null;
3426
+ findings: DiagnosticsSkillsFinding[];
2884
3427
  }
2885
3428
 
2886
3429
  // ── Memory proposal queue (DES-MEM-FACETED-001 §5.0, api-types 0.21.0) ─────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wicked-crew-api-types",
3
- "version": "0.25.0",
3
+ "version": "0.29.0",
4
4
  "description": "The wire contract of the wicked-crew daemon's /api/v1 REST surface and /ws CoreEvent frames \u2014 types only, zero runtime",
5
5
  "type": "module",
6
6
  "types": "./index.d.ts",