@nurix/etna 0.18.15 → 0.18.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -16
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/CLAUDE.md +71 -57
- package/data/harnesses/code/agents/{spec-mode.md → lld-mode.md} +12 -12
- package/data/harnesses/code/agents/maintenance.md +6 -31
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/hooks/hooks.json +0 -23
- package/data/harnesses/code/rules/testing.md +25 -0
- package/data/harnesses/code/skills/brainstorm/SKILL.md +1 -1
- package/data/harnesses/code/skills/brainstorm/exit-artifacts.md +2 -2
- package/data/harnesses/code/skills/documentation/SKILL.md +9 -5
- package/data/harnesses/code/skills/documentation/references/examples.md +1 -1
- package/data/harnesses/code/skills/documentation/references/style-guide.md +3 -1
- package/data/harnesses/code/skills/documentation/references/templates.md +99 -115
- package/data/harnesses/etna/CLAUDE.md +11 -12
- package/data/harnesses/etna/agents/{spec-mode.md → lld-mode.md} +12 -12
- package/data/harnesses/etna/agents/maintenance.md +6 -31
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/hooks/hooks.json +0 -23
- package/data/harnesses/etna/rules/testing.md +25 -0
- package/data/harnesses/etna/skills/brainstorm/SKILL.md +1 -1
- package/data/harnesses/etna/skills/brainstorm/exit-artifacts.md +2 -2
- package/data/harnesses/etna/skills/documentation/SKILL.md +9 -5
- package/data/harnesses/etna/skills/documentation/references/examples.md +1 -1
- package/data/harnesses/etna/skills/documentation/references/style-guide.md +3 -1
- package/data/harnesses/etna/skills/documentation/references/templates.md +99 -115
- package/data/harnesses/mobile/CLAUDE.md +78 -55
- package/data/harnesses/mobile/agents/{spec-mode.md → lld-mode.md} +12 -12
- package/data/harnesses/mobile/agents/maintenance.md +6 -31
- package/data/harnesses/mobile/harness.json +2 -2
- package/data/harnesses/mobile/hooks/hooks.json +0 -23
- package/data/harnesses/mobile/rules/testing.md +25 -0
- package/data/harnesses/mobile/skills/brainstorm/SKILL.md +1 -1
- package/data/harnesses/mobile/skills/brainstorm/exit-artifacts.md +2 -2
- package/data/harnesses/mobile/skills/documentation/SKILL.md +9 -5
- package/data/harnesses/mobile/skills/documentation/references/examples.md +1 -1
- package/data/harnesses/mobile/skills/documentation/references/style-guide.md +3 -1
- package/data/harnesses/mobile/skills/documentation/references/templates.md +99 -115
- package/package.json +1 -1
- package/data/harnesses/code/hooks/lib/redact.mjs +0 -35
- package/data/harnesses/code/hooks/prompt-recall.mjs +0 -155
- package/data/harnesses/code/hooks/session-reflect.mjs +0 -285
- package/data/harnesses/code/skills/distiller/SKILL.md +0 -90
- package/data/harnesses/code/skills/distiller/references/docs-mode.md +0 -299
- package/data/harnesses/code/skills/distiller/references/method.md +0 -94
- package/data/harnesses/etna/hooks/lib/redact.mjs +0 -35
- package/data/harnesses/etna/hooks/prompt-recall.mjs +0 -155
- package/data/harnesses/etna/hooks/session-reflect.mjs +0 -285
- package/data/harnesses/etna/skills/distiller/SKILL.md +0 -90
- package/data/harnesses/etna/skills/distiller/references/docs-mode.md +0 -299
- package/data/harnesses/etna/skills/distiller/references/method.md +0 -94
- package/data/harnesses/mobile/hooks/lib/redact.mjs +0 -35
- package/data/harnesses/mobile/hooks/prompt-recall.mjs +0 -155
- package/data/harnesses/mobile/hooks/session-reflect.mjs +0 -285
- package/data/harnesses/mobile/skills/distiller/SKILL.md +0 -90
- package/data/harnesses/mobile/skills/distiller/references/docs-mode.md +0 -299
- package/data/harnesses/mobile/skills/distiller/references/method.md +0 -94
|
@@ -4,7 +4,7 @@ Templates for all documentation needs.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## docs/README.md (Documentation Root)
|
|
8
8
|
|
|
9
9
|
Use for: the entry point for all documentation in a repo. Created during baseline audit.
|
|
10
10
|
|
|
@@ -72,7 +72,7 @@ See [CLAUDE.md](../CLAUDE.md) for documentation guidelines.
|
|
|
72
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
75
|
-
##
|
|
75
|
+
## docs/structure.md (Authority Map)
|
|
76
76
|
|
|
77
77
|
Use for: defining which folder owns which topic and how docs are maintained.
|
|
78
78
|
|
|
@@ -112,7 +112,7 @@ Plan (Status: Draft) → Implementation → Feature Doc or Architecture Update
|
|
|
112
112
|
|
|
113
113
|
## Folder Rules
|
|
114
114
|
|
|
115
|
-
- `docs/features/`: Living reference for implemented features. Each feature doc **carries** the `owns:` / `module:` / `keywords:` / `status:` frontmatter — `owns:` declares the code-path globs it owns (the only ownership key), `module:` is an optional bounded-context label, `keywords:` are discovery terms, `status:` is `active`/`draft`/`deprecated`. The SessionStart hook unions every feature's `owns:` globs into the injected ownership index.
|
|
115
|
+
- `docs/features/`: Living reference for implemented features. Each feature doc **carries** the `owns:` / `module:` / `keywords:` / `status:` frontmatter — `owns:` declares the code-path globs it owns (the only ownership key), `module:` is an optional bounded-context label, `keywords:` are discovery terms, `status:` is `active`/`draft`/`deprecated`. The SessionStart hook unions every feature's `owns:` globs into the injected ownership index.
|
|
116
116
|
- `docs/architecture/`: System-wide patterns and the cross-module steering layer (domain model, glossary, non-goals, references, ADRs). A module's cross-cutting prose lives here (or in a browse-folder `Readme.md`). Update when cross-cutting concerns change.
|
|
117
117
|
- `docs/operations/`: How-to procedures for setup, development, and deployment. Update when workflows change.
|
|
118
118
|
- `docs/history/plans/`: Historical. Must have `Status:` field. Never modify after completion.
|
|
@@ -131,24 +131,13 @@ Plan (Status: Draft) → Implementation → Feature Doc or Architecture Update
|
|
|
131
131
|
|
|
132
132
|
---
|
|
133
133
|
|
|
134
|
-
##
|
|
134
|
+
## Code-Path Ownership
|
|
135
135
|
|
|
136
136
|
**Ownership.** A feature doc is the canonical reference for the code paths in its **`owns:` frontmatter** — the only ownership key. To find what a doc owns, read its `owns:` globs; to find the doc for a path, match the path against the injected ownership index (the union of every feature's `owns:` globs), narrower glob wins. See **Frontmatter Derivation** in `SKILL.md` for turning code into `owns:` globs.
|
|
137
137
|
|
|
138
|
-
**Anchors (optional tooling key).** A doc may additionally carry an `anchors:` block — a qmd drift tripwire, orthogonal to ownership:
|
|
139
|
-
|
|
140
|
-
```yaml
|
|
141
|
-
---
|
|
142
|
-
anchors:
|
|
143
|
-
- "qmd query: feature-name persistence shape"
|
|
144
138
|
---
|
|
145
|
-
```
|
|
146
139
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
## 4. Scaffold Files (create if missing)
|
|
140
|
+
## Scaffold Files (create if missing)
|
|
152
141
|
|
|
153
142
|
These standard files must exist after every baseline. Create them with these scaffolds if the repo doesn't already have them.
|
|
154
143
|
|
|
@@ -241,14 +230,6 @@ description: Shared knowledge accumulated across agent sessions. All agents shou
|
|
|
241
230
|
- **Spec version:** v{harness-version}
|
|
242
231
|
<!-- v{harness-version} resolves to the installed harness version — read it from `.claude/harness.json` (`version`) / `docs/structure.md` frontmatter (`harness-version`); never hardcode a literal version here. -->
|
|
243
232
|
|
|
244
|
-
## Knowledge Tiers
|
|
245
|
-
|
|
246
|
-
Memory is a gradient — volume falls, signal rises, one hop down each tier. Tiers describe themselves here even before they fill:
|
|
247
|
-
|
|
248
|
-
- **Inbox** (`docs/memory/inbox/`) — per-actor queue of session lessons, written by the `session-reflect` hook at session end and consumed by the distiller on digestion. Transient by design, rarely read directly.
|
|
249
|
-
- **Fact-cards** (`docs/memory/facts/`) — distilled, provenance-linked atomic facts, written by the distiller (`facts` mode) from inbox entries and resurfaced at prompt time by the `prompt-recall` hook.
|
|
250
|
-
- **This index** — the curated read tier: the learnings list below, distilled from the colder tiers and hand-appended.
|
|
251
|
-
|
|
252
233
|
## Learnings
|
|
253
234
|
|
|
254
235
|
{Agents append learnings here as they discover gotchas, patterns, and architecture notes}
|
|
@@ -278,15 +259,15 @@ Append entries below, oldest at bottom. Format:
|
|
|
278
259
|
Memory file initialized during documentation baseline.
|
|
279
260
|
```
|
|
280
261
|
|
|
281
|
-
> The per-actor memory **inbox** (`docs/memory/inbox/`) is _not_ a baseline scaffold — the `session-reflect` hook creates it mechanically on the first reflected session. See **§11 Memory inbox entry** for its format.
|
|
282
|
-
|
|
283
262
|
---
|
|
284
263
|
|
|
285
|
-
##
|
|
264
|
+
## Feature Documentation — the LLD (docs/features/{feature}.md)
|
|
265
|
+
|
|
266
|
+
Use for: every feature in `docs/features/`. The feature doc is the feature's **LLD (low-level design)**: the same file persists from design (Status: Draft, written in LLD Mode) through implementation to live reference (Status: Active). Sections fill in as the feature matures — no template swap.
|
|
286
267
|
|
|
287
|
-
|
|
268
|
+
**Required vs optional.** Four things are required in every feature doc: the frontmatter, **Goal**, **Background**, and **Files** — plus **Current Behavior** once research or implementation exists (at Draft it's the pre-feature baseline; at Active it's how the feature works today). Every other section is optional: include it only when the feature has something real to put in it (no Redux → no Redux section; no risks worth naming → no Risks section). A **minimal form** is sanctioned for small features: frontmatter + Goal + a short how-it-works section + Files. A two-screen doc that is accurate beats a full-template doc with empty sections.
|
|
288
269
|
|
|
289
|
-
The four research-derived sections (**Affected Modules**, **Conventions to Follow**, **Current Behavior**, **Ownership Conflicts**) map 1:1 to
|
|
270
|
+
The four research-derived sections (**Affected Modules**, **Conventions to Follow**, **Current Behavior**, **Ownership Conflicts**) map 1:1 to the `lld-mode` agent's return shape. The orchestrator drops its findings into those slots without restructuring.
|
|
290
271
|
|
|
291
272
|
```yaml
|
|
292
273
|
---
|
|
@@ -294,8 +275,7 @@ owns: ["src/feature/**", "src/other/file.ts"] # required — code-path globs t
|
|
|
294
275
|
module: billing # optional — single bounded-context label; omit if cross-cutting
|
|
295
276
|
keywords: [invoice, pdf] # required — discovery terms
|
|
296
277
|
status: active # required — active | draft | deprecated
|
|
297
|
-
|
|
298
|
-
- "qmd query: wire envelope invariants"
|
|
278
|
+
last-reviewed: YYYY-MM-DD # optional tooling key — stamped on substantive review/update; the maintenance agent ranks audit priority by it (metadata, never narrated in the body)
|
|
299
279
|
---
|
|
300
280
|
```
|
|
301
281
|
|
|
@@ -332,22 +312,22 @@ High-level shape of the solution. ASCII diagram if it helps. Implementation spec
|
|
|
332
312
|
|
|
333
313
|
## Affected Modules
|
|
334
314
|
|
|
335
|
-
(From
|
|
315
|
+
(From lld-mode research) Files, routes, components, slices this feature involves. Group by module.
|
|
336
316
|
|
|
337
317
|
| Module | Path | Role in this feature |
|
|
338
318
|
|--------|------|----------------------|
|
|
339
319
|
|
|
340
320
|
## Conventions to Follow
|
|
341
321
|
|
|
342
|
-
(From
|
|
322
|
+
(From lld-mode research) Naming patterns, structural patterns, doc-fit heuristics confirmed for the area being touched. Cite the source file(s) where the convention is established.
|
|
343
323
|
|
|
344
324
|
## Current Behavior
|
|
345
325
|
|
|
346
|
-
(From
|
|
326
|
+
(From lld-mode research) How the relevant part of the system works *today*, before this feature lands. Without this baseline the design intent is hard to evaluate.
|
|
347
327
|
|
|
348
328
|
## Ownership Conflicts
|
|
349
329
|
|
|
350
|
-
(From
|
|
330
|
+
(From lld-mode research) Existing feature docs whose `owns:` globs overlap with the projected scope. Resolve before the plan starts — leave nothing unresolved.
|
|
351
331
|
|
|
352
332
|
| Conflicting doc | Overlapping path(s) | Resolution |
|
|
353
333
|
|-----------------|---------------------|------------|
|
|
@@ -426,17 +406,19 @@ Sections fill in across the feature's lifecycle. No template swap — they accum
|
|
|
426
406
|
| When | Sections written | Source |
|
|
427
407
|
| --- | --- | --- |
|
|
428
408
|
| Status: Draft (design) | Goal, Goals & Non-Goals, Background, Approach, Open Questions, Risks | User interview |
|
|
429
|
-
| Status: Draft (research) | Affected Modules, Conventions to Follow, Current Behavior, Ownership Conflicts |
|
|
409
|
+
| Status: Draft (research) | Affected Modules, Conventions to Follow, Current Behavior, Ownership Conflicts | lld-mode agent |
|
|
430
410
|
| Status: Draft → Active (implementation lands) | Architecture, Key Functions, State, Redux, Files; `owns:` globs confirmed against real code | Main agent (from actual code) |
|
|
431
411
|
|
|
432
412
|
Resolved Open Questions can be summarized into Background; unresolved ones stay visible.
|
|
433
413
|
|
|
434
414
|
---
|
|
435
415
|
|
|
436
|
-
##
|
|
416
|
+
## Changelog
|
|
437
417
|
|
|
438
418
|
Use for: recording code changes with function-level detail.
|
|
439
419
|
|
|
420
|
+
Change categories follow the Keep a Changelog vocabulary — **Added / Changed / Deprecated / Removed / Fixed / Security** — the same six the master `CHANGELOG.md` scaffold instructs. Include only the categories that apply; all six share the same bullet shape (function/file, then detail lines).
|
|
421
|
+
|
|
440
422
|
```
|
|
441
423
|
# YYYY-MM-DD - [Title]
|
|
442
424
|
|
|
@@ -451,7 +433,7 @@ Brief description of what changed and why.
|
|
|
451
433
|
- Purpose: What it does
|
|
452
434
|
- Used by: Where it's called
|
|
453
435
|
|
|
454
|
-
###
|
|
436
|
+
### Changed
|
|
455
437
|
- `existingFunction()` in `path/to/file.js`
|
|
456
438
|
- Changed: What's different
|
|
457
439
|
- Reason: Why it changed
|
|
@@ -461,6 +443,13 @@ Brief description of what changed and why.
|
|
|
461
443
|
- `oldFunction()` from `path/to/file.js`
|
|
462
444
|
- Replaced by: `newFunction()`
|
|
463
445
|
|
|
446
|
+
### Fixed
|
|
447
|
+
- `buggyFunction()` in `path/to/file.js`
|
|
448
|
+
- Bug: What was wrong, when it manifested
|
|
449
|
+
- Fix: What changed
|
|
450
|
+
|
|
451
|
+
(Deprecated / Security follow the same shape.)
|
|
452
|
+
|
|
464
453
|
## Breaking Changes
|
|
465
454
|
|
|
466
455
|
⚠️ **[Change description]**
|
|
@@ -474,6 +463,11 @@ Brief description of what changed and why.
|
|
|
474
463
|
- [ ] Step 1
|
|
475
464
|
- [ ] Step 2
|
|
476
465
|
|
|
466
|
+
## Verified
|
|
467
|
+
|
|
468
|
+
- What was exercised end-to-end and the observed result — real runs, real fixtures, measured timings
|
|
469
|
+
- A future agent diagnosing a regression starts from this evidence
|
|
470
|
+
|
|
477
471
|
## Files Changed
|
|
478
472
|
|
|
479
473
|
- `src/path/file1.js` - Core changes
|
|
@@ -482,7 +476,7 @@ Brief description of what changed and why.
|
|
|
482
476
|
|
|
483
477
|
---
|
|
484
478
|
|
|
485
|
-
##
|
|
479
|
+
## Plan (docs/history/plans/YYYY-MM-DD-feature-name.md)
|
|
486
480
|
|
|
487
481
|
Use for: every implementation plan. Plans are created here directly with `Status: Draft`, flip to `Status: In Progress` when work starts, and freeze at `Status: Complete` or `Status: Abandoned`. Once frozen, never edit — write a new dated plan if scope changes.
|
|
488
482
|
|
|
@@ -549,10 +543,49 @@ Why this work is happening *now*. Link to the spec for *what* we're building; th
|
|
|
549
543
|
|
|
550
544
|
---
|
|
551
545
|
|
|
552
|
-
##
|
|
546
|
+
## Operations Procedure (docs/operations/{procedure}.md)
|
|
547
|
+
|
|
548
|
+
Use for: any doc in `docs/operations/` — setup, development, deployment, maintenance procedures. A procedure is consulted mid-task, not read: exact commands, copy-paste runnable, no narrative.
|
|
549
|
+
|
|
550
|
+
Required: **Goal**, **Steps**, **Verification**. Optional: Preconditions, Troubleshooting, Rollback — include only when they carry real content.
|
|
551
|
+
|
|
552
|
+
If a procedure is a deterministic command list run frequently, script it and have the doc point at the script — a doc should carry judgment (when, why, what to check), not replace automation.
|
|
553
|
+
|
|
554
|
+
```
|
|
555
|
+
# {Procedure Name}
|
|
556
|
+
|
|
557
|
+
**Goal:** What this procedure accomplishes, one line.
|
|
558
|
+
|
|
559
|
+
## Preconditions
|
|
560
|
+
|
|
561
|
+
- Access, tools, or state required before starting — exact versions, env vars, credentials location
|
|
562
|
+
|
|
563
|
+
## Steps
|
|
564
|
+
|
|
565
|
+
1. One action per step, exact command in a code block
|
|
566
|
+
2. Note expected output where it disambiguates success from failure
|
|
567
|
+
|
|
568
|
+
## Verification
|
|
569
|
+
|
|
570
|
+
- The observable end state that confirms it worked — exact check commands, not "it should work now"
|
|
571
|
+
|
|
572
|
+
## Troubleshooting
|
|
573
|
+
|
|
574
|
+
- Symptom → cause → fix (append entries as failures are root-caused)
|
|
575
|
+
|
|
576
|
+
## Rollback
|
|
577
|
+
|
|
578
|
+
- How to undo, and when undo stops being possible
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
## Protected Files
|
|
553
584
|
|
|
554
585
|
Use for: documenting critical files that need approval before modification.
|
|
555
586
|
|
|
587
|
+
This doc answers *what is protected and why breaking it hurts* — nothing more. Don't duplicate function-level detail here (signatures, inputs/outputs): it drifts from both the code and the owning feature doc. The owning feature doc is the canonical reference for how the code works; link to it.
|
|
588
|
+
|
|
556
589
|
```
|
|
557
590
|
# Protected Files
|
|
558
591
|
|
|
@@ -574,29 +607,22 @@ path/to/another/file.js
|
|
|
574
607
|
| Category 1 | Specific impact |
|
|
575
608
|
| Category 2 | Specific impact |
|
|
576
609
|
|
|
577
|
-
##
|
|
610
|
+
## Why Protected
|
|
578
611
|
|
|
579
612
|
### Category 1: [Name]
|
|
580
613
|
|
|
581
|
-
**Purpose:** What these files do
|
|
582
|
-
|
|
583
|
-
**
|
|
584
|
-
|
|
585
|
-
| Function | Input | Output | Purpose |
|
|
586
|
-
|------------|-------|--------|----------------|
|
|
587
|
-
| `func1()` | Type | Type | What it does |
|
|
588
|
-
| `func2()` | Type | Type | What it does |
|
|
589
|
-
|
|
590
|
-
**Critical Behavior:**
|
|
591
|
-
- Important point 1
|
|
614
|
+
**Purpose:** What these files do, one or two lines
|
|
615
|
+
**Owning doc:** [docs/features/{feature}.md] — canonical reference for how this code works
|
|
616
|
+
**Invariants:**
|
|
617
|
+
- The behavior that must not break, stated as a constraint (not a function tour)
|
|
592
618
|
- Important point 2
|
|
593
619
|
```
|
|
594
620
|
|
|
595
621
|
---
|
|
596
622
|
|
|
597
|
-
##
|
|
623
|
+
## Architecture Decision Record (ADR)
|
|
598
624
|
|
|
599
|
-
Use for: capturing a single
|
|
625
|
+
Use for: capturing a single architectural decision with its context, alternatives considered, and consequences. Lives in `docs/architecture/adr/` — co-located with the architecture it decides. An ADR is the **source of truth for its decision**: the body always states the current truth. When the decision is revisited, update the ADR in place — rewrite the affected sections to what is now true, bump `last-updated`, and record the revision in the **Decision Changelog**. Readers get the current decision from the body; the journey lives only in the changelog section.
|
|
600
626
|
|
|
601
627
|
### When to write an ADR
|
|
602
628
|
|
|
@@ -614,9 +640,9 @@ Don't write an ADR for:
|
|
|
614
640
|
|
|
615
641
|
### Lifecycle
|
|
616
642
|
|
|
617
|
-
`Proposed` → `Accepted` → `Deprecated`
|
|
643
|
+
`Proposed` → `Accepted` → `Deprecated`
|
|
618
644
|
|
|
619
|
-
|
|
645
|
+
An ADR stays `Accepted` for as long as its question is live — revisions update it in place. Set `Deprecated` only when the decision's question itself is retired (the component is gone, the contract no longer exists); state where the replacement lives, if any.
|
|
620
646
|
|
|
621
647
|
### Naming
|
|
622
648
|
|
|
@@ -624,18 +650,18 @@ ADRs are **immutable after acceptance**. If the decision changes, write a new AD
|
|
|
624
650
|
|
|
625
651
|
### Structure
|
|
626
652
|
|
|
627
|
-
Three sections are required: **Decision**, **Context**, **Consequences
|
|
653
|
+
Three sections are required: **Decision**, **Context**, **Consequences** — plus the **Decision Changelog** once the ADR has been revised at least once. Everything else is optional — include only when warranted. Decision-first: readers should see the answer in the first paragraph.
|
|
628
654
|
|
|
629
655
|
### Template
|
|
630
656
|
|
|
631
657
|
```markdown
|
|
632
658
|
---
|
|
633
659
|
title: "ADR-NNN: Short title in sentence case"
|
|
634
|
-
status: Accepted # Proposed | Accepted | Deprecated
|
|
635
|
-
|
|
660
|
+
status: Accepted # Proposed | Accepted | Deprecated
|
|
661
|
+
created: YYYY-MM-DD
|
|
662
|
+
last-updated: YYYY-MM-DD # bump on every revision
|
|
636
663
|
decision-owner: Name
|
|
637
664
|
reviewers: [] # optional
|
|
638
|
-
supersedes: # optional, e.g., ADR-005
|
|
639
665
|
tags: [adr]
|
|
640
666
|
---
|
|
641
667
|
|
|
@@ -682,13 +708,7 @@ tags: [adr]
|
|
|
682
708
|
|
|
683
709
|
## Architecture in Detail
|
|
684
710
|
|
|
685
|
-
[Diagrams, code shape, layer responsibilities. Include when the decision implies non-trivial structure that readers need to understand alongside the *why*.]
|
|
686
|
-
|
|
687
|
-
## Implementation Plan
|
|
688
|
-
|
|
689
|
-
| Phase | Scope | Duration |
|
|
690
|
-
| ----- | ----- | -------- |
|
|
691
|
-
| 1 | … | … |
|
|
711
|
+
[Diagrams, code shape, layer responsibilities. Include when the decision implies non-trivial structure that readers need to understand alongside the *why*. Execution belongs in a plan doc (`docs/history/plans/`) — link to it, don't embed an implementation plan here.]
|
|
692
712
|
|
|
693
713
|
## Open Questions
|
|
694
714
|
|
|
@@ -701,6 +721,13 @@ tags: [adr]
|
|
|
701
721
|
## References
|
|
702
722
|
|
|
703
723
|
- [External link or cross-reference]
|
|
724
|
+
|
|
725
|
+
## Decision Changelog
|
|
726
|
+
|
|
727
|
+
<!-- Append-only, newest first. One entry per revision: what changed in the decision, the trigger, and the previous choice. Added on the first revision; the initial acceptance is the first entry. -->
|
|
728
|
+
|
|
729
|
+
- YYYY-MM-DD — [What changed] because [trigger]. (Previously: [old choice].)
|
|
730
|
+
- YYYY-MM-DD — Initial decision accepted.
|
|
704
731
|
```
|
|
705
732
|
|
|
706
733
|
### Rules
|
|
@@ -709,14 +736,15 @@ tags: [adr]
|
|
|
709
736
|
- Decision-first. The first paragraph after the heading is the answer; readers should not need to scroll.
|
|
710
737
|
- No section numbering (`§5.1.1`). Use markdown headings only — inserting a section shouldn't renumber everything.
|
|
711
738
|
- Frontmatter `status` is the single source of truth for lifecycle. Don't also write "Status: Accepted" in the body.
|
|
712
|
-
-
|
|
713
|
-
- The
|
|
739
|
+
- **Revisions update the body in place.** Rewrite the affected sections to the current truth, bump `last-updated`, and append a Decision Changelog entry naming the old choice and the trigger. Never leave the superseded decision narrated in the body — the changelog carries the journey.
|
|
740
|
+
- The **Decision Changelog** is the one sanctioned dated-history section in an active doc: append-only, newest first. Typo and formatting fixes don't get entries.
|
|
741
|
+
- Execution detail lives in a plan doc, not the ADR — link, don't embed.
|
|
714
742
|
|
|
715
743
|
---
|
|
716
744
|
|
|
717
|
-
##
|
|
745
|
+
## Project Root README
|
|
718
746
|
|
|
719
|
-
Use for: the human-facing entry point at the repository root (`/README.md`) — what visitors and new contributors land on. Distinct from `docs/README.md` (the agent/doc index — see
|
|
747
|
+
Use for: the human-facing entry point at the repository root (`/README.md`) — what visitors and new contributors land on. Distinct from `docs/README.md` (the agent/doc index — see **docs/README.md (Documentation Root)** above). The two coexist; don't merge them.
|
|
720
748
|
|
|
721
749
|
### Strategy
|
|
722
750
|
|
|
@@ -807,47 +835,3 @@ The full surface lives in [`{path/to/public-index}`]({path/to/public-index}).
|
|
|
807
835
|
The baseline audit **does** scaffold the project root README when missing. The risk this template guards against — template-shaped placeholders that look abandoned — is mitigated by the writing rules above: fill every section with concrete details from the repo (real package manager from the lockfile, real scripts, real layout, real status). Skip sections that genuinely don't apply rather than leaving stubs.
|
|
808
836
|
|
|
809
837
|
**Never overwrite an existing root README** during a baseline. If one is present — even sparse or outdated — leave it untouched. Treat updates to a pre-existing root README as a separate, explicit request.
|
|
810
|
-
|
|
811
|
-
---
|
|
812
|
-
|
|
813
|
-
## 11. Memory inbox entry (docs/memory/inbox/{actor}.md)
|
|
814
|
-
|
|
815
|
-
Use for: the capture tier of the knowledge gradient — the durable lessons of each session (corrections, root-caused failures, decisions with rationale, discovered constraints), reflected by the `session-reflect` hook at session end. It is the raw input to distillation: `distiller facts` digests entries into fact-cards and **consumes** them, so the file holds exactly what is undigested.
|
|
816
|
-
|
|
817
|
-
### File grain
|
|
818
|
-
|
|
819
|
-
One file **per actor**: `docs/memory/inbox/{actor}.md`. `actor` is `git config user.name` lowercased and kebab-slugged; fall back to `$USER`, then `agent`. Two actors write to different files, so the inbox is **append-only without merge conflicts** — no shared file to contend over, no lock, no last-writer-wins.
|
|
820
|
-
|
|
821
|
-
### File header
|
|
822
|
-
|
|
823
|
-
Written once, on the first entry:
|
|
824
|
-
|
|
825
|
-
```
|
|
826
|
-
# Memory inbox — {actor}
|
|
827
|
-
Session lessons awaiting distillation — appended by the session-reflect hook, consumed by `distiller facts`.
|
|
828
|
-
```
|
|
829
|
-
|
|
830
|
-
### Entry shape
|
|
831
|
-
|
|
832
|
-
One block per reflected session, at most 6 bullets. Each bullet is one complete, self-contained sentence tagged `[project]` (true of the repo or its tooling) or `[user]` (true of the user) — the tag routes it at distillation: project facts → `docs/memory/facts/`, user facts → user-level memory.
|
|
833
|
-
|
|
834
|
-
```
|
|
835
|
-
## YYYY-MM-DD HH:MM · session <id> · {repo}@{branch}
|
|
836
|
-
- [project] <one self-contained sentence, names and identifiers spelled out>
|
|
837
|
-
- [user] <one self-contained sentence>
|
|
838
|
-
```
|
|
839
|
-
|
|
840
|
-
### Examples
|
|
841
|
-
|
|
842
|
-
```
|
|
843
|
-
## 2026-06-08 22:26 · session bc06c4fc · etna@dev
|
|
844
|
-
- [project] The read_network_requests Chrome CDP tool only captures after its first call — arm it, trigger traffic, then read.
|
|
845
|
-
- [user] jane-doe prefers minimal fixes over defensive machinery; fail loudly instead of engineering around a problem.
|
|
846
|
-
```
|
|
847
|
-
|
|
848
|
-
### Rules
|
|
849
|
-
|
|
850
|
-
- **Hooks append, the distiller consumes.** A digested entry block is deleted by `distiller facts`; nothing else edits or reorders the file. A human may hand-append a lesson in the same shape — it rides the same queue.
|
|
851
|
-
- **Sessions are deduped by id.** The reflect hook skips a session whose id already appears in the file (SessionEnd re-fires on resume).
|
|
852
|
-
- **Secrets are redacted on write.** The privacy filter replaces secret shapes with `[redacted:{kind}]` (e.g. `[redacted:api-key]`, `[redacted:bearer]`, `[redacted:pem]`) before any line is appended — never paste a raw token in by hand either.
|
|
853
|
-
- **Don't curate here.** Promotion to fact-cards and `docs/memory/main.md` happens at distillation; the inbox stays a transient queue.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.17",
|
|
4
4
|
"description": "Bootstrap a project with a Nurix harness — copies the harness's CLAUDE.md, skills, agents, rules, and hooks directly into the project from a bundled payload (no marketplace fetch, no network).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// harness/code/hooks/lib/redact.mjs — shared privacy filter: redacts secret shapes before ledger append.
|
|
2
|
-
|
|
3
|
-
// Patterns ordered most-specific first; each replaces a match with [redacted:{kind}].
|
|
4
|
-
const RULES = [
|
|
5
|
-
// PEM private/public key blocks (multiline).
|
|
6
|
-
{ kind: "pem", re: /-----BEGIN[\s\S]*?-----END[^-]*-----/g },
|
|
7
|
-
// OpenAI / Anthropic-style keys (sk-..., sk-ant-...).
|
|
8
|
-
{ kind: "api-key", re: /\bsk-(?:ant-)?[A-Za-z0-9_-]{8,}\b/g },
|
|
9
|
-
// AWS access key IDs.
|
|
10
|
-
{ kind: "aws-key", re: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
11
|
-
// Bearer tokens in Authorization context.
|
|
12
|
-
{ kind: "bearer", re: /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/gi },
|
|
13
|
-
// password= / passwd= / secret= / token= / api_key= style assignments (= or :).
|
|
14
|
-
{
|
|
15
|
-
kind: "credential",
|
|
16
|
-
re: /\b(?:password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|client[_-]?secret)\b\s*[:=]\s*["']?[^\s"'&)\]},;[]{3,}/gi,
|
|
17
|
-
},
|
|
18
|
-
// Generic long hex/base64 token ONLY in a key-value credential context.
|
|
19
|
-
{
|
|
20
|
-
kind: "credential",
|
|
21
|
-
re: /\b(?:key|auth|cred|credential|signature|sig)\b\s*[:=]\s*["']?[A-Za-z0-9+/=_-]{32,}/gi,
|
|
22
|
-
},
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
// Redact secret shapes in arbitrary text; returns a string with placeholders.
|
|
26
|
-
function redact(text) {
|
|
27
|
-
if (text == null) return "";
|
|
28
|
-
let out = String(text);
|
|
29
|
-
for (const rule of RULES) {
|
|
30
|
-
out = out.replace(rule.re, `[redacted:${rule.kind}]`);
|
|
31
|
-
}
|
|
32
|
-
return out;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export { redact };
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
// plugins/code/hooks/prompt-recall.mjs — UserPromptSubmit hook: surface fact-cards relevant to the prompt via qmd BM25; stdout is injected as context.
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { execFileSync } from "node:child_process";
|
|
7
|
-
|
|
8
|
-
const MIN_PROMPT_CHARS = 24;
|
|
9
|
-
const MIN_SCORE = 0.45;
|
|
10
|
-
const MAX_CARDS = 3;
|
|
11
|
-
const MAX_STATEMENT_CHARS = 300;
|
|
12
|
-
const MAX_KEYWORDS = 8;
|
|
13
|
-
|
|
14
|
-
const STOPWORDS = new Set(
|
|
15
|
-
"a an and are as at be but by can could do does for from has have how i in is it its me my of on or our should so that the their then there these this to was we what when where which who why will with would you your".split(
|
|
16
|
-
" ",
|
|
17
|
-
),
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
// Read all of stdin synchronously; returns "" if nothing is piped.
|
|
21
|
-
function readStdin() {
|
|
22
|
-
try {
|
|
23
|
-
return fs.readFileSync(0, "utf8");
|
|
24
|
-
} catch (e) {
|
|
25
|
-
return "";
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Resolve the project root: CLAUDE_PROJECT_DIR env, else payload cwd, else process cwd.
|
|
30
|
-
function resolveProjectDir(payload) {
|
|
31
|
-
return process.env.CLAUDE_PROJECT_DIR || (payload && payload.cwd) || process.cwd();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Map a qmd://collection/relpath hit URI to the card file under docs/memory/facts/.
|
|
35
|
-
function cardPath(factsDir, fileUri) {
|
|
36
|
-
return path.join(factsDir, String(fileUri || "").replace(/^qmd:\/\/[^/]+\//, ""));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// A card's atomic statement: the body after frontmatter, whitespace-flattened and clipped.
|
|
40
|
-
function cardStatement(file) {
|
|
41
|
-
try {
|
|
42
|
-
const body = fs
|
|
43
|
-
.readFileSync(file, "utf8")
|
|
44
|
-
.replace(/^---\n[\s\S]*?\n---\n?/, "")
|
|
45
|
-
.replace(/\s+/g, " ")
|
|
46
|
-
.trim();
|
|
47
|
-
return body.length > MAX_STATEMENT_CHARS ? body.slice(0, MAX_STATEMENT_CHARS) + "…" : body;
|
|
48
|
-
} catch (e) {
|
|
49
|
-
return "";
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Run one qmd search mode against the facts collection; [] on any failure — recall is strictly optional.
|
|
54
|
-
function qmdHits(mode, query, collection) {
|
|
55
|
-
try {
|
|
56
|
-
const out = JSON.parse(
|
|
57
|
-
execFileSync("qmd", [mode, query, "--format", "json", "-n", String(MAX_CARDS + 2), "-c", collection], {
|
|
58
|
-
encoding: "utf8",
|
|
59
|
-
timeout: 10000,
|
|
60
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
61
|
-
}),
|
|
62
|
-
);
|
|
63
|
-
return Array.isArray(out) ? out : [];
|
|
64
|
-
} catch (e) {
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Main: search the facts collection for the prompt and emit unseen high-score cards.
|
|
70
|
-
function main() {
|
|
71
|
-
if (process.env.ETNA_REFLECT) return;
|
|
72
|
-
|
|
73
|
-
const raw = readStdin();
|
|
74
|
-
let payload = {};
|
|
75
|
-
try {
|
|
76
|
-
payload = raw ? JSON.parse(raw) : {};
|
|
77
|
-
} catch (e) {
|
|
78
|
-
payload = {};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const prompt = String(payload.prompt || "").trim();
|
|
82
|
-
if (!prompt || prompt.startsWith("/") || prompt.length < MIN_PROMPT_CHARS) return;
|
|
83
|
-
|
|
84
|
-
const projectDir = resolveProjectDir(payload);
|
|
85
|
-
const factsDir = path.join(projectDir, "docs", "memory", "facts");
|
|
86
|
-
let hasCards = false;
|
|
87
|
-
try {
|
|
88
|
-
hasCards = fs.readdirSync(factsDir).some((f) => f.endsWith(".md"));
|
|
89
|
-
} catch (e) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (!hasCards) return;
|
|
93
|
-
|
|
94
|
-
// BM25 keywords first (fast, strict-AND), vector similarity as the natural-language
|
|
95
|
-
// fallback (~1s warm) — `qmd query` (LLM expansion + rerank) is too slow to block a prompt.
|
|
96
|
-
const collection = `${path.basename(projectDir)}-facts`;
|
|
97
|
-
const keywords = Array.from(
|
|
98
|
-
new Set(
|
|
99
|
-
prompt
|
|
100
|
-
.toLowerCase()
|
|
101
|
-
.replace(/[^a-z0-9\s_./-]/g, " ")
|
|
102
|
-
.split(/\s+/)
|
|
103
|
-
.filter((w) => w.length > 2 && !STOPWORDS.has(w)),
|
|
104
|
-
),
|
|
105
|
-
)
|
|
106
|
-
.slice(0, MAX_KEYWORDS)
|
|
107
|
-
.join(" ");
|
|
108
|
-
let hits = keywords ? qmdHits("search", keywords, collection) : [];
|
|
109
|
-
if (!hits.length) hits = qmdHits("vsearch", prompt.replace(/\s+/g, " ").slice(0, 300), collection);
|
|
110
|
-
if (!hits.length) return;
|
|
111
|
-
|
|
112
|
-
// Inject each card at most once per session.
|
|
113
|
-
const sessionKey = String(payload.session_id || "any").replace(/[^a-zA-Z0-9-]/g, "");
|
|
114
|
-
const stateFile = path.join(os.tmpdir(), `etna-recall-${sessionKey}.txt`);
|
|
115
|
-
let seen = [];
|
|
116
|
-
try {
|
|
117
|
-
seen = fs.readFileSync(stateFile, "utf8").split("\n").filter(Boolean);
|
|
118
|
-
} catch (e) {
|
|
119
|
-
seen = [];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const fresh = hits
|
|
123
|
-
.filter((h) => h && h.score >= MIN_SCORE && h.file && !seen.includes(h.file))
|
|
124
|
-
.slice(0, MAX_CARDS);
|
|
125
|
-
const lines = [];
|
|
126
|
-
for (const hit of fresh) {
|
|
127
|
-
const statement = cardStatement(cardPath(factsDir, hit.file));
|
|
128
|
-
if (statement) {
|
|
129
|
-
lines.push(`- ${statement} [${String(hit.file).replace(/^qmd:\/\/[^/]+\//, "docs/memory/facts/")}]`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (!lines.length) return;
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
fs.appendFileSync(stateFile, fresh.map((h) => h.file).join("\n") + "\n", "utf8");
|
|
136
|
-
} catch (e) {
|
|
137
|
-
// Dedupe state is best-effort; a re-injection is harmless.
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
process.stdout.write(
|
|
141
|
-
`<memory-recall>\nProject memory possibly relevant to this prompt (distilled fact-cards):\n${lines.join("\n")}\n</memory-recall>\n`,
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
main();
|
|
147
|
-
} catch (e) {
|
|
148
|
-
try {
|
|
149
|
-
process.stderr.write(`prompt-recall hook: ${e && e.message ? e.message : e}\n`);
|
|
150
|
-
} catch (e2) {
|
|
151
|
-
// Nothing more we can do.
|
|
152
|
-
}
|
|
153
|
-
} finally {
|
|
154
|
-
process.exit(0);
|
|
155
|
-
}
|