@antoneeo/kb-agentic-skill 1.4.6 → 1.4.8

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/CHANGELOG.md CHANGED
@@ -2,6 +2,103 @@
2
2
 
3
3
  Every significant change to this skill is recorded here.
4
4
 
5
+ ## [1.4.8] - 2026-08-25
6
+
7
+ F-035 — second field report from the F-029 practitioner. Three defects in the corpus
8
+ letter's own enforcement: the promise is that every provenance is a real file, and two
9
+ of the three mechanisms that should hold it did not run.
10
+
11
+ ### Fixed
12
+ - **`prov:` below `GIVEN` was structurally impossible for any claim citing
13
+ `corpus/given/`.** `_note_frontmatter` read frontmatter from the cited file itself. A
14
+ corpus artifact is bytes and carries none — but it *is* a file, so the helper returned
15
+ `{}` rather than `None`, which the caller cannot tell apart from "resolved, field
16
+ absent", and the required-field error fired unconditionally. `DERIVED`, `RULING` and
17
+ `IMPORTED` were unreachable on any `given/` artifact, so every such claim was forced
18
+ to `GIVEN` whatever its real extraction chain. The helper now reads the artifact's
19
+ `.meta.md` **sidecar** when one exists and the cited file otherwise — sidecar-first,
20
+ not "non-`.md`", so a verbatim `.md` source stored in `given/` resolves the same way
21
+ as a `.txt` extraction while a `corpus/notes/*.md` note still resolves to its own
22
+ frontmatter. It also returns the path it actually read, and the three findings now
23
+ name that file instead of saying "note" about a sidecar.
24
+ - **`original_path:` was never verified.** Zero reads of it in either script: a folder
25
+ of originals could be moved and sixteen sidecars go dangling behind a green run. It is
26
+ now checked for resolution — a **warning**, never an error, because a bundle carries
27
+ artifacts and sidecars and never the originals, so after an import it dangles
28
+ legitimately. Absolute paths are tested as written; a relative one is tried against
29
+ the docs root's parent and against the docs root, and warns only if neither resolves.
30
+ `original_sha256` stays unverified for the reason already stated — we do not hold the
31
+ bytes — and `distillation.md` now states the two limits separately, because they were
32
+ never in the same position.
33
+ - **The duplicate-id error named the wrong defect.** "uniqueness is global across
34
+ topics/" describes a copied row; the reporter's two rows were hand-written and
35
+ different, and had collided in `kb_claim_id`, which hashes `path#locator#qty` and
36
+ excludes the text on purpose. The check now branches on the **claim text** — same text
37
+ is a copied row, different text is the collision — and the collision message explains
38
+ why the id cannot separate them and prescribes the two legitimate repairs (widen a
39
+ locator, or merge the rows), explicitly ruling out editing the qty to break the tie.
40
+ The id function itself is unchanged and its two constants are now pinned by tests:
41
+ `portability.md`'s cross-project de-duplication rests on them
42
+ (`ADR_2026-08-25_claim_id_collision.md`).
43
+
44
+ ### Added
45
+ - **A `GIVEN` row whose artifact declares a weaker chain now warns.** `provenance:` was
46
+ written into every sidecar and read by nothing; this is its first consumer. A
47
+ conformant corpus (`provenance: GIVEN`, or no field) is silent. **Scope, stated so it
48
+ is not mistaken for more:** it reads the row's first source and it reads the *field* —
49
+ a sidecar declaring `provenance: GIVEN` while its prose says "transcribed from a
50
+ photograph" stays silent, because prose is not machine-readable. The pair's larger
51
+ half is the fix above, which is what makes the honest declaration possible at all.
52
+
53
+ ### Fixed (second review round)
54
+ - **`original_path` classification was wrong on Windows, and the documented example was
55
+ the trigger.** `Path.is_absolute()` is False for a rooted-but-driveless path — exactly
56
+ the `/vault/manuals/xyz.pdf` form these templates print — so it was joined under the
57
+ docs root and silently rewritten onto the docs root's *drive*. That produced a warning
58
+ quoting a path nobody wrote (twice, since both candidates collapsed to it), could hide
59
+ a genuinely dangling pointer behind whatever happened to sit there, and warned falsely
60
+ whenever the corpus and the original lived on different drives. Rootedness is now
61
+ decided by `ntpath`/`posixpath`, the candidate list is de-duplicated, and the
62
+ backslash normalisation is an *additional* candidate rather than a rewrite (a
63
+ backslash is a legal character in a POSIX filename).
64
+ - **The pointer probe could crash the run.** `Path.exists()` re-raises `PermissionError`
65
+ and `ENAMETOOLONG`; `original_path` points outside the docs root by design, so the
66
+ validator must survive whatever is out there. It is now `is_file()` (a directory is not
67
+ a document) inside an `OSError` guard.
68
+ - **`_note_frontmatter` could read one file outside the docs root.** A source cell of
69
+ the form `#L1-2` — no path before the locator — made `confine_under` return the docs
70
+ root itself, and the sidecar name was then built from `base.parent`, i.e.
71
+ `<docs-root>.meta.md`. Both that case and a sidecar orphaned by a deleted artifact now
72
+ return "unresolved", which the source loop already reports.
73
+ - **The collision message asserted something it had not checked.** It claimed the two
74
+ rows "cite the same span with the same qty" — true only for a *computed* id. Two rows
75
+ sharing a hand-typed or stale id while citing different spans now get their own
76
+ message, naming the real repair (`claim-id --fill`).
77
+ - Frontmatter resolution is memoized per cited path: it now runs for every row, `GIVEN`
78
+ included, and a ledger citing one artifact from eighty rows was re-reading its sidecar
79
+ eighty times.
80
+ - The worked sidecar example carried trailing `# comment`s. The frontmatter reader is a
81
+ line regex and does not strip them, so the comment landed inside the value — harmless
82
+ while nothing read `original_path`, and a guaranteed false warning once something did.
83
+
84
+ ### Known limits, stated
85
+ - `ELICITED` is accepted with **no required field at all** — it is named in the
86
+ provenance branch but has no check below it. Found while fixing the above, deliberately
87
+ not repaired here: adding one is a new gate, not a repair.
88
+ - `corpus` and `graph` still have no `--errors-only`; on a corpus with many in-progress
89
+ artifacts the coverage warnings bury a lone error. Ranked last by the reporter.
90
+
91
+ ## [1.4.7] - 2026-08-06
92
+
93
+ ### Fixed
94
+ - **Shared-spine sync: the scoped re-review contradicted `dispatch.md` (ACTIVE in this lens).**
95
+ `dispatch.md` said "exactly three review touches per task, never a loop" while `review.md`
96
+ required every review-driven correction to be re-reviewed. Reconciled: the scoped re-review is
97
+ a round inside slot 2 or 3, never a fourth slot. Also in the shared `review.md`: an unproven or
98
+ stale completion claim is now a reviewer finding; a PASS carrying findings is provisional until
99
+ its corrections pass a round; the single log row keeps the round-1 verdict (`FAIL → PASS`).
100
+ Found by the code lens's late design review of F-034.
101
+
5
102
  ## [1.4.6] - 2026-08-06
6
103
 
7
104
  ### Changed
