@antoneeo/kb-agentic-skill 1.31.0 → 1.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,7 +39,8 @@ sdlc_check.py check # always, right after
39
39
  and, if a topic was skipped, *"a topic with the same name already exists; I have not
40
40
  merged them, because they may not be the same subject. Do you want me to look?"*
41
41
  4. If an imported ruling arrived: *"one decision came from the other project. I have kept
42
- it, marked as theirs, and it cannot settle anything here until you confirm it."*
42
+ it, marked as theirs, and it cannot settle anything here until you confirm it. The
43
+ question it had closed is open again here, with their decision beside the sources."*
43
44
 
44
45
  Do not explain the closure, the id hashing or the provenance model unless asked. The
45
46
  user asked to move their knowledge, not to learn how it is stored.
@@ -74,6 +75,11 @@ You choose topics; the export decides what must travel with them.
74
75
  When a partner row lives in an unselected topic, that topic is **added and reported** —
75
76
  never dropped, never silently.
76
77
  - **Not the `not-yet-honored-by` markers.** They describe this project's code, so the export drops them and says so, without a number.
78
+ - **Not the declarations.** A `DECLARED` row is this project's commitment to its own
79
+ recipient; carried elsewhere it would read as the importer's. The export leaves the
80
+ rows and the artifacts only they cite behind and says so in one line, without a
81
+ number. Nothing else is lost: no other row may point at a declaration, and the export
82
+ refuses one that does (run `check`).
77
83
 
78
84
  If a conflict partner resolves to no row at all, the export **refuses**: exporting a
79
85
  broken set is worse than exporting nothing.
@@ -83,7 +89,8 @@ tombstone sidecar stays home.
83
89
 
84
90
  ## 3. Import is additive, and decides nothing
85
91
 
86
- `import` writes files. It does not place concepts, merge bodies, or settle disagreements.
92
+ `import` writes files. It does not place concepts, merge bodies, or settle disagreements —
93
+ and it refuses to let the bundle settle them either: a ruling arrives foreign (§4).
87
94
 
88
95
  - **Never overwrites a topic.** A slug that already exists is reported and skipped: two
89
96
  projects using the word `pricing` may mean two different things, and that judgement is
@@ -96,7 +103,12 @@ tombstone sidecar stays home.
96
103
  It refuses on: a missing or unmarked `MANIFEST.md`; a path that escapes the docs root; an
97
104
  artifact whose name matches an existing one **with different bytes** (content-addressed
98
105
  names must mean equal content — a mismatch means one of the two is lying about its
99
- origin); an incomplete conflict set; a dangling `supersedes:`.
106
+ origin); an incomplete conflict set — a `CONTESTED` or `SUPERSEDED` pointer that would
107
+ resolve to no row, or to a row of this project that does not name it back (an import
108
+ never edits an existing topic); a row settled by a ruling the import does not write, or
109
+ by a row this project holds as `IMPORTED`; a ruling it cannot carry foreign (§4); a
110
+ `DECLARED` row (an export never ships one, so the bundle was edited by hand). Each
111
+ refusal names the row and writes nothing.
100
112
 
101
113
  After importing, run `check`. The import is deliberately not a validator.
102
114
 
@@ -108,19 +120,43 @@ another owner's decision, and importing it unchanged would make that decision bi
108
120
  here without anyone here granting it — the machine deciding, which reconciliation
109
121
  refuses everywhere else.
110
122
 
