@crewhaus/spec 0.2.3 → 0.3.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 (3) hide show
  1. package/dist/index.d.ts +3408 -105
  2. package/dist/index.js +256 -3
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -512,6 +512,62 @@ const feedbackBlock = z
512
512
  })
513
513
  .strict()
514
514
  .optional();
515
+ /**
516
+ * v0.3.0 §3.1/§9 — the `memory.wiki` sub-block: the update-in-place semantic
517
+ * tier over `@crewhaus/wiki-store` (`.crewhaus/wiki/<spec>/`). Presence (with
518
+ * `enabled` not `false`) registers the ten thredz-vocabulary `wiki_*` tools.
519
+ * `recallK` caps wiki hits fused into auto-recall (OPTIMIZABLE, PR 20);
520
+ * `embedder` is the `@crewhaus/embedder` factory grammar enabling hybrid
521
+ * recall on BOTH the wiki and the fact store; `autoRecall` fuses wiki recall
522
+ * into the session-start memory bundle; `requireSources` is the learning-mode
523
+ * write governance (`wiki_write` rejects bodies without a `## Sources`
524
+ * heading — the `learning:` lowering sets it in PR 17).
525
+ * `.strict()` so a typo'd sub-key fails the build.
526
+ */
527
+ const memoryWikiBlock = z
528
+ .object({
529
+ enabled: z.boolean().optional(),
530
+ recallK: z.number().int().positive().max(50).optional(),
531
+ embedder: z.string().min(1).optional(),
532
+ autoRecall: z.boolean().optional(),
533
+ requireSources: z.boolean().optional(),
534
+ })
535
+ .strict();
536
+ /**
537
+ * The shared duration-string grammar (Phase 3 §3.1 heartbeat, v0.3.0
538
+ * `memory.ttl` and `memory.dream.every`). Extended with `d` (days) in
539
+ * 0.3.0. Parsed to milliseconds at lower time by the compiler's
540
+ * `parseDurationToMs`.
541
+ */
542
+ const DURATION_REGEX = /^\d+(?:ms|s|m|h|d)$/;
543
+ /**
544
+ * v0.3.0 Goal 5 (§6/§9) — the `memory.dream` sub-block: scheduled memory
545
+ * consolidation. Nested under `memory:` because it consolidates the memory
546
+ * fabric (facts + wiki + continuity's spec-scoped agenda) — one shared zod
547
+ * object, minimal union churn.
548
+ *
549
+ * - `every` (required): the consolidation cadence in the shared duration
550
+ * grammar (`"24h"`, `"1d"`). Must be >= 5m (enforced at lower time) —
551
+ * consolidation is a maintenance pass, not a per-turn hook.
552
+ * - `mode`: `full` (default — deterministic phase + ONE bounded model
553
+ * synthesis session) | `deterministic` (no model, ever).
554
+ * - `budget_usd`: the model phase's item-27 spend cap (OPTIMIZABLE,
555
+ * PR 20). `0` — or omitting it — means deterministic only, regardless
556
+ * of `mode`; unattended model spend must be opted into by number.
557
+ * - `instructions`: optional playbook override; the default is the
558
+ * builtin `dream` skill body.
559
+ * `.strict()` so a typo'd sub-key fails the build.
560
+ */
561
+ const memoryDreamBlock = z
562
+ .object({
563
+ every: z
564
+ .string()
565
+ .regex(DURATION_REGEX, 'memory.dream.every must be a duration like "24h", "1d", "30m", or "300s"'),
566
+ mode: z.enum(["deterministic", "full"]).optional(),
567
+ budget_usd: z.number().nonnegative().optional(),
568
+ instructions: z.string().min(1).optional(),
569
+ })
570
+ .strict();
515
571
  /**
516
572
  * Feature #53 — cross-session memory block. Its mere presence wires the
517
573
  * Remember/Recall tools into the harness (no hand-editing). The auto-*
@@ -519,16 +575,183 @@ const feedbackBlock = z
519
575
  * outcomes into `.crewhaus/memories/<name>.jsonl` at run teardown;
520
576
  * `autoRecall` injects the top-`recallK` relevant memories into the system
521
577
  * prompt at session start (mirrors project-memory auto-load). Carried on the
522
- * interactive shapes that run a chat loop (cli, channel, managed, research).
578
+ * agent-loop shapes (cli, channel, managed, research, crew).
579
+ *
580
+ * v0.3.0 (§9) extensions — all optional so pre-0.3.0 specs parse (and lower)
581
+ * unchanged:
582
+ * - `backend`: `file` (the default when absent) | `thredz` (reserved — the
583
+ * store flip ships with the `thredz:` block, PR 16). Deliberately NOT
584
+ * zod-defaulted: only declared fields are carried into the IR, keeping
585
+ * existing memory bundles byte-identical.
586
+ * - `ttl`: explicit forgetting for auto-captured facts, as a duration
587
+ * string in the heartbeat grammar extended with `d` (days) — e.g. "90d".
588
+ * Must be >= 1h (enforced at lower time); omit to keep facts forever.
589
+ * - `wiki`: the semantic tier (see {@link memoryWikiBlock}).
590
+ * - `dream`: scheduled consolidation (see {@link memoryDreamBlock}, §6).
523
591
  * `.strict()` so a typo'd sub-key fails the build.
524
592
  */