package/README.md CHANGED
@@ -8,7 +8,7 @@ The sibling of [`@antoneeo/agentic-sdlc-skill`](https://www.npmjs.com/package/@a
8
8
 
9
9
  Two axes are kept apart on purpose — **abstraction** (a topic made of topics: the graph edges) and **certainty** (how strongly the corpus supports a statement: the claim rows).
10
10
 
11
- 1. **Intake — the source becomes untouchable.** Every document enters verbatim under `corpus/given/`, content-addressed by a raw-byte digest. A new version is appended with `supersedes:`, never overwritten. Non-text files get a **stored canonical extraction** (`.txt`, registered extractor) — those are the bytes locators point at, so an id stays valid across re-ingestion. On a large binary corpus the extraction **is** the artifact: the digest moves onto it (immutability still enforced, on the bytes that actually matter) and the original stays where it lives, recorded as `original_path:` + `original_sha256:`. 233 MB of manuals do not enter your repository to protect bytes no locator addresses — and the two recorded fields are honest about their limit: they let a human re-verify, they check nothing on their own. Spoken input is a note with `origin: elicited`; a synthesis carries `derived_from:`; your decision carries `basis:`. A note with none of the three is model knowledge dressed as a source, and the validator rejects it.
11
+ 1. **Intake — the source becomes untouchable.** Every document enters verbatim under `corpus/given/`, content-addressed by a raw-byte digest. A new version is appended with `supersedes:`, never overwritten. Non-text files get a **stored canonical extraction** (`.txt`, registered extractor) — those are the bytes locators point at, so an id stays valid across re-ingestion. On a large binary corpus the extraction **is** the artifact: the digest moves onto it (immutability still enforced, on the bytes that actually matter) and the original stays where it lives, recorded as `original_path:` + `original_sha256:`. 233 MB of manuals do not enter your repository to protect bytes no locator addresses — and the two recorded fields are honest about their limits, which differ: `original_sha256` checks nothing on its own (we do not hold the bytes), while `original_path` is verified to still resolve, and warns when it does not — a folder you reorganised should not leave sixteen sidecars pointing at nothing behind a green run. Spoken input is a note with `origin: elicited`; a synthesis carries `derived_from:`; your decision carries `basis:`. A note with none of the three is model knowledge dressed as a source, and the validator rejects it.
12
12
 
13
13
  2. **Extraction — the unit is the claim.** Rows of `id | claim | valid | qty | about | source | prov | state`. The **id hashes the location and the quantity, never the text**, so an LLM rephrasing mints no new identity. The locator (`p=17@412-509`) is verified: the validator opens the extraction and checks the span exists — and `anchor <path> <phrase>` produces it for you, matching spaces as `\s+` because a PDF extraction breaks phrases mid-line. **Gates are extracted alongside powers**: for every row saying what something can do, the source is asked what must hold first — default-off, licence tier, version floor, dependency — because "yes, supported" without the gate is a plan that fails on site. The rule is *ask*, never *produce*: a source that states no gate yields no row. Validity scopes are half-open ("until March" and "from March" do not conflict). Quantities are typed — mixed kinds or currencies **refuse to sum**. What the source does not assert becomes a `gaps:` line, never a claim.
14
14
 
@@ -1,6 +1,6 @@
1
- {
2
- "name": "kb-agentic-skill",
3
- "version": "1.4.6",
4
- "description": "Knowledge-Base & Document-First protocol with risk triage, Vision governance, signal distillation and optional devPNT integration.",
5
- "author": "Antonio Pinto (https://github.com/Antoneeo)"
6
- }
1
+ {
2
+ "name": "kb-agentic-skill",
3
+ "version": "1.4.8",
4
+ "description": "Knowledge-Base & Document-First protocol with risk triage, Vision governance, signal distillation and optional devPNT integration.",
5
+ "author": "Antonio Pinto (https://github.com/Antoneeo)"
6
+ }
package/package.json CHANGED
@@ -1,51 +1,51 @@
1
- {
2
- "name": "@antoneeo/kb-agentic-skill",
3
- "version": "1.4.6",
4
- "description": "Knowledge-Base & Document-First protocol for Claude Code, Gemini CLI, Google Antigravity and Codex with risk triage, Vision governance, signal distillation and optional devPNT integration.",
5
- "keywords": [
6
- "claude-code",
7
- "claude-skill",
8
- "gemini-cli",
9
- "skill",
10
- "knowledge-base",
11
- "documentation",
12
- "ai-agent"
13
- ],
14
- "author": "Antonio Pinto (https://github.com/Antoneeo)",
15
- "license": "MIT",
16
- "publishConfig": {
17
- "access": "public"
18
- },
19
- "bin": {
20
- "kb-agentic-init": "scripts/init.js",
21
- "kb-agentic-install-skill": "scripts/postinstall.js"
22
- },
23
- "scripts": {
24
- "postinstall": "node scripts/postinstall.js",
25
- "preuninstall": "node scripts/preuninstall.js"
26
- },
27
- "files": [
28
- "skills/kb-agentic-skill/SKILL.md",
29
- "skills/kb-agentic-skill/templates.md",
30
- "skills/kb-agentic-skill/taxonomy.md",
31
- "skills/kb-agentic-skill/guides.md",
32
- "skills/kb-agentic-skill/vision.md",
33
- "skills/kb-agentic-skill/distillation.md",
34
- "skills/kb-agentic-skill/reconciliation.md",
35
- "skills/kb-agentic-skill/elicitation.md",
36
- "skills/kb-agentic-skill/review.md",
37
- "skills/kb-agentic-skill/dispatch.md",
38
- "skills/kb-agentic-skill/routing.md",
39
- "skills/kb-agentic-skill/portability.md",
40
- "skills/kb-agentic-skill/ENFORCEMENT.md",
41
- "skills/kb-agentic-skill/scripts/sdlc_check.py",
42
- "skills/kb-agentic-skill/scripts/sdlc_core.py",
43
- "gemini-extension.json",
44
- "README.md",
45
- "CHANGELOG.md",
46
- "scripts/lib.js",
47
- "scripts/init.js",
48
- "scripts/postinstall.js",
49
- "scripts/preuninstall.js"
50
- ]
51
- }
1
+ {
2
+ "name": "@antoneeo/kb-agentic-skill",
3
+ "version": "1.4.8",
4
+ "description": "Knowledge-Base & Document-First protocol for Claude Code, Gemini CLI, Google Antigravity and Codex with risk triage, Vision governance, signal distillation and optional devPNT integration.",
5
+ "keywords": [
6
+ "claude-code",
7
+ "claude-skill",
8
+ "gemini-cli",
9
+ "skill",
10
+ "knowledge-base",
11
+ "documentation",
12
+ "ai-agent"
13
+ ],
14
+ "author": "Antonio Pinto (https://github.com/Antoneeo)",
15
+ "license": "MIT",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "bin": {
20
+ "kb-agentic-init": "scripts/init.js",
21
+ "kb-agentic-install-skill": "scripts/postinstall.js"
22
+ },
23
+ "scripts": {
24
+ "postinstall": "node scripts/postinstall.js",
25
+ "preuninstall": "node scripts/preuninstall.js"
26
+ },
27
+ "files": [
28
+ "skills/kb-agentic-skill/SKILL.md",
29
+ "skills/kb-agentic-skill/templates.md",
30
+ "skills/kb-agentic-skill/taxonomy.md",
31
+ "skills/kb-agentic-skill/guides.md",
32
+ "skills/kb-agentic-skill/vision.md",
33
+ "skills/kb-agentic-skill/distillation.md",
34
+ "skills/kb-agentic-skill/reconciliation.md",
35
+ "skills/kb-agentic-skill/elicitation.md",
36
+ "skills/kb-agentic-skill/review.md",
37
+ "skills/kb-agentic-skill/dispatch.md",
38
+ "skills/kb-agentic-skill/routing.md",
39
+ "skills/kb-agentic-skill/portability.md",
40
+ "skills/kb-agentic-skill/ENFORCEMENT.md",
41
+ "skills/kb-agentic-skill/scripts/sdlc_check.py",
42
+ "skills/kb-agentic-skill/scripts/sdlc_core.py",
43
+ "gemini-extension.json",
44
+ "README.md",
45
+ "CHANGELOG.md",
46
+ "scripts/lib.js",
47
+ "scripts/init.js",
48
+ "scripts/postinstall.js",
49
+ "scripts/preuninstall.js"
50
+ ]
51
+ }
@@ -1,148 +1,148 @@
1
- ---
2
- name: kb-agentic
3
- version: 1.4.6
4
- description: Knowledge-Base & Document-First protocol with risk-proportional triage, Vision as a guide, Signal Distillation, a complete Standalone mode and optional symbiosis with devPNT. Use for user documentation, knowledge extraction, SOPs, research notes, decision logs and knowledge management.
5
- author: Antonio Pinto (https://github.com/Antoneeo)
6
- copyright: (c) 2026 Antonio Pinto
7
- ---
8
-
9
- # KB Agentic
10
-
11
- **Why this skill exists:** to prevent *knowledge degradation and myopia* — acting from partial understanding, where new information contradicts un-indexed notes and hard-won knowledge evaporates between sessions. Triage, the Vision Gate, signal distillation, the documentation lifecycle, and operative guide routers are all one defense against that.
12
-
13
- This skill guides knowledge management and documentation with a Document-First process proportional to risk. It works fully even without devPNT. When devPNT is available and configured for the current project, the skill works in symbiosis with its governance: M-VISION, Master Plan, Action Plan and versioned artifacts become the authoritative frame for milestones and knowledge nodes.
14
-
15
- Support files in the skill directory:
16
- - `templates.md`: templates for Vision, Knowledge ANALYSIS, Research SPIKE, SOP GUIDE, audit plan, and handoff.
17
- - `portability.md`: carrying knowledge between projects — what a bundle is, why export is a closure rather than a selection, and the rule that knowledge crosses a project boundary while authority does not (`prov: IMPORTED`). Read before `export`/`import`.
18
- - `taxonomy.md`: placing a claim in the topic graph — descent over the generated index, the five verdicts (EXISTS / INADEQUATE / MISSING / GENERALIZES / UNPLACED), the sibling rule, guarded re-parenting, canonicalization. Run at L3 before drafting.
19
- - `guides.md`: pipeline for distilling user-provided indications into `ai_docs/reference/GUIDE_[topic].md`.
20
- - `vision.md`: how to write a Vision a cold reviewer can actually apply — the properties that make a rule hold, the minimum operable sections, and the blind check.
21
- - `distillation.md`: from a source to claim rows — intake (content-addressed originals, stored canonical extraction, every provenance a real file), the claim table, extraction discipline, signal rules (symbiosis with `distill`).
22
- - `reconciliation.md`: what happens when two claims meet — five outcomes, detect-and-hold (the machine never decides), rulings with mandatory `basis:`, the claim state machine, the batched escalation form.
23
- - `review.md`: the review discipline — when a review is due, how to request one, how to receive findings, how to review.
24
- - `dispatch.md`: opt-in subagent execution of an approved plan.
25
- - `routing.md`: which lens owns this unit of work. Read ONLY when a sibling lens skill is installed alongside this one; a single-lens install never reads it.
26
- - `scripts/sdlc_check.py` + `scripts/sdlc_core.py`: the mechanical validator for the docs root (`check`, `validate`, `index`, `stale`, `mark`, `gate`, `plan`, `orient`, `migrate`, and the knowledge overlay: `graph`, `corpus`, `claim-id`, `anchor`, `export`, `import`). Two files: the core is the family's shared spine; the entry point IS the knowledge overlay — the claim ledger and topic-graph checks live inside it, so the core alone runs none of them. Copy both, or neither.
27
- - `ENFORCEMENT.md`: optional setup for CI and hooks.
28
-
29
- Read these files only when needed. `SKILL.md` is the operating contract; the support files are progressive resources.
30
-
31
- ## Knowledge Values
32
-
33
- - **Understand before writing/updating:** do not modify or create knowledge documents without checking root cause, user constraints, and existing notes — trust the primary sources and existing guides, not memory.
34
- - **Apply DRY and Single Source of Truth:** do not duplicate knowledge across files; extend existing notes (`ANALYSIS_[topic].md` or `GUIDE_[topic].md`).
35
- - **Signal & Distillation:** eliminate fluff, filler, and speculative statements; keep deterministic facts, constraints, decisions, and clear SOP steps (symbiosis with `distill`).
36
- - **Lifecycle & Freshness:** mark superseded knowledge (`status: SUPERSEDED` or `DEPRECATED`) so outdated information does not cause hallucination.
37
- - **Protect the Vision & User Style:** align all knowledge work with user strategic goals, operational preferences, and expected outcomes.
38
- - **Map-First Navigation:** rely on `ai_docs/README.md`, `INDEX.md`, and `reference/INDEX.md` router for targeted retrieval before creating new documents.
39
-
40
- ## Rule Zero: Triage
41
-
42
- Always classify the request before choosing the process. Declare the chosen level to the user when you start operational work.
43
-
44
- **Declare the level WITH the router verdict** (one line, for L2, L3 and Spike — L1 declares the level alone): the result of the guide-router lookup described under `## Operative Guides`, i.e. `Level: L2 · router: no match` or `Level: L3 · router: GUIDE_release_sop.md → read`. Name the guide you matched, or `no match`.
45
-
46
- | Level | Criteria | Required process |
47
- |---|---|---|
48
- | **L1 - Quick Fact / Snippet** | One claim row added to an existing topic; a typo; a preference update. No node created, no source entering the corpus, no frontmatter change. | Implement edit directly in existing note. No new documents. |
49
- | **L2 - Propagation of settled knowledge** | The fact is **already settled** in the corpus and the work carries it into existing documents — restating, correcting a stale copy, updating an SOP that quotes it. No node created or superseded, no hierarchy change, no node frontmatter change, no new source ingested. | Mini-analysis in message: objective, impact, sources, validation. Update existing document or create single SOP/note. |
50
- | **L3 - New knowledge unit / Corpus** | A source enters the corpus; a topic node is created or superseded; the hierarchy moves; a conflict must be reconciled; or what a claim asserts changes. | Full workflow: Vision Gate, Spec Elicitation, Taxonomy Pass, Knowledge Analysis, Distillation, Review, Indexing. |
51
- | **Spike - Exploration** | Time-boxed exploratory research or draft without merging into official KB. | Outcome in `ai_docs/solutions/SPIKE_[topic].md`. |
52
-
53
- Cross-cutting rules:
54
- - **The unit of measure here is knowledge, never file count.** In this domain risk is
55
- knowledge-shaped: carrying one settled fact into eight documents is small, and one
56
- claim that re-parents a node is not. Do not import the code lens's file thresholds —
57
- a level is chosen by what the change does to the corpus and the graph. What keeps
58
- this from becoming an escape hatch is the trigger list below, which overrides the
59
- level whatever the size, plus one limit: **propagation that changes what a claim
60
- asserts is not propagation** — it is a new knowledge unit, so L3.
61
- - **Domain routing (multi-lens installs only).** After the level is set, and only when a sibling lens skill of this family is installed (`agentic-sdlc`, `mkt-agentic-sdlc`), run the router in `routing.md` for every L2, L3 and Spike: it decides which lens's method and validation rules govern this unit of work. L1 never reaches it, and a single-lens install never reads the file — detection fails open. In such a project, never refer to a document whose meaning differs by lens ("threat model", "vision", `principles.md`, `handoff.md`) by its bare name: qualify it with its domain, or name its path.
62
- - Personal data, credentials, security-sensitive processes, authN/authZ specs are high-risk: never L1.
63
- - **Escalation triggers — ANY of these makes it L3, whatever the file count:** the change touches the topic hierarchy (`parents:`, a `GENERALIZES` verdict, a re-parent); it touches more than one node's frontmatter; it creates or supersedes a node other nodes reference. Re-shaping the graph is a unit of change, never a side effect of placing one claim.
64
- - Adding one claim row to an existing topic is L1: the `id` may be left empty (the validator fills it — `claim-id --fill`), and no check errors on a hand-written row.
65
- - If a bigger impact emerges during L1/L2 work, stop, reclassify and declare it.
66
- - When in doubt, pick the higher level.
67
- - **No useless questions.** Every question to the practitioner passes the legality test of the question discipline (`elicitation.md`): searched first with the search named, and the blocked decision named. Claim-conflict escalations additionally carry their own mandated form and are batched at run end (`reconciliation.md` §4).
68
-
69
- ## Write Triggers
70
-
71
- Triage decides IF documentation is due; this table decides WHICH document each event produces, and when. **One event, one destination:** when the trigger fires and the document does not exist, create it; when it exists, update it — never duplicate it.
72
-
73
- | Document | Write trigger | Phase |
74
- |---|---|---|
75
- | `solutions/ANALYSIS_[topic].md` | Every L3, after elicitation and before any drafting. On topic match with an existing analysis, update that one instead of a new file. | 3 |
76
- | `solutions/SPIKE_[topic].md` | Closing any Spike — including a negative outcome. | — |
77
- | `vision/features/VISION_[feature].md` | Multi-milestone topic at analysis time, or retroactive trigger (second ANALYSIS on same theme). | 3 |
78
- | `audit/handoff.md` (workstream registry) | **Never by hand — generated by `sdlc_check.py index` from the `HANDOFF_*.md` sources**, and `validate` errors when the two disagree. Regenerate at every L3 closure and at session end; the `Date:` header is derived, so no writer touches it. Inventory for lookup, never a work board. | 5 / session end |
79
- | `audit/HANDOFF_[topic].md` | **One per OPEN workstream, with or without volatile state** — it is the authored home of that workstream's registry row (frontmatter `workstream`/`level`/`branch`/`status`/`since`/`next`/`details`/`updated`), so no file means no row. Carries the resume logistics too; the ANALYSIS Diary keeps the durable narrative (DRY). DELETED at closure — deleting it *is* removing the row. | 4 / 5 / session end |
80
- | `audit/project_notes.md` | A note true for the whole project rather than one workstream. Appended verbatim to the generated registry, so regenerating cannot destroy notes that belong to no workstream. | 5 / session end |
81
- | `audit/handoff.md` — converting an existing project | **Lazily, at the first write — and then ALL AT ONCE.** Converting one row at a time is the state that loses the others, so `index` refuses to write while anything in the file is unaccounted for and names it. Until the first source exists nothing generates and nothing errors. | 5 / session end |
82
- | `audit/audit_plan.md` (Standalone) | Bootstrap, and whenever a mapped area changes state (`sdlc_check.py mark`). | 1 |
83
- | `reference/GUIDE_[topic].md` (`source_kind: document`) | Origin+purpose test (`guides.md`), or a proactive proposal accepted by user. | 4 / 5 |
84
- | `reference/GUIDE_[topic].md` (`source_kind: code`/`domain`) | Recognized high-complexity domain/concept with no CURRENT guide $\rightarrow$ duty to write autonomously. | 4 / 5 |
85
- | `audit/reviews/REVIEW_LOG.md` | Every completed review — recorded in log. | 3 / 5 |
86
- | ADR / Decision Log — `architecture/` or devPNT DB | A strategic decision was taken (pattern, policy, structural change): record before DONE. | 5 |
87
- | `strategic/architecture.md`, `strategic/existing_features.md` | Bootstrap; update at closure when the knowledge catalog actually changed. | 1 / 5 |
88
- | `vision/project_vision.md`, `roadmap.md`, `principles.md` | Bootstrap as `Status: DRAFT`; promoted to APPROVED only by explicit user confirmation. | 1 / 2 |
89
- | `topics/<slug>.md` | A placement verdict creates it (MISSING/INADEQUATE-child, `taxonomy.md`); reconciliation updates its claim rows. One node per topic — a similar-but-distinct concept is a sibling with `related:` + a written distinction, never a merge and never a duplicate. Merged/renamed nodes become tombstones (`status: SUPERSEDED` + `redirect_to:`), never deleted. | 4 |
90
- | a KB bundle (`export`) | Knowledge must leave this project. Export is L1 — it writes nothing into the corpus. **Importing one is L3**: a source enters the corpus and nodes are created, and a bundle is external input, so never L1 whatever its size (`portability.md`). | — |
91
- | `corpus/given/*` + sidecar | A source arrives: it becomes a content-addressed artifact with a sidecar carrying digest/date/`supersedes:`. A text source is copied verbatim. A non-text source yields its stored canonical extraction — copied **beside** the original when that is small enough to keep, or **instead of it** on a large binary corpus, where the original stays where it lives and is recorded as `original_path:`/`original_sha256:` (`distillation.md` §1, which owns this rule). Never edited after ingest — the digest check on whatever `given/` holds is what enforces it. | 4 |
92
- | `corpus/notes/*` | Something is said (`origin: elicited`), synthesised (`derived_from:`), or ruled (`basis:`). A note with none of the three is refused by the validator. | 4 / 5 |
93
- | `solutions/PLAN_[topic].md` + its ledger | A source too long for one context is ingested: **one task per reading window** (30 pages by default; the plan states the window used), each ending at a declared `extracted_through:`. That ledger is the register the ingestion resumes from across sessions — ingestion never builds a second one (`distillation.md` §3, `templates.md`). | 4 |
94
- | `corpus/given/*.meta.md` — `extracted_through:` | Every reading window closes by advancing it (`p=<n>`, `L<n>`, `complete`). Required once any claim cites the artifact: unstated, "I am finished" cannot be falsified, which is how a sampled 200-page manual passes for an ingested one. | 4 |
95
- | `INDEX.md`, `reference/INDEX.md`, `topics/INDEX.md`, `corpus/INDEX.md` | Regenerated by `sdlc_check.py index` at closure — never by hand; `validate` fails on a hand-edited one. | 5 |
96
-
97
- ## Operating Modes
98
-
99
- ### Full Standalone
100
- Use this mode when devPNT is unavailable, not configured for the current project, or the user explicitly asks for a filesystem-only workflow.
101
- - Source of truth: `ai_docs/` (`vision/`, `solutions/`, `reference/`, `audit/`, `strategic/`).
102
-
103
- ### Hybrid in symbiosis with devPNT
104
- Use this mode when `devpnt_*` tools are available and point at the current project.
105
- - devPNT governs `M-VISION`, Master Plan, Action Plan, and versioned artifacts.
106
- - Local `ai_docs/` serves as readable context, Standalone fallback, local handoff, or shadow copy.
107
-
108
- ## L3 Workflow
109
-
110
- ### 1. Audit and Alignment
111
- - Read `ai_docs/audit/handoff.md` (workstream registry) to see active topics.
112
- - Read `ai_docs/README.md`, `ai_docs/INDEX.md` and `ai_docs/reference/INDEX.md` (the guide router) before exploring notes. The router is a mandatory read, not an optional one: it is the only orientation step that tells you a guide already governs the work you are about to do.
113
- - If `ai_docs/` is missing, create the bootstrap set: `README.md`, `vision/` docs, `strategic/` docs, and `audit/audit_plan.md`, then run `sdlc_check.py index`.
114
-
115
- ### 2. Vision Gate
116
- - Read `project_vision.md`, `roadmap.md`, `principles.md` (or devPNT `M-VISION`).
117
- - Verify request aligns with expected benefits, user goals, and success signals.
118
-
119
- ### 3. Request Analysis & Taxonomy Pass
120
- - Run spec elicitation round (`elicitation.md`) before drafting analysis.
121
- - Run taxonomy pass (`taxonomy.md`): verify whether topics, categories, or SOPs already exist in `ai_docs/`. Avoid duplication.
122
- - Create or update `ai_docs/solutions/ANALYSIS_[topic].md`.
123
- - **Design review gate (end of Phase 3, before any drafting):** the analysis is reviewed by somebody other than its author — a subagent with fresh context, or a declared self-pass when none is available. Follow `review.md`; log the outcome in `audit/reviews/REVIEW_LOG.md`. A knowledge structure reviewed only by the person who chose it is not reviewed.
124
-
125
- ### 4. Knowledge Processing & Distillation
126
- - **Isolate the work (Branch/worktree hygiene).** Distillation rewrites existing notes: do it on a branch or a worktree, never directly on the shared corpus, so a half-finished reconciliation is never what the next reader finds.
127
- - Before drafting (L2/L3; L1 exempt), **consult the guide router** for a guide covering the task and read it first (the consult trigger, `guides.md` §0). A targeted description match, not a blanket read. Its result is the router verdict already declared with the triage level (Rule Zero).
128
- - Execute knowledge extraction using **Signal Distillation** (`distillation.md`).
129
- - **Opt-in subagent execution**: for an L3 with an approved analysis, the work MAY be executed via subagents per `dispatch.md`; default stays same-session.
130
- - Handle conflicting or outdated information via **Reconciliation** (`reconciliation.md`). Mark obsolete files `status: SUPERSEDED`.
131
-
132
- ### 5. Closure & Indexing
133
- - Run verification checks (`sdlc_check.py check --root <project_root>`).
134
- - If the work was governed by user-provided indications and is reusable, **PROPOSE distilling a guide** (proactive trigger, `guides.md` §1) — a proposal for the user, never a silent write, never from model knowledge.
135
- - Update `audit/handoff.md`.
136
- - Regenerate manifests: `python <skill_dir>/scripts/sdlc_check.py index --root <project_root>`.
137
- - Mark output clean and complete.
138
-
139
- ## Operative Guides & Router
140
- Guides are consulted, created, and distilled per `guides.md`.
141
- - **Consult (before acting)**: check `ai_docs/reference/INDEX.md` — the guide router — and declare the verdict with the triage level.
142
- - **Propose proactively**: when the user hands over indications that will govern future work, propose distilling them into a guide. A proposal, never a silent write, and never from model knowledge — a guide's whole value is that every claim traces to what the user actually provided.
143
- - Distill from user instructions into `ai_docs/reference/GUIDE_[topic].md`.
144
-
145
- ## Mechanical Enforcement
146
- - `scripts/sdlc_check.py index`: updates `ai_docs/INDEX.md` and `ai_docs/reference/INDEX.md`.
147
- - `scripts/sdlc_check.py validate`: validates YAML frontmatter (`status`, `description`).
148
- - `scripts/sdlc_check.py check`: checks dirty closures and missing indexes.
1
+ ---
2
+ name: kb-agentic
3
+ version: 1.4.8
4
+ description: Knowledge-Base & Document-First protocol with risk-proportional triage, Vision as a guide, Signal Distillation, a complete Standalone mode and optional symbiosis with devPNT. Use for user documentation, knowledge extraction, SOPs, research notes, decision logs and knowledge management.
5
+ author: Antonio Pinto (https://github.com/Antoneeo)
6
+ copyright: (c) 2026 Antonio Pinto
7
+ ---
8
+
9
+ # KB Agentic
10
+
11
+ **Why this skill exists:** to prevent *knowledge degradation and myopia* — acting from partial understanding, where new information contradicts un-indexed notes and hard-won knowledge evaporates between sessions. Triage, the Vision Gate, signal distillation, the documentation lifecycle, and operative guide routers are all one defense against that.
12
+
13
+ This skill guides knowledge management and documentation with a Document-First process proportional to risk. It works fully even without devPNT. When devPNT is available and configured for the current project, the skill works in symbiosis with its governance: M-VISION, Master Plan, Action Plan and versioned artifacts become the authoritative frame for milestones and knowledge nodes.
14
+
15
+ Support files in the skill directory:
16
+ - `templates.md`: templates for Vision, Knowledge ANALYSIS, Research SPIKE, SOP GUIDE, audit plan, and handoff.
17
+ - `portability.md`: carrying knowledge between projects — what a bundle is, why export is a closure rather than a selection, and the rule that knowledge crosses a project boundary while authority does not (`prov: IMPORTED`). Read before `export`/`import`.
18
+ - `taxonomy.md`: placing a claim in the topic graph — descent over the generated index, the five verdicts (EXISTS / INADEQUATE / MISSING / GENERALIZES / UNPLACED), the sibling rule, guarded re-parenting, canonicalization. Run at L3 before drafting.
19
+ - `guides.md`: pipeline for distilling user-provided indications into `ai_docs/reference/GUIDE_[topic].md`.
20
+ - `vision.md`: how to write a Vision a cold reviewer can actually apply — the properties that make a rule hold, the minimum operable sections, and the blind check.
21
+ - `distillation.md`: from a source to claim rows — intake (content-addressed originals, stored canonical extraction, every provenance a real file), the claim table, extraction discipline, signal rules (symbiosis with `distill`).
22
+ - `reconciliation.md`: what happens when two claims meet — five outcomes, detect-and-hold (the machine never decides), rulings with mandatory `basis:`, the claim state machine, the batched escalation form.
23
+ - `review.md`: the review discipline — when a review is due, how to request one, how to receive findings, how to review.
24
+ - `dispatch.md`: opt-in subagent execution of an approved plan.
25
+ - `routing.md`: which lens owns this unit of work. Read ONLY when a sibling lens skill is installed alongside this one; a single-lens install never reads it.
26
+ - `scripts/sdlc_check.py` + `scripts/sdlc_core.py`: the mechanical validator for the docs root (`check`, `validate`, `index`, `stale`, `mark`, `gate`, `plan`, `orient`, `migrate`, and the knowledge overlay: `graph`, `corpus`, `claim-id`, `anchor`, `export`, `import`). Two files: the core is the family's shared spine; the entry point IS the knowledge overlay — the claim ledger and topic-graph checks live inside it, so the core alone runs none of them. Copy both, or neither.
27
+ - `ENFORCEMENT.md`: optional setup for CI and hooks.
28
+
29
+ Read these files only when needed. `SKILL.md` is the operating contract; the support files are progressive resources.
30
+
31
+ ## Knowledge Values
32
+
33
+ - **Understand before writing/updating:** do not modify or create knowledge documents without checking root cause, user constraints, and existing notes — trust the primary sources and existing guides, not memory.
34
+ - **Apply DRY and Single Source of Truth:** do not duplicate knowledge across files; extend existing notes (`ANALYSIS_[topic].md` or `GUIDE_[topic].md`).
35
+ - **Signal & Distillation:** eliminate fluff, filler, and speculative statements; keep deterministic facts, constraints, decisions, and clear SOP steps (symbiosis with `distill`).
36
+ - **Lifecycle & Freshness:** mark superseded knowledge (`status: SUPERSEDED` or `DEPRECATED`) so outdated information does not cause hallucination.
37
+ - **Protect the Vision & User Style:** align all knowledge work with user strategic goals, operational preferences, and expected outcomes.
38
+ - **Map-First Navigation:** rely on `ai_docs/README.md`, `INDEX.md`, and `reference/INDEX.md` router for targeted retrieval before creating new documents.
39
+
40
+ ## Rule Zero: Triage
41
+
42
+ Always classify the request before choosing the process. Declare the chosen level to the user when you start operational work.
43
+
44
+ **Declare the level WITH the router verdict** (one line, for L2, L3 and Spike — L1 declares the level alone): the result of the guide-router lookup described under `## Operative Guides`, i.e. `Level: L2 · router: no match` or `Level: L3 · router: GUIDE_release_sop.md → read`. Name the guide you matched, or `no match`.
45
+
46
+ | Level | Criteria | Required process |
47
+ |---|---|---|
48
+ | **L1 - Quick Fact / Snippet** | One claim row added to an existing topic; a typo; a preference update. No node created, no source entering the corpus, no frontmatter change. | Implement edit directly in existing note. No new documents. |
49
+ | **L2 - Propagation of settled knowledge** | The fact is **already settled** in the corpus and the work carries it into existing documents — restating, correcting a stale copy, updating an SOP that quotes it. No node created or superseded, no hierarchy change, no node frontmatter change, no new source ingested. | Mini-analysis in message: objective, impact, sources, validation. Update existing document or create single SOP/note. |
50
+ | **L3 - New knowledge unit / Corpus** | A source enters the corpus; a topic node is created or superseded; the hierarchy moves; a conflict must be reconciled; or what a claim asserts changes. | Full workflow: Vision Gate, Spec Elicitation, Taxonomy Pass, Knowledge Analysis, Distillation, Review, Indexing. |
51
+ | **Spike - Exploration** | Time-boxed exploratory research or draft without merging into official KB. | Outcome in `ai_docs/solutions/SPIKE_[topic].md`. |
52
+
53
+ Cross-cutting rules:
54
+ - **The unit of measure here is knowledge, never file count.** In this domain risk is
55
+ knowledge-shaped: carrying one settled fact into eight documents is small, and one
56
+ claim that re-parents a node is not. Do not import the code lens's file thresholds —
57
+ a level is chosen by what the change does to the corpus and the graph. What keeps
58
+ this from becoming an escape hatch is the trigger list below, which overrides the
59
+ level whatever the size, plus one limit: **propagation that changes what a claim
60
+ asserts is not propagation** — it is a new knowledge unit, so L3.
61
+ - **Domain routing (multi-lens installs only).** After the level is set, and only when a sibling lens skill of this family is installed (`agentic-sdlc`, `mkt-agentic-sdlc`), run the router in `routing.md` for every L2, L3 and Spike: it decides which lens's method and validation rules govern this unit of work. L1 never reaches it, and a single-lens install never reads the file — detection fails open. In such a project, never refer to a document whose meaning differs by lens ("threat model", "vision", `principles.md`, `handoff.md`) by its bare name: qualify it with its domain, or name its path.
62
+ - Personal data, credentials, security-sensitive processes, authN/authZ specs are high-risk: never L1.
63
+ - **Escalation triggers — ANY of these makes it L3, whatever the file count:** the change touches the topic hierarchy (`parents:`, a `GENERALIZES` verdict, a re-parent); it touches more than one node's frontmatter; it creates or supersedes a node other nodes reference. Re-shaping the graph is a unit of change, never a side effect of placing one claim.
64
+ - Adding one claim row to an existing topic is L1: the `id` may be left empty (the validator fills it — `claim-id --fill`), and no check errors on a hand-written row.
65
+ - If a bigger impact emerges during L1/L2 work, stop, reclassify and declare it.
66
+ - When in doubt, pick the higher level.
67
+ - **No useless questions.** Every question to the practitioner passes the legality test of the question discipline (`elicitation.md`): searched first with the search named, and the blocked decision named. Claim-conflict escalations additionally carry their own mandated form and are batched at run end (`reconciliation.md` §4).
68
+
69
+ ## Write Triggers
70
+
71
+ Triage decides IF documentation is due; this table decides WHICH document each event produces, and when. **One event, one destination:** when the trigger fires and the document does not exist, create it; when it exists, update it — never duplicate it.
72
+
73
+ | Document | Write trigger | Phase |
74
+ |---|---|---|
75
+ | `solutions/ANALYSIS_[topic].md` | Every L3, after elicitation and before any drafting. On topic match with an existing analysis, update that one instead of a new file. | 3 |
76
+ | `solutions/SPIKE_[topic].md` | Closing any Spike — including a negative outcome. | — |
77
+ | `vision/features/VISION_[feature].md` | Multi-milestone topic at analysis time, or retroactive trigger (second ANALYSIS on same theme). | 3 |
78
+ | `audit/handoff.md` (workstream registry) | **Never by hand — generated by `sdlc_check.py index` from the `HANDOFF_*.md` sources**, and `validate` errors when the two disagree. Regenerate at every L3 closure and at session end; the `Date:` header is derived, so no writer touches it. Inventory for lookup, never a work board. | 5 / session end |
79
+ | `audit/HANDOFF_[topic].md` | **One per OPEN workstream, with or without volatile state** — it is the authored home of that workstream's registry row (frontmatter `workstream`/`level`/`branch`/`status`/`since`/`next`/`details`/`updated`), so no file means no row. Carries the resume logistics too; the ANALYSIS Diary keeps the durable narrative (DRY). DELETED at closure — deleting it *is* removing the row. | 4 / 5 / session end |
80
+ | `audit/project_notes.md` | A note true for the whole project rather than one workstream. Appended verbatim to the generated registry, so regenerating cannot destroy notes that belong to no workstream. | 5 / session end |
81
+ | `audit/handoff.md` — converting an existing project | **Lazily, at the first write — and then ALL AT ONCE.** Converting one row at a time is the state that loses the others, so `index` refuses to write while anything in the file is unaccounted for and names it. Until the first source exists nothing generates and nothing errors. | 5 / session end |
82
+ | `audit/audit_plan.md` (Standalone) | Bootstrap, and whenever a mapped area changes state (`sdlc_check.py mark`). | 1 |
83
+ | `reference/GUIDE_[topic].md` (`source_kind: document`) | Origin+purpose test (`guides.md`), or a proactive proposal accepted by user. | 4 / 5 |
84
+ | `reference/GUIDE_[topic].md` (`source_kind: code`/`domain`) | Recognized high-complexity domain/concept with no CURRENT guide $\rightarrow$ duty to write autonomously. | 4 / 5 |
85
+ | `audit/reviews/REVIEW_LOG.md` | Every completed review — recorded in log. | 3 / 5 |
86
+ | ADR / Decision Log — `architecture/` or devPNT DB | A strategic decision was taken (pattern, policy, structural change): record before DONE. | 5 |
87
+ | `strategic/architecture.md`, `strategic/existing_features.md` | Bootstrap; update at closure when the knowledge catalog actually changed. | 1 / 5 |
88
+ | `vision/project_vision.md`, `roadmap.md`, `principles.md` | Bootstrap as `Status: DRAFT`; promoted to APPROVED only by explicit user confirmation. | 1 / 2 |
89
+ | `topics/<slug>.md` | A placement verdict creates it (MISSING/INADEQUATE-child, `taxonomy.md`); reconciliation updates its claim rows. One node per topic — a similar-but-distinct concept is a sibling with `related:` + a written distinction, never a merge and never a duplicate. Merged/renamed nodes become tombstones (`status: SUPERSEDED` + `redirect_to:`), never deleted. | 4 |
90
+ | a KB bundle (`export`) | Knowledge must leave this project. Export is L1 — it writes nothing into the corpus. **Importing one is L3**: a source enters the corpus and nodes are created, and a bundle is external input, so never L1 whatever its size (`portability.md`). | — |
91
+ | `corpus/given/*` + sidecar | A source arrives: it becomes a content-addressed artifact with a sidecar carrying digest/date/`supersedes:`. A text source is copied verbatim. A non-text source yields its stored canonical extraction — copied **beside** the original when that is small enough to keep, or **instead of it** on a large binary corpus, where the original stays where it lives and is recorded as `original_path:`/`original_sha256:` (`distillation.md` §1, which owns this rule). Never edited after ingest — the digest check on whatever `given/` holds is what enforces it. | 4 |
92
+ | `corpus/notes/*` | Something is said (`origin: elicited`), synthesised (`derived_from:`), or ruled (`basis:`). A note with none of the three is refused by the validator. | 4 / 5 |
93
+ | `solutions/PLAN_[topic].md` + its ledger | A source too long for one context is ingested: **one task per reading window** (30 pages by default; the plan states the window used), each ending at a declared `extracted_through:`. That ledger is the register the ingestion resumes from across sessions — ingestion never builds a second one (`distillation.md` §3, `templates.md`). | 4 |
94
+ | `corpus/given/*.meta.md` — `extracted_through:` | Every reading window closes by advancing it (`p=<n>`, `L<n>`, `complete`). Required once any claim cites the artifact: unstated, "I am finished" cannot be falsified, which is how a sampled 200-page manual passes for an ingested one. | 4 |
95
+ | `INDEX.md`, `reference/INDEX.md`, `topics/INDEX.md`, `corpus/INDEX.md` | Regenerated by `sdlc_check.py index` at closure — never by hand; `validate` fails on a hand-edited one. | 5 |
96
+
97
+ ## Operating Modes
98
+
99
+ ### Full Standalone
100
+ Use this mode when devPNT is unavailable, not configured for the current project, or the user explicitly asks for a filesystem-only workflow.
101
+ - Source of truth: `ai_docs/` (`vision/`, `solutions/`, `reference/`, `audit/`, `strategic/`).
102
+
103
+ ### Hybrid in symbiosis with devPNT
104
+ Use this mode when `devpnt_*` tools are available and point at the current project.
105
+ - devPNT governs `M-VISION`, Master Plan, Action Plan, and versioned artifacts.
106
+ - Local `ai_docs/` serves as readable context, Standalone fallback, local handoff, or shadow copy.
107
+
108
+ ## L3 Workflow
109
+
110
+ ### 1. Audit and Alignment
111
+ - Read `ai_docs/audit/handoff.md` (workstream registry) to see active topics.
112
+ - Read `ai_docs/README.md`, `ai_docs/INDEX.md` and `ai_docs/reference/INDEX.md` (the guide router) before exploring notes. The router is a mandatory read, not an optional one: it is the only orientation step that tells you a guide already governs the work you are about to do.
113
+ - If `ai_docs/` is missing, create the bootstrap set: `README.md`, `vision/` docs, `strategic/` docs, and `audit/audit_plan.md`, then run `sdlc_check.py index`.
114
+
115
+ ### 2. Vision Gate
116
+ - Read `project_vision.md`, `roadmap.md`, `principles.md` (or devPNT `M-VISION`).
117
+ - Verify request aligns with expected benefits, user goals, and success signals.
118
+
119
+ ### 3. Request Analysis & Taxonomy Pass
120
+ - Run spec elicitation round (`elicitation.md`) before drafting analysis.
121
+ - Run taxonomy pass (`taxonomy.md`): verify whether topics, categories, or SOPs already exist in `ai_docs/`. Avoid duplication.
122
+ - Create or update `ai_docs/solutions/ANALYSIS_[topic].md`.
123
+ - **Design review gate (end of Phase 3, before any drafting):** the analysis is reviewed by somebody other than its author — a subagent with fresh context, or a declared self-pass when none is available. Follow `review.md`; log the outcome in `audit/reviews/REVIEW_LOG.md`. A knowledge structure reviewed only by the person who chose it is not reviewed.
124
+
125
+ ### 4. Knowledge Processing & Distillation
126
+ - **Isolate the work (Branch/worktree hygiene).** Distillation rewrites existing notes: do it on a branch or a worktree, never directly on the shared corpus, so a half-finished reconciliation is never what the next reader finds.
127
+ - Before drafting (L2/L3; L1 exempt), **consult the guide router** for a guide covering the task and read it first (the consult trigger, `guides.md` §0). A targeted description match, not a blanket read. Its result is the router verdict already declared with the triage level (Rule Zero).
128
+ - Execute knowledge extraction using **Signal Distillation** (`distillation.md`).
129
+ - **Opt-in subagent execution**: for an L3 with an approved analysis, the work MAY be executed via subagents per `dispatch.md`; default stays same-session.
130
+ - Handle conflicting or outdated information via **Reconciliation** (`reconciliation.md`). Mark obsolete files `status: SUPERSEDED`.
131
+
132
+ ### 5. Closure & Indexing
133
+ - Run verification checks (`sdlc_check.py check --root <project_root>`).
134
+ - If the work was governed by user-provided indications and is reusable, **PROPOSE distilling a guide** (proactive trigger, `guides.md` §1) — a proposal for the user, never a silent write, never from model knowledge.
135
+ - Update `audit/handoff.md`.
136
+ - Regenerate manifests: `python <skill_dir>/scripts/sdlc_check.py index --root <project_root>`.
137
+ - Mark output clean and complete.
138
+
139
+ ## Operative Guides & Router
140
+ Guides are consulted, created, and distilled per `guides.md`.
141
+ - **Consult (before acting)**: check `ai_docs/reference/INDEX.md` — the guide router — and declare the verdict with the triage level.
142
+ - **Propose proactively**: when the user hands over indications that will govern future work, propose distilling them into a guide. A proposal, never a silent write, and never from model knowledge — a guide's whole value is that every claim traces to what the user actually provided.
143
+ - Distill from user instructions into `ai_docs/reference/GUIDE_[topic].md`.
144
+
145
+ ## Mechanical Enforcement
146
+ - `scripts/sdlc_check.py index`: updates `ai_docs/INDEX.md` and `ai_docs/reference/INDEX.md`.
147
+ - `scripts/sdlc_check.py validate`: validates YAML frontmatter (`status`, `description`).
148
+ - `scripts/sdlc_check.py check`: checks dirty closures and missing indexes.
@@ -1,87 +1,96 @@
1
- # Subagent Execution Discipline
2
-
3
- Opt-in orchestration for L3 work with an approved design: the orchestrator
4
- drives a `PLAN_[feature].md` through subagents instead of implementing every
5
- task in the same session. Default stays same-session; this is an escalation,
6
- never a requirement.
7
-
8
- ## Trigger
9
-
10
- Only for L3 (an approved E-TDD in Hybrid, or an ANALYSIS Action Plan in
11
- Standalone). Never for L1/L2 — the plan/ledger machinery is overhead a small
12
- change does not need. The plan is always `derived-from` the accepted design:
13
- it is never independently authored, exactly like an E-TDD is never authored
14
- without an E-ISP.
15
-
16
- ## The loop
17
-
18
- 1. `sdlc_check.py plan validate PLAN_[feature].md` — zero-execution schema +
19
- confinement + ledger cross-check. Non-zero exit = **no dispatch**. This is
20
- the hard gate: "no valid plan, no dispatch."
21
- 2. For each task, in plan order:
22
- - Read the task's status from the sidecar ledger
23
- (`PLAN_[feature].ledger.json`). `status: done` (exact sentinel) → skip,
24
- never re-dispatch. Anything else (pending, failed, missing, or a
25
- corrupt-but-parseable entry) → treat as pending and dispatch.
26
- - `sdlc_check.py plan brief PLAN_[feature].md --task <id>` — prints the
27
- task block, the `produces` of prior-order tasks (interfaces), and
28
- `guides` pointers (paths, never pasted content) to stdout.
29
- - Spawn the subagent with that brief as its entire context window.
30
- - Run `task.verify` out of band (the orchestrator executes it — the
31
- validator only ever prints it, never runs it) plus the one-shot review
32
- below.
33
- - Write `{status, verify_result, timestamp}` back to the ledger. The
34
- validator never writes the ledger — single-writer, orchestrator-owned.
35
-
36
- **Guide consumption under dispatch.** Selecting each task's `guides` field IS the
37
- consult trigger (`guides.md` §0) applied at plan-authoring time: the orchestrator
38
- runs the router lookup (project router `ai_docs/reference/INDEX.md` + the agent-KB
39
- router) when populating `guides`. A dispatched context-free subagent does **NOT**
40
- run its own router consult — it reads the guide pointers handed to it in the
41
- brief. The router verdict (Rule Zero) is therefore declared ONCE, by the
42
- orchestrator, when it authors the plan; a dispatched subagent does not declare
43
- one. (Proactive guide-creation stays at closure — the same broad final pass
44
- below — so it needs no separate dispatch hook.)
45
-
46
- ## Model tiers (client-relative, no provider names)
47
-
48
- - Default dispatch: **economy** implementer tier.
49
- - After **two** consecutive `verify_result: fail` on the same task: escalate
50
- to the **deep** tier for the retry (ADR 2026-07-02). Do not escalate on the
51
- first failure — a single fail is often a brief or environment issue, not a
52
- capability gap.
53
-
54
- ## Review slots — one-shot, not iterative
55
-
56
- Exactly three review touches per task, never a loop:
57
-
58
- 1. Inline self-review by the implementer subagent before it reports done
59
- (the standard critical-review pass, not a separate call).
60
- 2. One reviewer pass per task (Hybrid: reuse the devPNT code-review gate;
61
- Standalone: the `review.md` discipline).
62
- 3. One broad final pass over the whole plan at closure, after all tasks are
63
- DONE — catches cross-task drift a per-task review cannot see.
64
-
65
- If a review FAILs, fix and re-run `verify` — that is a normal loop iteration
66
- via the ledger's fail path, not an extra review slot.
67
-
68
- ## Ledger protocol summary
69
-
70
- Read skip-if-done dispatch-if-pending write. The ledger is the only
71
- memory the loop needs across sessions or context compaction: a resumed
72
- orchestrator re-reads it and picks up exactly where it left off, never
73
- re-running a DONE task.
74
-
75
- ## Degradation
76
-
77
- No subagent-spawning tool available → the orchestrator runs each task in the
78
- same session, against the same plan and ledger, with the same one-shot review
79
- slots. No capability is lost, only the parallelism/isolation subagents would
80
- have added.
81
-
82
- ## Hybrid note
83
-
84
- The plan's `derived-from` points at the accepted E-TDD document key. Per-task
85
- review reuses the devPNT independent reviewers (§4.6 code review gate) rather
86
- than restating review doctrine see `review.md` for the single definition
87
- both modes share.
1
+ # Subagent Execution Discipline
2
+
3
+ Opt-in orchestration for L3 work with an approved design: the orchestrator
4
+ drives a `PLAN_[feature].md` through subagents instead of implementing every
5
+ task in the same session. Default stays same-session; this is an escalation,
6
+ never a requirement.
7
+
8
+ ## Trigger
9
+
10
+ Only for L3 (an approved E-TDD in Hybrid, or an ANALYSIS Action Plan in
11
+ Standalone). Never for L1/L2 — the plan/ledger machinery is overhead a small
12
+ change does not need. The plan is always `derived-from` the accepted design:
13
+ it is never independently authored, exactly like an E-TDD is never authored
14
+ without an E-ISP.
15
+
16
+ ## The loop
17
+
18
+ 1. `sdlc_check.py plan validate PLAN_[feature].md` — zero-execution schema +
19
+ confinement + ledger cross-check. Non-zero exit = **no dispatch**. This is
20
+ the hard gate: "no valid plan, no dispatch."
21
+ 2. For each task, in plan order:
22
+ - Read the task's status from the sidecar ledger
23
+ (`PLAN_[feature].ledger.json`). `status: done` (exact sentinel) → skip,
24
+ never re-dispatch. Anything else (pending, failed, missing, or a
25
+ corrupt-but-parseable entry) → treat as pending and dispatch.
26
+ - `sdlc_check.py plan brief PLAN_[feature].md --task <id>` — prints the
27
+ task block, the `produces` of prior-order tasks (interfaces), and
28
+ `guides` pointers (paths, never pasted content) to stdout.
29
+ - Spawn the subagent with that brief as its entire context window.
30
+ - Run `task.verify` out of band (the orchestrator executes it — the
31
+ validator only ever prints it, never runs it) plus the one-shot review
32
+ below.
33
+ - Write `{status, verify_result, timestamp}` back to the ledger. The
34
+ validator never writes the ledger — single-writer, orchestrator-owned.
35
+
36
+ **Guide consumption under dispatch.** Selecting each task's `guides` field IS the
37
+ consult trigger (`guides.md` §0) applied at plan-authoring time: the orchestrator
38
+ runs the router lookup (project router `ai_docs/reference/INDEX.md` + the agent-KB
39
+ router) when populating `guides`. A dispatched context-free subagent does **NOT**
40
+ run its own router consult — it reads the guide pointers handed to it in the
41
+ brief. The router verdict (Rule Zero) is therefore declared ONCE, by the
42
+ orchestrator, when it authors the plan; a dispatched subagent does not declare
43
+ one. (Proactive guide-creation stays at closure — the same broad final pass
44
+ below — so it needs no separate dispatch hook.)
45
+
46
+ ## Model tiers (client-relative, no provider names)
47
+
48
+ - Default dispatch: **economy** implementer tier.
49
+ - After **two** consecutive `verify_result: fail` on the same task: escalate
50
+ to the **deep** tier for the retry (ADR 2026-07-02). Do not escalate on the
51
+ first failure — a single fail is often a brief or environment issue, not a
52
+ capability gap.
53
+
54
+ ## Review slots — one-shot, not iterative
55
+
56
+ Exactly three review touches per task, never a loop:
57
+
58
+ 1. Inline self-review by the implementer subagent before it reports done
59
+ (the standard critical-review pass, not a separate call).
60
+ 2. One reviewer pass per task (Hybrid: reuse the devPNT code-review gate;
61
+ Standalone: the `review.md` discipline).
62
+ 3. One broad final pass over the whole plan at closure, after all tasks are
63
+ DONE — catches cross-task drift a per-task review cannot see.
64
+
65
+ If a review FAILs, fix and re-run `verify` — that is a normal loop iteration
66
+ via the ledger's fail path, not an extra review slot.
67
+
68
+ **The scoped re-review is a round INSIDE slot 2 or 3, never a fourth slot.**
69
+ `review.md` §Receiving requires every review-driven correction to be
70
+ re-reviewed against the correction alone that round belongs to the slot whose
71
+ findings caused it, and is bounded by the same cap of 3 rounds. "Never a loop"
72
+ bounds the SLOTS (no unbounded re-reviewing of a task), not the rounds inside
73
+ one slot: a fix nobody looked at is the defect the round exists to catch. Slot 1
74
+ is the implementer's own pass, not an independent review, so its fixes carry no
75
+ re-review round. The ledger records the round on the task's fail path as usual.
76
+
77
+ ## Ledger protocol summary
78
+
79
+ Read skip-if-done dispatch-if-pending write. The ledger is the only
80
+ memory the loop needs across sessions or context compaction: a resumed
81
+ orchestrator re-reads it and picks up exactly where it left off, never
82
+ re-running a DONE task.
83
+
84
+ ## Degradation
85
+
86
+ No subagent-spawning tool available the orchestrator runs each task in the
87
+ same session, against the same plan and ledger, with the same one-shot review
88
+ slots. No capability is lost, only the parallelism/isolation subagents would
89
+ have added.
90
+
91
+ ## Hybrid note
92
+
93
+ The plan's `derived-from` points at the accepted E-TDD document key. Per-task
94
+ review reuses the devPNT independent reviewers (§4.6 code review gate) rather
95
+ than restating review doctrine — see `review.md` for the single definition
96
+ both modes share.
@@ -43,16 +43,39 @@ corpus artifact on its own. Then `sha256:` is the extraction's digest — immuta
43
43
  enforced exactly as before — and the original is recorded, not copied:
44
44
 
45
45
  ```yaml
46
- sha256: <digest of this extraction> # enforced: the bytes locators address
47
- original_path: /vault/manuals/xyz.pdf # recorded: where the original lives
48
- original_sha256: <digest at ingest> # recorded, NOT verified — we do not hold it
46
+ sha256: <digest of this extraction>
47
+ original_path: /vault/manuals/xyz.pdf
48
+ original_sha256: <digest at ingest>
49
49
  ```
50
50
 
51
+ `sha256:` is the enforced one — the bytes the locators address. `original_path:` says
52
+ where the original lives and is checked to still resolve. `original_sha256:` is recorded
53
+ and never verified. **Write no trailing `# comment` on these lines**: the frontmatter
54
+ reader is a line regex and does not strip them, so the comment becomes part of the
55
+ value — which on `original_path:` now means a pointer that cannot resolve.
56
+
51
57
  Say the limit out loud, because a field that looks like a guarantee and is not is worse
52
- than an absent one: **`original_sha256` is never checked.** It lets a human re-verify by
53
- hand and it dates the ingest; it detects nothing on its own. The digest that bites is
54
- `sha256:`. Same honesty as a guide's `source_hash`, which proves the source is unchanged
55
- and never that the guide described it correctly.
58
+ than an absent one and say it **per field**, because the two are not in the same
59
+ position:
60
+
61
+ - **`original_sha256` is never checked.** We do not hold the bytes, so nothing can. It
62
+ lets a human re-verify by hand and it dates the ingest; it detects nothing on its own.
63
+ Same honesty as a guide's `source_hash`, which proves the source is unchanged and
64
+ never that the guide described it correctly.
65
+ - **`original_path` IS checked — for resolution only.** "We do not hold it" was never a
66
+ reason to leave the *pointer* unverified: it costs one `exists()`, and on a corpus
67
+ whose whole premise is that every provenance is a real file, a pointer that has gone
68
+ dangling in silence is the failure this design most wants to catch. A path that does
69
+ not resolve is a **warning**, never an error: a bundle carries artifacts and sidecars
70
+ and never the originals (`portability.md` §1), so after an import it dangles
71
+ legitimately. Absolute paths are tested as written; a relative one is tried against
72
+ the docs root's parent (the project root in the standard layout) and against the docs
73
+ root itself, and warns only if neither resolves. "Absolute" here means what either
74
+ platform calls rooted, `/vault/...` included: testing it with `Path.is_absolute()`
75
+ alone was a real defect, because on Windows a rooted path with no drive letter is not
76
+ absolute and got silently joined onto the docs root's drive.
77
+
78
+ The digest that bites is `sha256:`, on the bytes the locators actually address.
56
79
 
57
80
  ## 2. The claim — one falsifiable assertion
58
81
 
@@ -69,6 +92,15 @@ Claims live in the owning topic's `## Claims` table (`templates.md` has the temp
69
92
  - **id** — leave empty when writing by hand; `sdlc_check.py claim-id --fill <file>`
70
93
  computes it (`sha256(path#locator#qty)`, text excluded — a paraphrase must not mint a
71
94
  new identity). An empty id is a `[note]`, never an error.
95
+ **Its limit, stated as plainly as `original_sha256`'s:** excluding the text is what
96
+ makes the same assertion mint the same id in another project, which is what makes
97
+ `portability.md`'s de-duplication mechanical instead of a judgement call. The price is
98
+ that **one span cannot carry two different assertions at the same qty** — the id
99
+ cannot tell them apart, and the validator refuses the pair. The fix is to widen one
100
+ locator to the span that actually carries its assertion, or to merge the two rows.
101
+ It is **not** to edit the qty or nudge the locator until the hash differs: that
102
+ distorts the evidence to satisfy a hash function, which is the one repair this
103
+ ledger exists to prevent.
72
104
  - **valid** — `-`, `from X`, `until X`, `from X until Y`, `if <condition>`. Half-open:
73
105
  `until 2026-03-01` and `from 2026-03-01` do NOT overlap. A time-bounded fact is not a
74
106
  conflict with its successor — write the scope, or reconciliation will manufacture one.
@@ -124,14 +124,20 @@ A fix made in response to a finding is new, unreviewed work — stopping after
124
124
  therefore gets a **scoped re-review** before the review can PASS: hand the
125
125
  re-reviewer the original findings and ONLY the correction (the fix diff/range
126
126
  for code, the amended sections for a document), and require a per-finding
127
- verdict — `ADDRESSED`, `NOT ADDRESSED`, or `CONTESTED` with evidence. The
128
- re-review also checks the correction itself for new blocker-level
129
- breakage and nothing else: out-of-scope observations become separately
127
+ verdict — `ADDRESSED`, `NOT ADDRESSED`, or `CONTESTED` with evidence. **A PASS
128
+ that carried findings is provisional until its corrections pass that round** —
129
+ the commonest real case is a PASS with non-blocking findings the author then
130
+ fixes, and stopping there ships precisely the unreviewed version this rule
131
+ exists to catch. The re-review also checks the correction itself for new
132
+ blocker-level breakage — and nothing else: out-of-scope observations become separately
130
133
  recorded findings, never an extension of the loop. Expect two rounds as the
131
134
  norm, not the exception — round 1 finds, round 2 verifies the fixes — inside
132
135
  the same cap of 3 (§When a review is due). One logical review stays ONE
133
- REVIEW_LOG row, its rounds narrated inside; a scoped re-review is a round,
134
- not a new review.
136
+ REVIEW_LOG row a scoped re-review is a round, not a new review — with the
137
+ rounds narrated in the row's notes and **the verdict column carrying the
138
+ round-1 verdict and the final one (`FAIL → PASS`), never the final one alone**:
139
+ a first-round FAIL is the highest-value evidence the gate produces (§When a
140
+ review is due), and collapsing it into a bare `PASS` erases exactly that.
135
141
 
136
142
  ## Reviewing
137
143
 
@@ -145,6 +151,15 @@ When you are the reviewer:
145
151
  (see `## Requesting`).
146
152
  - Cite evidence as `file:line` for every finding — a finding without a
147
153
  location is not actionable.
154
+ - **An unproven completion claim is a finding** (closure reviews, on the diff).
155
+ When the work under review states or implies that something passes, is fixed,
156
+ is clean or is complete, the evidence must be present and must post-date the
157
+ final relevant change; a claim resting on a stale run, on a narrower check
158
+ than the claim needs, or on a delegated agent's own report rather than the
159
+ diff, is a finding — name the claim and what would prove it. This is the
160
+ enforcement point of the author-side rule in `SKILL.md` §5 Closure, and the
161
+ reason a requester hands it over is that the reviewer cannot cite a rule it
162
+ was never given.
148
163
  - **Say what you could NOT verify.** When a claim in the artifact cannot be
149
164
  verified from the inputs you were given (it lives in unchanged code, another
150
165
  document, or an environment you cannot reach), report it as a
@@ -31,6 +31,8 @@ Pure stdlib. ASCII output only (Windows-console safe).
31
31
  """
32
32
  import argparse
33
33
  import hashlib
34
+ import ntpath
35
+ import posixpath
34
36
  import re
35
37
  import sys
36
38
  from pathlib import Path
@@ -272,10 +274,94 @@ def kb_fill_ids(text):
272
274
 
273
275
 
274
276
  def _note_frontmatter(root, rel):
275
- p = root / rel
276
- if not p.is_file():
277
- return None
278
- return sdlc_core.load_frontmatter(sdlc_core.read_text(p).splitlines()) or {}
277
+ """The frontmatter that DECLARES the cited file, and the path it came from.
278
+
279
+ A corpus artifact carries none of its own: `given/x.txt` is bytes, and what
280
+ says how those bytes were obtained is the `x.txt.meta.md` sidecar beside it.
281
+ Resolving the cited path alone returned `{}` for such a file -- which the
282
+ caller cannot tell apart from "resolved, field absent" -- and that made
283
+ DERIVED, RULING and IMPORTED impossible for every claim citing `given/`.
284
+
285
+ Sidecar FIRST when one exists, the cited file otherwise. Not "non-.md ->
286
+ sidecar": a verbatim `.md` source stored in `given/` is declared by its
287
+ sidecar exactly like a `.txt` extraction, and reading its own frontmatter
288
+ would read the SOURCE's, which says nothing about how it was extracted. A
289
+ `corpus/notes/*.md` note has no sidecar and still resolves to its own.
290
+
291
+ Returns `(meta, label)`. The label names the file actually read, so a
292
+ finding can say where it looked instead of sending the reader to the wrong
293
+ file -- the same defect this release fixes in the duplicate-id message.
294
+ """
295
+ base = sdlc_core.confine_under(root, rel)
296
+ if base is None or base == root.resolve():
297
+ # None: the path escapes the docs root, and the source loop said so.
298
+ # Equal to the root: an empty or dot-only path cell (a source written
299
+ # `#L1-2`, with no file before the locator). Forming a sidecar name
300
+ # from that would step back OUT of the root through `base.parent` and
301
+ # read `<docs-root>.meta.md` -- outside the tree this helper confines.
302
+ return None, rel
303
+ if not base.is_file():
304
+ # The cited file is gone. Its sidecar may still be lying there, but a
305
+ # claim on a missing artifact is already an error from the source loop,
306
+ # and answering from an orphan sidecar would add a second finding about
307
+ # a file that is not there.
308
+ return None, rel
309
+ side = base.parent / (base.name + ".meta.md")
310
+ if side.is_file():
311
+ return (sdlc_core.load_frontmatter(
312
+ sdlc_core.read_text(side).splitlines()) or {}, rel + ".meta.md")
313
+ return (sdlc_core.load_frontmatter(
314
+ sdlc_core.read_text(base).splitlines()) or {}, rel)
315
+
316
+
317
+ def _kb_pointer_resolves(p):
318
+ """A recorded pointer resolves when it names a FILE we can stat.
319
+
320
+ `is_file()` and not `exists()`: `original_path` names a document, so a
321
+ directory that happens to sit at that path is not the original. Any OSError
322
+ is "does not resolve" and never a traceback -- the field points OUTSIDE the
323
+ docs root by design, so the validator has to survive whatever lives there
324
+ (an unreadable parent on a network vault, a name too long, a reparse point).
325
+ """
326
+ try:
327
+ return p.is_file()
328
+ except OSError:
329
+ return False
330
+
331
+
332
+ def _kb_original_candidates(op, root):
333
+ """Every place a recorded `original_path` could legitimately be, in order.
334
+
335
+ `Path.is_absolute()` is NOT the test, and using it was a real defect: on
336
+ Windows a rooted-but-driveless path -- `/vault/manuals/xyz.pdf`, the exact
337
+ form this project's own templates print -- is not absolute, so it was
338
+ joined under the docs root and silently rewritten onto the docs root's
339
+ DRIVE. That produced a warning quoting a path nobody wrote, and could hide
340
+ a genuinely dangling pointer behind a file that happened to exist there.
341
+ Anything EITHER platform calls rooted is now taken as written; only a
342
+ genuinely relative pointer is joined.
343
+
344
+ A relative pointer is tried against the docs root's parent (the project
345
+ root in the standard layout) and against the docs root itself, because
346
+ `--root` and `migrate` both allow a docs root that does not sit directly
347
+ under the project root.
348
+ """
349
+ raw = (op or "").strip()
350
+ forms = [raw]
351
+ if "\\" in raw:
352
+ # A path authored on Windows and read anywhere. Tried as a SECOND form,
353
+ # never instead of the first: a backslash is a legal character in a
354
+ # POSIX filename, and rewriting it unconditionally invented a path.
355
+ forms.append(raw.replace("\\", "/"))
356
+ out, seen = [], set()
357
+ for f in forms:
358
+ cands = ([Path(f)] if (ntpath.isabs(f) or posixpath.isabs(f))
359
+ else [root.parent / f, root / f])
360
+ for c in cands:
361
+ if str(c) not in seen:
362
+ seen.add(str(c))
363
+ out.append(c)
364
+ return out
279
365
 
280
366
 
281
367
  def kb_check_claims(root):
@@ -287,6 +373,15 @@ def kb_check_claims(root):
287
373
  return errors, warnings, notes
288
374
  all_ids = {} # id -> "file:line"
289
375
  all_rows = {} # id -> (row, rel)
376
+ # Frontmatter is now resolved for every row, GIVEN included, so a ledger
377
+ # citing one artifact from 80 rows would otherwise stat and decode that
378
+ # artifact's sidecar 80 times. Keyed by the cited path, per run.
379
+ fm_cache = {}
380
+
381
+ def _declaring_frontmatter(rel):
382
+ if rel not in fm_cache:
383
+ fm_cache[rel] = _note_frontmatter(root, rel)
384
+ return fm_cache[rel]
290
385
  per_file_rows = []
291
386
  for p in sorted(topics.glob("*.md")):
292
387
  rel = "topics/" + p.name
@@ -320,25 +415,46 @@ def kb_check_claims(root):
320
415
  kb_check_locator(target, loc, where, errors)
321
416
  # --- provenance ---
322
417
  prov = row["prov"]
418
+ # Resolved ONCE, for every provenance: the non-GIVEN classes read it
419
+ # for their required field, and GIVEN reads it to notice that the
420
+ # artifact declares a weaker chain than the row claims.
421
+ meta, meta_where = (None, "")
422
+ if "#" in first:
423
+ meta, meta_where = _declaring_frontmatter(first.rsplit("#", 1)[0])
323
424
  if prov not in PROVENANCES:
324
425
  errors.append("%s: prov %r not in %s" % (where, prov, "/".join(PROVENANCES)))
325
426
  elif prov in ("DERIVED", "RULING", "ELICITED", "IMPORTED"):
326
- meta = _note_frontmatter(root, first.rsplit("#", 1)[0])
327
427
  if meta is None:
328
428
  pass # unresolvable source already reported
329
429
  elif prov == "DERIVED" and not meta.get("derived_from"):
330
- errors.append("%s: DERIVED claim's note carries no 'derived_from:' "
331
- "— model knowledge disguised as a source" % where)
430
+ errors.append("%s: DERIVED claim's source (%s) carries no "
431
+ "'derived_from:' — model knowledge disguised as "
432
+ "a source" % (where, meta_where))
332
433
  elif prov == "RULING" and not meta.get("basis"):
333
- errors.append("%s: RULING note carries no 'basis:' — a preference "
334
- "is not a fact; no basis, no ruling" % where)
434
+ errors.append("%s: RULING source (%s) carries no 'basis:' — a "
435
+ "preference is not a fact; no basis, no ruling"
436
+ % (where, meta_where))
335
437
  elif prov == "IMPORTED" and not meta.get("imported_from"):
336
438
  # F-030: IMPORTED exists so a foreign decision cannot pass for
337
439
  # a local one. Without the origin the class says nothing and
338
440
  # the row is a RULING with the label filed off.
339
- errors.append("%s: IMPORTED note carries no 'imported_from:' — "
340
- "the class exists to name whose decision this "
341
- "was; unnamed, it is a RULING in disguise" % where)
441
+ errors.append("%s: IMPORTED source (%s) carries no "
442
+ "'imported_from:' — the class exists to name "
443
+ "whose decision this was; unnamed, it is a "
444
+ "RULING in disguise" % (where, meta_where))
445
+ elif prov == "GIVEN":
446
+ # F-035: a row resting on an OCR, a transcription or a
447
+ # translation is not the same evidence as one resting on a
448
+ # deterministic text layer. The sidecar could say so only in
449
+ # prose, and prose is not a check -- which is how three rows
450
+ # whose evidence was a reading of an image passed as GIVEN.
451
+ declared = ((meta or {}).get("provenance") or "").strip()
452
+ if declared and declared.upper() != "GIVEN":
453
+ warnings.append(
454
+ "%s: prov GIVEN, but %s declares 'provenance: %s' — the "
455
+ "row reads as first-hand evidence and its artifact does "
456
+ "not. File the row at the provenance the chain actually "
457
+ "has, or correct the sidecar" % (where, meta_where, declared))
342
458
  # --- grammar cells ---
343
459
  try:
344
460
  kb_parse_scope(row["valid"])
@@ -368,9 +484,49 @@ def kb_check_claims(root):
368
484
  "moved silently" % (where, row["id"], expect))
369
485
  if row["id"]:
370
486
  if row["id"] in all_ids:
371
- errors.append("%s: duplicate id %s (also at %s) uniqueness "
372
- "is global across topics/" % (where, row["id"],
373
- all_ids[row["id"]]))
487
+ # F-035: one message served two different defects. Same id
488
+ # with the SAME text is a copied row. Same id with DIFFERENT
489
+ # text is a collision: kb_claim_id hashes path#locator#qty
490
+ # and excludes the text on purpose, so two distinct
491
+ # assertions about one span cannot be told apart. The source
492
+ # and qty do NOT discriminate -- the id already implies them.
493
+ prev_row = all_rows[row["id"]][0]
494
+
495
+ def _cell(r, k):
496
+ return (r[k] or "").strip()
497
+
498
+ def _first_src(r):
499
+ return (r["source"] or "").split(";")[0].strip()
500
+
501
+ same_text = _cell(prev_row, "claim") == _cell(row, "claim")
502
+ same_span = (_first_src(prev_row) == _first_src(row)
503
+ and _cell(prev_row, "qty") == _cell(row, "qty"))
504
+ if same_text:
505
+ errors.append("%s: duplicate id %s (also at %s) — uniqueness "
506
+ "is global across topics/" % (where, row["id"],
507
+ all_ids[row["id"]]))
508
+ elif same_span:
509
+ errors.append(
510
+ "%s: id %s collides with the row at %s — two "
511
+ "DIFFERENT rows cite the same span with the same "
512
+ "qty, and the id function cannot separate them (it "
513
+ "hashes path#locator#qty and excludes the text on "
514
+ "purpose). Widen one locator to the span that "
515
+ "actually carries its assertion, or merge the two "
516
+ "rows — do not edit the qty to break the tie"
517
+ % (where, row["id"], all_ids[row["id"]]))
518
+ else:
519
+ # Same id, different text AND different span: the id
520
+ # cannot have been computed from both rows, so it was
521
+ # hand-typed or left stale after a source was repointed.
522
+ # Saying "they cite the same span" here would name a
523
+ # cause that is provably not this one.
524
+ errors.append(
525
+ "%s: id %s is also on the row at %s, which cites a "
526
+ "different span — the id was not computed from this "
527
+ "row (hand-typed, or left stale after its source "
528
+ "moved). Run 'sdlc_check.py claim-id --fill %s'"
529
+ % (where, row["id"], all_ids[row["id"]], rel))
374
530
  else:
375
531
  all_ids[row["id"]] = where
376
532
  all_rows[row["id"]] = (row, rel)
@@ -889,6 +1045,24 @@ def kb_corpus_check(root):
889
1045
  errors.append("%s: raw-byte digest changed since ingest — "
890
1046
  "given/ is never edited; this is the check, "
891
1047
  "not a convention" % rel)
1048
+ # F-035: `original_sha256` is not verified because we do not hold
1049
+ # the bytes -- a limit stated out loud in distillation.md. That
1050
+ # reason does not extend to the PATH, which costs one exists(). A
1051
+ # corpus whose premise is "every provenance is a real file" cannot
1052
+ # let 16 sidecars go dangling behind a green run.
1053
+ op = (meta.get("original_path") or "").strip()
1054
+ if op:
1055
+ cands = _kb_original_candidates(op, root)
1056
+ tried = [str(c) for c in cands]
1057
+ resolved = any(_kb_pointer_resolves(c) for c in cands)
1058
+ if not resolved:
1059
+ warnings.append(
1060
+ "%s: original_path %r does not resolve (tried %s) — the "
1061
+ "extraction is intact, the pointer to the original is "
1062
+ "not. A warning and not an error: a bundle carries "
1063
+ "artifacts and sidecars, never the originals, so after "
1064
+ "an import this dangles legitimately"
1065
+ % (rel, op, ", ".join(tried)))
892
1066
  sup = (meta.get("supersedes") or "").strip()
893
1067
  if sup:
894
1068
  superseded.add(sup)
@@ -447,8 +447,8 @@ adds values to the existing columns rather than a second table.
447
447
 
448
448
  | date | doc_key | tier | reviewer | findings_raised | findings_real | verdict | revise_rounds |
449
449
  |---|---|---|---|---|---|---|---|
450
- | 2026-06-11 | ANALYSIS_login_sso.md | design | subagent (opus, fresh ctx) | 4 | 3 | PASS | 2 |
451
- | 2026-06-12 | diff feature/sso-login | closure | self-pass (declared; no subagent facility) | 2 | 2 | PASS | 1 |
450
+ | 2026-06-11 | ANALYSIS_login_sso.md | design | subagent (opus, fresh ctx) | 4 | 3 | FAIL → PASS | 2 |
451
+ | 2026-06-12 | diff feature/sso-login | closure | self-pass (declared; no subagent facility) | 2 | 2 | PASS with findings → corrections re-reviewed, PASS | 2 |
452
452
 
453
453
  ## Notes
454
454
  <!-- One short paragraph per review that found something worth remembering: what
@@ -467,6 +467,13 @@ records the realization actually used — fresh subagent, one-shot client run, o
467
467
  honest; writing nothing, or implying independence you did not have, is the failure
468
468
  this column exists to prevent. `findings_real` is how many raised findings survived
469
469
  triage: over time it is the only evidence of whether the gate earns its cost.
470
+ `revise_rounds` counts **review rounds**, not fix cycles: the first review is round 1
471
+ and every scoped re-review adds one. A review that produced findings which were then
472
+ corrected therefore always reads ≥ 2 — the corrections are unreviewed work until a
473
+ round verifies them (`review.md` §Receiving) — and 3 is the ceiling, past which the
474
+ residue goes to the user rather than into a fourth round. `verdict` carries the
475
+ round-1 verdict and the final one when they differ (`FAIL → PASS`): collapsing a
476
+ first-round FAIL into a bare `PASS` erases the evidence this log exists to keep.
470
477
  Concurrent reviews: `init` writes a `.gitattributes` stanza giving this file
471
478
  `merge=union` — a **built-in** driver (no per-clone `git config`, unlike
472
479
  `merge=ours`, which silently does nothing until every clone configures it).
@@ -661,8 +668,34 @@ original_sha256: <digest at ingest — RECORDED, never checked: we do not hold t
661
668
  ---
662
669
  ```
663
670
 
664
- Keep `original_sha256`'s limit visible wherever it is written: it lets a human
665
- re-verify by hand and it dates the ingest, and it detects nothing on its own.
671
+ The two fields are **not** in the same position, and writing them as if they were is
672
+ how a dangling pointer survives a green run:
673
+
674
+ - `original_sha256` detects nothing on its own — we do not hold the bytes. It lets a
675
+ human re-verify by hand and it dates the ingest. Keep that limit visible wherever the
676
+ field is written.
677
+ - `original_path` **is** checked for resolution, and warns when it does not resolve
678
+ (never errors — an imported bundle carries no originals). **Write it absolute, or
679
+ relative to the project root**; the validator tries the docs root's parent first and
680
+ the docs root second, and names both in the warning. The convention was implicit
681
+ until F-035 and is stated here because a pointer nobody can resolve is worth less
682
+ than no pointer at all.
683
+
684
+ Write these two values with **no trailing `# comment`**: the frontmatter reader does
685
+ not strip inline comments, so the comment lands inside the value.
686
+
687
+ `provenance:` has a consumer too: a claim row filed `prov: GIVEN` whose artifact's
688
+ sidecar declares anything else warns. Declare the chain the artifact actually has —
689
+ an OCR, a transcription from an image, a translation are not first-hand evidence, and
690
+ saying so in the sidecar's prose is not a check.
691
+
692
+ Two limits of that warning, stated so it is not mistaken for more than it is. It reads
693
+ the row's **first** source, so a weak artifact cited second is not compared. And it can
694
+ only read the **field**: a sidecar that says `provenance: GIVEN` while its prose says
695
+ "transcribed from a photograph" is silent, because prose is not machine-readable — the
696
+ warning catches the author who declared the chain honestly and then filed the row too
697
+ strongly, never the author who declared it wrongly. Nothing requires a `given/` sidecar
698
+ to carry `provenance:` at all; adding that requirement is a new gate, not this one.
666
699
 
667
700
  ## ai_docs/corpus/notes/RULING_[topic]_[date].md (practitioner ruling)
668
701