@journeykit/canon 0.6.0 → 0.8.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/dist/index.js +12 -2
- package/dist/skills/SKILL.src.md +46 -0
- package/dist/skills/reference/canon-artifact-ingest.md +72 -0
- package/dist/skills/reference/canon-authoring.md +155 -0
- package/dist/skills/reference/canon-context.md +137 -0
- package/dist/skills/reference/canon-fmt.md +43 -0
- package/dist/skills/reference/canon-gate.md +199 -0
- package/dist/skills/reference/canon-inventory.md +102 -0
- package/dist/skills/reference/canon-learn.md +245 -0
- package/dist/skills/reference/canon-plan-import.md +143 -0
- package/dist/skills/reference/canon-plugins.md +131 -0
- package/dist/skills/reference/canon-policy.md +119 -0
- package/dist/skills/reference/canon-report-dashboard.md +343 -0
- package/dist/skills/reference/canon-retrieve.md +123 -0
- package/dist/skills/reference/canon-session-ingest.md +214 -0
- package/dist/skills/reference/canon-storage.md +152 -0
- package/dist/skills/reference/canon-subject.md +161 -0
- package/dist/skills/reference/canon-vocab.md +115 -0
- package/dist/skills/scripts/canon-retrieve-pre-dispatch.sh +126 -0
- package/package.json +6 -4
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# canon-report-dashboard
|
|
2
|
+
|
|
3
|
+
canon's single status surface: `canon report` renders TEN panels to
|
|
4
|
+
markdown, `canon report --snapshot` exports the same ten marts to
|
|
5
|
+
Parquet, and `canon dashboard` serves a small zero-network web app that
|
|
6
|
+
renders SEVEN of them from such a snapshot. Neither surface computes an
|
|
7
|
+
aggregate itself — both read canon's precomputed
|
|
8
|
+
`crates/canon-store/sql/views.sql` marts, and each run reads the corpus
|
|
9
|
+
ONCE (`crates/canon-report/src/query.rs`'s `PIN_CORPUS_SQL`), so no two
|
|
10
|
+
panels within one report, or two files within one snapshot, can
|
|
11
|
+
disagree. Across surfaces the input is what differs: a dashboard renders
|
|
12
|
+
the snapshot it was given, so it matches a report only insofar as the
|
|
13
|
+
two were generated from the same corpus.
|
|
14
|
+
|
|
15
|
+
They can still disagree about what a number MEANS, and that is the
|
|
16
|
+
failure this skill is written against: a friendly label or a paraphrase
|
|
17
|
+
asserting something its query never computed. Every claim below is kept
|
|
18
|
+
verbatim with the prose `canon report` actually renders
|
|
19
|
+
(`canon_report::render`'s `pub const *_PANEL` literals and its inline
|
|
20
|
+
one-liners), and `packages/dashboard/test/panel-copy.test.ts` pins this
|
|
21
|
+
file to those literals. Copy a claim; do not paraphrase it.
|
|
22
|
+
|
|
23
|
+
## Generated, never hand-edited
|
|
24
|
+
|
|
25
|
+
`.canon/REPORT.md` carries a leading `<!-- GENERATED by \`canon report\` …
|
|
26
|
+
DO NOT EDIT. -->` comment. If it looks stale or wrong, the fix is
|
|
27
|
+
**always** re-running `canon report` (or fixing the underlying
|
|
28
|
+
ledger/policy it renders) — never hand-editing the file. The report
|
|
29
|
+
header embeds only input-derived digests (corpus hash, policy hash,
|
|
30
|
+
ledger-head hash) and **no timestamp, no `source_git_sha`**, so a
|
|
31
|
+
committed report is reproducible from named inputs and `canon report
|
|
32
|
+
--check` can byte-diff it deterministically. `source_git_sha` belongs
|
|
33
|
+
only in `--snapshot`'s `manifest.json` (below), which is never
|
|
34
|
+
drift-checked.
|
|
35
|
+
|
|
36
|
+
## `canon report [--repo <dir>]`
|
|
37
|
+
|
|
38
|
+
Renders the current ten-panel report to `<repo>/.canon/REPORT.md`.
|
|
39
|
+
`--repo` resolves through the nearest-`canon.yaml`-ancestor walk every
|
|
40
|
+
subcommand uses — omit it (or pass `.`) to run from any subdirectory.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
canon report # writes .canon/REPORT.md
|
|
44
|
+
canon report --repo /path/to/repo
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Between the digest header and the first panel, the report may also
|
|
48
|
+
render a `## Kinds not read directly` section. It is config-derived
|
|
49
|
+
(`canon.yaml`'s static `routing`/`tiers` tables), not a live backend
|
|
50
|
+
probe, and an empty set renders NO section at all — so a repo routing
|
|
51
|
+
nothing to a not-directly-readable backend renders exactly as before
|
|
52
|
+
the section existed.
|
|
53
|
+
|
|
54
|
+
## `canon report --check` — the CI drift gate
|
|
55
|
+
|
|
56
|
+
Regenerates the report in memory and byte-compares it against the
|
|
57
|
+
committed `.canon/REPORT.md`:
|
|
58
|
+
|
|
59
|
+
- `MISSING` (exit 1) — no report has ever been written; run `canon report` first.
|
|
60
|
+
- no drift (exit 0) — the committed file is byte-identical to what the
|
|
61
|
+
current inputs would render now.
|
|
62
|
+
- `DRIFT` (exit 1) — the committed file disagrees with current inputs
|
|
63
|
+
(hand-edited, or the corpus/policy/ledger moved and nobody re-ran
|
|
64
|
+
`canon report`).
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
canon report --check # wire into CI exactly like canon format --check / canon gate check
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Wire it as a CI step or pre-push hook the same way `canon gate
|
|
71
|
+
install-hooks` wires `canon gate check`; run `canon report --check`
|
|
72
|
+
directly wherever CI already runs those.
|
|
73
|
+
|
|
74
|
+
Exports all ten views to `<dir>/<table>.parquet` plus a
|
|
75
|
+
`<dir>/manifest.json`. One explicit `COPY` per view — the exported
|
|
76
|
+
columns are each view's own `SELECT` list, wider than the curated column
|
|
77
|
+
subset the markdown tables render. `file` is always
|
|
78
|
+
`<table>.parquet`, and the table order is the report's own panel order,
|
|
79
|
+
with `mart_scope_status`, `mart_subjects`, `mart_review_rounds`,
|
|
80
|
+
`mart_review_totals` and `mart_run_observability` APPENDED last so an
|
|
81
|
+
existing consumer's order never moves:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"generated_at": "...",
|
|
86
|
+
"source_git_sha": "...",
|
|
87
|
+
"source_digest": "...",
|
|
88
|
+
"tables": [
|
|
89
|
+
{ "table": "mart_trust_matrix", "file": "mart_trust_matrix.parquet" },
|
|
90
|
+
{ "table": "mart_session_costs", "file": "mart_session_costs.parquet" },
|
|
91
|
+
{ "table": "mart_role_memory", "file": "mart_role_memory.parquet" },
|
|
92
|
+
{ "table": "mart_flywheel_funnel", "file": "mart_flywheel_funnel.parquet" },
|
|
93
|
+
{ "table": "mart_review_burndown", "file": "mart_review_burndown.parquet" },
|
|
94
|
+
{ "table": "mart_scope_status", "file": "mart_scope_status.parquet" },
|
|
95
|
+
{ "table": "mart_subjects", "file": "mart_subjects.parquet" },
|
|
96
|
+
{ "table": "mart_review_rounds", "file": "mart_review_rounds.parquet" },
|
|
97
|
+
{ "table": "mart_review_totals", "file": "mart_review_totals.parquet" },
|
|
98
|
+
{ "table": "mart_run_observability", "file": "mart_run_observability.parquet" }
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This is where `source_git_sha`/`generated_at` live (never in the
|
|
104
|
+
drift-checked markdown header) — a snapshot is output-inclusive
|
|
105
|
+
provenance. `source_digest` is one 12-hex fingerprint over the same
|
|
106
|
+
corpus/policy/ledger digests the report header renders, so a snapshot's
|
|
107
|
+
provenance is comparable to a report's without re-deriving anything.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
canon report --snapshot /tmp/canon-snap
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## `canon dashboard [--repo <dir>] [--snapshot <dir>] [--port <n>]`
|
|
114
|
+
|
|
115
|
+
Serves the dashboard web app locally, pointed at a real snapshot:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# build the app once
|
|
119
|
+
bun install && bun run --cwd packages/dashboard build
|
|
120
|
+
|
|
121
|
+
canon dashboard # regenerates a fresh snapshot every run, serves it
|
|
122
|
+
canon dashboard --snapshot /tmp/canon-snap --port 8080
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- `--snapshot` omitted: `canon dashboard` calls `canon report --snapshot`
|
|
126
|
+
itself every run, into the conventional `.canon/dashboard-snapshot`
|
|
127
|
+
scratch directory — always fresh.
|
|
128
|
+
- `--snapshot <dir>` given: served AS-IS when `<dir>` already has a
|
|
129
|
+
`manifest.json` (a separately generated snapshot, or a downloaded CI
|
|
130
|
+
artifact) — never silently regenerated over it. If it has no
|
|
131
|
+
`manifest.json`, one is generated there once.
|
|
132
|
+
- Prints the URL to open, then serves until Ctrl-C. If the app is not
|
|
133
|
+
built, `canon dashboard` fails with the exact build command to run.
|
|
134
|
+
|
|
135
|
+
The dashboard renders SEVEN panels — trust matrix, session costs, role
|
|
136
|
+
memory, flywheel funnel, review burn-down, review rounds, review totals.
|
|
137
|
+
**Scope status
|
|
138
|
+
and subjects are exported to the snapshot but have no dashboard panel**;
|
|
139
|
+
for those two, read `.canon/REPORT.md` or query the Parquet directly.
|
|
140
|
+
Do not read a missing dashboard panel as an empty mart.
|
|
141
|
+
|
|
142
|
+
The dashboard's freshness banner shows the snapshot's
|
|
143
|
+
`generated_at`/`source_git_sha`/`source_digest` with a "scoped to this
|
|
144
|
+
snapshot, not necessarily the live checkout" note — treat "matches
|
|
145
|
+
`canon report`" as "matches the inputs that produced THIS snapshot".
|
|
146
|
+
|
|
147
|
+
## Reading the ten panels
|
|
148
|
+
|
|
149
|
+
In `canon report`'s own render order. Each panel names the view that
|
|
150
|
+
computes it; nothing here is derived a second time.
|
|
151
|
+
|
|
152
|
+
- **Trust matrix** (`mart_trust_matrix`) — Change/task coverage × green × who.
|
|
153
|
+
`covered` means at least one `evidence_record` exists for this `task_id`,
|
|
154
|
+
NOT that policy's REQUIRED evidence is present — that is `canon gate
|
|
155
|
+
check`'s question, and this view deliberately does not answer it. `green`
|
|
156
|
+
is the plain `verdict = 'faithful'` proxy over the latest attestation, not
|
|
157
|
+
`canon-gate`'s multi-rung trust-ladder classifier, which the view refuses
|
|
158
|
+
to re-implement in SQL. `evidence_count` counts ATTESTATIONS, superseded
|
|
159
|
+
versions included, because a re-attestation can only arrive as a new
|
|
160
|
+
version; `green` and `who` are read off one whole winner row, so they can
|
|
161
|
+
never come from two different attestations.
|
|
162
|
+
|
|
163
|
+
- **Session costs** (`mart_session_costs`) — token/cost per
|
|
164
|
+
`(session, client, role, workspace_label)`. Every `run`/`session`/
|
|
165
|
+
`token_usage` record is folded to its latest version first, so
|
|
166
|
+
a re-ingested corrected cost REPLACES the superseded figure instead of being summed with it.
|
|
167
|
+
`workspace_label` is NOT a repo identity: it is the last non-empty path
|
|
168
|
+
segment of the `token_usage` event's workspace key and nothing more, so it
|
|
169
|
+
SPLITS one repo whose worktrees sit in differently-named directories and
|
|
170
|
+
MERGES two different repos sharing a directory name. Two stronger fields
|
|
171
|
+
exist and this mart reads neither — the event's own `workspace_key`, and
|
|
172
|
+
`Session.project_key`, which `canon-cli` stamps to the main worktree's key.
|
|
173
|
+
|
|
174
|
+
- **Role memory** (`mart_role_memory`) — per-`(role, regime_key)` strategy
|
|
175
|
+
counts. `hit_rate` is NOT a retrieval hit rate: it is the fraction of that
|
|
176
|
+
namespace's distilled strategies carrying no `demotion` flag, i.e.
|
|
177
|
+
exactly `active_count / strategy_count`.
|
|
178
|
+
`avg_source_trajectories` is the mean number of source trajectories a
|
|
179
|
+
strategy was distilled from — an explicitly-named stand-in, because
|
|
180
|
+
canon records no per-strategy reward or effect metric.
|
|
181
|
+
There is no effect column, and this panel measures no effect.
|
|
182
|
+
|
|
183
|
+
- **Flywheel funnel** (`mart_flywheel_funnel`) — verdicts → distilled →
|
|
184
|
+
retrieved → applied, per role, where
|
|
185
|
+
the last three stages count STRATEGIES, so the funnel narrows by construction.
|
|
186
|
+
`applied` is the retrieved set narrowed to strategies whose recipient run
|
|
187
|
+
has something recorded about how it ended, split into
|
|
188
|
+
`applied_attributed` (a resolved trajectory of the same role stamped with
|
|
189
|
+
that run id) and `applied_proxy` (only the run's own terminal status).
|
|
190
|
+
Both rules are CO-OCCURRENCE inside one run, never causation:
|
|
191
|
+
canon stores no edge from a strategy to a verdict,
|
|
192
|
+
so neither column can separate guidance that was followed from guidance
|
|
193
|
+
that was ignored. A large drop between two stages is the actionable
|
|
194
|
+
signal; `retrieved 0` means no run's recorded guidance names a strategy
|
|
195
|
+
that exists now.
|
|
196
|
+
|
|
197
|
+
- **Review burn-down** (`mart_review_burndown`) — a per-day trend over raw
|
|
198
|
+
`Divergence.status` events, with the `evidence_*` columns breaking the same
|
|
199
|
+
window down by `EvidenceRecord.verdict`. `divergence_open_running_total` is
|
|
200
|
+
a running `opened - resolved` event count, NOT the number open now —
|
|
201
|
+
the two differ whenever one `resolved` record closes several findings on a
|
|
202
|
+
scenario. For current state per scenario, run `canon divergence status`.
|
|
203
|
+
|
|
204
|
+
- **Scope status** (`mart_scope_status`) —
|
|
205
|
+
Every specified scenario × evidence-verified × plan-carried × spec-covered (`mart_scope_status`). Driven by the spec corpus, so a scenario no plan task declares still appears — with a NULL `task_id`, which is the worklist entry.
|
|
206
|
+
Grain is one row per authored `(project_id, scenario_id)`; the driving
|
|
207
|
+
side carries the project, so two spec roots authoring one scenario id
|
|
208
|
+
are two rows by construction. `evidence_covered`/`green` count either
|
|
209
|
+
side — the task the scenario is declared by, or scenario-keyed evidence
|
|
210
|
+
authored directly against it (`scenario_verdict`). `spec_covered IS
|
|
211
|
+
NULL` means NO coverage overlay row exists for that scenario — a
|
|
212
|
+
different state from an overlay row that exists and says
|
|
213
|
+
`spec_covered = false`. Read-only reporting; never a `canon gate` input.
|
|
214
|
+
|
|
215
|
+
- **Subjects** (`mart_subjects`) —
|
|
216
|
+
Per-domain subject rollup: status × scenario coverage.
|
|
217
|
+
One row per `subject` record, folded to its latest version, so a subject
|
|
218
|
+
that walked proposed → shipped is still one row. `scenario_count` is how
|
|
219
|
+
many `scenario_ids` the subject links; `covered_scenarios` is how many of
|
|
220
|
+
those carry a latest NON-divergent evidence verdict (`faithful` or
|
|
221
|
+
`not_applicable`), the same last-wins fold the `verifying -> shipped` gate
|
|
222
|
+
uses. Read-only reporting; never a `canon gate` input. See `canon-subject`.
|
|
223
|
+
|
|
224
|
+
- **Review rounds** (`mart_review_rounds`) — one row per `(change_id, round)`
|
|
225
|
+
over `Finding` records (`canon finding add`, then `canon finding close` to
|
|
226
|
+
move a finding's disposition), folded to the latest version
|
|
227
|
+
of each `{change_id}__{round}__{seq}` finding first, so a finding raised
|
|
228
|
+
`open` and later closed is counted once and in one disposition bucket.
|
|
229
|
+
A round that found
|
|
230
|
+
nothing wrote no `Finding` and so has no row: the table
|
|
231
|
+
counts the rounds that FOUND something, never the rounds RUN
|
|
232
|
+
(s42's round 12 returned MERGEABLE with zero findings and is absent),
|
|
233
|
+
and canon has no record kind that marks a round as run.
|
|
234
|
+
`fix_of_fix` counts
|
|
235
|
+
findings whose SOURCED `introduced_by` equals the `resolution_sha` of a
|
|
236
|
+
finding earlier in the SAME change, ordered strictly by `(round, seq)`.
|
|
237
|
+
That is
|
|
238
|
+
a commit-id equality join over two recorded fields and nothing more:
|
|
239
|
+
it reads no git history, computes no blame,
|
|
240
|
+
and establishes no causal claim.
|
|
241
|
+
`introduced_by_unsourced` is the UNKNOWN bucket —
|
|
242
|
+
a finding with no sourced `introduced_by` is counted there and NEVER as
|
|
243
|
+
not-a-fix-of-fix.
|
|
244
|
+
What the count MEANS is one sentence, and every surface that reports it
|
|
245
|
+
states this sentence and no paraphrase of it:
|
|
246
|
+
`fix_of_fix` bounds NOTHING — not from below, not from above: it
|
|
247
|
+
UNDER-counts, because an unsourced finding is never counted and a fix in
|
|
248
|
+
one change that breaks something first found while reviewing a DIFFERENT
|
|
249
|
+
change is not counted at all; it OVER-counts, because a `resolution_sha`
|
|
250
|
+
commit may carry work BEYOND the fix and every finding recording that
|
|
251
|
+
commit is counted regardless; and for any individual match the data
|
|
252
|
+
cannot say whether the fix or the other work in that commit introduced
|
|
253
|
+
the defect.
|
|
254
|
+
Both directions are live in canon's own rows —
|
|
255
|
+
`f438c610` closed s42's round 8 AND shipped s42's whole feature, so
|
|
256
|
+
round 9's six matches cannot be attributed either way.
|
|
257
|
+
`reviewed_sha` is the greatest value any of
|
|
258
|
+
the round's findings recorded and reads `—` when none did; a round that
|
|
259
|
+
reviewed an uncommitted working tree is the usual reason, but the view
|
|
260
|
+
cannot distinguish that from findings that left the field unset.
|
|
261
|
+
|
|
262
|
+
- **Review totals** (`mart_review_totals`) — one row per `change_id`,
|
|
263
|
+
and every column a `sum()`, `count(*)` or `max()` over the
|
|
264
|
+
`mart_review_rounds` rows above. This is the panel a release note is
|
|
265
|
+
copied from: the per-round table left "N findings across R rounds" as
|
|
266
|
+
hand arithmetic, and hand arithmetic over a generated table is what
|
|
267
|
+
put four wrong figures into this release line's published notes, so
|
|
268
|
+
this panel exists so a release note is a COPY rather than a
|
|
269
|
+
computation. The view's only `FROM` is `mart_review_rounds`, and
|
|
270
|
+
every panel in one report — or one `--snapshot` export — is computed
|
|
271
|
+
in a single DuckDB process over one materialized read of the corpus,
|
|
272
|
+
so the total and the rows it totals are one computation over one
|
|
273
|
+
input and cannot disagree: a record written to the ledger mid-run
|
|
274
|
+
reaches BOTH tables or neither, never one and not the other — though the
|
|
275
|
+
digest header beside these panels is a SEPARATE read outside that pin,
|
|
276
|
+
taken before it in a report and after it in a `--snapshot`, so header
|
|
277
|
+
and panels can still describe the corpus a moment apart. (Both halves
|
|
278
|
+
matter. The SQL
|
|
279
|
+
argument alone held while `canon report` ran one `duckdb` process per
|
|
280
|
+
mart against a live ledger, and a finding written between two of them
|
|
281
|
+
reached one panel and not the other — s43 round 6's blocker.
|
|
282
|
+
`crates/canon-report/src/query.rs`'s `PIN_CORPUS_SQL` is the other
|
|
283
|
+
half.)
|
|
284
|
+
`rounds_recorded` is `count(*)` over those rows, and that view holds a
|
|
285
|
+
row only for a round that RECORDED a finding, so
|
|
286
|
+
this column counts the rounds that FOUND something, never the rounds RUN;
|
|
287
|
+
canon has no record kind for a review round, so the rounds-RUN number
|
|
288
|
+
is not derivable from this corpus at all. `highest_round` is
|
|
289
|
+
`max(round)`, and it witnesses nothing about rounds: above
|
|
290
|
+
`rounds_recorded` it says only that some round number below it has no
|
|
291
|
+
row, and canon cannot say why — `round` is author-supplied and
|
|
292
|
+
nothing requires a change's rounds to be numbered from 1 or without
|
|
293
|
+
gaps, so a change whose only finding is labelled round 7 shows the
|
|
294
|
+
same gap six silent rounds would. Neither the gap nor its absence
|
|
295
|
+
evidences a round that RAN. Neither column is the rounds-RUN count —
|
|
296
|
+
`highest_round` is a round NUMBER, not a count, and `round` is
|
|
297
|
+
author-supplied. The splits sum the per-round `FILTER` counts, so
|
|
298
|
+
`severity_* = findings`, `disposition_* = findings` and
|
|
299
|
+
`introduced_by_sourced + introduced_by_unsourced = findings` all hold
|
|
300
|
+
at this grain too. `fix_of_fix` is `sum(fix_of_fix)` over a column
|
|
301
|
+
nothing records, and it means exactly what it means one panel up:
|
|
302
|
+
`fix_of_fix` bounds NOTHING — not from below, not from above: it
|
|
303
|
+
UNDER-counts, because an unsourced finding is never counted and a fix in
|
|
304
|
+
one change that breaks something first found while reviewing a DIFFERENT
|
|
305
|
+
change is not counted at all; it OVER-counts, because a `resolution_sha`
|
|
306
|
+
commit may carry work BEYOND the fix and every finding recording that
|
|
307
|
+
commit is counted regardless; and for any individual match the data
|
|
308
|
+
cannot say whether the fix or the other work in that commit introduced
|
|
309
|
+
the defect.
|
|
310
|
+
There is no defect rate, no quality score and no comparison between changes here:
|
|
311
|
+
a row is one change's review history, and `findings` moves with how
|
|
312
|
+
much reviewing happened rather than with the change.
|
|
313
|
+
|
|
314
|
+
Two marts in `views.sql` are NOT report panels and are not exported by
|
|
315
|
+
`--snapshot`: `mart_records_by_kind` and `mart_session_run_handoff`.
|
|
316
|
+
Read them with DuckDB directly against `views.sql`.
|
|
317
|
+
|
|
318
|
+
- **Run observability** (`mart_run_observability`) — one row per folded
|
|
319
|
+
`run_id`, appended as the tenth report/snapshot table after the established
|
|
320
|
+
nine tables. It reports lifecycle identifiers/status/timestamps, latency
|
|
321
|
+
only when both timestamps exist, event and label counts, child-run count,
|
|
322
|
+
guidance count, lineage provider/model, and token/cost source totals. These
|
|
323
|
+
are bounded observations and proxies: event counts do not infer tool calls,
|
|
324
|
+
retries, or human interventions, and no field claims guidance caused an
|
|
325
|
+
outcome. `directive_event_count` and `guidance_count` are counts only;
|
|
326
|
+
directive and guidance text is never emitted. `*_unknown` columns explicitly
|
|
327
|
+
expose missing joins/coverage, including missing session/project/task/lineage,
|
|
328
|
+
end timestamps, event times, costs, and guidance. Workspace absolute paths
|
|
329
|
+
and verbatim directives/guidance are intentionally omitted. The adapter
|
|
330
|
+
contract is strict read-only validation; this panel does not execute
|
|
331
|
+
adapters or enforce provider sandbox capabilities. Privacy-safe omission of
|
|
332
|
+
paths and directive/guidance text is part of the current semantics.
|
|
333
|
+
|
|
334
|
+
## What this skill does NOT cover
|
|
335
|
+
|
|
336
|
+
- The underlying view layer that computes the marts — see `canon-storage`.
|
|
337
|
+
- `canon gate`'s evidence-gated checkbox flip / trust ladder — see
|
|
338
|
+
`canon-gate`. The trust-matrix and scope-status panels READ the same
|
|
339
|
+
corpus `canon gate check` validates but never gate or flip anything.
|
|
340
|
+
- Strategy distillation/promotion — see `canon-learn`; the role-memory and
|
|
341
|
+
flywheel panels are read-only views over that store.
|
|
342
|
+
- Authoring subjects and their status lifecycle — see `canon-subject`; the
|
|
343
|
+
subjects panel is the read-only rollup over those records.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# canon-retrieve
|
|
2
|
+
|
|
3
|
+
`canon retrieve` surfaces a role's promoted strategy memory (see
|
|
4
|
+
`canon-learn`) at task-dispatch time. It is a fail-soft, advisory-only
|
|
5
|
+
read: it NEVER blocks a dispatch and NEVER gates reproducibility.
|
|
6
|
+
|
|
7
|
+
## `canon retrieve --role <r> --regime <k> [--k <n>] [--repo <dir>] [--json]`
|
|
8
|
+
|
|
9
|
+
Prints the top-`k` retrievable strategies stored for `regime_key = <k>`
|
|
10
|
+
(active strategies plus backward-compatible legacy rows without a lifecycle;
|
|
11
|
+
guardrails are just strategies distilled from a failure-polarity verdict),
|
|
12
|
+
newest-distilled first, capped at `--k` (default 5). Demoted,
|
|
13
|
+
quarantined, rejected, and rolled-back rows are not retrievable.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
canon retrieve --role dev --regime dev/canon/join-spine/9c93d024b1a2
|
|
17
|
+
canon retrieve --role dev --regime dev/canon/join-spine/9c93d024b1a2 --k 3 --json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- `--regime` is the FULL `regime_key` string
|
|
21
|
+
(`<role>/<repo>/<area>/<hash>`) — the same serialization the write side
|
|
22
|
+
produces.
|
|
23
|
+
- Assembling `--regime` in a script or hook? Use `canon regime-key
|
|
24
|
+
--role <r> --repo <r> --area <a> --hash <h>`: it runs canon's ONE
|
|
25
|
+
canonical `regime_key` serializer and prints the validated key, so a
|
|
26
|
+
caller routes through the exact normalizer the write path uses instead
|
|
27
|
+
of a drifting shell slug. Exits `2` with empty stdout on a malformed
|
|
28
|
+
result (empty segment / bad `<hash>`) — a clean fail-soft branch for
|
|
29
|
+
`|| exit 0` callers.
|
|
30
|
+
- `--role` MUST equal `--regime`'s leading segment — a mismatch is a
|
|
31
|
+
clean usage error, exit `2` (mirroring `canon gate check`'s
|
|
32
|
+
0-clean/1-red/2-usage convention), never a panic or a silently-ignored
|
|
33
|
+
mismatch.
|
|
34
|
+
- `--repo` resolves through the same nearest-`canon.yaml`-ancestor walk
|
|
35
|
+
`canon context`/`canon format`/`canon gate` use. The resolved
|
|
36
|
+
`canon.yaml` `learn:` section names the store root (`.canon/learn` by
|
|
37
|
+
default); a repo with no `canon.yaml` still works, defaulting cleanly.
|
|
38
|
+
|
|
39
|
+
**FAIL-SOFT, always exit `0`** once `--role`/`--regime` agree: a store
|
|
40
|
+
outage, an empty/nonexistent store, or a malformed on-disk row all
|
|
41
|
+
degrade to an empty guidance list — never a nonzero exit or an error a
|
|
42
|
+
caller must handle.
|
|
43
|
+
|
|
44
|
+
## The pre-dispatch hook (`scripts/canon-retrieve-pre-dispatch.sh`)
|
|
45
|
+
|
|
46
|
+
A generic, fail-soft, advisory-only shell script that fires at a
|
|
47
|
+
PreToolUse-equivalent point for a `task`-shaped dispatch (Claude Code's
|
|
48
|
+
`Task` tool / Codex's equivalent). It reads the tool's `subagent_type`
|
|
49
|
+
as `--role`, assembles `--regime` by routing its repo/area segments
|
|
50
|
+
through `canon regime-key` (so it never re-derives the join key with a
|
|
51
|
+
drifting shell slug), calls `canon retrieve --json`, and emits the
|
|
52
|
+
result as a `PreToolUse` `hookSpecificOutput.additionalContext` JSON
|
|
53
|
+
envelope on stdout — Claude Code's convention for surfacing advisory
|
|
54
|
+
context without a permission decision. It NEVER blocks: a missing
|
|
55
|
+
`canon`/`jq`, a non-`Task` tool call, or empty guidance are all silent
|
|
56
|
+
no-ops, exit `0`.
|
|
57
|
+
|
|
58
|
+
Segment sources and override env vars
|
|
59
|
+
(`CANON_RETRIEVE_ROLE`/`CANON_RETRIEVE_AREA`/`CANON_RETRIEVE_HASH`/
|
|
60
|
+
`CANON_RETRIEVE_REGIME`) are documented in the script's own header.
|
|
61
|
+
|
|
62
|
+
### Wiring it
|
|
63
|
+
|
|
64
|
+
Place the script wherever your repo keeps materialized hook scripts
|
|
65
|
+
(e.g. `.claude/hooks/canon-retrieve-pre-dispatch.sh`), then register the
|
|
66
|
+
hook-seam entry with `canon gate install-hooks`:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
canon gate install-hooks --repo . --event PreToolUse \
|
|
70
|
+
--matcher Task --command "sh .claude/hooks/canon-retrieve-pre-dispatch.sh" \
|
|
71
|
+
--timeout 15
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This merges the standard `{matcher, hooks: [{type: "command", command,
|
|
75
|
+
timeout}]}` entry into BOTH `<repo>/.claude/settings.json` and
|
|
76
|
+
`<repo>/.codex/hooks.json`, additive and idempotent — see the
|
|
77
|
+
`canon-gate` skill for `install-hooks`'s full contract.
|
|
78
|
+
|
|
79
|
+
## Replay vs. live retrieval
|
|
80
|
+
|
|
81
|
+
A run manifest records `injected_guidance` — a full SNAPSHOT (each entry
|
|
82
|
+
`{strategy_id, title, content}`) of what `canon retrieve` returned at
|
|
83
|
+
dispatch time, visible in `--json` output. It is never a live pointer, so
|
|
84
|
+
a later edit or demotion of the source strategy can never retroactively
|
|
85
|
+
change what a manifest already recorded.
|
|
86
|
+
|
|
87
|
+
- **New run:** dispatch calls `canon retrieve` (live), then records the
|
|
88
|
+
result into the run manifest's `injected_guidance`.
|
|
89
|
+
- **Replay:** reuse the manifest's recorded `injected_guidance` UNCHANGED
|
|
90
|
+
— never a fresh `canon retrieve` call. A replay MUST read the stored
|
|
91
|
+
guidance, so a live store change after the original run (a strategy
|
|
92
|
+
added, edited, demoted, or removed) can never perturb the replay.
|
|
93
|
+
|
|
94
|
+
`canon retrieve` and the pre-dispatch script work standalone today — a
|
|
95
|
+
caller can print retrieved guidance with zero manifest integration.
|
|
96
|
+
|
|
97
|
+
## Lifecycle and legacy limitations
|
|
98
|
+
|
|
99
|
+
Retrieval is a read path, not an activation or evidence path. It serves only
|
|
100
|
+
exact role/regime matches and excludes `quarantined`, `rejected`,
|
|
101
|
+
`rolled_back`, and demoted strategies. Newly distilled strategies are
|
|
102
|
+
quarantined by default and therefore do not appear until the paired evaluation
|
|
103
|
+
and signed approval workflow in `canon-learn` activates them.
|
|
104
|
+
|
|
105
|
+
Rows written before lifecycle tracking (`lifecycle: null`) remain readable for
|
|
106
|
+
backward compatibility when they are not demoted. They have no current
|
|
107
|
+
quarantine, paired-evaluation, or SSH-approval provenance; retrieval MUST NOT
|
|
108
|
+
interpret them as proof of the signed workflow, upgrade them, or claim
|
|
109
|
+
measured uplift. The repository currently has no published measured uplift
|
|
110
|
+
result.
|
|
111
|
+
|
|
112
|
+
Role/regime retrieval is exact and advisory, not similarity search or a
|
|
113
|
+
quality guarantee. A live lookup is also not a replayable proof: dispatch
|
|
114
|
+
manifests snapshot the returned `{strategy_id, title, content}` entries, and
|
|
115
|
+
replay uses that snapshot rather than refreshing the store.
|
|
116
|
+
|
|
117
|
+
## What this skill does NOT cover
|
|
118
|
+
|
|
119
|
+
- Strategy distillation/promotion/demotion — see the `canon-learn`
|
|
120
|
+
skill.
|
|
121
|
+
- `canon gate install-hooks`'s full contract — see the
|
|
122
|
+
`canon-gate` skill; this skill documents only the one entry it
|
|
123
|
+
installs for `canon retrieve`.
|