norn-cli 3.3.0 → 4.0.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 (58) hide show
  1. package/dist/cli.js +1374 -260
  2. package/dist/knowledgeIndexWorker.js +68 -22
  3. package/package.json +3 -3
  4. package/schemas/norn.config.schema.json +13 -0
  5. package/CLAUDE.md +0 -69
  6. package/NOW.md +0 -223
  7. package/demos/agent-workbench/README.md +0 -156
  8. package/demos/agent-workbench/agents.nornagent +0 -87
  9. package/demos/agent-workbench/contracts/domain-answer.schema.json +0 -28
  10. package/demos/agent-workbench/contracts/domain-question.schema.json +0 -34
  11. package/demos/agent-workbench/contracts/router-verdict.schema.json +0 -43
  12. package/demos/agent-workbench/contracts/ticket.schema.json +0 -29
  13. package/demos/agent-workbench/contracts/verdict.schema.json +0 -48
  14. package/demos/agent-workbench/fake-openai-server.js +0 -128
  15. package/demos/agent-workbench/fixtures/aligned-ticket.json +0 -10
  16. package/demos/agent-workbench/fixtures/cross-domain-ticket.json +0 -10
  17. package/demos/agent-workbench/fixtures/invalid-output-ticket.json +0 -8
  18. package/demos/agent-workbench/norn.config.json +0 -15
  19. package/demos/agent-workbench/prompts/ticket-router.md +0 -11
  20. package/demos/agent-workbench/workbench.norn +0 -32
  21. package/demos/knowledge-editor/README.md +0 -74
  22. package/demos/knowledge-editor/agents.nornagent +0 -16
  23. package/demos/knowledge-editor/api.nornapi +0 -10
  24. package/demos/knowledge-editor/handbook/ops/flow.svg +0 -1
  25. package/demos/knowledge-editor/handbook/ops/retired.md +0 -9
  26. package/demos/knowledge-editor/handbook/ops/runbook.md +0 -14
  27. package/demos/knowledge-editor/handbook/payments/charges.md +0 -25
  28. package/demos/knowledge-editor/handbook/payments/pricing-tiers.draft.md +0 -7
  29. package/demos/knowledge-editor/handbook/payments/refunds/policy.md +0 -12
  30. package/demos/knowledge-editor/support.norn +0 -10
  31. package/demos/mcp-ticket-testing/README.md +0 -114
  32. package/demos/mcp-ticket-testing/agents.nornagent +0 -77
  33. package/demos/mcp-ticket-testing/contracts/test-run.schema.json +0 -31
  34. package/demos/mcp-ticket-testing/expectations/proj-142.md +0 -12
  35. package/demos/mcp-ticket-testing/fixtures/proj-142.json +0 -13
  36. package/demos/mcp-ticket-testing/prompts/backend-tester.md +0 -12
  37. package/demos/mcp-ticket-testing/prompts/frontend-tester.md +0 -14
  38. package/demos/mcp-ticket-testing/prompts/reporter.md +0 -10
  39. package/demos/mcp-ticket-testing/servers/browser-server.js +0 -133
  40. package/demos/mcp-ticket-testing/servers/house-server.js +0 -125
  41. package/demos/mcp-ticket-testing/tickets.norn +0 -32
  42. package/demos/nornenv-region-refactor/README.md +0 -64
  43. package/demos/nornenv-showcase/README.md +0 -62
  44. package/demos/nornenv-showcase/norn.config.json +0 -16
  45. package/demos/nornenv-showcase/showcase.norn +0 -70
  46. package/demos/nornenv-showcase/showcase.nornapi +0 -26
  47. package/demos/nornenv-showcase/showcase.nornsql +0 -20
  48. package/demos/tests-showcase/01-single-requests.norn +0 -31
  49. package/demos/tests-showcase/02-sequences.norn +0 -54
  50. package/demos/tests-showcase/03-sidecars.norn +0 -42
  51. package/demos/tests-showcase/04-api-plus-sql.norn +0 -27
  52. package/demos/tests-showcase/db/testDb.nornsql +0 -12
  53. package/demos/tests-showcase/demo-api.nornapi +0 -17
  54. package/demos/tests-showcase/norn.config.json +0 -16
  55. package/playground/ai.norn +0 -15
  56. package/playground/ai_orchastration.nornagent +0 -34
  57. package/playground/knowedge_base/ACDC/testing_notes_new_clients.md +0 -11
  58. package/playground/knowedge_base/nexus_system_prompt.md +0 -1
@@ -7592,11 +7592,14 @@ function readAgentPromptBlock(lines, openingLineNumber, opening, stripTrailingCo
7592
7592
  };
7593
7593
  }
7594
7594
 
7595
+ // src/agents/agentTypes.ts
7596
+ var AGENT_REASONING_EFFORTS = ["none", "low", "medium", "high", "xhigh", "max"];
7597
+
7595
7598
  // src/agents/nornAgentParser.ts