525
593
  const memoryBlock = z
526
594
  .object({
527
595
  enabled: z.boolean().optional(),
596
+ backend: z.enum(["file", "thredz"]).optional(),
597
+ ttl: z
598
+ .string()
599
+ .regex(DURATION_REGEX, 'memory.ttl must be a duration like "90d", "12h", "30m", "60s", or "500ms"')
600
+ .optional(),
528
601
  autoCapture: z.boolean().optional(),
529
602
  autoCaptureThreshold: z.number().int().positive().optional(),
530
603
  autoRecall: z.boolean().optional(),
531
604
  recallK: z.number().int().positive().max(50).optional(),
605
+ wiki: memoryWikiBlock.optional(),
606
+ dream: memoryDreamBlock.optional(),
607
+ })
608
+ .strict()
609
+ .optional();
610
+ /**
611
+ * v0.3.0 Goal 1 (§2.1) — the top-level `continuity:` block: focus, plans,
612
+ * goals, the proof-of-action ladder, the requirements ledger, and teardown
613
+ * handoff. THE release's one sanctioned default-on behavior change
614
+ * (ROADMAP.md:9): on the emit-wired agent-loop shapes (cli, channel, managed,
615
+ * research, crew) an ABSENT key lowers to the default-on config —
616
+ * `continuity: false` is the opt-out that restores prior bundle bytes
617
+ * exactly (byte-diff-pinned).
618
+ *
619
+ * Forms: boolean shorthand (`continuity: true|false`) or the strict object:
620
+ * - `enabled`: `false` disables (same as the `false` shorthand).
621
+ * - `plan`: plan/goal persistence + the Plan and Goal tool families
622
+ * (default true; `false` keeps only FocusRead/FocusWrite + MemoryClear).
623
+ * - `proof`: `ladder` (default — claimed is free, proven is machine-checked)
624
+ * | `require` (refuse plan completion below proven; `init` templates)
625
+ * | `off` (no verification). See §2.4.
626
+ * - `ledger`: the verbatim requirements ledger (§2.3; default true).
627
+ * - `handoff`: deterministic teardown handoff.md (§2.8; default true).
628
+ * - `scope`: `auto` (default) | `spec` | `session` — §2.7/§14.5: `auto`
629
+ * resolves at lower time to `spec` on cli/research/crew/managed (managed
630
+ * additionally tenant-fenced at boot) and `session` (per-conversation)
631
+ * on channel; explicit `session` is only accepted on shapes with session
632
+ * routing (channel).
633
+ * - `focusMaxChars`: hard cap on the mutable tail block (default 4096;
634
+ * OPTIMIZABLE, PR 20).
635
+ *
636
+ * Also carried (spec-parsed, compiled with an ignored-note comment) on
637
+ * workflow, batch, voice, browser; deliberately NOT on graph/pipeline/eval/
638
+ * onchain/onchain-game — the strict union rejects it loudly there, which
639
+ * beats silent dead config.
640
+ */
641
+ const continuityObject = z
642
+ .object({
643
+ enabled: z.boolean().optional(),
644
+ plan: z.boolean().optional(),
645
+ proof: z.enum(["ladder", "require", "off"]).optional(),
646
+ ledger: z.boolean().optional(),
647
+ handoff: z.boolean().optional(),
648
+ scope: z.enum(["auto", "spec", "session"]).optional(),
649
+ focusMaxChars: z.number().int().positive().optional(),
650
+ })
651
+ .strict();
652
+ const continuityBlock = z.union([z.boolean(), continuityObject]).optional();
653
+ /**
654
+ * v0.3.0 Goal 3 (§4.1) — the top-level `thredz:` block: ONE knob that flips
655
+ * the memory fabric's wiki backend to a hosted Thredz wiki over the published
656
+ * `thredz-mcp` stdio server (npm, v0.2.0 — 25 tools incl. `goal_*`/`task_*`).
657
+ *
658
+ * Forms:
659
+ * - boolean shorthand: `thredz: true` ≡ `{ api_key: "$THREDZ_API_KEY" }`
660
+ * (`false` ≡ absent — the explicit opt-out).
661
+ * - string shorthand: `thredz: $THREDZ_API_KEY` — THE one argument.
662
+ * - the strict object:
663
+ * · `api_key` (required): credential-lowered to an `IrSecretRef`
664
+ * (`lowerCredential` — fail-fast on a malformed `$…` env ref).
665
+ * · `base_url`: self-hosted / local Thredz API base
666
+ * (`THREDZ_API_BASE` in the synthesized server env).
667
+ * · `visibility`: `private` (DEFAULT — overrides Thredz's
668
+ * shared-by-default foot-gun) | `shared`; becomes the synthesized
669
+ * server's `THREDZ_DEFAULT_VISIBILITY`.
670
+ * · `goals`: mirror continuity goal writes to Thredz `goal_write`/
671
+ * `goal_update` (spec-scoped ONLY, §14.5 decision 5). Default: on
672
+ * when continuity goals are on.
673
+ * · `agents`: register an addressable agent handle at boot
674
+ * (idempotent `agent_register`). `true` derives the handle from the
675
+ * spec name; a string names it explicitly. Default off.
676
+ *
677
+ * Carried on the five memory shapes (cli, channel, managed, research, crew);
678
+ * the strict unions reject it loudly elsewhere. Emit-wiring in this release
679
+ * is the cli shape (compiled bundle + `crewhaus run`); the other four carry
680
+ * the block with the 0.2.3-convention ignored-note comment.
681
+ */
682
+ const THREDZ_HANDLE_RE = /^[a-z][a-z0-9-]{2,31}$/;
683
+ const thredzObject = z
684
+ .object({
685
+ api_key: z.string().min(1),
686
+ base_url: z.string().url().optional(),
687
+ visibility: z.enum(["private", "shared"]).optional(),
688
+ goals: z.boolean().optional(),
689
+ agents: z
690
+ .union([
691
+ z.boolean(),
692
+ z
693
+ .string()
694
+ .regex(THREDZ_HANDLE_RE, "thredz.agents must be a lowercase handle matching ^[a-z][a-z0-9-]{2,31}$ (or true to derive one from the spec name)"),
695
+ ])
696
+ .optional(),
697
+ })
698
+ .strict();
699
+ const thredzBlock = z.union([z.boolean(), z.string().min(1), thredzObject]).optional();
700
+ /**
701
+ * v0.3.0 Goal 2 (§3.3, PR 17) — the top-level `learning:` block: continual
702
+ * learning as a first-class capability. Presence (with `enabled` not `false`)
703
+ * registers the builtin `learning-loop` skill with `domain`/`curriculum`/
704
+ * `sources` substituted at compile time, gates in the `/study` `/reflect`
705
+ * (and, with `exam`, `/exam`) slash commands, and enforces Sources-required
706
+ * wiki writes deterministically.
707
+ *
708
+ * Learning NEEDS a wiki — the knowledge lives there, not in the prompt — so
709
+ * the compiler REQUIRES `memory.wiki` (local files) or `thredz:` (hosted)
710
+ * alongside this block (cross-field CompilerError otherwise).
711
+ *
712
+ * - `domain` (required): one sentence naming the field of expertise —
713
+ * substituted into the learning-loop skill body.
714
+ * - `curriculum`: spec-relative path to an agent-editable checkbox-ladder
715
+ * file (e.g. `curriculum.md`). Optional; without it the skill keeps the
716
+ * ladder in the wiki. Whether the file EXISTS is a runtime concern.
717
+ * - `sources`: source-allowlist hints (domains/patterns) woven into the
718
+ * skill's STUDY gathering rules. Deliberately NOT optimizable — an
719
+ * allowlist is a security surface (§7.5).
720
+ * - `exam`: spec-relative `dataset` (jsonl) + `graders` (yaml) paths for
721
+ * the first-class competency exam: `/exam` drives a programmatic
722
+ * eval-runner invocation (the `run_exam` tool — no Bash shell-out), and
723
+ * every failed sample is logged as a knowledge gap automatically.
724
+ * - `study`: unattended-study toggles, both default ON —
725
+ * · `on_heartbeat`: prepend the study-rotation preamble (gaps first,
726
+ * ~3:1 study:reflect, bounded per tick) to channel heartbeat
727
+ * instructions;
728
+ * · `on_dream`: seed the dream model phase's findings with the top
729
+ * open knowledge gaps + the next unmastered curriculum rung.
730
+ *
731
+ * Carried on the five memory shapes (cli, channel, managed, research, crew);
732
+ * the strict unions reject it loudly elsewhere. `.strict()` throughout so a
733
+ * typo'd sub-key fails the build.
734
+ */
735
+ const learningBlock = z
736
+ .object({
737
+ enabled: z.boolean().optional(),
738
+ domain: z.string().min(1),
739
+ curriculum: z.string().min(1).optional(),
740
+ sources: z.array(z.string().min(1)).optional(),
741
+ exam: z
742
+ .object({
743
+ dataset: z.string().min(1),
744
+ graders: z.string().min(1),
745
+ })
746
+ .strict()
747
+ .optional(),
748
+ study: z
749
+ .object({
750
+ on_heartbeat: z.boolean().optional(),
751
+ on_dream: z.boolean().optional(),
752
+ })
753
+ .strict()
754
+ .optional(),
532
755
  })