111
- So an imported ruling arrives as **`prov: IMPORTED`** (owner ruling, 2026-08-03):
112
-
113
- - its text, span and original `basis:` travel **verbatim** — the knowledge is not lost,
114
- and pretending it is a `DERIVED` synthesis would make the row lie about where it came
115
- from;
116
- - its note carries `imported_from:`, and the validator refuses an `IMPORTED` row without
117
- it: unnamed, the class says nothing and the row is a `RULING` with the label filed off;
118
- - **it cannot supersede a local row.** The validator errors on `SUPERSEDED <id>` when
119
- that id is `IMPORTED`.
120
-
121
- **Re-ratification** is one act: read it, write your own note with your own `basis:`, and
122
- set the row's `prov` to `RULING`. That is you deciding, which is the only thing that ever
123
- settles anything here.
123
+ So `import` makes every `RULING` row of a topic it writes arrive as **`prov: IMPORTED`**
124
+ (owner rulings 2026-08-03 and 2026-10-23):
125
+
126
+ - **its note arrives as a renamed copy**, `corpus/notes/imported_<source project>_<name>`,
127
+ whose frontmatter adds `imported_from: <source project> — <original path>` and reads
128
+ `origin: imported`. Its text and original `basis:` travel **verbatim** — the knowledge
129
+ is not lost, and pretending it is a `DERIVED` synthesis would make the row lie about
130
+ where it came from. The note is never edited under its own name: a line inserted into
131
+ it would move the line every locator cites. The row cites the copy at the same text
132
+ (its locator and id move with it), and a bundle note `derived_from:` the ruling names
133
+ the copy. The original name is never written, so a row copied by hand out of a skipped
134
+ topic cannot cite a foreign note as local;
135
+ - **the question it had closed reopens.** The rows it superseded and the ruling itself
136
+ arrive as one `CONTESTED` set; a ruling already contested with a newer source keeps
137
+ that partner, one that superseded nothing arrives `OK`;
138
+ - the validator refuses an `IMPORTED` row whose note lacks `imported_from:` (unnamed,
139
+ the class says nothing), a `SUPERSEDED <id>` pointing at an `IMPORTED` row, and a
140
+ `RULING` row on a note that names a foreign origin.
141
+
142
+ **Ratification** is the ordinary ruling of `reconciliation.md`, and it is you deciding,
143
+ which is the only thing that ever settles anything here: write your own note with your
144
+ own `basis:`, add a `RULING` row on it, and mark every row `CONTESTED` with the imported
145
+ one — following the sets transitively — `SUPERSEDED <your id>`, the `IMPORTED` row
146
+ included. Relabelling the `IMPORTED` row `RULING` is refused: another project's note
147
+ cannot carry your decision.
148
+
149
+ Any bundle note that declares a `basis:` lands this way, renamed, even when its ruling row
150
+ does not travel with it — otherwise it would land first under its own name and a later
151
+ import of its ruling would find it there. A ruling `import` cannot carry this way is
152
+ refused, never copied: one not resting on a note cited by line, on a note with no
153
+ frontmatter, or on a note that already names a foreign origin (the source relabelled an
154
+ imported row); a row superseded both by a ruling and by a row that is not one; a ruling
155
+ another row superseded while rows still depend on it. A tree imported before this
156
+ conversion existed (≤ kb 1.31.0) holds the foreign ruling as `RULING` on its original
157
+ note: delete the imported topic and its files and import again, or convert it by hand as
158
+ above. The origin is named after the exporting folder (`source_project`), so two clones
159
+ of one project in differently named folders name it differently.
124
160
 
125
161
  ## 5. Triage
126
162
 
@@ -24,6 +24,15 @@ Subject-sameness is semantic judgement; no query performs it. Classify:
24
24
  | **coexistence** | contradictory only if scopes overlapped — and by the half-open rule they do not | both rows stay `OK`. "12k until March" and "15k from March" are two truths, not a conflict |
25
25
  | **conflict** | contradictory, scopes overlap | **every** row in the set becomes `CONTESTED` listing all counterparts. Nothing is picked |
26
26
 
27
+ **A declaration is not a source.** A `DECLARED` row (`distillation.md` §1: what this
28
+ project sent) never joins the five outcomes with a fact: it never corroborates a row,
29
+ never refines or supersedes one, never contests one. Against the ledger it is either
30
+ consistent (`OK`) or it **diverges**: its assertion contradicts a non-superseded,
31
+ non-DECLARED row, scopes overlapping → the DECLARED row alone becomes
32
+ `DIVERGES <id>[, ..]`; the ledger row is not touched, because our own wrong statement
33
+ puts nothing about the product in doubt. Two declarations that contradict each other
34
+ are `CONTESTED` — both are still held by their recipients.
35
+
27
36
  The machine then verifies (the `graph` check): ids recompute, sources resolve and their