7596
7599
  var IDENTIFIER = "[a-zA-Z_][a-zA-Z0-9_]*";
7597
- var MODEL_PROVIDERS = ["anthropic", "google", "local", "openai"];
7600
+ var MODEL_PROVIDERS = ["anthropic", "google", "local", "openai", "openai_compatible"];
7598
7601
  var MODEL_PROVIDER = new RegExp(`^(${MODEL_PROVIDERS.join("|")})$`, "i");
7599
- var MODEL_DIRECTIVES = ["provider", "name", "apiKey", "baseUrl"];
7602
+ var MODEL_DIRECTIVES = ["provider", "name", "reasoning", "apiKey", "baseUrl"];
7600
7603
  var MCP_DIRECTIVES = {
7601
7604
  transport: { transport: "both", repeatable: false },
7602
7605
  command: { transport: "stdio", repeatable: false },
@@ -7635,6 +7638,7 @@ function splitHeaderDirective(value) {
7635
7638
  }
7636
7639
  var LIMIT_DIRECTIVES = {
7637
7640
  max_tokens: "maxTokens",
7641
+ max_input_tokens: "maxInputTokens",
7638
7642
  max_depth: "maxDepth",
7639
7643
  max_invocations: "maxInvocations",
7640
7644
  max_turns: "maxTurns",
@@ -8069,6 +8073,23 @@ end mcp
8069
8073
  ));
8070
8074
  model.invalid = true;
8071
8075
  }
8076
+ const reasoning = model.values.reasoning;
8077
+ if (reasoning && !reasoning.value.includes("{{") && !AGENT_REASONING_EFFORTS.some((effort) => effort === reasoning.value.toLowerCase())) {
8078
+ errors.push(parseError(
8079
+ reasoning.lineNumber,
8080
+ `Unknown reasoning effort '${reasoning.value}' in model '${model.alias}'. Expected one of: ${AGENT_REASONING_EFFORTS.join(", ")}.`,
8081
+ "agent-model-reasoning-invalid"
8082
+ ));
8083
+ model.invalid = true;
8084
+ }
8085
+ if (reasoning && provider && !provider.value.includes("{{") && provider.value.toLowerCase() !== "openai") {
8086
+ errors.push(parseError(
8087
+ reasoning.lineNumber,
8088
+ `Model '${model.alias}' can declare reasoning only with provider openai. Provider '${provider.value}' does not support this directive yet.`,
8089
+ "agent-model-reasoning-provider"
8090
+ ));
8091
+ model.invalid = true;
8092
+ }
8072
8093
  if (model.invalid || !provider || !name) {
8073
8094
  return;
8074
8095
  }
@@ -8077,6 +8098,7 @@ end mcp
8077
8098
  providerTemplate: provider.value,
8078
8099
  nameTemplate: name.value,
8079
8100
  targetTemplate: `${provider.value}/${name.value}`,
8101
+ ...reasoning ? { reasoningTemplate: reasoning.value } : {},
8080
8102
  ...model.values.apiKey ? { apiKeyTemplate: model.values.apiKey.value } : {},
8081
8103
  ...model.values.baseUrl ? { baseUrlTemplate: model.values.baseUrl.value } : {},
8082
8104
  lineNumber: model.lineNumber,
@@ -8116,6 +8138,13 @@ end mcp
8116
8138
  "agent-model-alias-unknown"
8117
8139
  ));
8118
8140
  pending.invalid = true;
8141
+ } else if (model.alias !== pending.modelAlias.value) {
8142
+ errors.push(parseError(
8143
+ pending.modelAlias.lineNumber,
8144
+ `Model alias '${pending.modelAlias.value}' in agent '${pending.name}' does not match the declared alias '${model.alias}'. Model aliases are case-sensitive.`,
8145
+ "agent-model-alias-case-mismatch"
8146
+ ));
8147
+ pending.invalid = true;
8119
8148
  }
8120
8149
  }
8121
8150
  }
@@ -8151,6 +8180,7 @@ end mcp
8151
8180
  accepts: pending.accepts,
8152
8181
  returns: pending.returns,
8153
8182
  maxTokens: pending.maxTokens?.value,
8183
+ maxInputTokens: pending.maxInputTokens?.value,
8154
8184
  maxDepth: pending.maxDepth?.value,
8155
8185
  maxInvocations: pending.maxInvocations?.value,
8156
8186
  maxTurns: pending.maxTurns?.value,