533
756
  .strict()
534
757
  .optional();
@@ -699,12 +922,11 @@ const cliOptionsBlock = z
699
922
  * message at each tick; pair with HEARTBEAT.md in cwd for richer
700
923
  * playbook reads.
701
924
  */
702
- const HEARTBEAT_DURATION_REGEX = /^\d+(?:ms|s|m|h)$/;
703
925
  const heartbeatBlock = z
704
926
  .object({
705
927
  every: z
706
928
  .string()
707
- .regex(HEARTBEAT_DURATION_REGEX, 'heartbeat.every must be a duration like "2h", "30m", "60s", or "500ms"'),
929
+ .regex(DURATION_REGEX, 'heartbeat.every must be a duration like "1d", "2h", "30m", "60s", or "500ms"'),
708
930
  instructions: z.string().min(1),
709
931
  })
710
932
  .strict()
@@ -757,6 +979,9 @@ const cliSchema = z
757
979
  budget: budgetBlock,
758
980
  feedback: feedbackBlock,
759
981
  memory: memoryBlock,
982
+ continuity: continuityBlock,
983
+ thredz: thredzBlock,
984
+ learning: learningBlock,
760
985
  observability: observabilityBlock,
761
986
  cli: cliOptionsBlock,
762
987
  chains: chainsBlock,