28
37
  spans exist, `CONTESTED` is **symmetric** (editing one cell to `OK` fails the check —
29
38
  the cheapest laundering), a `CONTESTED` pointer at a `SUPERSEDED` row is an error,
@@ -73,6 +82,9 @@ premise: signed amendments arrive later.
73
82
  | newer source vs a ruling | new set {new row, ruling row}; escalation shows the `basis:` |
74
83
  | member refined | the refining row inherits the member's contested relations (re-judged); counterparts rewritten to the new id |
75
84
  | corroboration of a contested member | source appended; the set untouched — more evidence is not new information, and the escalation form shows source counts |
85
+ | a declaration contradicts the ledger | the DECLARED row → `DIVERGES <ledger ids>`; the ledger rows stay as they are; escalation names it |
86
+ | a new send corrects it | the new DECLARED row; the divergent one → `SUPERSEDED <new-id>` (a declaration is superseded only by a later declaration) |
87
+ | a ruling changes the decision | the ledger row → `SUPERSEDED <ruling-id>`; `check` then refuses the stale `DIVERGES` until the declaration is re-judged against the ruling: `OK` if it now agrees, `DIVERGES <ruling-id>` if not |
76
88
  | bundled row split (`distillation.md` §3) | N atomic rows `OK`, each on its narrowed span; the bundle → `SUPERSEDED <id1>[, <id2> ..]`, text intact; contested relations re-judged per successor |
77
89
 
78
90
  A superseded row keeps its `not-yet-honored-by` marker with its text. Copy the marker to each successor that still states behavior the code does not have; the analysis or shadow citing the predecessor's id satisfies the successor.
@@ -103,7 +115,11 @@ record like any claim-table edit.
103
115
  Escalations are presented **once, at the end of a run** — the pipeline never stops
104
116
  mid-ingest to ask. Each escalation names: the claims in the set, each one's source
105
117
  (reopenable), date and provenance, and one line on why the machine cannot decide
106
- ("same subject, overlapping validity, GIVEN vs GIVEN, no newer source"). **A question
118
+ ("same subject, overlapping validity, GIVEN vs GIVEN, no newer source"). A divergence
119
+ is escalated the same way, naming the recipient and the day it was sent: the
120
+ practitioner corrects it with a new send or changes the decision. One limit stated
121
+ out loud: `DIVERGES` is one-way, so unlike `CONTESTED` a hand edit back to `OK` has
122
+ no counterpart to fail on — the diff review is what catches it. **A question
107
123
  that cannot fill this form is not askable** — it is the symptom that the answer is in
108
124
  the corpus and was not searched. Generic confirmations are not questions.
109
125
 
@@ -122,10 +138,15 @@ An `IMPORTED` row is a ruling made by the owner of a different project
122
138
 
123
139
  - It keeps its text, its span and its original `basis:` verbatim — the knowledge is not
124
140
  lost, and relabelling it `DERIVED` would make the row lie about its origin.
125
- - Its note must carry `imported_from:`. Unnamed, the class says nothing and the row is a
126
- `RULING` with the label filed off; the validator refuses it.
127
- - **It cannot settle anything here.** `SUPERSEDED <id>` pointing at an `IMPORTED` row is
128
- an error: nobody in this project granted that decision its authority.
129
-
130
- Re-ratification is one act, and it is you deciding: read it, write your own note with
131
- your own `basis:`, set `prov` to `RULING`.
141
+ - It rests on a renamed copy of its note (`imported_<project>_<name>`) that carries
142
+ `imported_from:`. Unnamed, the class says nothing and the row is a `RULING` with the
143
+ label filed off; the validator refuses it.
144
+ - **It cannot settle anything here.** `import` reopens the set it had settled: its former
145
+ members and the ruling arrive `CONTESTED` with each other. `SUPERSEDED <id>` pointing at
146
+ an `IMPORTED` row is an error: nobody in this project granted that decision its
147
+ authority.
148
+
149
+ Ratification is the ruling procedure above, and it is you deciding: your own note with
150
+ your own `basis:`, a `RULING` row on it, and every row `CONTESTED` with the imported one —
151
+ transitively, when it sits in more than one set — `SUPERSEDED <your id>`, the imported row
152
+ included. A `RULING` on a note that names a foreign origin is refused.
@@ -187,7 +187,10 @@ can block forever gets removed. **Log one row per completed review, PASS or FAIL
187
187
  logging only passes would erase exactly that evidence. The row goes in
