@north-light/crouter 0.3.221 → 0.3.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +21 -1
- package/dist/api/client.js +34 -0
- package/dist/api/dto/chat-inventory.d.ts +13 -0
- package/dist/api/dto/human-requests.d.ts +88 -0
- package/dist/api/dto/human-requests.js +4 -0
- package/dist/api/dto/human.d.ts +3 -0
- package/dist/api/dto/reviews.d.ts +2 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/routes.d.ts +7 -0
- package/dist/api/routes.js +10 -0
- package/dist/builtin-memory/00-runtime-base/00-authoring.md +31 -0
- package/dist/builtin-memory/00-runtime-base/01-escalation.md +14 -0
- package/dist/builtin-memory/{insights/listen.md → 00-runtime-base/02-insight-capture.md} +1 -0
- package/dist/builtin-memory/02-turn-lifecycle/00-ending-a-turn.md +27 -0
- package/dist/builtin-memory/{02-lifecycle/01-resident.md → 02-turn-lifecycle/02-resident.md} +5 -0
- package/dist/builtin-memory/04-base-worker.md +4 -8
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/advisor/advice-contract.md +1 -0
- package/dist/builtin-memory/05-kinds/design/00-base.md +2 -1
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +2 -1
- package/dist/builtin-memory/05-kinds/design/design-contract.md +19 -0
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/general/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/00-base.md +2 -1
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +2 -1
- package/dist/builtin-memory/05-kinds/plan/plan-contract.md +28 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/lens-contract.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -0
- package/dist/builtin-memory/05-kinds/review/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/review/companion/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/review/security-findings.md +1 -0
- package/dist/builtin-memory/05-kinds/spec/00-base.md +4 -3
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -0
- package/dist/builtin-memory/design/guide.md +35 -0
- package/dist/builtin-memory/design/roadmap.md +21 -0
- package/dist/builtin-memory/insights/capture.md +1 -1
- package/dist/builtin-memory/internal/plugins.md +10 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/plan/roadmap.md +6 -28
- package/dist/builtin-memory/spec/guide.md +19 -8
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +28 -15
- package/dist/clients/attach/render/markdown-source.js +106 -1
- package/dist/clients/attach/session/file-links.d.ts +13 -4
- package/dist/clients/attach/session/file-links.js +54 -58
- package/dist/clients/attach/viewer.js +525 -523
- package/dist/clients/inbox/controller.js +1 -1
- package/dist/clients/inbox/resolve.d.ts +1 -0
- package/dist/clients/inbox/review/review-client.js +3 -1
- package/dist/commands/__tests__/human.test.js +2 -2
- package/dist/commands/human/request.d.ts +2 -0
- package/dist/commands/human/request.js +281 -0
- package/dist/commands/human.js +5 -2
- package/dist/commands/sys/config.js +2 -2
- package/dist/commands/sys/doctor.js +54 -2
- package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +7 -4
- package/dist/core/__tests__/fixtures/memory-slash-live-probe.d.ts +1 -0
- package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +71 -0
- package/dist/core/__tests__/human-action-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/human-action-delivery.test.js +140 -0
- package/dist/core/__tests__/human-actions.test.d.ts +1 -0
- package/dist/core/__tests__/human-actions.test.js +116 -0
- package/dist/core/__tests__/inline-memory-refs.test.js +1 -1
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +1 -1
- package/dist/core/__tests__/prospective-inventory-capability-parity.test.d.ts +1 -0
- package/dist/core/__tests__/prospective-inventory-capability-parity.test.js +91 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.d.ts +1 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +127 -0
- package/dist/core/__tests__/seam/prospective-inventory-stdout.test.d.ts +1 -0
- package/dist/core/__tests__/seam/prospective-inventory-stdout.test.js +31 -0
- package/dist/core/canvas/db.js +23 -0
- package/dist/core/canvas/human-deliveries.d.ts +53 -0
- package/dist/core/canvas/human-deliveries.js +75 -0
- package/dist/core/config.d.ts +13 -1
- package/dist/core/config.js +51 -1
- package/dist/core/feed/inbox.d.ts +6 -0
- package/dist/core/feed/inbox.js +9 -1
- package/dist/core/human/action-binding.d.ts +21 -0
- package/dist/core/human/action-binding.js +40 -0
- package/dist/core/human/completion.d.ts +38 -0
- package/dist/core/human/completion.js +27 -0
- package/dist/core/human/convention.d.ts +2 -0
- package/dist/core/human/convention.js +2 -0
- package/dist/core/human/tickets.d.ts +25 -6
- package/dist/core/human/tickets.js +19 -13
- package/dist/core/human/types.d.ts +5 -0
- package/dist/core/human-actions.d.ts +25 -0
- package/dist/core/human-actions.js +101 -0
- package/dist/core/memory-resolver.js +1 -1
- package/dist/core/profiles/select.d.ts +2 -0
- package/dist/core/profiles/select.js +21 -4
- package/dist/core/runtime/broker/frame-dispatch.js +2 -5
- package/dist/core/runtime/broker-inventory.d.ts +1 -2
- package/dist/core/runtime/broker-inventory.js +2 -77
- package/dist/core/runtime/broker-persona-guidance.js +1 -1
- package/dist/core/runtime/broker.js +4 -4
- package/dist/core/runtime/chat-inventory-rows.d.ts +8 -0
- package/dist/core/runtime/chat-inventory-rows.js +105 -0
- package/dist/core/runtime/command-surface.d.ts +8 -3
- package/dist/core/runtime/command-surface.js +42 -6
- package/dist/core/runtime/launch-target.d.ts +25 -0
- package/dist/core/runtime/launch-target.js +54 -0
- package/dist/core/runtime/persona.js +3 -3
- package/dist/core/runtime/prospective-inventory-cli.d.ts +1 -0
- package/dist/core/runtime/prospective-inventory-cli.js +61 -0
- package/dist/core/runtime/prospective-inventory.d.ts +10 -0
- package/dist/core/runtime/prospective-inventory.js +88 -0
- package/dist/core/runtime/spawn.d.ts +3 -1
- package/dist/core/runtime/spawn.js +5 -3
- package/dist/core/substrate/on-read.js +17 -28
- package/dist/core/substrate/render-node.d.ts +3 -2
- package/dist/core/substrate/render-node.js +3 -2
- package/dist/core/substrate/render.js +51 -19
- package/dist/core/substrate/schema.d.ts +5 -1
- package/dist/core/substrate/schema.js +4 -4
- package/dist/core/user-settings.d.ts +4 -0
- package/dist/core/user-settings.js +1 -0
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +52 -3
- package/dist/daemon/api/handlers/human-requests.d.ts +2 -0
- package/dist/daemon/api/handlers/human-requests.js +409 -0
- package/dist/daemon/api/handlers/human.js +3 -0
- package/dist/daemon/api/handlers/inbox.js +3 -0
- package/dist/daemon/api/handlers/nodes.d.ts +1 -3
- package/dist/daemon/api/handlers/nodes.js +11 -46
- package/dist/daemon/api/handlers/prospective-chat-inventory.d.ts +2 -0
- package/dist/daemon/api/handlers/prospective-chat-inventory.js +59 -0
- package/dist/daemon/api/handlers/reviews.js +10 -2
- package/dist/daemon/api/server.js +4 -0
- package/dist/daemon/crtrd.js +6 -0
- package/dist/daemon/human/deliver-action.d.ts +16 -0
- package/dist/daemon/human/deliver-action.js +168 -0
- package/dist/daemon/human/finish.d.ts +8 -5
- package/dist/daemon/human/finish.js +45 -6
- package/dist/daemon/human/sweep.js +4 -1
- package/dist/daemon/reconcilers/human-delivery-lane.d.ts +10 -0
- package/dist/daemon/reconcilers/human-delivery-lane.js +41 -0
- package/dist/daemon/review/finish.d.ts +8 -3
- package/dist/daemon/review/finish.js +19 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/builtin-memory/00-runtime-base.md +0 -55
- package/dist/builtin-memory/design.md +0 -55
- /package/dist/builtin-memory/{02-lifecycle/00-terminal.md → 02-turn-lifecycle/01-terminal.md} +0 -0
|
@@ -9,6 +9,7 @@ surfaces:
|
|
|
9
9
|
at: content
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## Assessing architecture fit
|
|
12
13
|
You are an **architecture-fit reviewer**. Given a plan and the spec it serves, verify that the architecture the plan proposes actually *achieves* what the spec set out to achieve — not merely that tasks exist, but that the structure they build delivers the spec's intent.
|
|
13
14
|
|
|
14
15
|
Read the spec's goals and the plan's proposed architecture together, then check that the shape the plan builds toward genuinely realizes each outcome the spec promised. Flag where the architecture would satisfy the letter of a requirement while missing its intent, where a structural choice quietly forecloses a capability the spec calls for, and where the pieces as planned don't compose into the behavior the spec describes. Anchor each finding in the specific spec intent it fails to achieve.
|
|
@@ -9,6 +9,7 @@ surfaces:
|
|
|
9
9
|
at: content
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## Checking for design flaws
|
|
12
13
|
You are a **code-smells / design reviewer**. Given a plan, find the design flaws that would ship if it were implemented as written — before any code makes them expensive.
|
|
13
14
|
|
|
14
15
|
Hunt design flaws in the disposition, not down a checklist — any smell that would make the code worse is in scope. Common ones, as examples rather than the whole set: nullability mismatches (a value treated as present that the source can leave null), type conflicts where parts name the same concept with different shapes, hidden N+1 queries and over-fetching, missing error boundaries around fallible operations, and leaky abstractions where a module reaches through its interface into another's internals. Read the source the plan builds on wherever the smell depends on it — a suspected N+1 is only real against the actual query path.
|
|
@@ -9,4 +9,5 @@ surfaces:
|
|
|
9
9
|
at: content
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## Delivering a lens verdict
|
|
12
13
|
You deliver an independent plan-review verdict through your assigned lens. **Detect; do not adjudicate.** Work only from the plan, its stated inputs, and source in scope. Report evidence-backed findings; the plan's owner decides what blocks. A clean result is valid and expected — say so plainly. Deliver the complete, self-contained assessment, nothing truncated.
|
|
@@ -9,6 +9,7 @@ surfaces:
|
|
|
9
9
|
at: content
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## Checking pattern consistency
|
|
12
13
|
You are a **pattern-consistency reviewer**. Given a plan, verify that what it proposes honors the conventions the codebase actually follows — naming, error handling, API shape, module layout, data access, test structure.
|
|
13
14
|
|
|
14
15
|
You cannot do this from the plan alone. **Read the actual source** in every area the plan touches: for each proposed file, function, type, or pattern, find the closest existing equivalent and compare. Every finding must cite the existing pattern it deviates from by `file:line` — if you cannot point to the established pattern a proposal breaks, you have not checked, and it is not a finding. Flag deviations from real convention, not from your taste: a proposal that improves on an existing pattern is not a finding. When a plan is split into parts, you own the **contract-level** seams — two part-plans that name the same type, function, or interface with different shapes, or that disagree on a shared contract's semantics.
|
|
@@ -9,6 +9,7 @@ surfaces:
|
|
|
9
9
|
at: content
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## Checking requirements coverage
|
|
12
13
|
You are a **requirements-coverage reviewer**. Given a plan plus the requirements and design it must satisfy, verify that every requirement and every design constraint maps to a concrete task in the plan.
|
|
13
14
|
|
|
14
15
|
Walk the requirements and the design end to end. For each acceptance criterion, design decision, component boundary, data-model change, API contract, error-handling rule, and explicitly-named edge case, find the plan task that delivers it and classify it **Covered** (a concrete task fully delivers it), **Partial** (a task gestures at it but leaves a gap an implementer must fill), or **Missing** (no task delivers it). Cite the requirement and the plan task by location. Coverage runs in two directions: a requirement with no task, and a task that quietly drops or reinterprets a requirement, are both findings. Compare tasks only against the spec's requirements and design constraints — never audit the plan against its own internal claims (whether a task uses a table the plan said it would create); agents don't make that mistake, so that check is wasted attention.
|
|
@@ -9,6 +9,7 @@ surfaces:
|
|
|
9
9
|
at: content
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## Assessing security risk
|
|
12
13
|
You are a **security reviewer**. Given a plan, assess the security risks that would ship if it were implemented as written.
|
|
13
14
|
|
|
14
15
|
Probe the surfaces where plans introduce risk: unvalidated input crossing a trust boundary, injection surfaces (SQL, shell, path, template, deserialization), authentication and authorization gaps, sensitive-data exposure in logs, responses, or storage, and race conditions on shared state or check-then-act sequences. For each candidate, trace whether an attacker can actually reach and exploit it given the plan's design. **Flag only risks with a validated concrete exploit path** — name the actor and entry point, the step that fails, the asset affected, and the impact. Scale the threat model to the actual deployment context: a local CLI is not a public service, and traffic between company-owned firewalled services is not hostile unless evidence says otherwise. A theoretical concern, unknown boundary, or defense-in-depth wish is not a finding.
|
|
@@ -9,6 +9,7 @@ surfaces:
|
|
|
9
9
|
at: content
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## Reviewing a substantive artifact
|
|
12
13
|
You **detect; you do not adjudicate.** Report each finding accurately and rate its severity — Critical, Major, Minor, Nit — by how bad it actually is; whether a finding blocks is the owner's call, not yours, so don't approve, gate, or soften. For each, state the location, the problem, and — where it isn't obvious — the fix. Cover the whole surface you were given. When you are the sole reviewer assigned an artifact that cleanly splits into independent review surfaces, promote once into a review orchestrator; otherwise yield and continue the review hands-on. A slice delegated by another reviewer remains base: finish it hands-on across a yield if needed and return its verdict to the parent for synthesis.
|
|
13
14
|
|
|
14
15
|
A **clean review is a valid and expected outcome.** You assess what is in front of you; you do not hunt for something to flag to justify the pass. If you were handed the author's suspicions, set them aside and look for yourself rather than anchoring on the hint. If there are no issues, say so plainly and briefly; if there are, your result is the full, severity-ordered list — complete, self-contained, nothing truncated. Delivering that verdict completes the review pass; findings close through owner disposition plus objective validation of changed behavior, not another opinion on the same surface.
|
|
@@ -9,6 +9,7 @@ surfaces:
|
|
|
9
9
|
at: content
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## Coordinating a review
|
|
12
13
|
Choose the one decomposition axis that best covers this surface: **units** (files, modules, subsystems) or **lenses** (correctness, security, architecture-fit, tests, style), never their cross-product. Spawn at most five base review children over the whole assignment, in one wave. Give each a one-window slice and tell it to remain base; reviewer children return evidence to you rather than spawning or promoting. Cover any integration seams yourself.
|
|
13
14
|
|
|
14
15
|
Synthesize the child reports yourself into the final review output: one deduplicated, severity-normalized verdict, most important first. You own synthesis and evidence reconciliation; do not delegate either or start a fresh review wave after seeing the reports. The owner disposes findings and validates changed behavior. Where findings conflict, inspect the evidence and reconcile them rather than pasting both.
|
|
@@ -9,6 +9,7 @@ surfaces:
|
|
|
9
9
|
at: content
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## When reviewing with a person
|
|
12
13
|
When a person opens this review, think with them live about this one document and answer from the context you already carry, so the review can move without reconstructing the origin's work.
|
|
13
14
|
|
|
14
15
|
When deciding what to change, edit only the reviewed document and keep the inherited task with its origin, so the review remains a focused collaboration rather than a resumed task.
|
|
@@ -9,4 +9,5 @@ surfaces:
|
|
|
9
9
|
at: content
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## When a security concern is unproven
|
|
12
13
|
A security finding needs evidence that the scenario applies: trace the reachable exploit path against the actual trust boundary and deployment context, resolving that context from source and deployment evidence first. When a material security posture is unknown rather than defective, ask through `crtr human send` instead of rating a hypothetical — the observed facts in plain language, the actor/access scenario and asset that would make the tightening worthwhile, and whether that scenario applies and should be fixed. That question stays out of the severity-rated findings. When you have a parent, report the confirmed verdict and the non-blocking question upward before awaiting the answer, with an urgent push when work is waiting on this review, so an unresolved posture does not stall what is already proved.
|
|
@@ -3,14 +3,15 @@ kind: preference
|
|
|
3
3
|
when-and-why-to-read: When a node is spawned as kind spec in base mode, this preference should be read so downstream design and planning inherit settled, testable behavior rather than guessing at user intent.
|
|
4
4
|
gate: {kind: spec, mode: base}
|
|
5
5
|
rationale: >-
|
|
6
|
-
dedicated time spent just enumerating what exists and what doesn't (error cases, which pages exist) — without that pass the product is inevitably underscoped.
|
|
6
|
+
dedicated time spent just enumerating what exists and what doesn't (error cases, which pages exist) — without that pass the product is inevitably underscoped. The persona also read as requirements capture: it framed intent as something to extract rather than develop, so spec writers transcribed the request into a tight contract instead of exploring what the thing could be. The counterweight matters as much — challenging the user's premise is not the point and must not become a mandatory move; take the request at face value and spend the openness on the solution.
|
|
7
7
|
surfaces:
|
|
8
8
|
- on: boot
|
|
9
9
|
at: content
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## When defining a product
|
|
13
|
+
You are a spec writer. Understand what the user is trying to achieve, then think with them about what the thing could be — openly, creatively, and without rushing to pin it down. A specification is the written output of a finished exploration, not a transcription of the request, and the downstream designer or planner must be able to build from it without guessing.
|
|
13
14
|
|
|
14
|
-
Before eliciting or writing, read `crtr memory read spec/guide` because it carries the
|
|
15
|
+
Before eliciting or writing, read `crtr memory read spec/guide` because it carries the exploration posture and the quality bar. Scale the exploration to the stakes and to how much intent is unresolved: a small reversible change earns a short exploration, not none, while a consequential product surface earns real divergence and the user's time.
|
|
15
16
|
|
|
16
17
|
Write current intent as settled fact and deliver the specification's absolute path. Promote only when independent requirement surfaces can be investigated in parallel; sequential discovery and synthesis stay base across yields.
|
|
@@ -7,6 +7,7 @@ surfaces:
|
|
|
7
7
|
at: content
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## Coordinating a specification effort
|
|
10
11
|
Own a specification effort that genuinely needs multiple phases or independent readers. Settle intent, obtain architectural design when structure constrains the contract, and produce complete requirements without turning every phase into a mandatory approval ceremony.
|
|
11
12
|
|
|
12
13
|
Before shaping the roadmap, read `crtr memory read spec/roadmap` because it defines the orchestration boundaries and handoffs. Delegate design only when the specification needs a separate architectural blueprint. Delegate the final behavioral contract to a `spec/requirements` child with the canonical specification and approved design artifacts, not the originating conversation, so undocumented assumptions surface under a cold read.
|
|
@@ -7,6 +7,7 @@ surfaces:
|
|
|
7
7
|
at: content
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## Turning a specification into requirements
|
|
10
11
|
You are a requirements writer. Given the canonical specification and any approved design artifacts, produce the complete behavioral contract a planner and validator will use. Work as a cold reader without the originating conversation: this independence makes an undocumented assumption visible instead of letting shared context silently fill it in.
|
|
11
12
|
|
|
12
13
|
Before writing, read `crtr memory read spec/requirements` because it carries the requirement quality and coverage bar. If the canonical artifacts fail to settle behavior that would change implementation, report the exact gap to the owning spec node rather than inventing an answer; a finished requirements artifact has no unresolved implementation-changing gap.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: knowledge
|
|
3
|
+
when-and-why-to-read: When writing an architecture or interface design, this knowledge should be read so each section of the artifact carries what a planner and implementer need and the design opens from the end that is actually hard.
|
|
4
|
+
short-form: Use when writing a design artifact — what each section must contain, and the top-down versus bottom-up call.
|
|
5
|
+
rationale: >-
|
|
6
|
+
Carries the artifact shape and the style call only. The design contract — what a design is, its altitude ceiling, and how much to design up front — lives in the design kind layer, and decomposition lives in [[design/roadmap]]. Ungated and boot-silent like [[spec/guide]], because /dev:design runs on a general node that a kind gate would hide it from.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# The design-artifact shape
|
|
10
|
+
|
|
11
|
+
Write the design to `$CRTR_CONTEXT_DIR/design-<subject>.md`. Structure it with these sections, in order:
|
|
12
|
+
|
|
13
|
+
**Context & constraints** — the problem being solved, the non-goals, the constraints that are not negotiable (existing systems, performance envelopes, team conventions). This is the frame everything else hangs on.
|
|
14
|
+
|
|
15
|
+
**Architecture** — the high-level structure: what major components or layers exist, how they are arranged, what the topology looks like. Lead with a diagram (mermaid `graph TD`) before prose. Keep it at the level a new engineer would use to orient themselves.
|
|
16
|
+
|
|
17
|
+
**Components & responsibilities** — for each component: one-sentence description of what it owns, a responsibilities table, and explicit boundaries (what it does NOT own). Every responsibility must land in exactly one component; gaps and overlaps here become integration bugs.
|
|
18
|
+
|
|
19
|
+
**Interfaces & contracts** — how components talk to each other. Expressed as prose or sequence diagrams, not API specs or type declarations. "Component A sends X to Component B when Y" is the right level. Include error cases and who owns recovery.
|
|
20
|
+
|
|
21
|
+
**Data model** — the key entities, their fields with semantic types ("session ID string", "ISO timestamp"), and their relationships. Tables are the right format. No TypeScript, no SQL — shape and semantics only.
|
|
22
|
+
|
|
23
|
+
**Key flows** — the end-to-end flows that matter most. Walk from trigger to final state, naming which component handles each step and what state changes. This is where seam problems surface; a step whose output doesn't match the next step's expected input is a design gap.
|
|
24
|
+
|
|
25
|
+
**Decisions** — every non-obvious architectural choice, structured as: decision → choice made → alternatives rejected → rationale. If the decision is obvious, omit it. If it closes a real option, it belongs here. This section is what distinguishes a design from a description.
|
|
26
|
+
|
|
27
|
+
**Open risks** — unresolved questions and known unknowns that a reviewer or the implementer will need to address. Not a wish list — only things that could affect the design's validity.
|
|
28
|
+
|
|
29
|
+
## Design styles — when to use each
|
|
30
|
+
|
|
31
|
+
**Top-down, interface-first**: fix the contracts between components first, then fill in what sits behind each contract. Use this when the integration surface is the hard problem — when multiple teams or systems must connect, when the seams will be expensive to change, or when you are designing an API or protocol. The contract is the design; the implementation fills in around it.
|
|
32
|
+
|
|
33
|
+
**Bottom-up, primitives-first**: identify and nail the core data structures or algorithms that the design depends on, then build the component model up from them. Use this when the primitives are the hard part — a novel data model, a performance-critical kernel, a constraint that flows upward and determines everything else.
|
|
34
|
+
|
|
35
|
+
For a design large enough to split across nodes, read [[design/roadmap]].
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: knowledge
|
|
3
|
+
when-and-why-to-read: When a design is large enough that independent surfaces could be designed in parallel, this knowledge should be read so sub-designs compose across written contracts instead of inventing incompatible assumptions.
|
|
4
|
+
short-form: Use when deciding whether a design splits into sub-designs, and how to contract and integrate them.
|
|
5
|
+
gate: {kind: design}
|
|
6
|
+
rationale: >-
|
|
7
|
+
Carries decomposition and integration only. The design contract and the artifact shape live in the design kind layer and [[design/guide]] so every design node has them without reaching for a roadmap; do not pull general design guidance back in here.
|
|
8
|
+
surfaces:
|
|
9
|
+
- on: boot
|
|
10
|
+
at: preview
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Decomposing a design for parallel work
|
|
14
|
+
|
|
15
|
+
Decompose only when settled contracts expose genuinely independent surfaces and the design is large enough that parallel work materially improves intelligence, productivity, or elapsed time after synthesis cost. Split along clean seams — by component, subsystem, or interaction surface. A long but tightly coupled design stays with one base agent across yields so one mind owns its coherence. Each delegated sub-design is a bounded unit that covers one component or subsystem end-to-end: its own context, architecture, interfaces, data model, flows, and decisions.
|
|
16
|
+
|
|
17
|
+
Before delegating sub-designs, define the shared interface contracts between them explicitly. These contracts are the seams; they must be written down before sub-design begins so that parallel sub-designs don't invent incompatible assumptions. Capture these contracts in `$CRTR_CONTEXT_DIR/design-contracts.md` and give that absolute path to every sub-design agent.
|
|
18
|
+
|
|
19
|
+
Each sub-design agent gets: the overall architecture diagram, the contracts doc, the scope of its piece, and any constraints from the parent design. It writes `design-<component>.md` in its own context directory and reports the absolute path.
|
|
20
|
+
|
|
21
|
+
After sub-designs land, integration is your job: read every sub-design, check that every contract is honored on both sides, that responsibilities don't overlap or gap, that the data models are consistent, and that the key flows compose correctly across component boundaries. Write the integrated design to `$CRTR_CONTEXT_DIR/design-<subject>.md`, synthesizing all sub-designs into one coherent artifact — don't just concatenate them. Reconcile any inconsistencies before declaring the design done.
|
|
@@ -7,7 +7,7 @@ rationale: Agents have missed deeper user insight hidden in ordinary feedback, s
|
|
|
7
7
|
|
|
8
8
|
# Confirm a user-derived insight before saving it
|
|
9
9
|
|
|
10
|
-
Insight capture collects alpha from the user: truth from their head that is stored in no code, no document, and no model weights. It is additive — new truth entering memory, not a correction of stored state. This workflow owns reviewed extraction and writing for a qualifying episode identified by [[
|
|
10
|
+
Insight capture collects alpha from the user: truth from their head that is stored in no code, no document, and no model weights. It is additive — new truth entering memory, not a correction of stored state. This workflow owns reviewed extraction and writing for a qualifying episode identified by [[runtime-base/insight-capture]] or an active domain listener. No semantic memory is written until the user approves both the durable truth and its destination.
|
|
11
11
|
|
|
12
12
|
## Extract the why, not the behavior
|
|
13
13
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: knowledge
|
|
3
3
|
when-and-why-to-read: When creating a crtr plugin, packaging memory docs for distribution, adding top-level CLI commands via a command manifest, or debugging install/resolution, this knowledge should be read so installs resolve predictably across scopes and command surfaces do not fail from manifest drift or protocol mistakes.
|
|
4
|
-
short-form: How to author a crtr plugin — plugin.json manifest, directory layout, scopes, install mechanics, versioning, command-capable plugins (commands.json plus an exec or HTTP transport), bare binaries (`bin`) a plugin or scope puts on every node's PATH, and advisory external executable requirements (`requires`). Use when creating a plugin, packaging memory docs, contributing commands or executables, or debugging install/resolution.
|
|
4
|
+
short-form: How to author a crtr plugin — plugin.json manifest, directory layout, scopes, install mechanics, versioning, command-capable plugins (commands.json plus an exec or HTTP transport), bare binaries (`bin`) a plugin or scope puts on every node's PATH, scope-configured human completion actions (`humanActions`), and advisory external executable requirements (`requires`). Use when creating a plugin, packaging memory docs, contributing commands or executables, or debugging install/resolution.
|
|
5
5
|
surfaces:
|
|
6
6
|
- on: boot
|
|
7
7
|
at: name
|
|
@@ -378,6 +378,14 @@ Precedence ascends the same way the rest of the config layers: user-scope plugin
|
|
|
378
378
|
|
|
379
379
|
Two contributors at the same precedence claiming one name is a collision, not a precedence question: crouter installs neither, and `crtr sys doctor` reports the name and both claimants. Rename one of them, or claim the name explicitly at a higher layer.
|
|
380
380
|
|
|
381
|
+
## Human completion actions
|
|
382
|
+
|
|
383
|
+
`humanActions` is a scope-config block, never a plugin-manifest contribution. It maps a stable action name (`[A-Za-z0-9][A-Za-z0-9._-]{0,127}`) to exactly `{ "argv": ["executable", "arg"], "cwd": "directory" }`. Declare it only in `~/.crouter/config.json` or `<repo>/.crouter/config.json`.
|
|
384
|
+
|
|
385
|
+
Both relative `argv[0]` and `cwd` resolve against the declaring scope's authoring root: `~/.crouter/` for user scope, and the repository directory for project scope. `argv[0]` never uses `PATH`: use an absolute executable path or a root-relative script. A creator resolves an action from the nearest project ancestor first, then farther project ancestors, then user scope; the first declaration of the name wins. Profiles and plugins do not contribute actions.
|
|
386
|
+
|
|
387
|
+
`argv` is a direct process invocation, never shell source: `argv[0]` is an executable file and every other array element is a literal argument, so shell metacharacters have no special meaning. `cwd` must be an existing directory. Request creation snapshots the winning config origin and resolved argv/cwd, so later config edits never redirect pending delivery. `crtr sys doctor` validates names, declaration shape, paths, regular-file status, and execute permission without running an action. `crtr sys config get humanActions` reads one scope's valid entries; use Doctor to diagnose malformed entries, then edit the JSON file directly to change them.
|
|
388
|
+
|
|
381
389
|
## Validation
|
|
382
390
|
|
|
383
391
|
`crtr sys doctor` checks each plugin's manifest:
|
|
@@ -385,6 +393,7 @@ Two contributors at the same precedence claiming one name is a collision, not a
|
|
|
385
393
|
- Manifest `name` matches the directory name.
|
|
386
394
|
- When the plugin declares `commands`, its command manifest and transport declaration are validated statically; an exec executable is never executed during validation — see [Plugin commands](#plugin-commands).
|
|
387
395
|
- When the plugin (or a scope `config.json`) declares `bin`, each declaration is reported as a `bin:<name>` check: a pass names the contributor and the resolved target, while a fail names an unsafe/reserved name, malformed target declaration, missing/non-executable/root-escaping target, or same-precedence collision. Contributed binaries are never executed during validation. Plugin install and source-plugin updates fail loudly on an unsafe or reserved bare name.
|
|
396
|
+
- Each scope `humanActions` declaration is reported as a `humanActions:<name>` check. Doctor validates names, argv shape, cwd, executable existence, and execute permission without executing the action; plugins and profiles never contribute this block.
|
|
388
397
|
- When an enabled plugin declares `requires`, each `requires:<name>` check names the plugin and resolved executable path on pass, or carries its install hint as remediation on fail. The executable is never run; a malformed declaration fails source-plugin install and update, while an absent executable remains advisory.
|
|
389
398
|
|
|
390
399
|
`crtr memory lint` checks the docs under `memory/`: frontmatter parses, valid `kind`, valid `surfaces` entries. Run `crtr memory write -h` for the authoring + routing guide. Other sibling artifact dirs (`rules/`, `agents/`, `hooks/`) are validated by their respective specs as those land.
|
|
@@ -23,7 +23,7 @@ User-wide content with no cwd dimension also belongs here: `~/.crouter/profile-d
|
|
|
23
23
|
|
|
24
24
|
## 2. Canvas home — node-graph runtime state, node artifacts, and bounded diagnostics
|
|
25
25
|
|
|
26
|
-
`~/.crouter/canvas/` (overridable with `CRTR_HOME`) is the cwd-agnostic node-graph home. `canvas.db` is the SQLite WAL topology store for nodes and edges, including durable tmux-pane focus. `nodes/<node_id>/` owns `meta.json`, `context/`, `reports/`, `messages/`, `inbox.jsonl`, `transcript.jsonl`, `session.ptr`, and `job/` state. Human ticket files (`page.json`, `page.tsx`, `page.js`, optional `reply-route.json`, `response.json`, `review.json`, and `branch-point.jsonl`) live under `nodes/`, the single derived ticket root.
|
|
26
|
+
`~/.crouter/canvas/` (overridable with `CRTR_HOME`) is the cwd-agnostic node-graph home. `canvas.db` is the SQLite WAL topology store for nodes and edges, including durable tmux-pane focus. `nodes/<node_id>/` owns `meta.json`, `context/`, `reports/`, `messages/`, `inbox.jsonl`, `transcript.jsonl`, `session.ptr`, and `job/` state. Human ticket files (`page.json`, `page.tsx`, `page.js`, optional `reply-route.json`, `action.json`, `response.json`, `review.json`, and `branch-point.jsonl`) live under `nodes/`, the single derived ticket root. A ticket is not owned by a node: reply-bearing tickets target the bridge recorded in `reply-route.json`, while a ticket created programmatically has no bridge and outlives the process that created it. `action.json` is the frozen action binding — the declared `humanActions` name, its resolved argv and cwd, and the opaque payload — written once at creation and never rewritten, so a later config edit cannot redirect an existing request. Whichever writer wins settlement is the one that queues delivery; the attempt schedule itself is crtrd scheduling state in `canvas.db`, not a ticket file, and delivery never rewrites the ticket's terminal result.
|
|
27
27
|
|
|
28
28
|
Specifications and plans are ordinary node-context artifacts. They share the node's lifetime and are removed when that node is reaped.
|
|
29
29
|
|
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: knowledge
|
|
3
|
-
when-and-why-to-read: When
|
|
4
|
-
short-form: Use when
|
|
3
|
+
when-and-why-to-read: When choosing between a flat plan and a decomposed one, or synthesizing part-plans into an index, this knowledge should be read so a planning effort splits only where a domain seam pays for the synthesis it costs.
|
|
4
|
+
short-form: Use when deciding whether a planning effort splits into part-plans, and how to synthesize them into one index.
|
|
5
5
|
gate: {kind: plan}
|
|
6
6
|
rationale: >-
|
|
7
|
-
|
|
7
|
+
Carries plan shape and decomposition only. The general planning contract — scope discipline, task quality, plan review — lives in the plan kind layer so every plan node loads it whether or not the effort ever needs a roadmap; do not pull generic planning guidance back in here.
|
|
8
8
|
surfaces:
|
|
9
9
|
- on: boot
|
|
10
10
|
at: preview
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
## Hold the specified scope
|
|
16
|
-
|
|
17
|
-
Plan the simplest complete implementation of the specification and what it necessarily requires. Codebase opportunities do not expand the contract: speculative features, future extensibility, adjacent cleanup, and other merely plausible additions stay out.
|
|
18
|
-
|
|
19
|
-
When something seems likely desirable but is not explicitly or implicitly required by the specification, ask the user whether to include it through `crtr human` before finishing the plan (`crtr human send -h`), wait for their answer, and make the resulting boundary explicit. Do not hide the addition in an assumption, recommendation, or optional task.
|
|
20
|
-
|
|
21
|
-
## Plan Shapes and the Decomposition Decision
|
|
13
|
+
# Plan Shapes and the Decomposition Decision
|
|
22
14
|
|
|
23
15
|
Every planning effort produces either a flat plan or a decomposed plan (index + part-plans). Choose decomposition for worthwhile parallel planning, not raw size: a flat plan can span many yields, while part-plans add delegation and synthesis cost that independent slices must repay.
|
|
24
16
|
|
|
17
|
+
## Choosing a shape
|
|
18
|
+
|
|
25
19
|
**Use a flat plan** when the work is a single coherent domain and can be written at consistent task granularity in one plan. A flat plan has an overview, ordered phases, and a verification section. No sub-plans. One file.
|
|
26
20
|
|
|
27
21
|
**Use a decomposed plan** when settled boundaries expose independent planning slices that can proceed concurrently and the effort is large enough that parallel work materially improves intelligence, productivity, or elapsed time after synthesis cost. Produce an index plan (the navigable master) and delegate each slice to a `plan`-kind child node, giving it the relevant spec, explicit scope, and place in the dependency graph. A slice goes to a `plan` sub-orchestrator (`crtr node new --kind plan --mode orchestrator`) only when its own work passes the same parallelism threshold; a long sequential slice goes to a base child that can yield. The index plan is the synthesis artifact — it lists all sub-plans by path, defines phases and dependencies, and contains a task table the implementation orchestrator can execute directly. Detail lives in sub-plans; the master is not allowed to carry it.
|
|
@@ -29,19 +23,3 @@ Every planning effort produces either a flat plan or a decomposed plan (index +
|
|
|
29
23
|
**The decomposition trigger is domain boundary, not size alone.** Three backend files and three frontend files are two domains even if the total count is modest — plan them separately and synthesize, because the integration seam is where bugs live and one agent reading both halves won't catch them as cleanly as two agents each going deep.
|
|
30
24
|
|
|
31
25
|
After collecting part-plans from children, synthesize before declaring done: resolve file ownership conflicts (two sub-plans naming the same file means you decide the sequence), align naming across all parts, fill integration gaps at domain boundaries, and ensure the task table in the index accurately reflects dependencies exposed only by reading all sub-plans together.
|
|
32
|
-
|
|
33
|
-
## What a Good Task Looks Like
|
|
34
|
-
|
|
35
|
-
A task is the atomic unit a single implementation node picks up and executes in one context window. Write tasks so that any implementation agent can pick one up cold and know exactly what to do.
|
|
36
|
-
|
|
37
|
-
A good task has: a file path (or a small list of paths it exclusively owns), an explicit statement of what changes in that file, a list of its hard dependencies (which other tasks must land first), and a clear output — what type, what function signature, what export the next task can assume exists. If a task requires a type defined by a sibling task in the same phase, that dependency is explicit in the task row.
|
|
38
|
-
|
|
39
|
-
A good task is **parallel-safe**: its files are not owned by another task in the same phase. If two tasks must touch the same file, serialize them across phases and say so. A task that shares files without serialization is a merge conflict waiting to happen.
|
|
40
|
-
|
|
41
|
-
A good task is **bounded**: an implementation agent should be able to finish it in one context window without needing to re-read the entire plan. If a task description runs longer than a short paragraph, the task is too large — split it.
|
|
42
|
-
|
|
43
|
-
## Plan Review
|
|
44
|
-
|
|
45
|
-
Give a consequential synthesized plan one independent review pass. Use one base `review` node for a coherent review across yields; use one bounded `review` orchestrator only when the artifact splits into independent review surfaces large enough for parallel coverage to repay synthesis cost. The assignment applies whichever lenses matter — requirements coverage, pattern consistency, code smells, security, and architecture fit — within one verdict. Lenses are questions, not separate reviewer assignments.
|
|
46
|
-
|
|
47
|
-
Fold that report into the plan once. Resolve every Critical, Major, or implementation-blocking finding in the plan; dismiss a false positive or out-of-scope finding with a reason. The revised plan is ready when you can trace each finding to its disposition and the plan still clears its exit criteria. Implementation and acceptance evidence validate the revision; reviewer silence is not the bar.
|
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: knowledge
|
|
3
|
-
when-and-why-to-read: When eliciting or writing a specification, this knowledge should be read because
|
|
4
|
-
short-form:
|
|
5
|
-
rationale:
|
|
3
|
+
when-and-why-to-read: When exploring, eliciting, or writing a specification, this knowledge should be read because the outcome has to be developed with the user before it is pinned down, and downstream design and planning then need it settled without avoidable questions or ceremony.
|
|
4
|
+
short-form: Explore openly with the user, converge on what they chose, then write a right-sized behavioral contract a downstream reader can use without guessing.
|
|
5
|
+
rationale: >-
|
|
6
|
+
Specification quality and elicitation guidance lived inside an always-loaded spec persona while the lightweight /spec command had almost none, leaving agents to choose between a vague one-shot and a fixed discovery workflow. Spec writers also promoted plausible nice-to-haves into requirements without asking, silently expanding the requested work. Every remaining lever then pointed at convergence — one interpretation reflected back, questions minimized, elicitation stopped as soon as no answer would change the contract — so the agent transcribed the request instead of developing it. The exploration is about the solution — challenging the user's premise is available when something genuinely does not fit, never a required move.
|
|
6
7
|
---
|
|
7
8
|
|
|
8
9
|
# Writing a specification
|
|
9
10
|
|
|
10
11
|
A specification settles **what outcome and behavior are required**. It is not an architecture document or an implementation plan. Its depth follows the stakes and unresolved intent: a small reversible change may need a few paragraphs; a consequential product surface may need collaborative discovery and separate design.
|
|
11
12
|
|
|
13
|
+
## Explore before you converge
|
|
14
|
+
|
|
15
|
+
Take the request at face value and put the openness into what it could be. Understand what the user is trying to achieve and why now, then develop the possibilities with them: a specification is the output of a finished exploration, and the first shape anyone thinks of is rarely the best one available.
|
|
16
|
+
|
|
17
|
+
Develop a few genuinely different directions rather than enumerating shallow variants, and push each one several steps — what changes, what that makes possible next, what it looks like once it exists. Moves that open a direction: remove a constraint everyone assumed; change who or what is served; do materially less than asked and see what survives; ask what happens if nothing changes at all. Where the surrounding system or prior art would feed the thinking, read for it while you think, not as a validation pass afterward.
|
|
18
|
+
|
|
19
|
+
Bring these to the user as live options, in plain language, with what each buys and closes off. Do not open with objections, feasibility verdicts, or a recommendation, and do not pre-reject an unusual but coherent direction — nothing is committed until the user picks, so divergence is free. If something in the request genuinely does not fit what they are trying to achieve, say so once; questioning their premise is not the job.
|
|
20
|
+
|
|
21
|
+
Converge when the user has chosen among live options and what remains is detail.
|
|
22
|
+
|
|
12
23
|
## Elicit without interrogating
|
|
13
24
|
|
|
14
|
-
Investigate before asking. Read the request, relevant code and documents, and already-settled decisions first. A fact available from the project is not a question for the user.
|
|
25
|
+
Investigate before asking. Read the request, relevant code and documents, and already-settled decisions first. A fact available from the project is not a question for the user; intent never is such a fact.
|
|
15
26
|
|
|
16
|
-
Reflect a concrete interpretation
|
|
27
|
+
Reflect a concrete interpretation back so the user can confirm or correct it. Resolve the uncertainty whose answer could most change behavior, scope, or acceptance. When a decision really belongs to the user, give them a focused question with a proposed default or concrete options; use one decision or one small coherent set rather than a questionnaire.
|
|
17
28
|
|
|
18
|
-
Spend attention where judgment is load-bearing, not where detail is merely available. Keep settled points moving and fold each answer into the specification as current truth.
|
|
29
|
+
Spend attention where judgment is load-bearing, not where detail is merely available. Keep settled points moving and fold each answer into the specification as current truth. Once converged, stop eliciting when another answer would not materially change the behavioral contract. Explicit approval is warranted when the user is co-authoring the document or the remaining decision is consequential; ordinary reversible work does not need a ritual approval loop.
|
|
19
30
|
|
|
20
|
-
##
|
|
31
|
+
## Commit only what the user chose
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
Exploration is unbounded; the document is not. What you explored and the user did not choose stays out — speculative features, future extensibility, adjacent cleanup, and other merely plausible additions are not requirements just because they came up. The bar is not smallness for its own sake: nothing enters the specification without the user's assent.
|
|
23
34
|
|
|
24
35
|
When something seems likely desirable but is not explicitly or implicitly required by the request, ask the user whether to include it through `crtr human` before finishing the specification (`crtr human send -h`), wait for their answer, and make the resulting boundary explicit. Do not hide the addition in an assumption, recommendation, or optional requirement.
|
|
25
36
|
|
|
@@ -44,10 +44,9 @@ export interface SlashDoc {
|
|
|
44
44
|
description: string;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// `memory list` already chose
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
const resolvedMemoryPaths = new Map<string, string>();
|
|
47
|
+
// `memory list` already chose each winning document. Keep its canonical body
|
|
48
|
+
// for disclosure classification and its path for the existing execution read.
|
|
49
|
+
const resolvedMemoryDocs = new Map<string, { path: string; canonicalBody: string }>();
|
|
51
50
|
|
|
52
51
|
async function defaultListMemoryDocs(): Promise<MemoryListItem[]> {
|
|
53
52
|
const snapshot = createMemoryDocSnapshot();
|
|
@@ -68,15 +67,24 @@ async function defaultListMemoryDocs(): Promise<MemoryListItem[]> {
|
|
|
68
67
|
return item.slash === true ? [item.name] : [];
|
|
69
68
|
});
|
|
70
69
|
const resolved = snapshot.resolve(names);
|
|
71
|
-
|
|
72
|
-
for (const [name, doc] of resolved)
|
|
70
|
+
resolvedMemoryDocs.clear();
|
|
71
|
+
for (const [name, doc] of resolved) {
|
|
72
|
+
resolvedMemoryDocs.set(name, { path: doc.path, canonicalBody: doc.body });
|
|
73
|
+
}
|
|
73
74
|
return items;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
function defaultReadMemoryDoc(name: string): string {
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
return
|
|
77
|
+
function defaultReadMemoryDoc(name: string): { canonicalBody: string; executionBody: string } {
|
|
78
|
+
const resolved = resolvedMemoryDocs.get(name);
|
|
79
|
+
if (resolved === undefined) throw new Error(`memory slash document was not resolved: ${name}`);
|
|
80
|
+
return {
|
|
81
|
+
canonicalBody: resolved.canonicalBody,
|
|
82
|
+
executionBody: readMemoryDocContent(resolved.path),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function hasNodeRelativeExpansion(body: string): boolean {
|
|
87
|
+
return body.includes("$CRTR_CONTEXT_DIR") || body.includes("$CRTR_NODE_ID");
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
/** `/`-joined path-derived doc name -> the registered slash-command name. */
|
|
@@ -110,28 +118,33 @@ export default async function (pi: ExtensionAPI) {
|
|
|
110
118
|
const docs = await discoverSlashDocs();
|
|
111
119
|
for (const doc of docs) {
|
|
112
120
|
let expansion: DeterministicCommandExpansion | undefined;
|
|
121
|
+
let discloseExpansion = false;
|
|
113
122
|
let loadError: unknown;
|
|
114
123
|
try {
|
|
124
|
+
const body = defaultReadMemoryDoc(doc.name);
|
|
115
125
|
expansion = {
|
|
116
126
|
kind: "memory-slash",
|
|
117
127
|
commandName: doc.commandName,
|
|
118
|
-
body:
|
|
128
|
+
body: body.executionBody,
|
|
119
129
|
};
|
|
130
|
+
discloseExpansion = !hasNodeRelativeExpansion(body.canonicalBody);
|
|
120
131
|
} catch (err) {
|
|
121
132
|
loadError = err;
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
// pi preserves unknown registration fields on the resolved command object.
|
|
125
|
-
// The broker discloses
|
|
126
|
-
//
|
|
127
|
-
//
|
|
136
|
+
// The broker discloses deterministic expansions through get_commands. A
|
|
137
|
+
// node-relative body still executes through the private expansion below,
|
|
138
|
+
// but cannot promise a concrete payload before the future node exists.
|
|
128
139
|
pi.registerCommand(doc.commandName, {
|
|
129
140
|
description: doc.description,
|
|
130
141
|
// Chat-capable: the handler's whole outcome is a conversation turn
|
|
131
142
|
// carrying the expanded document, which any surface hosting the
|
|
132
143
|
// conversation receives. Only when the expansion loaded — without it the
|
|
133
144
|
// sole outcome is a terminal-only error notify, so it stays undisclosed.
|
|
134
|
-
...(expansion === undefined
|
|
145
|
+
...(expansion === undefined
|
|
146
|
+
? {}
|
|
147
|
+
: { gateway: true as const, ...(discloseExpansion ? { expansion } : {}) }),
|
|
135
148
|
handler: async (args, ctx) => {
|
|
136
149
|
if (expansion === undefined) {
|
|
137
150
|
ctx.ui.notify(
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
// GLYPHS. Private-use `U+XXXX` references become their glyphs (see
|
|
23
23
|
// glyph-codepoints.ts), so an agent that cannot type a Nerd Font character can
|
|
24
24
|
// still put one on screen.
|
|
25
|
+
import { pathToFileURL } from 'node:url';
|
|
25
26
|
import { expandGlyphCodepoints } from './glyph-codepoints.js';
|
|
26
27
|
const ATX_HEADING = /^(\s{0,3})(#{3,6})(?:[ \t]+|$)(.*?)([ \t]+#+[ \t]*)?$/;
|
|
27
28
|
const FENCE_OPEN = /^\s{0,3}(`{3,}|~{3,})/;
|
|
@@ -126,7 +127,111 @@ export function styleAttachMarkdownSource(markdown) {
|
|
|
126
127
|
* message keeps `styleAttachMarkdownSource` alone — the `U+XXXX` escape hatch
|
|
127
128
|
* exists for agents that cannot type a Nerd Font character. */
|
|
128
129
|
export function styleAttachAgentMarkdown(markdown) {
|
|
129
|
-
return expandGlyphCodepoints(styleAttachMarkdownSource(markdown));
|
|
130
|
+
return expandGlyphCodepoints(styleAttachMarkdownSource(rewriteAbsoluteFileLinks(markdown)));
|
|
131
|
+
}
|
|
132
|
+
/** Give iTerm a hidden fragment so its Semantic History action receives the local target. */
|
|
133
|
+
function rewriteAbsoluteFileLinks(markdown) {
|
|
134
|
+
let fence;
|
|
135
|
+
return markdown.split('\n').map((line) => {
|
|
136
|
+
const fenceMatch = FENCE_OPEN.exec(line);
|
|
137
|
+
if (fence !== undefined) {
|
|
138
|
+
if (fenceMatch?.[1][0] === fence)
|
|
139
|
+
fence = undefined;
|
|
140
|
+
return line;
|
|
141
|
+
}
|
|
142
|
+
if (fenceMatch) {
|
|
143
|
+
fence = fenceMatch[1][0];
|
|
144
|
+
return line;
|
|
145
|
+
}
|
|
146
|
+
return rewriteInlineFileLinks(line);
|
|
147
|
+
}).join('\n');
|
|
148
|
+
}
|
|
149
|
+
function rewriteInlineFileLinks(line) {
|
|
150
|
+
let result = '';
|
|
151
|
+
let rest = line;
|
|
152
|
+
while (rest !== '') {
|
|
153
|
+
const opening = rest.match(/`+/);
|
|
154
|
+
const prose = opening === null ? rest : rest.slice(0, opening.index);
|
|
155
|
+
result += rewriteProseFileLinks(prose);
|
|
156
|
+
if (opening === null)
|
|
157
|
+
break;
|
|
158
|
+
const delimiter = opening[0];
|
|
159
|
+
const end = rest.indexOf(delimiter, prose.length + delimiter.length);
|
|
160
|
+
if (end === -1)
|
|
161
|
+
return result + rest.slice(prose.length);
|
|
162
|
+
result += rest.slice(prose.length, end + delimiter.length);
|
|
163
|
+
rest = rest.slice(end + delimiter.length);
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
function rewriteProseFileLinks(prose) {
|
|
168
|
+
let result = '';
|
|
169
|
+
let index = 0;
|
|
170
|
+
while (index < prose.length) {
|
|
171
|
+
const start = prose.indexOf('[', index);
|
|
172
|
+
if (start === -1)
|
|
173
|
+
return result + prose.slice(index);
|
|
174
|
+
const link = parseLocalMarkdownLink(prose, start);
|
|
175
|
+
if (link === undefined) {
|
|
176
|
+
result += prose.slice(index, start + 1);
|
|
177
|
+
index = start + 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
result += prose.slice(index, start) + `[${link.label}](${pathToFileURL(link.path).href}#crtr-file-open)`;
|
|
181
|
+
index = link.end;
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
function parseLocalMarkdownLink(line, start) {
|
|
186
|
+
const labelEnd = closingDelimiter(line, start + 1, ']');
|
|
187
|
+
if (labelEnd === undefined || line[labelEnd + 1] !== '(')
|
|
188
|
+
return undefined;
|
|
189
|
+
const destinationStart = labelEnd + 2;
|
|
190
|
+
if (line[destinationStart] === '<') {
|
|
191
|
+
const destinationEnd = closingDelimiter(line, destinationStart + 1, '>');
|
|
192
|
+
if (destinationEnd === undefined || line[destinationEnd + 1] !== ')')
|
|
193
|
+
return undefined;
|
|
194
|
+
const path = localPathFromDestination(line.slice(destinationStart + 1, destinationEnd));
|
|
195
|
+
return path === undefined ? undefined : { label: line.slice(start + 1, labelEnd), path, end: destinationEnd + 2 };
|
|
196
|
+
}
|
|
197
|
+
let depth = 0;
|
|
198
|
+
for (let index = destinationStart; index < line.length; index++) {
|
|
199
|
+
if (line[index] === '\\') {
|
|
200
|
+
index++;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (line[index] === '(') {
|
|
204
|
+
depth++;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (line[index] !== ')' || depth-- > 0)
|
|
208
|
+
continue;
|
|
209
|
+
const path = localPathFromDestination(line.slice(destinationStart, index));
|
|
210
|
+
return path === undefined ? undefined : { label: line.slice(start + 1, labelEnd), path, end: index + 1 };
|
|
211
|
+
}
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
function closingDelimiter(text, start, delimiter) {
|
|
215
|
+
for (let index = start; index < text.length; index++) {
|
|
216
|
+
if (text[index] === '\\') {
|
|
217
|
+
index++;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (text[index] === delimiter)
|
|
221
|
+
return index;
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
function localPathFromDestination(destination) {
|
|
226
|
+
const unescaped = destination.replace(/\\([!"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-])/g, '$1');
|
|
227
|
+
let path = unescaped;
|
|
228
|
+
try {
|
|
229
|
+
path = decodeURIComponent(unescaped);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// A literal percent is legal in a path but not in URI decoding.
|
|
233
|
+
}
|
|
234
|
+
return path.startsWith('/') ? path : undefined;
|
|
130
235
|
}
|
|
131
236
|
/** Copy a pi message for terminal rendering and decorate only Markdown-bearing
|
|
132
237
|
* text/thinking blocks. The broker's source event is never mutated. */
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export
|
|
3
|
-
|
|
1
|
+
/** A local file requested through iTerm Semantic History. */
|
|
2
|
+
export interface FileOpenInput {
|
|
3
|
+
file: string | undefined;
|
|
4
|
+
remaining: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Strip the private iTerm Semantic History input and return its local path.
|
|
8
|
+
* iTerm expands `\\1` as a shell-escaped word, so decode that representation
|
|
9
|
+
* without ever evaluating it as shell input.
|
|
10
|
+
*/
|
|
11
|
+
export declare function extractFileOpenInput(data: string): FileOpenInput | undefined;
|
|
12
|
+
export type LinkedFileOpenResult = 'opened' | 'missing' | 'failed';
|
|
4
13
|
/** Replace this local viewer pane with Neovim, then reattach it when Neovim exits. */
|
|
5
14
|
export declare function openLinkedFileInPane(opts: {
|
|
6
|
-
|
|
15
|
+
file: string;
|
|
7
16
|
pane: string;
|
|
8
17
|
cwd: string;
|
|
9
18
|
nodeId: string;
|