@@ -785,6 +1010,9 @@ const workflowSchema = z
785
1010
  permissions: permissionsBlock,
786
1011
  compaction: compactionBlock,
787
1012
  failure_taxonomy: failureTaxonomyBlock,
1013
+ // v0.3.0 — carried but not emit-wired in 0.3.0 (ignored-note comment in
1014
+ // the generated bundle; NOT default-on here).
1015
+ continuity: continuityBlock,
788
1016
  chains: chainsBlock,
789
1017
  wallets: walletsBlock,
790
1018
  contracts: contractsBlock,
@@ -888,6 +1116,9 @@ const channelSchema = z
888
1116
  budget: budgetBlock,
889
1117
  feedback: feedbackBlock,
890
1118
  memory: memoryBlock,
1119
+ continuity: continuityBlock,
1120
+ thredz: thredzBlock,
1121
+ learning: learningBlock,
891
1122
  observability: observabilityBlock,
892
1123
  heartbeat: heartbeatBlock,
893
1124
  gateway: channelGatewayBlock,
@@ -984,6 +1215,9 @@ const managedSchema = z
984
1215
  failure_taxonomy: failureTaxonomyBlock,
985
1216
  budget: budgetBlock,
986
1217
  memory: memoryBlock,
1218
+ continuity: continuityBlock,
1219
+ thredz: thredzBlock,
1220
+ learning: learningBlock,
987
1221
  observability: observabilityBlock,
