@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 +97 -0
- package/README.md +1 -1
- package/gemini-extension.json +6 -6
- package/package.json +51 -51
- package/skills/kb-agentic-skill/SKILL.md +148 -148
- package/skills/kb-agentic-skill/dispatch.md +96 -87
- package/skills/kb-agentic-skill/distillation.md +39 -7
- package/skills/kb-agentic-skill/review.md +20 -5
- package/skills/kb-agentic-skill/scripts/sdlc_check.py +189 -15
- package/skills/kb-agentic-skill/templates.md +37 -4
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
|
|
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
|
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "kb-agentic-skill",
|
|
3
|
-
"version": "1.4.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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>
|
|
47
|
-
original_path: /vault/manuals/xyz.pdf
|
|
48
|
-
original_sha256: <digest at ingest>
|
|
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
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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.
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
134
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
|
331
|
-
"— model knowledge disguised as
|
|
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
|
|
334
|
-
"is not a fact; no basis, no ruling"
|
|
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
|
|
340
|
-
"the class exists to name
|
|
341
|
-
"was; unnamed, it is a
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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 |
|
|
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
|
-
|
|
665
|
-
|
|
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
|
|