@@ -8555,29 +8585,38 @@ end model`,
8555
8585
  }
8556
8586
  const knowledgeGrantMatch = trimmed.match(/^knowledge(?:\s+(.*))?$/i);
8557
8587
  if (knowledgeGrantMatch) {
8558
- const alias = (knowledgeGrantMatch[1] ?? "").trim();
8559
- if (!new RegExp(`^${IDENTIFIER}$`).test(alias)) {
8588
+ const rawValue = (knowledgeGrantMatch[1] ?? "").trim();
8589
+ const aliases = rawValue ? rawValue.split(",").map((part) => part.trim()) : [];
8590
+ if (aliases.length === 0 || aliases.some((alias) => !new RegExp(`^${IDENTIFIER}$`).test(alias))) {
8560
8591
  errors.push(parseError(
8561
8592
  index,
8562
- `Invalid knowledge grant '${trimmed}' in agent '${pending.name}'. Expected: knowledge <Alias>.`,
8593
+ `Invalid knowledge grant '${trimmed}' in agent '${pending.name}'. Expected: knowledge <Alias>[, <Alias>\u2026].`,
8563
8594
  "agent-knowledge-grant-invalid"
8564
8595
  ));
8565
8596
  pending.invalid = true;
8566
- } else if (pending.knowledge.some((grant) => grant.alias.toLowerCase() === alias.toLowerCase())) {
8567
- errors.push(parseError(
8568
- index,
8569
- `Agent '${pending.name}' grants knowledge '${alias}' more than once.`,
8570
- "agent-knowledge-grant-duplicate"
8571
- ));
8572
- pending.invalid = true;
8573
- } else if (!knowledgeBases.has(alias.toLowerCase())) {
8574
- errors.push(parseError(
8575
- index,
8576
- `Unknown knowledge alias '${alias}' in agent '${pending.name}'.`,
8577
- "agent-knowledge-alias-unknown"
8578
- ));
8579
- pending.invalid = true;
8580
- } else {
8597
+ continue;
8598
+ }
8599
+ const granted = new Set(pending.knowledge.map((grant) => grant.alias.toLowerCase()));
8600
+ for (const alias of aliases) {
8601
+ if (granted.has(alias.toLowerCase())) {
8602
+ errors.push(parseError(
8603
+ index,
8604
+ `Agent '${pending.name}' grants knowledge '${alias}' more than once.`,
8605
+ "agent-knowledge-grant-duplicate"
8606
+ ));
8607
+ pending.invalid = true;
8608
+ continue;
8609
+ }
8610
+ if (!knowledgeBases.has(alias.toLowerCase())) {
8611
+ errors.push(parseError(
8612
+ index,
8613
+ `Unknown knowledge alias '${alias}' in agent '${pending.name}'.`,
8614
+ "agent-knowledge-alias-unknown"
8615
+ ));
8616
+ pending.invalid = true;
8617
+ continue;
8618
+ }
8619
+ granted.add(alias.toLowerCase());
8581
8620
  pending.knowledge.push({ alias, lineNumber: index });
8582
8621
  }
8583
8622
  continue;
@@ -9533,6 +9572,7 @@ function isNonNegativeInteger(value) {
9533
9572
  var AGENT_LIMIT_CONFIG_KEYS = /* @__PURE__ */ new Set([
9534
9573
  "_comment",
9535
9574
  "max_tokens",
9575
+ "max_input_tokens",
9536
9576
  "max_depth",
9537
9577
  "max_invocations",
9538
9578
  "max_turns",
@@ -9542,7 +9582,7 @@ function hasOnlyKeys(value, allowedKeys) {
9542
9582
  return Object.keys(value).every((key) => allowedKeys.has(key));
9543
9583
  }
9544
9584
  function hasValidNornAgentLimits(value) {
9545
- return (value.max_tokens === void 0 || isPositiveInteger(value.max_tokens)) && (value.max_depth === void 0 || isPositiveInteger(value.max_depth)) && (value.max_invocations === void 0 || isPositiveInteger(value.max_invocations)) && (value.max_turns === void 0 || isPositiveInteger(value.max_turns)) && (value.contract_retries === void 0 || isNonNegativeInteger(value.contract_retries));
9585
+ return (value.max_tokens === void 0 || isPositiveInteger(value.max_tokens)) && (value.max_input_tokens === void 0 || isPositiveInteger(value.max_input_tokens)) && (value.max_depth === void 0 || isPositiveInteger(value.max_depth)) && (value.max_invocations === void 0 || isPositiveInteger(value.max_invocations)) && (value.max_turns === void 0 || isPositiveInteger(value.max_turns)) && (value.contract_retries === void 0 || isNonNegativeInteger(value.contract_retries));
9546
9586
  }
9547
9587
  function isNornAgentProviderConfig(value) {
9548
9588
  if (!isObjectRecord(value)) {
@@ -9562,7 +9602,13 @@ function isNornAgentRecordingConfig(value) {
9562
9602
  }
9563
9603
  return hasOnlyKeys(value, /* @__PURE__ */ new Set(["_comment", "enabled"])) && (value.enabled === void 0 || typeof value.enabled === "boolean");
9564
9604
  }
9565
- var AGENT_PROVIDER_NAMES = /* @__PURE__ */ new Set(["openai", "anthropic", "google", "local"]);
9605
+ var AGENT_PROVIDER_NAMES = /* @__PURE__ */ new Set([
9606
+ "openai",
9607
+ "openai_compatible",
9608
+ "anthropic",
9609
+ "google",
9610
+ "local"
9611
+ ]);
9566
9612
  function isNornAgentsConfig(value) {
9567
9613
  if (value === void 0) {
9568
9614
  return true;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "norn-cli",
3
3
  "displayName": "Norn",
4
4
  "description": "Version-controlled API and database tests. Author in VS Code, then run the same files from the CLI and CI.",
5
- "version": "3.3.0",
5
+ "version": "4.0.0",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"
@@ -10,7 +10,7 @@
10
10
  "license": "SEE LICENSE IN LICENSE",
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "https://github.com/PeterCrest/Norn"
13
+ "url": "git+https://github.com/PeterCrest/Norn.git"
14
14
  },
15
15
  "bugs": {
16
16
  "url": "https://github.com/PeterCrest/Norn/issues"
@@ -665,6 +665,6 @@
665
665
  "tough-cookie": "^6.0.0"
666
666
  },
667
667
  "bin": {
668
- "norn": "./dist/cli.js"
668
+ "norn": "dist/cli.js"
669
669
  }
670
670
  }
@@ -440,6 +440,11 @@
440
440
  "minimum": 1,
441
441
  "description": "Provider-specific maximum output-token ceiling for each agent invocation."
442
442
  },
443
+ "max_input_tokens": {
444
+ "type": "integer",
445
+ "minimum": 1,
446
+ "description": "Optional cumulative provider-reported input-token budget for one top-level agent invocation subtree."
447
+ },
443
448
  "max_depth": {
444
449
  "type": "integer",
445
450
  "minimum": 1,
@@ -492,6 +497,11 @@
492
497
  "minimum": 1,
493
498
  "description": "Maximum output-token ceiling for each agent invocation. This is a ceiling, not a spend; hosted providers default to 16000 and local defaults to 4096."
494
499
  },
500
+ "max_input_tokens": {
501
+ "type": "integer",
502
+ "minimum": 1,
503
+ "description": "Optional cumulative provider-reported input-token budget for one top-level agent invocation subtree. When omitted, Norn records usage without applying this spend guardrail."
504
+ },
495
505
  "max_depth": {
496
506
  "type": "integer",
497
507
  "minimum": 1,
@@ -536,6 +546,9 @@
536
546
  "openai": {
537
547
  "$ref": "#/definitions/agentProviderLimits"
538
548
  },
549
+ "openai_compatible": {
550
+ "$ref": "#/definitions/agentProviderLimits"
551
+ },
539
552
  "anthropic": {
540
553
  "$ref": "#/definitions/agentProviderLimits"
541
554
  },
package/CLAUDE.md DELETED
@@ -1,69 +0,0 @@
1
- # Norn Extension — Claude Code instructions
2
-
3
- ## Read these first
4
-
5
- 1. **[`NOW.md`](NOW.md)** — the tool-agnostic active-work board shared by every agent and by
6
- Peter. Whatever sits under **Active now** is the current priority. This board, not any
7
- assistant's memory, is the source of truth for what we are working on.
8
- 2. **[`AGENTS.md`](AGENTS.md)** — the full repo instructions (CLI parity requirement, how to
9
- run the local CLI, code style, testing). Everything in that file applies here.
10
-
11
- ## Skills: consult `.github/skills/` — always
12
-
13
- This repo keeps its accumulated implementation knowledge in **[`.github/skills/`](.github/skills/)**,
14
- maintained by every agent that works here. Treat it as required reading, not an optional
15
- reference.
16
-
17
- **Before touching any area of the codebase, check `.github/skills/` for a skill covering it
18
- and read that skill.** Do this even for changes that look small — the skills exist precisely
19
- because these areas have non-obvious rules that are easy to break.
20
-
21
- Current skills and what they cover:
22
-
23
- | Skill | Read it when working on |
24
- |-------|------------------------|
25
- | `norn-syntax-highlighting` | TextMate grammars in `syntaxes/`, token scopes, highlighting bugs |
26
- | `norn-agent-workbench` | `.nornagent` sidecars, agent `run` steps, providers, contracts, record/replay, agent graph |
27
- | `norn-intellisense-preferences` | Completion providers, IntelliSense behaviour |
28
- | `norn-value-resolution` | Variables, interpolation, `.nornenv`, value precedence |
29
- | `add-norn-keyword` | Adding a keyword to the `.norn` language |
30
- | `add-assertion-operator` | Adding an assertion operator |
31
- | `add-sequence-step-type` | Adding a new sequence step type |
32
- | `add-imported-sidecar-language` | Adding a new imported sidecar file type |
33
- | `norn-cli-integration` | `src/cli.ts` and CLI parity |
34
- | `norn-editor-decorations` | Drawing onto the editor — end-of-line chips, hiding markup, folding, caret traps |
35
- | `norn-debugger-integration` | DAP / debugger work |
36
- | `norn-response-panel` | The response webview panel |
37
- | `norn-test-explorer` | VS Code Test Explorer integration |
38
- | `norn-sequence-tags` | Sequence tags and filtering |
39
- | `norn-api-coverage` | `.nornapi` coverage features |
40
- | `norn-mcp-tools` | MCP client and tool wiring |
41
- | `norn-k8s` | Kubernetes features |
42
- | `norn-terminal` | Norn Terminal / PTY work |
43
- | `norn-env-templates` | `.nornenv` templates |
44
- | `norn-publishing` | Packaging and publishing the extension |
45
- | `norn-starter-catalog` | Starter templates catalog |
46
- | `norn-title-cards` | Title cards |
47
- | `demo-task-helper` | Building demos |
48
- | `readme-sync` | Keeping README/docs in sync with features |
49
-
50
- The table can go stale — list the directory when the work doesn't obviously map to a row.
51
-
52
- ### Maintaining skills
53
-
54
- Per `AGENTS.md`, skills are living documents:
55
-
56
- - If a skill is **wrong or outdated**, fix it as part of the change.
57
- - If a skill is **missing** for an area you just learned, write one in Agent Skills format.
58
- - After creating or editing any skill, run `npm run validate:skills`.
59
-
60
- Capture the lesson that was hard to learn, not a restatement of the code.
61
-
62
- ## Verification
63
-
64
- Never use `npx norn` — that runs the published package, not local changes:
65
-
66
- ```bash
67
- npm run compile
68
- node ./dist/cli.js tests/file.norn --env prelive
69
- ```
package/NOW.md DELETED
@@ -1,223 +0,0 @@
1
- # NOW — Active Work Board
2
-
3
- > **The single source of truth for what we are working on _right now_** — for Peter and
4
- > for every AI agent (Claude, Codex, Copilot, Cursor, any). Deliberately tool-agnostic:
5
- > it lives here in the repo, **not** in any one assistant's memory or private notes.
6
- >
7
- > We have a lot of planning docs; this board exists so nobody (human or AI) has to guess
8
- > what's current. If it's on this board, it's the work. If it's not, it isn't — yet.
9
-
10
- ## How this board works
11
-
12
- - **Empty by default.** Nothing is "current" unless it has been explicitly **Accepted**
13
- onto this board by Peter. An empty "Active now" means: no directed work in flight —
14
- don't start big work off a backlog/plan doc without Peter accepting it here first.
15
- - **Accepting work:** Peter says "Accept X" (or moves it here). That's the signal it
16
- becomes current. Until then, ideas live in the plan/backlog docs, not here.
17
- - **This board is an _index_, not the detail.** Each item is one or two lines + a link to
18
- its real plan doc. Keep the detail in the linked doc; keep this page short and scannable.
19
- - **Finishing work:** when an item is done, move it to "Recently done" (keep the last
20
- handful), then prune. Don't let this page grow.
21
- - **Agents:** treat items under "Active now" as the current priority order. Do these
22
- before picking up anything from the wider `Docs/` set.
23
-
24
- ---
25
-
26
- ## Active now
27
-
28
- ### 📚 Knowledge Base — the third pillar
29
- **Accepted 2026-08-15.** Norn covers **models** (providers, contracts, evals, record/replay) and
30
- **capabilities** (MCP servers and tools). What an agent still cannot get is **context**. The user
31
- writes prose in a declared folder and Norn derives the structure; because our runs have ground
32
- truth, *"did editing this document make agent runs better?"* is answerable here and nowhere else.
33
- Eight stages, ordered by dependency — **1 → 2 → 3** is the spine and where a wrong call is most
34
- expensive. **Stages 1, 2, 3 and 4 are built as of 2026-08-15.** Stage 1 established declared membership,
35
- topic identity, source-bounded domains, tombstones and deterministic DAG lineage. Stage 2 replaced
36
- its temporary JSON cache with one project-global built-in SQLite authority: atomic content-addressed
37
- snapshots, occurrence-scoped authored metadata, versioned action caches, conservative typed entity
38
- crossing, FTS5 diagnostics, retention/pins, and a queued extension worker. The calibrated fixture
39
- kept all nine entity kinds; exact values cross automatically while service spelling aliases remain
40
- proposals only. The database/WAL byte test proves `.nornenv` values and ciphertext do not persist.
41
- The CLI remains deliberately narrow and generic at index-backed `knowledge ls --explain`. Stage 3
42
- now applies sidecar-local grants to one lazily pinned snapshot per top-level run, routes exact typed
43
- entities with one conservative lexical fallback, assembles literal cache-stable prompt blocks under
44
- four explicit budgets, offers the bounded `load_knowledge` tool, and writes durable masked SQLite
45
- run/invocation/domain/topic/load telemetry independently of rolling replay files. Pure replay stays
46
- offline; a live tail fails closed without its recorded snapshot. The extension awaits its worker
47
- queue and the compiled local CLI shares identical runner semantics without new subcommands.
48
- **Stage 4 turned the ordinary Markdown editor into the authoring surface** — extension-only, no CLI
49
- command. A declared document now shows its own interpretation (topics, effective type and its
50
- origin, `use_when`, token estimates, exact entities that cross into the project), honest recorded
51
- use from Stage 3 telemetry, folded metadata, dimmed exclusions, and actions that write
52
- `type`/`use_when` and exclusions without moving a topic hash. The cost boundary is the design: a
53
- keystroke parses the open buffer once per version and reads cached memory, while every SQLite read
54
- runs on the index worker. Counts are raw co-occurrence — distinct completed runs for the headline,
55
- invocations and earlier lineage versions in their own rows, no causal language anywhere. Nine
56
- Stage 1, eleven Stage 2, eleven Stage 3 and eighteen Stage 4 focused tests pass under Node 24; 345
57
- pass across every vscode-free suite. **One gate stays open: the visual smoke test
58
- (light/dark/high-contrast) needs Peter's eyes — fixture at
59
- [`demos/knowledge-editor`](demos/knowledge-editor).**
60
- **Stage 5, the coverage panel, is the next dependency boundary and its cold-session handoff is
61
- ready.** Reviewing it against the built code moved three things: the mock's `402 runs 94%` is two
62
- different measurements plus one that does not exist anywhere (`K-20`), a panel row must be an
63
- endpoint rather than an entity or it contradicts itself on the first screen (`K-48`), and the
64
- "stale ref" list is where extractor false positives accumulate (`K-46`). Three gates need Peter
65
- before code: those, plus CLI scope and `K-47`.
66
- → **Stage 5 implementation handoff (start here next session):
67
- [`Docs/to-be-built/knowledge-base-stage-5.md`](Docs/to-be-built/knowledge-base-stage-5.md).**
68
- → **Stage 4 build record:
69
- [`Docs/to-be-built/knowledge-base-stage-4.md`](Docs/to-be-built/knowledge-base-stage-4.md).**
70
- → **Stage 3 build record:
71
- [`Docs/to-be-built/knowledge-base-stage-3.md`](Docs/to-be-built/knowledge-base-stage-3.md).**
72
- → Stage 2 build record: [`Docs/to-be-built/knowledge-base-stage-2.md`](Docs/to-be-built/knowledge-base-stage-2.md).
73
- → Stage 1 build record: [`Docs/to-be-built/knowledge-base-stage-1.md`](Docs/to-be-built/knowledge-base-stage-1.md).
74
- → Decision record: [`knowledge-base-refinement.md`](Docs/to-be-built/knowledge-base-refinement.md)
75
- (`K-01`…`K-45`, every Stage 1/2 gate shut and the Stage 3 review carried forward). Reasoning lives
76
- here; **do not re-litigate** — if a decision looks wrong, say so and stop.
77
- → Design and the *why*: [`knowledge-base.md`](Docs/to-be-built/knowledge-base.md).
78
-
79
- **The `K-28` spike answered its question — markdown cuts it, no custom renderer.** It and the
80
- `playground/knowledgeBase/` development corpus were deleted at the Stage 1 gate. Its durable
81
- findings remain in skill `norn-editor-decorations`; production knowledge code shares no spike
82
- foundation.
83
-
84
- Two decisions the spike overturned, both worth knowing before reading anything else: per-topic
85
- metadata sits **inline under each heading** (`K-13`), which removed the binding-key problem rather
86
- than solving it; and **depth counts** for domains (`K-30`), so a folder inside a domain is a
87
- sub-domain and splitting an oversized domain actually works.
88
- → **Refinement worksheet (start here): [`Docs/to-be-built/knowledge-base-refinement.md`](Docs/to-be-built/knowledge-base-refinement.md)**
89
- → Parent spec: [`Docs/to-be-built/knowledge-base.md`](Docs/to-be-built/knowledge-base.md)
90
- → Runtime floor and SQLite authority are recorded in `K-11` / `K-12` / `K-31`; Stage 2 applied
91
- them and removed the temporary Stage 1 JSON store.
92
-
93
- ---
94
-
95
- ## Backlog / awaiting acceptance
96
-
97
- _Nothing awaiting acceptance._ (Slice 5f was scrapped 2026-08-15 — it had decayed into a reminder
98
- to update docs. Docs get reviewed as one pass when the website is updated for the knowledge base.
99
- Its spec remains at [`agent-workbench-slice-5f.md`](Docs/to-be-built/agent-workbench-slice-5f.md)
100
- if it is ever wanted.)
101
-
102
- ## Parked
103
-
104
- ### Contract & Drift + Voiden-playbook marketing
105
- The previous direction, parked 2026-08-07 — untested, not failed. Stages 1+2 are built and
106
- shipped in 2.9.8; the QA dogfood and the "why Norn, now" hook never happened. Full
107
- narrative and how to resume:
108
- [`Docs/random/parked_contract_drift_and_marketing.md`](Docs/random/parked_contract_drift_and_marketing.md)
109
-
110
- ## Recently done
111
-
112
- - **3.2.0 — the knowledge base ships, quietly** (2026-08-19) — **done.** Stages 1–4 of the
113
- Knowledge Base are in the published build: declared `knowledge` blocks in `.nornagent`,
114
- the project-global SQLite authority, grant-scoped routing with the bounded `load_knowledge`
115
- tool, and the Markdown authoring surface. **Deliberately undocumented in public** — Peter's
116
- call at release time. Stages 5–8 are unbuilt and the coverage panel is still the next
117
- dependency boundary, so the website says nothing about it and neither does the README. The
118
- feature is reachable by anyone who writes the block; that is accepted, not overlooked.
119
- **Agent graph details now open on click, not hover.** The 650ms open delay and 120ms hide
120
- delay are gone: the reader either waited on a canvas that looked inert or was chased across
121
- it by a popover they never asked for. A second click closes, Escape dismisses, and the
122
- navigation that used to live on the tile — reveal source, step replay, reveal declaration —
123
- is now one labelled button in the detail footer, so a tile has exactly one gesture. The
124
- website's two "hover an agent or a boundary" sentences were updated to match, and the
125
- response panel's agent invocation tree got the docs line it never had.
126
- Verified at 426 Extension Host tests and 198/200 regression; the two failures are the live
127
- SQL Server Docker fixture, which was not running and was accepted as a known failure.
128
-
129
- - **Agent results view** (2026-08-14; performance follow-up 2026-08-15) — **done.** The normal
130
- response panel has a safe themed invocation tree; rows/details now start collapsed and large
131
- MCP values remain host-side until reveal/copy. Streamed contract/agent rows also have distinct
132
- toggle identities. → [Spec](Docs/to-be-built/agent-results-view.md)
133
- - **3.1.0 — MCP authoring parity, and the changelog is gone** (2026-08-13) — **done.**
134
- Three fixes that all came out of one painful hour authoring a real server by hand. **`header`
135
- now reads like HTTP**: `header Name: value` is accepted alongside the original no-colon form,
136
- because the colon form is what a `.norn` request writes and what everyone arrives with —
137
- rejecting it silently produced a header *named* `Authorization:` and the only complaint came
138
- from `Headers.append` at dial time, three layers from the line at fault. The name is now
139
- validated against the RFC 9110 token set at parse time, so an unsendable one is red in the
140
- editor. The header name and value carry `.norn`'s own `entity.name.tag.http` /
141
- `string.unquoted.http` scopes, so the identical header is the identical colour in both files,
142
- and the quoted-string pattern is gone from that rule — it advertised an escape that does not
143
- exist, and quotes really do end up inside the token. IntelliSense offers the shared
144
- `commonHeaders` list after `header `, inserting the colon form and chaining into the
145
- `Bearer {{$env.…}}` value completion.
146
- **`run mcp` completions now see sidecar-declared servers.** The precedence rule lived in three
147
- copies and the completion copy was left on config-only, so a project that declared every server
148
- in its sidecar offered no aliases at all while running perfectly. It is now one shared module,
149
- `src/mcpAliasScope.ts`, and the alias carries its origin — which matters because the tools cache
150
- is written beside whatever *declared* the server, so tool-name completions and signature help
151
- were reading the wrong `.norn-cache` even once an alias resolved. **Config `mcp.servers` stays**:
152
- supporting both is one function and one line per caller, so retiring it would have been strictly
153
- more work than keeping it.
154
- **`CHANGELOG.md` deleted** at Peter's call — the release record is git history plus this board,
155
- and a changelog was a third place saying the same thing and the likeliest to drift.
156
- Also fixed: `@vscode/test-electron` 2.5.2 could not launch VS Code 1.133 (the binary was renamed
157
- from `Electron` to `Code`), which had left the Extension Host suite dark.
158
- - **Agent Workbench — declared MCP servers** (2026-08-12) — **done, all five items.**
159
- Agents can now *do* things. A `.nornagent` sidecar declares the servers it uses beside the
160
- models it uses — `mcp <Alias> … end mcp`, stdio and http, credentials as `.nornenv`
161
- references — and each agent is granted a whole server with `mcp <Alias>` or named tools with
162
- `tools <Alias>.<tool>`. The two stay visibly different statements on purpose: a granted tool
163
- executes when the model asks, so the authored grant *is* the permission boundary and dropping
164
- four characters must not widen it from one tool to twenty. `session run | agent | call` makes
165
- shared server state a decision rather than a surprise; `run` stays the default because five
166
- agents testing one website are one testing session, and `session agent` is how an agent asks
167
- not to inherit a login it never earned. Config `mcp.servers` keeps working with the sidecar
168
- winning, `run mcp` steps reach declared servers so a server is provable with no model and no
169
- cost, and a granted server is a real node on the canvas — **declared servers only**, per
170
- Peter: the graph draws the program the file describes.
171
- Four corrections the build forced on the spec, all recorded in it: grant expansion **cannot**
172
- live above the runner (pure replay never runs that preflight, and a derived root definition
173
- never reaches sub-agents), so it is an injected hook with a live and a replay implementation;
174
- drift between record and replay is only knowable where a live listing happens, so the
175
- acceptance criterion demanding both was impossible as written; preflight needed a session
176
- boundary of its own, or `session agent` was already broken by the check that proves an agent's
177
- tools exist; and the spec's own demo sketch used an input expression the language does not
178
- have. Fixed along the way: a templated URL resolving differently mid-run silently reused the
179
- old session, and a refused connection reported the SDK's "fetch failed" instead of naming what
180
- it dialled. 60 focused tests across five items; 256 pass across every runnable suite, plus
181
- type-check, lint, bundles, skill validation, and regression at 198/200 (two live-SQL-Server
182
- sequences needing a database).
183
- → Spec: [`Docs/to-be-built/agent-workbench-mcp-servers.md`](Docs/to-be-built/agent-workbench-mcp-servers.md)
184
- · Demo: [`demos/mcp-ticket-testing`](demos/mcp-ticket-testing)
185
- - **Agent Workbench Slice 5e — reporting** (2026-08-10) — **done.**
186
- A verdict is now visible in the response panel, the canvas, the debugger, and JUnit, derived
187
- once through `agentHopPresentation` so the surfaces cannot disagree. The load-bearing move was
188
- putting the verdict on `AgentHopResult` rather than the step: a recording persists the trace
189
- and never step results, so a step-only verdict vanished the moment a past run was reopened —
190
- the round-trip is now a test. Two bugs the build surfaced: a judged hop has `success === true`
191
- even when the verdict failed, so the canvas drew a **green node for a failed run** until the
192
- verdict was counted explicitly; and the runner emits its `completed` event before the verdict
193
- exists, so the deep-cloned event log needed patching or replay playback showed no verdict.
194
- Also: judge statements now draw a sequence handoff edge from whatever produced the value they
195
- judge, live progress is emitted as `stepType: 'judge'`, and JUnit gets one testcase per
196
- statement with `ExpectationsUnmet` and `JudgeError` kept distinct. 13 new focused tests; 159
197
- pass across every runnable suite, plus type-check, lint, bundles, regression, skill validation,
198
- and a real CLI run producing JUnit.
199
- → [`Docs/to-be-built/agent-workbench-slice-5e.md`](Docs/to-be-built/agent-workbench-slice-5e.md)
200
- - **Agent Workbench Slice 5c — judge execution and the result contract** (2026-08-10) — **done,
201
- one deliverable deferred.** A `judge` statement now runs its judge and produces a verdict.
202
- The design that carries it: **the model is never asked for an overall result** — it rules on
203
- each expectation and quotes evidence, and Norn computes `passed`, checks one ruling per
204
- expectation with indexes in range, and verifies each quote occurs in the judged text. A judge
205
- is an ordinary agent invocation; everything judge-specific lives in a derived definition, so
206
- there is no judge runtime and no `if (isJudge)` in the runner. `builtInContracts` generalises
207
- "a contract that lives in code" and rides the existing generation/validation path.
208
- A provider or contract failure sets an error and no verdict, so "the judge said no" never
209
- reads like "the judge never answered". **A failed judge stops the sequence**, the same as a
210
- failed `assert` — it is the concluding check on an orchestration, so later steps must not run
211
- on a disproved premise; the run is still recorded and replayable. A malformed reply *shape* is
212
- corrected up to `contract_retries` via the opt-in `retryReturnsContract` (off for `run
213
- <Agent>`); the verdict itself is never re-rolled. Evidence that cannot be found in the judged
214
- text is flagged and still passes — strictness is intended to become per-expectation, not
215
- global. **Deferred:** the automatic replay drift check —
216
- `expectationsHash` is recorded and diffable, but pure replay never re-reads the `.norn`, so
217
- the comparison needs `runLineNumber` plumbing and lands with 5d, which owns the policy.
218
- Fixed along the way: judge hops reported false prompt drift on every replay. 18 new focused
219
- tests; 141 pass across every runnable suite, plus type-check, lint, bundles, regression, skill
220
- validation, and a real CLI run against a deterministic judge server.
221
- → [`Docs/to-be-built/agent-workbench-slice-5c.md`](Docs/to-be-built/agent-workbench-slice-5c.md)
222
- ---
223
- _Last updated: 2026-08-15_