988
1222
  })
989
1223
  .strict();
@@ -1101,6 +1335,13 @@ const crewSchema = z
1101
1335
  permissions: permissionsBlock,
1102
1336
  compaction: compactionBlock,
1103
1337
  failure_taxonomy: failureTaxonomyBlock,
1338
+ // v0.3.0 — crew joins the memory-carrying shapes (§9: emit-wired; the
1339
+ // roles share the spec-scoped stores — the plan IS the coordination
1340
+ // surface, §2.7).
1341
+ memory: memoryBlock,
1342
+ continuity: continuityBlock,
1343
+ thredz: thredzBlock,
1344
+ learning: learningBlock,
1104
1345
  chains: chainsBlock,
1105
1346
  wallets: walletsBlock,
1106
1347
  contracts: contractsBlock,
@@ -1139,6 +1380,9 @@ const researchSchema = z
1139
1380
  compaction: compactionBlock,
1140
1381
  failure_taxonomy: failureTaxonomyBlock,
1141
1382
  memory: memoryBlock,
1383
+ continuity: continuityBlock,
1384
+ thredz: thredzBlock,
1385
+ learning: learningBlock,
1142
1386
  chains: chainsBlock,
1143
1387
  wallets: walletsBlock,
1144
1388
  contracts: contractsBlock,
@@ -1173,6 +1417,9 @@ const batchSchema = z
1173
1417
  permissions: permissionsBlock,
1174
1418
  compaction: compactionBlock,
1175
1419
  failure_taxonomy: failureTaxonomyBlock,
1420
+ // v0.3.0 — carried but not emit-wired in 0.3.0 (ignored-note comment in
1421
+ // the generated bundle; NOT default-on here).
1422
+ continuity: continuityBlock,
1176
1423
  chains: chainsBlock,
1177
1424
  wallets: walletsBlock,
1178
1425
  contracts: contractsBlock,
@@ -1213,6 +1460,9 @@ const voiceSchema = z
1213
1460
  permissions: permissionsBlock,
1214
1461
  compaction: compactionBlock,
1215
1462
  failure_taxonomy: failureTaxonomyBlock,
1463
+ // v0.3.0 — carried but not emit-wired in 0.3.0 (ignored-note comment in
1464
+ // the generated bundle; NOT default-on here).
1465
+ continuity: continuityBlock,
1216
1466
  })
1217
1467
  .strict();
1218
1468
  // Browser target (Section 25 BROW). Computer-use / browser-driver agent.
@@ -1244,6 +1494,9 @@ const browserSchema = z
1244
1494
  permissions: permissionsBlock,
1245
1495
  compaction: compactionBlock,
1246
1496
  failure_taxonomy: failureTaxonomyBlock,
1497
+ // v0.3.0 — carried but not emit-wired in 0.3.0 (ignored-note comment in
1498
+ // the generated bundle; NOT default-on here).
1499
+ continuity: continuityBlock,
1247
1500
  })
1248
1501
  .strict();
1249
1502
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/spec",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "User-facing spec schema (Zod) + YAML parser",
6
6
  "main": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "test": "bun test src"
16
16
  },
17
17
  "dependencies": {
18
- "@crewhaus/errors": "0.2.3",
18
+ "@crewhaus/errors": "0.3.0",
19
19
  "yaml": "^2.6.0",
20
20
  "zod": "^3.23.8"
21
21
  },