188
188
  `ai_docs/audit/reviews/REVIEW_LOG.md` (create it if absent — `templates.md`):
189
189
  `| date | doc_key | tier | model | reviewer | findings_raised | findings_real | verdict | revise_rounds |`,
190
- with `tier` = `design` or `closure` in Standalone. **`model` records the capability
190
+ with `tier` = `design` or `closure` in Standalone. In the code lens, a wrong behaviour
191
+ the owner reports in a merged L3 change is one row too, tier `post-merge`, `model`
192
+ `none (owner report)`, reviewer `owner`, the owner quoted in the notes: a defect
193
+ record, not a review, so `benefit` keeps it out of every review figure. **`model` records the capability
191
194
  tier that actually ran** — `deep`, `light`, `economy`, `single (client exposes no
192
195
  choice)`, `below floor: <reason>`, or `none (not due)` on a NOT DUE row — never a provider name in doctrine (a project
193
196
  may of course write its own model names in its own log). Without it the floor above
@@ -195,6 +198,34 @@ is a belief: the row would say a gate ran and never whether it could do its job,
195
198
  the correlation with `revise_rounds` and `findings_real` — the only evidence the
196
199
  policy pays — could not be drawn.
197
200
 
201
+ **The owner approves the design before the work starts (L3).** The reviewer judges
202
+ whether the design holds and serves the Vision; only the owner can say whether it is
203
+ what they meant beyond what the Vision wrote, and an intent error costs least before
204
+ the work exists. So the final design PASS — final once any scoped re-review it owed
205
+ has passed (§Review-driven corrections) — does not start the work: the agent
206
+ presents the design and asks the owner whether to proceed, in the form
207
+ `elicitation.md` §The form of a question gives a reserved approval. The presentation
208
+ is a short summary — what the change does (the Functional Spec, or the lens's
209
+ equivalent), for whom and through which surfaces (use cases, contract), what it
210
+ touches (Impact), the threats and their mitigations, the review's verdict with any
211
+ open findings — and the path of the full design. At the round cap, the hand-over the
212
+ cap prescribes, followed by this one question, takes the place of the form, and the
213
+ owner's answer settles both. An approval is recorded verbatim in the request record
214
+ under a label that begins `design approved`; a no or a correction takes its own
215
+ label, and the design goes back — where the revision changes what the review judged,
216
+ moment 1 runs again on that part first (the delta rule above). Only after the
217
+ approval does the ANALYSIS go `PLANNED` → `IN_PROGRESS` and the work start: code,
218
+ drafting or production, by lens. An instruction given before the design was shown
219
+ ("carry on", "do the whole unit") does not approve it. Where a lens's own phases
220
+ already have the owner approve the design — the marketing lens's strategy USER GATE
221
+ — that gate is this approval, never a second question, and the lens's own unattended
222
+ path applies. Otherwise, unattended, the unit is `BLOCKED on the user`
223
+ (`elicitation.md` Unattended path), never approved by assumption; a late arrival
224
+ (moment 1b) asks before any further work. Hybrid: the owner's approval of the
225
+ `E-ISP`/`E-TDD` proposal in devPNT, under devPNT's own authorization rules, is this
226
+ approval. In Standalone, `validate` notes an L3 ANALYSIS in implementation, started
227
+ on or after 2026-10-31, whose request record holds no `design approved` entry.
228
+
198
229
  **One schema for both modes**, meaning one required CORE plus each mode's own
199
230
  **mode-specific** realization columns. The core is what both modes actually share: `date`, `doc_key`,
200
231
  `tier`, `model`, `findings_raised`, `findings_real`, `verdict`, `revise_rounds`.
@@ -285,6 +316,11 @@ The binding inputs, by review type:
285
316
  clause reads both), and the **probe harness** (`ai_docs/solutions/harness_[feature]/`).
286
317
  Hand these *in addition to* the design artifact — the reviewer checks the artifact
