@antoneeo/kb-agentic-skill 1.13.0 → 1.15.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.
- package/CHANGELOG.md +81 -0
- package/README.md +30 -1
- package/gemini-extension.json +6 -6
- package/package.json +51 -51
- package/skills/kb-agentic-skill/ENFORCEMENT.md +33 -16
- package/skills/kb-agentic-skill/SKILL.md +288 -274
- package/skills/kb-agentic-skill/dispatch.md +39 -1
- package/skills/kb-agentic-skill/distillation.md +38 -7
- package/skills/kb-agentic-skill/guides.md +16 -12
- package/skills/kb-agentic-skill/reconciliation.md +22 -0
- package/skills/kb-agentic-skill/review.md +80 -3
- package/skills/kb-agentic-skill/scripts/sdlc_check.py +46 -28
- package/skills/kb-agentic-skill/scripts/sdlc_core.py +293 -4
- package/skills/kb-agentic-skill/templates.md +17 -9
|
@@ -45,12 +45,50 @@ below — so it needs no separate dispatch hook.)
|
|
|
45
45
|
|
|
46
46
|
## Model tiers (client-relative, no provider names)
|
|
47
47
|
|
|
48
|
-
- Default dispatch: **economy** implementer tier.
|
|
48
|
+
- Default dispatch: **economy** implementer tier. This is legal precisely
|
|
49
|
+
because a dispatched task carries `task.verify` — the threshold signal that
|
|
50
|
+
makes a wrong cheap answer visible. **The floor that governs every role, and
|
|
51
|
+
the rule that a tier may be lowered only where such a signal exists, is
|
|
52
|
+
`review.md` §The capability floor** — cited, never restated here.
|
|
49
53
|
- After **two** consecutive `verify_result: fail` on the same task: escalate
|
|
50
54
|
to the **deep** tier for the retry (ADR 2026-07-02). Do not escalate on the
|
|
51
55
|
first failure — a single fail is often a brief or environment issue, not a
|
|
52
56
|
capability gap.
|
|
53
57
|
|
|
58
|
+
## What may be delegated at all
|
|
59
|
+
|
|
60
|
+
The sections above govern the opt-in PLAN loop; **this one is family-wide** and
|
|
61
|
+
applies to any pass, at any level, dispatch or no dispatch. Delegation is not a
|
|
62
|
+
token-saving technique — a second context re-reads, re-briefs and reports back,
|
|
63
|
+
so the total spend goes UP. What it buys is a main context that stays clean, and
|
|
64
|
+
independence where independence is the point. Judge a pass on two axes:
|
|
65
|
+
|
|
66
|
+
| | output falsifiable cheaply (`file:line`, an assertion result, a diff) | output is judgement |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| **reads ≫ report** | **delegate** — the whole win lives here | **never**: the biggest apparent win is where plausible-but-wrong enters |
|
|
69
|
+
| **reads ≈ report** | keep it: the brief costs more than the pass | keep it |
|
|
70
|
+
|
|
71
|
+
- **Delegate**: locating and verifying (a pass that returns citable facts and
|
|
72
|
+
discards its exploration trace), executing a probe or battery and reporting
|
|
73
|
+
the assertion table, and every independent review — the last for correctness,
|
|
74
|
+
not economy, and it is mandatory regardless of what it costs.
|
|
75
|
+
- **Never delegate**: authoring a governed artifact (Vision, ANALYSIS/E-ISP,
|
|
76
|
+
threat model, use cases, the Interface Contract), the triage level and router
|
|
77
|
+
verdict, a reconciliation or scope decision, and any pass whose result the
|
|
78
|
+
orchestrator must hold across many later turns — delegating that one just
|
|
79
|
+
moves the reading into the main context via the report.
|
|
80
|
+
- **The delegability condition**: a task whose brief cannot be expressed as
|
|
81
|
+
**pointers** — paths, ids, a command — is **not delegable** yet; a brief that
|
|
82
|
+
must carry the content pays those tokens twice, once to write it and once to
|
|
83
|
+
read it. `plan brief` is this rule already realized for the PLAN loop (it
|
|
84
|
+
prints `guides` as paths, never pasted content); the condition generalizes it.
|
|
85
|
+
|
|
86
|
+
**Declared residual**, so the silence is not read as prohibition: this section
|
|
87
|
+
settles the CLASSIFICATION. *When* to reach for a delegated search or
|
|
88
|
+
orientation pass — the trigger, its brief format and its output contract — is a
|
|
89
|
+
separate unit and is not answered here; until it lands, reaching for one is the
|
|
90
|
+
orchestrator's judgement against the table above, not a rule.
|
|
91
|
+
|
|
54
92
|
## Review slots — one-shot, not iterative
|
|
55
93
|
|
|
56
94
|
Exactly three review touches per task, never a loop:
|
|
@@ -96,11 +96,19 @@ Claims live in the owning topic's `## Claims` table (`templates.md` has the temp
|
|
|
96
96
|
makes the same assertion mint the same id in another project, which is what makes
|
|
97
97
|
`portability.md`'s de-duplication mechanical instead of a judgement call. The price is
|
|
98
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.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
cannot tell them apart, and the validator refuses the pair. That refusal is a
|
|
100
|
+
symptom of a non-atomic row (§3): the fix is to **narrow each locator to the
|
|
101
|
+
sub-span that carries its own assertion** — narrow spans make atomic rows
|
|
102
|
+
id-distinct by construction. Merge ONLY when the two rows state the same single
|
|
103
|
+
assertion twice; merging distinct assertions manufactures a bundled row (§3),
|
|
104
|
+
trading the mechanical error for a semantic one no check can see. Narrowing is
|
|
105
|
+
not nudging: a narrowed locator still addresses exactly the words that support
|
|
106
|
+
its assertion, and the validator still opens the span. What stays forbidden is
|
|
107
|
+
editing the qty, or moving a locator onto bytes that do NOT carry the assertion,
|
|
108
|
+
until the hash differs — that distorts the evidence to satisfy a hash function,
|
|
109
|
+
which is the one repair this ledger exists to prevent. A locator edit mints a
|
|
110
|
+
NEW id; citers of the old one are reached by the cascade (`reconciliation.md`
|
|
111
|
+
§Splitting a bundled row).
|
|
104
112
|
- **valid** — `-`, `from X`, `until X`, `from X until Y`, `if <condition>`. Half-open:
|
|
105
113
|
`until 2026-03-01` and `from 2026-03-01` do NOT overlap. A time-bounded fact is not a
|
|
106
114
|
conflict with its successor — write the scope, or reconciliation will manufacture one.
|
|
@@ -117,8 +125,8 @@ Claims live in the owning topic's `## Claims` table (`templates.md` has the temp
|
|
|
117
125
|
the file and checks.
|
|
118
126
|
- **prov** — `GIVEN | ELICITED | DERIVED | RULING | IMPORTED`. `IMPORTED` is a ruling that came from another project (`portability.md`): it keeps its text and original `basis:`, its note must say `imported_from:`, and it may not supersede a local row until you re-ratify it. Information for whoever resolves a
|
|
119
127
|
conflict; never a rank.
|
|
120
|
-
- **state** — `OK`, `CONTESTED <
|
|
121
|
-
transitions).
|
|
128
|
+
- **state** — `OK`, `CONTESTED <id>[,..]`, `SUPERSEDED <id>[,..]` (`reconciliation.md`
|
|
129
|
+
owns the transitions; a split bundle names ALL its successors).
|
|
122
130
|
|
|
123
131
|
## 3. Extraction discipline
|
|
124
132
|
|
|
@@ -127,6 +135,29 @@ the offset span it came from. The extractor **invents nothing**: no labels, no
|
|
|
127
135
|
summaries-as-claims, no filling of gaps from model knowledge. What the source does not
|
|
128
136
|
assert does not become a row — it may become a `gaps:` entry on the topic.
|
|
129
137
|
|
|
138
|
+
**One row asserts one thing — the span is not the unit.** The extraction unit is a
|
|
139
|
+
source span; the unit of use is one assertion, and they are not the same size: a row
|
|
140
|
+
can be atomic as provenance and plural as meaning. The common source of plurality is
|
|
141
|
+
the **reviewer-comment shape** — one comment under one feature heading asserting
|
|
142
|
+
several facts about several subjects (a capability at the door, another actor's
|
|
143
|
+
console, a verification flow). Extracted as one span it becomes one legal row, and
|
|
144
|
+
every downstream citation of it is faithful to the text while asserting what the
|
|
145
|
+
source did not: the off-topic part is read as applying to the citing context
|
|
146
|
+
(borrowed capability), another actor's surface counts as this actor's (wrong actor),
|
|
147
|
+
an ambiguous subject silently switches referent — and the writer who avoids all
|
|
148
|
+
three by quoting only the on-topic part has lost an assertion instead. Both halves
|
|
149
|
+
of the north star, violated by one bundled row. So **emit one row per assertion even
|
|
150
|
+
when the source packs several into one sentence**, and **cite the narrowest span
|
|
151
|
+
that supports the assertion, not the paragraph that contains it** — narrow spans are
|
|
152
|
+
also what make atomic rows id-distinct (§2). Sub-line precision needs no
|
|
153
|
+
re-extraction on a stored `.txt`: a form-feed-free file is one page, so
|
|
154
|
+
`p=1@<a>-<b>` addresses character offsets below the line; re-extraction is needed
|
|
155
|
+
only for a source with no character-addressable stored form. A bundled row already
|
|
156
|
+
in the ledger is repaired by splitting (`reconciliation.md` §Splitting a bundled
|
|
157
|
+
row), never edited in place. The validator can only hint here — a `[note]` on
|
|
158
|
+
suspicious claim text, heuristic and fallible, never an error: atomicity is
|
|
159
|
+
meaning, and meaning is verified at the ingestion review.
|
|
160
|
+
|
|
130
161
|
**Exhaust the source; never sample it.** *Invents nothing* is a floor, and a floor is not
|
|
131
162
|
a target: an extractor that stops the moment nothing it wrote is false stops on page
|
|
132
163
|
twenty of a two-hundred-page manual, with every row it emitted correct. A source is
|
|
@@ -60,6 +60,10 @@ already cover how to do this well?*
|
|
|
60
60
|
match was not targeted (T7). Two guides are legitimate only when they cover
|
|
61
61
|
distinct concerns — typically an operative guide plus the comprehension map of
|
|
62
62
|
the component being touched; three is a smell, not a thorough lookup.
|
|
63
|
+
- **Check a `code` guide against the code before you trust it.** No check can do
|
|
64
|
+
this for you (§6): the reader is the only one who sees that the map and the
|
|
65
|
+
component have parted. A divergence you observe is yours to close in the same
|
|
66
|
+
unit of work.
|
|
63
67
|
- **Under subagent dispatch** the consult happens at plan-authoring time (the
|
|
64
68
|
orchestrator populates each task's `guides` field); a context-free subagent
|
|
65
69
|
reads the pointers it was handed and does not run its own router lookup. See
|
|
@@ -271,17 +275,17 @@ state it explicitly when handing off a newly created guide.
|
|
|
271
275
|
|
|
272
276
|
## 6. Maintenance
|
|
273
277
|
|
|
274
|
-
- **Source changed**:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
and
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
278
|
+
- **Source changed**: new snapshot, regenerate the guide from it (new
|
|
279
|
+
`source_hash`); a guide REPLACING another is `status: SUPERSEDED` on the old
|
|
280
|
+
one. `sdlc_check.py stale` (also under `--hybrid`) reports `[stale]` when a
|
|
281
|
+
guide's recorded hash and its snapshot file diverge — the signal to regenerate.
|
|
282
|
+
- **`source_kind: code` freshness is NOT mechanized.** `stale` compares the guide
|
|
283
|
+
to its snapshot, and a code snapshot is a frozen copy — so a map whose component
|
|
284
|
+
was rewritten reports FRESH forever (measured 2026-09-06: +388/-71 lines in the
|
|
285
|
+
guided module, `stale` silent). The duty is therefore the author's, twice: when
|
|
286
|
+
you MODIFY code a comprehension guide describes, refresh the guide in the same
|
|
287
|
+
closure (docs travel with the code); when you merely READ one, check it against
|
|
288
|
+
the code before trusting it. Too large to absorb → mark it `SUPERSEDED` rather
|
|
289
|
+
than leave a confident-wrong map badged CURRENT, which is worse than none.
|
|
286
290
|
- **Agent-global KB guides** use the same pipeline and validator via
|
|
287
291
|
`--root ~/.agentic-sdlc`; freshness via the same `stale` engine.
|
|
@@ -69,6 +69,28 @@ premise: signed amendments arrive later.
|
|
|
69
69
|
| newer source vs a ruling | new set {new row, ruling row}; escalation shows the `basis:` |
|
|
70
70
|
| member refined | the refining row inherits the member's contested relations (re-judged); counterparts rewritten to the new id |
|
|
71
71
|
| corroboration of a contested member | source appended; the set untouched — more evidence is not new information, and the escalation form shows source counts |
|
|
72
|
+
| 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 |
|
|
73
|
+
|
|
74
|
+
## 3-bis. Splitting a bundled row
|
|
75
|
+
|
|
76
|
+
A row carrying more than one assertion (`distillation.md` §3 — atomic as provenance,
|
|
77
|
+
plural as meaning) is repaired by supersession, never in place:
|
|
78
|
+
|
|
79
|
+
1. Write one atomic row per assertion, each citing the **narrowed span** that carries
|
|
80
|
+
it (`p=1@<a>-<b>` reaches below a line on a stored `.txt`). Narrow spans mint
|
|
81
|
+
distinct ids by construction.
|
|
82
|
+
2. The bundled row becomes `SUPERSEDED <id1>, <id2>, ...` — every successor named,
|
|
83
|
+
its text intact, exactly as refinement keeps superseded text.
|
|
84
|
+
3. Contested relations of the bundle are **re-judged per successor**: a counterpart
|
|
85
|
+
usually contests ONE of the assertions, not all of them.
|
|
86
|
+
4. The cascade runs (§2, *the cascade runs*): every living document citing the fallen
|
|
87
|
+
id joins the revision worklist, and each citer is revised to the successor in ITS
|
|
88
|
+
scope — the moment a borrowed-capability citation dies.
|
|
89
|
+
|
|
90
|
+
This is not new information arriving (§2): the source said N things all along; the
|
|
91
|
+
ledger only now says them one at a time. No ruling and no `basis:` is needed — the
|
|
92
|
+
split is a repair of our own extraction, and it travels with its own diary/commit
|
|
93
|
+
record like any claim-table edit.
|
|
72
94
|
|
|
73
95
|
## 4. Escalation — batched, and in the legal form only
|
|
74
96
|
|
|
@@ -82,15 +82,92 @@ its outcome. The trigger term "permission-gated" scopes the STOP; the row word
|
|
|
82
82
|
|
|
83
83
|
Use a different model from the author's where the client allows it.
|
|
84
84
|
|
|
85
|
+
**The capability floor — which tier may run this role.** Independence says the
|
|
86
|
+
reviewer must not be the author; it says nothing about whether the reviewer *can
|
|
87
|
+
do the job*. A gate run below the capability its judgement needs produces the
|
|
88
|
+
myopia the gate exists to catch, **while reporting as an independent review** —
|
|
89
|
+
worse than no gate, because it certifies. So each role carries a minimum tier.
|
|
90
|
+
Tiers are **client-relative capability levels, never provider names** (a name
|
|
91
|
+
rots at the next model release; the tier→model binding lives in the client's own
|
|
92
|
+
agent definition, never in this file):
|
|
93
|
+
|
|
94
|
+
| Role | Floor | Why there |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| **authoring a governed artifact** (Vision, the analysis/design, use cases, the Interface Contract, threat model — `dispatch.md`'s never-list owns the enumeration) | **deep** | the purest case of the rule below: the output is judgement, nothing scores it, and `dispatch.md`'s never-list forbids moving it elsewhere — so the session's own capability IS this work's capability |
|
|
97
|
+
| design review, closure review, any verdict on judgement | **deep** | the output is a judgement whose wrongness no check can see |
|
|
98
|
+
| a light conformance or consistency pass (schema, coverage, citation resolution) | **light** | the criterion is stated and mechanical |
|
|
99
|
+
| implementer dispatch, probe execution, anchor resolution, index regeneration | **economy** | a threshold signal catches a wrong cheap answer |
|
|
100
|
+
|
|
101
|
+
The rule underneath the table: **a tier may be lowered only where a threshold
|
|
102
|
+
signal exists** — `task.verify`, a test, a diff against a spec, an assertion
|
|
103
|
+
harness — because that is what makes a wrong cheap answer visible instead of
|
|
104
|
+
plausible. Where the output is judgement and nothing scores it, there is no
|
|
105
|
+
signal to lower against, and the floor is the whole policy.
|
|
106
|
+
|
|
107
|
+
**A session's floor is the highest floor among the roles it performs itself.** The
|
|
108
|
+
table above binds reviewers and dispatched subagents, chosen per call — but the
|
|
109
|
+
session doing the authoring was chosen by the USER before the agent existed, and
|
|
110
|
+
an agent cannot re-tier itself mid-session. So the rule that reaches it is not
|
|
111
|
+
routing, it is **disclosure**: where a session can determine that it is below the
|
|
112
|
+
floor for a role it performs itself, it says so **once per session, at the first
|
|
113
|
+
such role** — not once per unit — and the disclosure is repeated in the review
|
|
114
|
+
row's existing `reviewer` cell (Hybrid: `notes`), which is what makes the pair
|
|
115
|
+
below visible without adding a column.
|
|
116
|
+
|
|
117
|
+
**Say plainly how thin this reach is.** A session knows its model NAME; tiers here
|
|
118
|
+
are deliberately client-relative and this file maps no name to a tier, so the
|
|
119
|
+
antecedent is satisfiable only where a session can recognize its own tier — which
|
|
120
|
+
is also the session least likely to run the check. Where a session **cannot
|
|
121
|
+
determine its own tier**, it owes nothing: the rule binds what an agent can know
|
|
122
|
+
and say, never a capability it cannot acquire. **The reliably working half of this
|
|
123
|
+
rule is the one addressed to the user, not to the agent** — which is why the
|
|
124
|
+
package READMEs carry the tier-selection guidance, and why that is not a footnote.
|
|
125
|
+
|
|
126
|
+
**Never both below the floor on the same unit — except when nothing better
|
|
127
|
+
exists.** Where the authoring session ran below floor, the independent review's
|
|
128
|
+
floor becomes non-negotiable and the same unit should not ALSO run a below-floor
|
|
129
|
+
review. **The exception is the case *When independence and capability conflict*
|
|
130
|
+
governs below**: where no independent rung at or above the floor exists at all,
|
|
131
|
+
that rule wins, the review still runs, BOTH disclosures are recorded, and the
|
|
132
|
+
result is not a violation but the worst admissible state — honestly labelled.
|
|
133
|
+
Forbidding it outright would push the agent toward the abstention that rule
|
|
134
|
+
explicitly rejects.
|
|
135
|
+
|
|
136
|
+
**When independence and capability conflict, independence wins.** A client whose
|
|
137
|
+
only independent rung is bound below the floor (a single-model client, or a
|
|
138
|
+
subagent facility whose agent definition fixes a cheap model) still runs the
|
|
139
|
+
gate: a below-floor *independent* reviewer catches the class the author is
|
|
140
|
+
structurally blind to, while a self-pass catches none of it. What the floor then
|
|
141
|
+
requires is **disclosure, not abstention** — the row records the tier that
|
|
142
|
+
actually ran (`below floor: <reason>`), and that record IS the remedy. Where a
|
|
143
|
+
higher-capability path exists but is permission-gated, nothing new applies: *The
|
|
144
|
+
gated rung* above already governs, one question per gate per intact context.
|
|
145
|
+
Where the client exposes no capability choice at all, the row says
|
|
146
|
+
`single (client exposes no choice)` and the floor is satisfied by construction —
|
|
147
|
+
it is a routing rule, and a routing rule with one route is not a failure.
|
|
148
|
+
|
|
85
149
|
**Rounds are capped at 3.** FAIL → revise → re-review. If findings still stand
|
|
86
150
|
after the third, stop and surface them to the user with the artifact — a gate that
|
|
87
151
|
can block forever gets removed. **Log one row per completed review, PASS or FAIL**
|
|
88
152
|
— a FAIL surfaced to the user is the highest-value outcome the gate produces, and
|
|
89
153
|
logging only passes would erase exactly that evidence. The row goes in
|
|
90
154
|
`ai_docs/audit/reviews/REVIEW_LOG.md` (create it if absent — `templates.md`):
|
|
91
|
-
`| date | doc_key | tier | reviewer | findings_raised | findings_real | verdict | revise_rounds |`,
|
|
92
|
-
with `tier` = `design` or `closure` in Standalone.
|
|
93
|
-
|
|
155
|
+
`| date | doc_key | tier | model | reviewer | findings_raised | findings_real | verdict | revise_rounds |`,
|
|
156
|
+
with `tier` = `design` or `closure` in Standalone. **`model` records the capability
|
|
157
|
+
tier that actually ran** — `deep`, `light`, `economy`, `single (client exposes no
|
|
158
|
+
choice)`, or `below floor: <reason>` — never a provider name in doctrine (a project
|
|
159
|
+
may of course write its own model names in its own log). Without it the floor above
|
|
160
|
+
is a belief: the row would say a gate ran and never whether it could do its job, and
|
|
161
|
+
the correlation with `revise_rounds` and `findings_real` — the only evidence the
|
|
162
|
+
policy pays — could not be drawn.
|
|
163
|
+
|
|
164
|
+
**One schema for both modes**, meaning one required CORE plus each mode's own
|
|
165
|
+
**mode-specific** realization columns. The core is what both modes actually share: `date`, `doc_key`,
|
|
166
|
+
`tier`, `model`, `findings_raised`, `findings_real`, `verdict`, `revise_rounds`.
|
|
167
|
+
Around it, Standalone adds `reviewer` and Hybrid adds `instrument` and `notes` —
|
|
168
|
+
a devPNT row carries no `reviewer` column at all, so "one identical column list"
|
|
169
|
+
was never true of the two modes. This costs nothing for the reason `templates.md`
|
|
170
|
+
§`ai_docs/audit/reviews/REVIEW_LOG.md` owns and states.
|
|
94
171
|
The log is how the gate's value is measured over time; skipping it makes the gate
|
|
95
172
|
unfalsifiable, the same defect as an unnamed EXISTS or a faked router verdict.
|
|
96
173
|
|
|
@@ -80,6 +80,19 @@ sdlc_core.set_profile(
|
|
|
80
80
|
# sdlc_core.py in three distributions to buy nothing.
|
|
81
81
|
"taxonomy_pass", "subagent_dispatch", "question_discipline",
|
|
82
82
|
),
|
|
83
|
+
# F-046: the per-turn line moved from this file's own `remind` command into
|
|
84
|
+
# the shared spine, which now carries the machine for all three lenses. The
|
|
85
|
+
# kb duties below are unchanged; what is new is the first one -- an unread
|
|
86
|
+
# protocol cannot be applied, and until now nothing said so.
|
|
87
|
+
remind_line=(
|
|
88
|
+
"first decide if this skill governs the work; if yes, LOAD it once -- an "
|
|
89
|
+
"unread protocol cannot be applied. Triage: L1 quick fact, L2 propagation, "
|
|
90
|
+
"L3 new knowledge unit, Spike; declare the level. On this project's "
|
|
91
|
+
"domain, read topics/INDEX.md first and cite claim ids, or state no "
|
|
92
|
+
"coverage. Decisions today get recorded in the KB at close. Revising a "
|
|
93
|
+
"document? Re-read it whole and rewrite to current state -- never append "
|
|
94
|
+
"a delta."
|
|
95
|
+
),
|
|
83
96
|
design_gate_between=("### 3. Request Analysis & Taxonomy Pass",
|
|
84
97
|
"### 4. Knowledge Processing & Distillation"),
|
|
85
98
|
)
|
|
@@ -174,6 +187,13 @@ def kb_claim_id(source_path, locator, qty_key=""):
|
|
|
174
187
|
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:12]
|
|
175
188
|
|
|
176
189
|
|
|
190
|
+
# F-047: bundling tells for the atomicity note in kb_check_claims. Literal
|
|
191
|
+
# substrings, deliberately few and conservative: the note is a hint, and a
|
|
192
|
+
# noisy hint trains readers to skip notes. Field-calibrated on the motivating
|
|
193
|
+
# reviewer-comment shape ("states X, that Y, and that Z").
|
|
194
|
+
KB_BUNDLE_HINTS = (", and that ", "; ", ", while ", " and also ")
|
|
195
|
+
|
|
196
|
+
|
|
177
197
|
def kb_unquote(value):
|
|
178
198
|
"""Strip ONE pair of wrapping YAML quotes (then re-strip whitespace).
|
|
179
199
|
load_frontmatter keeps values verbatim by design; paths with spaces force
|
|
@@ -485,6 +505,20 @@ def kb_check_claims(root):
|
|
|
485
505
|
if not m:
|
|
486
506
|
errors.append("%s: about must be '<predicate> -> <slug>' or '-'"
|
|
487
507
|
% where)
|
|
508
|
+
# --- atomicity hint (F-047): a row atomic as provenance can be
|
|
509
|
+
# plural as meaning. Text cannot PROVE plurality, so this is a
|
|
510
|
+
# note — never a warning or an error, never the exit code.
|
|
511
|
+
hints = [h for h in KB_BUNDLE_HINTS if h in row["claim"]]
|
|
512
|
+
if hints:
|
|
513
|
+
notes.append("%s: claim text of %s reads like more than one "
|
|
514
|
+
"assertion (matched %s) — one row asserts one "
|
|
515
|
+
"thing; if it is a bundle, split it "
|
|
516
|
+
"(distillation.md section 3, reconciliation.md "
|
|
517
|
+
"section 'Splitting a bundled row'). Heuristic "
|
|
518
|
+
"only: a genuinely "
|
|
519
|
+
"single assertion needs no change"
|
|
520
|
+
% (where, row["id"] or "(id pending)",
|
|
521
|
+
", ".join(repr(h) for h in hints)))
|
|
488
522
|
# --- id: recompute, or note fill-pending ---
|
|
489
523
|
if not row["id"]:
|
|
490
524
|
notes.append("%s: id missing — fill-pending, run "
|
|
@@ -521,14 +555,22 @@ def kb_check_claims(root):
|
|
|
521
555
|
"is global across topics/" % (where, row["id"],
|
|
522
556
|
all_ids[row["id"]]))
|
|
523
557
|
elif same_span:
|
|
558
|
+
# F-047: recommending a merge here was a trap — merging
|
|
559
|
+
# distinct assertions manufactures a bundled row, trading
|
|
560
|
+
# this mechanical error for a semantic one no check sees.
|
|
524
561
|
errors.append(
|
|
525
562
|
"%s: id %s collides with the row at %s — two "
|
|
526
563
|
"DIFFERENT rows cite the same span with the same "
|
|
527
564
|
"qty, and the id function cannot separate them (it "
|
|
528
565
|
"hashes path#locator#qty and excludes the text on "
|
|
529
|
-
"purpose).
|
|
530
|
-
"
|
|
531
|
-
"rows
|
|
566
|
+
"purpose). Narrow each locator to the sub-span that "
|
|
567
|
+
"carries its own assertion — narrow spans make "
|
|
568
|
+
"atomic rows id-distinct (on a stored .txt, "
|
|
569
|
+
"p=<page>@<a>-<b> reaches below a line). Merge ONLY "
|
|
570
|
+
"if the two rows state the same single assertion; "
|
|
571
|
+
"merging distinct assertions trades this mechanical "
|
|
572
|
+
"error for a semantic one no check can see — do not "
|
|
573
|
+
"edit the qty to break the tie"
|
|
532
574
|
% (where, row["id"], all_ids[row["id"]]))
|
|
533
575
|
else:
|
|
534
576
|
# Same id, different text AND different span: the id
|
|
@@ -557,7 +599,7 @@ def kb_check_claims(root):
|
|
|
557
599
|
m = re.match(r"^(CONTESTED|SUPERSEDED) ([0-9a-f, ]+)$", state)
|
|
558
600
|
if not m:
|
|
559
601
|
errors.append("%s: state must be OK, 'CONTESTED <id>[,..]' or "
|
|
560
|
-
"'SUPERSEDED <id>': %r" % (where, state))
|
|
602
|
+
"'SUPERSEDED <id>[,..]': %r" % (where, state))
|
|
561
603
|
continue
|
|
562
604
|
kind = m.group(1)
|
|
563
605
|
targets = [t.strip() for t in m.group(2).split(",") if t.strip()]
|
|
@@ -1884,26 +1926,6 @@ def kb_cmd_orient(argv):
|
|
|
1884
1926
|
return rc
|
|
1885
1927
|
|
|
1886
1928
|
|
|
1887
|
-
def kb_cmd_remind(_argv):
|
|
1888
|
-
"""F-041 item B: the per-turn one-line reminder (UserPromptSubmit, opt-in).
|
|
1889
|
-
|
|
1890
|
-
A CONSTANT, zero-read line -- the kb minimum an agent needs re-armed every
|
|
1891
|
-
prompt: triage, the recall trigger, the capture moment, the revision gesture. Constant by
|
|
1892
|
-
contract: nothing repo-controlled may ride a line injected into the agent's
|
|
1893
|
-
context every turn, and zero reads is what keeps the per-turn cost flat.
|
|
1894
|
-
The vision's non-goal bans per-turn GRAPH queries; this line only re-arms
|
|
1895
|
-
the entry-into-topic criterion, it never runs the descent. Never wired by
|
|
1896
|
-
init and never a default: ENFORCEMENT par.4 documents the manual snippet
|
|
1897
|
-
and the ~50 tokens/prompt cost -- the project chooses. Fail-open like every
|
|
1898
|
-
hook surface: argv is ignored whole, the exit code is always 0."""
|
|
1899
|
-
print("kb-agentic: triage before acting -- L1 quick fact, L2 propagation of "
|
|
1900
|
-
"settled knowledge, L3 new knowledge unit, Spike exploration; declare "
|
|
1901
|
-
"the level. Answering about this project's domain? Read topics/INDEX.md "
|
|
1902
|
-
"under the docs root first and cite claim ids, or state no coverage. "
|
|
1903
|
-
"Decisions made today get recorded in the KB at session close. Revising an existing document? Re-read it whole and rewrite it to current state -- never append a delta.")
|
|
1904
|
-
return 0
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
1929
|
def kb_cmd_help():
|
|
1908
1930
|
"""The spine's usage, then the overlay's own commands.
|
|
1909
1931
|
|
|
@@ -1946,10 +1968,6 @@ def main(argv=None):
|
|
|
1946
1968
|
# spine runs it on the raw argv, the overlay only appends the topic router.
|
|
1947
1969
|
if argv and argv[0] == "orient":
|
|
1948
1970
|
return kb_cmd_orient(argv)
|
|
1949
|
-
# `remind` too: it is a hook surface (UserPromptSubmit), so a stray flag
|
|
1950
|
-
# must print the line, not an argparse usage error into someone's prompt.
|
|
1951
|
-
if argv and argv[0] == "remind":
|
|
1952
|
-
return kb_cmd_remind(argv)
|
|
1953
1971
|
# Forward-by-default: anything not intercepted goes to the spine untouched.
|
|
1954
1972
|
# Never a hand-copied command tuple - that is how a spine command gets
|
|
1955
1973
|
# silently dropped (mkt_check.py ships that exact defect with `migrate`).
|