287
318
  **against** them, not only for internal consistency.
319
+ - **The constraint view comes last.** Where the ANALYSIS carries ids, the reviewer
320
+ derives the callers and behaviours first, then compares with
321
+ `sdlc_check.py constraints --unit <id>`: a constraint the design states and the
322
+ diff breaks is a finding. The view never replaces the ANALYSIS or the diff in the
323
+ request.
288
324
  - **For a closure review, the diff and the test evidence**: the run output that
289
325
  backs every completion claim, dated after the final relevant change.
290
326
  - **For a design or closure review of an L3/E3 unit, the request record**
@@ -615,6 +615,11 @@ class Grounds:
615
615
  return [{"kind": "untraceable", "path": rel}]
616
616
  seen = seen | {rel}
617
617
  if rel.startswith("corpus/given/"):
618
+ # F-084: a document this project SENT is our own declaration, not
619
+ # ground for a fact about the product.
620
+ side = self._meta(rel + ".meta.md")
621
+ if _v(side, "provenance").upper() == "DECLARED":
622
+ return [{"kind": "declared", "path": rel}]
618
623
  return [{"kind": "document", "path": rel}]
619
624
  if not _inside(self.docs, rel) or not (self.docs / rel).is_file():
620
625
  return [{"kind": "untraceable", "path": rel}]
@@ -643,6 +648,8 @@ def ground_class(leaves):
643
648
  return "statement"
644
649
  if "untraceable" in kinds:
645
650
  return "untraceable"
651
+ if "declared" in kinds:
652
+ return "declared"
646
653
  if "kb" in kinds:
647
654
  return "imported"
648
655
  return "documents"
@@ -707,12 +714,15 @@ def build_kb(docs):
707
714
  kind, rest = _kind(r)
708
715
  f = {"text": r["claim"], "valid": r["valid"], "qty": r["qty"], "about": r["about"],
709
716
  "origin": (r["prov"] or "").strip().lower(), "topic": slug,
710
- "state": {"SUPERSEDED": "superseded", "CONTESTED": "contested"}.get(kind, "ok")}
717
+ "state": {"SUPERSEDED": "superseded", "CONTESTED": "contested",
718
+ "DIVERGES": "diverges"}.get(kind, "ok")}
711
719
  if kind == "SUPERSEDED":
712
720
  f["successors"] = _ids(rest)
713
721
  f["chain_ends"] = chain_ends(r["id"], by_id)
714
722
  if kind == "CONTESTED":
715
723
  f["with"] = _ids(rest)
724
+ if kind == "DIVERGES":
725
+ f["diverges_from"] = _ids(rest)
716
726
  srcs = []
717
727
  for src in r["source"].split(";"):
718
728
  src = src.strip()
@@ -1246,7 +1256,9 @@ def chronology(docs, pages):
1246
1256
  text = _text(docs / rel) if (docs / rel).is_file() else None
1247
1257
  if text is not None:
1248
1258
  for row in sdlc_core.parse_review_log(text)[0]:
1249
- add(row["date"], "review", "%s — %s: %s" % (
1259
+ # F-085: a `post-merge` row is the owner's defect report, not a review
1260
+ kind = "defect" if row["tier"].lower().startswith("post-merge") else "review"
1261
+ add(row["date"], kind, "%s — %s: %s" % (
1250
1262
  row["doc_key"], row["tier"], row["verdict"]), rel)
1251
1263
  for p in sorted(docs.glob("architecture/ADR_*.md")):
1252
1264
  rel = "architecture/" + p.name
@@ -1269,7 +1281,9 @@ def chronology(docs, pages):
1269
1281
  continue
1270
1282
  if kind == "source":
1271
1283
  name = p.name[:-len(suffix)]
1272
- add(date, kind, name, rel, source=name)
1284
+ # F-084: a DECLARED sidecar's date is the day it was SENT
1285
+ sent = _v(fm, "provenance").upper() == "DECLARED"
1286
+ add(date, "sent" if sent else kind, name, rel, source=name)
1273
1287
  else:
1274
1288
  add(date, kind, title_of(rel, text), rel)
1275
1289
  # Most recent first. Within one day a document's later entry is the more recent