@onlooker-community/ecosystem 0.34.0 → 0.43.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/.claude/skills/writing-tests/SKILL.md +27 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.github/workflows/release.yml +14 -14
- package/.release-please-manifest.json +7 -7
- package/AGENTS.md +116 -0
- package/CHANGELOG.md +175 -0
- package/CLAUDE.md +5 -0
- package/docs/lesson-promotion-pipeline.md +6 -6
- package/docs/superpowers/plans/2026-08-10-lesson-confirmation.md +1273 -0
- package/docs/superpowers/plans/2026-08-11-lesson-judging.md +1232 -0
- package/docs/superpowers/plans/2026-08-11-lesson-unconfirm.md +458 -0
- package/docs/superpowers/plans/2026-08-12-author-key.md +567 -0
- package/docs/superpowers/plans/2026-08-13-approved-pool.md +747 -0
- package/docs/superpowers/plans/2026-08-13-librarian-cluster.md +392 -0
- package/docs/superpowers/plans/2026-08-14-criterion-scores-consumers.md +1374 -0
- package/docs/superpowers/plans/2026-08-14-criterion-scores-schema.md +277 -0
- package/docs/superpowers/plans/2026-08-16-cartographer-undocumented-entity.md +741 -0
- package/docs/superpowers/specs/2026-08-10-lesson-confirmation-design.md +213 -0
- package/docs/superpowers/specs/2026-08-11-lesson-judging-design.md +267 -0
- package/docs/superpowers/specs/2026-08-11-lesson-unconfirm-design.md +152 -0
- package/docs/superpowers/specs/2026-08-12-author-key-design.md +317 -0
- package/docs/superpowers/specs/2026-08-13-approved-pool-design.md +232 -0
- package/docs/superpowers/specs/2026-08-14-criterion-scores-design.md +254 -0
- package/docs/superpowers/specs/2026-08-16-cartographer-undocumented-entity-design.md +249 -0
- package/package.json +2 -2
- package/plugins/assayer/.claude-plugin/plugin.json +1 -1
- package/plugins/assayer/CHANGELOG.md +7 -0
- package/plugins/assayer/scripts/lib/assayer-config.sh +6 -0
- package/plugins/cartographer/.claude-plugin/plugin.json +1 -1
- package/plugins/cartographer/CHANGELOG.md +54 -0
- package/plugins/cartographer/README.md +56 -1
- package/plugins/cartographer/config.json +7 -1
- package/plugins/cartographer/scripts/hooks/cartographer-post-write.sh +4 -4
- package/plugins/cartographer/scripts/hooks/cartographer-session-start.sh +7 -4
- package/plugins/cartographer/scripts/lib/cartographer-collect.sh +8 -1
- package/plugins/cartographer/scripts/lib/cartographer-config.sh +26 -0
- package/plugins/cartographer/scripts/lib/cartographer-events.sh +90 -0
- package/plugins/cartographer/scripts/lib/cartographer-filter.sh +105 -0
- package/plugins/cartographer/scripts/lib/cartographer-omission.sh +130 -0
- package/plugins/cartographer/scripts/lib/cartographer-resolve.sh +132 -0
- package/plugins/cartographer/scripts/run-audit.sh +136 -53
- package/plugins/cartographer/skills/cartographer/SKILL.md +33 -9
- package/plugins/curator/.claude-plugin/plugin.json +1 -1
- package/plugins/curator/CHANGELOG.md +7 -0
- package/plugins/curator/scripts/lib/curator-emit.sh +2 -1
- package/plugins/historian/.claude-plugin/plugin.json +1 -1
- package/plugins/historian/CHANGELOG.md +7 -0
- package/plugins/historian/scripts/lib/historian-emit.sh +2 -1
- package/plugins/librarian/.claude-plugin/plugin.json +1 -1
- package/plugins/librarian/CHANGELOG.md +71 -0
- package/plugins/librarian/config.json +30 -1
- package/plugins/librarian/docs/adr/002-agent-definitions-are-shared-assets.md +82 -0
- package/plugins/librarian/scripts/hooks/librarian-session-end.sh +28 -0
- package/plugins/librarian/scripts/hooks/librarian-session-start.sh +42 -12
- package/plugins/librarian/scripts/lib/librarian-author-key.sh +279 -0
- package/plugins/librarian/scripts/lib/librarian-cli.sh +346 -2
- package/plugins/librarian/scripts/lib/librarian-emit.sh +2 -1
- package/plugins/librarian/scripts/lib/librarian-lesson-judge.sh +349 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-promote.sh +225 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-review.sh +312 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-rubric.sh +56 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-storage.sh +82 -13
- package/plugins/librarian/scripts/lib/librarian-lesson-transform.sh +6 -1
- package/plugins/librarian/scripts/lib/librarian-lesson-validate.sh +131 -62
- package/plugins/librarian/skills/librarian/SKILL.md +98 -1
- package/plugins/tribunal/.claude-plugin/plugin.json +1 -1
- package/plugins/tribunal/CHANGELOG.md +63 -0
- package/plugins/tribunal/agents/tribunal-judge-adversarial.md +34 -0
- package/plugins/tribunal/agents/tribunal-judge-security.md +33 -0
- package/plugins/tribunal/agents/tribunal-judge-standard.md +12 -0
- package/plugins/tribunal/config.json +1 -0
- package/plugins/tribunal/scripts/lib/tribunal-aggregate.sh +154 -9
- package/plugins/tribunal/scripts/lib/tribunal-gate.sh +195 -16
- package/plugins/tribunal/skills/tribunal/SKILL.md +5 -3
- package/scripts/lib/prompt-rules.sh +6 -1
- package/test/bats/archivist-inject.bats +1 -1
- package/test/bats/assayer-extract.bats +2 -2
- package/test/bats/bursar-session-start.bats +3 -3
- package/test/bats/cartographer-config.bats +43 -0
- package/test/bats/cartographer-events.bats +267 -0
- package/test/bats/cartographer-filter.bats +169 -0
- package/test/bats/cartographer-lock.bats +3 -3
- package/test/bats/cartographer-omission.bats +202 -0
- package/test/bats/cartographer-resolve.bats +287 -0
- package/test/bats/cartographer-run-audit.bats +325 -0
- package/test/bats/compass-sanitizer.bats +11 -11
- package/test/bats/compass-transcript.bats +2 -2
- package/test/bats/config.bats +15 -15
- package/test/bats/curator-session-start.bats +10 -3
- package/test/bats/emit-payload-default.bats +52 -0
- package/test/bats/governor-ledger.bats +1 -1
- package/test/bats/historian-prompt-submit.bats +1 -1
- package/test/bats/inspector-post-write-hook.bats +4 -4
- package/test/bats/librarian-author-key.bats +477 -0
- package/test/bats/librarian-cli.bats +16 -16
- package/test/bats/librarian-lesson-judge.bats +884 -0
- package/test/bats/librarian-lesson-promote.bats +552 -0
- package/test/bats/librarian-lesson-review.bats +900 -0
- package/test/bats/librarian-lesson-transform.bats +17 -3
- package/test/bats/librarian-session-end.bats +161 -0
- package/test/bats/librarian-session-start.bats +36 -2
- package/test/bats/lineage-config.bats +1 -1
- package/test/bats/lineage-redact.bats +5 -5
- package/test/bats/session-tracker.bats +4 -4
- package/test/bats/tribunal-aggregate.bats +354 -1
- package/test/bats/tribunal-gate.bats +631 -0
- package/test/bats/tribunal-judge-agents.bats +119 -0
- package/test/bats/tribunal-jury.bats +1 -1
- package/test/bats/turn-tracker.bats +1 -1
- package/test/bats/warden-sanitizer.bats +3 -3
- package/test/bats/worktree-tracker.bats +2 -2
- package/test/node/lesson-validate-agreement.test.mjs +35 -6
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Per-Criterion Scores — Design
|
|
2
|
+
|
|
3
|
+
**Status:** Approved, not started.
|
|
4
|
+
**Tracked by:** `ecosystem-pht`.
|
|
5
|
+
**Spans two repositories.** The payload lives in `@onlooker-community/schema`;
|
|
6
|
+
the consumers live here.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## What this is
|
|
11
|
+
|
|
12
|
+
Tribunal's rubrics declare a `weight` and a `min_pass` per criterion, and
|
|
13
|
+
`tribunal-rubric.sh` validates both are numbers in `[0,1]`. **Neither is ever
|
|
14
|
+
applied.**
|
|
15
|
+
|
|
16
|
+
`tribunal_aggregate` takes the rubric as its third parameter and explicitly
|
|
17
|
+
discards it (`: "$_rubric"`); `weighted_mean` falls through to the same jq
|
|
18
|
+
expression as `mean`, a plain average of each judge's single overall score.
|
|
19
|
+
`min_pass` is enforced nowhere at all, because `TribunalVerdictPayload` carries
|
|
20
|
+
one scalar `score`, one boolean `passed`, and `criteria_evaluated` — a list of
|
|
21
|
+
criterion *names* with no scores attached. The orchestrator never learns what a
|
|
22
|
+
judge scored on any individual criterion.
|
|
23
|
+
|
|
24
|
+
So a rubric cannot express "this one criterion is a floor." A judge scoring 0.3
|
|
25
|
+
on a safety-critical criterion and 0.95 on everything else can still report
|
|
26
|
+
`passed: true`, and under `gate_policy: majority` a single dissenter is
|
|
27
|
+
outvoted regardless.
|
|
28
|
+
|
|
29
|
+
## Why it matters now
|
|
30
|
+
|
|
31
|
+
`ecosystem-4z8.3` shipped a public tier for lesson promotion whose entire
|
|
32
|
+
safety argument was a disclosure floor at `min_pass` 0.9 — a near-veto, on the
|
|
33
|
+
reasoning that correctness rots and `applies_to` retires it, but harm does not:
|
|
34
|
+
a leaked credential never expires on its own.
|
|
35
|
+
|
|
36
|
+
That floor could not be built, so the public tier ships `gate_policy:
|
|
37
|
+
unanimous` instead — a deliberate stand-in intended to deliver the property
|
|
38
|
+
that mattered ("a single judge's objection cannot be outvoted"), though not
|
|
39
|
+
disclosure-specific.
|
|
40
|
+
|
|
41
|
+
**The stand-in does not work.** Both librarian rubrics declare `judge_types:
|
|
42
|
+
["standard", "adversarial"]`, a panel of two, and at panel size two
|
|
43
|
+
`unanimous` (`passed == count`) and `majority` (`passed * 2 > count`) return
|
|
44
|
+
the same answer for every possible pass count — 0, 1, or 2. They diverge only
|
|
45
|
+
at three judges or more, and `librarian_lesson_judge`'s `usable` check refuses
|
|
46
|
+
any panel whose judge-type multiset does not exactly match the rubric's, so a
|
|
47
|
+
third judge never reaches the gate. **The public tier has never been stricter
|
|
48
|
+
than the org tier.**
|
|
49
|
+
|
|
50
|
+
Two individually-correct changes combined to produce an inert one. Tracked as
|
|
51
|
+
`ecosystem-j74`.
|
|
52
|
+
|
|
53
|
+
That makes this design load-bearing rather than an improvement: the public
|
|
54
|
+
tier has no extra protection today, and the floor below is what gives it one.
|
|
55
|
+
The `4z8.3` spec and the `lesson-promotion-public` rubric comment both still
|
|
56
|
+
describe `unanimous` as a working guarantee and must be corrected.
|
|
57
|
+
|
|
58
|
+
## The payload
|
|
59
|
+
|
|
60
|
+
Add `criterion_scores` to `TribunalVerdictPayload`, alongside the existing
|
|
61
|
+
`criteria_evaluated`:
|
|
62
|
+
|
|
63
|
+
```jsonc
|
|
64
|
+
"criterion_scores": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"additionalProperties": { "type": "number", "minimum": 0, "maximum": 1 }
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Optional, not required
|
|
71
|
+
|
|
72
|
+
Three shipped plugins emit this payload, and the runtime emitter validates
|
|
73
|
+
whenever the schema package is resolvable. Making the field required would
|
|
74
|
+
invalidate every existing producer the moment the version bumps.
|
|
75
|
+
|
|
76
|
+
Optional also matches reality during the rollout: the ecosystem consumes the
|
|
77
|
+
new version before its judge agents emit the field, so consumers must handle
|
|
78
|
+
absence anyway.
|
|
79
|
+
|
|
80
|
+
### A map, not a parallel array
|
|
81
|
+
|
|
82
|
+
`criteria_evaluated` is already a name list. A scores array beside it would
|
|
83
|
+
have to stay index-aligned, with nothing enforcing that — and a
|
|
84
|
+
silently-misaligned pair attributes each score to the wrong criterion, which is
|
|
85
|
+
worse than having no scores.
|
|
86
|
+
|
|
87
|
+
A map makes the association explicit and lets the aggregator look up by the
|
|
88
|
+
rubric's own criterion name.
|
|
89
|
+
|
|
90
|
+
### `additionalProperties`, not fixed keys
|
|
91
|
+
|
|
92
|
+
Criterion names come from a rubric, and rubrics are user-extensible per
|
|
93
|
+
ADR-004. Librarian's `lesson-promotion` already defines `grounding`,
|
|
94
|
+
`scope_accuracy`, `generality`, and `disclosure` — none of which appear in
|
|
95
|
+
tribunal's default rubric. Enumerating keys in the schema would make every new
|
|
96
|
+
rubric a schema change.
|
|
97
|
+
|
|
98
|
+
### `criteria_evaluated` and `criterion_scores` may disagree
|
|
99
|
+
|
|
100
|
+
Deliberately not constrained. A judge may evaluate a criterion it cannot score,
|
|
101
|
+
or score one it did not list. Cross-field consistency belongs at ingest, not in
|
|
102
|
+
a JSON Schema — the same reasoning the lesson contract gives for leaving
|
|
103
|
+
`agreed <= judges` out of `ZConsensus`.
|
|
104
|
+
|
|
105
|
+
### The block needs a reason of its own
|
|
106
|
+
|
|
107
|
+
`tribunal.gate.blocked` carries a closed `reason` enum — `low_score`,
|
|
108
|
+
`meta_override`, `bias_detected`, `dissent_unresolved` — under
|
|
109
|
+
`additionalProperties: false`. None of them describes a criterion floor, and
|
|
110
|
+
the whole premise of the floor is a block that fires *while the aggregate
|
|
111
|
+
clears `score_threshold`*. Emitting `low_score` would be false, and
|
|
112
|
+
indistinguishable from a genuine threshold miss.
|
|
113
|
+
|
|
114
|
+
So the same release adds `criterion_floor` to the enum plus an optional
|
|
115
|
+
`failed_criterion` string. It ships here rather than with the consumers
|
|
116
|
+
because the event log is append-only: every floor block emitted before the
|
|
117
|
+
enum existed would be permanently recorded as a low-score block.
|
|
118
|
+
|
|
119
|
+
Librarian's own gate needs no schema change for this — its verdict reason is
|
|
120
|
+
written to on-disk proposal JSON, not to a schema-validated event.
|
|
121
|
+
|
|
122
|
+
### Both files change together
|
|
123
|
+
|
|
124
|
+
`src/types.ts` is **hand-written**; `schemas/payload/plugins-safety.json` is the
|
|
125
|
+
runtime contract. `scripts/generate-types.js` cross-checks them, but its own
|
|
126
|
+
header says divergence is currently a warning rather than a hard failure
|
|
127
|
+
(`TODO(ONL-6 hard fail)`). Nothing will stop the two from drifting, so they must
|
|
128
|
+
be edited in step and the test fixture must exercise the new field.
|
|
129
|
+
|
|
130
|
+
## The consumers
|
|
131
|
+
|
|
132
|
+
Second PR, after the schema version publishes.
|
|
133
|
+
|
|
134
|
+
### Judges emit it
|
|
135
|
+
|
|
136
|
+
`tribunal-judge-standard` already receives the rubric with its criteria and is
|
|
137
|
+
told to "score each criterion in [0,1]". It has nowhere to put the result; its
|
|
138
|
+
output contract gains `criterion_scores`.
|
|
139
|
+
|
|
140
|
+
**Neither `tribunal-judge-adversarial` nor `tribunal-judge-security` has a
|
|
141
|
+
rubric section at all.** Both report `criteria_evaluated` lists drawn from their
|
|
142
|
+
own investigative lenses — `edge-cases`, `concurrency`, `idempotency` for one;
|
|
143
|
+
`injection`, `secrets`, `path-traversal` for the other — disjoint from every
|
|
144
|
+
rubric in the repo.
|
|
145
|
+
|
|
146
|
+
So the adversarial agent needs a rubric section before any floor can be
|
|
147
|
+
trusted, and the consumer plan must state which judge types are expected to
|
|
148
|
+
score which criteria. A floor on a criterion no judge scores is not a floor.
|
|
149
|
+
|
|
150
|
+
### `weighted_mean` becomes real
|
|
151
|
+
|
|
152
|
+
`tribunal_aggregate` already receives the rubric it discards. Weight each
|
|
153
|
+
criterion, then average judges per criterion.
|
|
154
|
+
|
|
155
|
+
**Normalize by the sum of weights rather than assuming 1.0.** Tribunal's own
|
|
156
|
+
`tribunal-rubric.sh` does validate the total, rejecting any rubric outside
|
|
157
|
+
0.99–1.01 — so a mis-summed rubric cannot reach the aggregator through *that*
|
|
158
|
+
path. But librarian does not use it: `librarian_lesson_rubric_get` performs no
|
|
159
|
+
validation at all, and a librarian rubric summing to 1.30 would silently
|
|
160
|
+
mis-score. Normalizing costs one division and removes the difference between
|
|
161
|
+
the two paths.
|
|
162
|
+
|
|
163
|
+
**Degrade to `mean` when scores are absent**, which is the state of every
|
|
164
|
+
verdict emitted before the judges are updated. Silently producing 0 for a
|
|
165
|
+
missing criterion would turn an un-upgraded judge into a blocking one.
|
|
166
|
+
|
|
167
|
+
**Absent must stay distinguishable from zero, in the code as well as the
|
|
168
|
+
contract.** `jq`'s `// 0` idiom is reflexive throughout these libraries and
|
|
169
|
+
collapses exactly that distinction — a judge that did not score a criterion
|
|
170
|
+
would read as a judge that scored it 0.0, the failure mode this whole section
|
|
171
|
+
exists to avoid. Use `has()` or an explicit `null` test.
|
|
172
|
+
|
|
173
|
+
**Look criterion names up with `--arg`, never on a dotted path.** Hyphenated
|
|
174
|
+
names are already in shipped agent contracts (`path-traversal`, `edge-cases`),
|
|
175
|
+
and `jq '.criterion_scores.path-traversal'` is a *compile* error — `jq` exits 3
|
|
176
|
+
with empty stdout, which `awk` then reads as 0. That fails toward blocking, on
|
|
177
|
+
a name the rubric author is entitled to write. `.criterion_scores[$name]` with
|
|
178
|
+
`--arg name` is correct for every name.
|
|
179
|
+
|
|
180
|
+
### Librarian needs signature changes the bead did not budget for
|
|
181
|
+
|
|
182
|
+
Librarian does not call either tribunal function named above. Its
|
|
183
|
+
`librarian_lesson_aggregate` takes **no rubric parameter** and hardcodes a
|
|
184
|
+
plain mean; `librarian_lesson_gate` receives no criteria either. Both need
|
|
185
|
+
their signatures widened before the public tier's floor can exist, and the
|
|
186
|
+
consumer plan must schedule that work rather than assuming tribunal's
|
|
187
|
+
aggregator is shared.
|
|
188
|
+
|
|
189
|
+
### `min_pass` becomes enforceable
|
|
190
|
+
|
|
191
|
+
`tribunal_gate_decide` blocks when any criterion scores below its floor,
|
|
192
|
+
regardless of the aggregate or the gate policy. That is the whole point: a
|
|
193
|
+
floor a strong weighted mean cannot average away.
|
|
194
|
+
|
|
195
|
+
**Absent scores must not block.** A verdict with no `criterion_scores` cannot
|
|
196
|
+
violate a floor it never reported, and treating absence as violation would make
|
|
197
|
+
every pre-upgrade judge fail every rubric with a floor.
|
|
198
|
+
|
|
199
|
+
### The public tier gets its real floor
|
|
200
|
+
|
|
201
|
+
Librarian's `lesson-promotion-public` swaps `gate_policy: unanimous` back to
|
|
202
|
+
`majority`, relying on `disclosure`'s `min_pass: 0.9` to do the blocking.
|
|
203
|
+
|
|
204
|
+
This is the point where the public tier first becomes stricter than the org
|
|
205
|
+
tier in fact rather than in intent — see `ecosystem-j74`. The swap is
|
|
206
|
+
therefore not a simplification, and the decision was to let this work supply
|
|
207
|
+
the protection rather than widen the panel now and pay for a third Opus judge
|
|
208
|
+
on every public candidate.
|
|
209
|
+
|
|
210
|
+
The `4z8.3` spec records `unanimous` as an explicit stand-in and says "when
|
|
211
|
+
`pht` lands, this can narrow to a true per-criterion floor." That spec, and the
|
|
212
|
+
librarian rubric comment describing the weights as inert, both need updating —
|
|
213
|
+
and the correction is not only that the stand-in was replaced, but that it
|
|
214
|
+
never had the effect it claimed.
|
|
215
|
+
|
|
216
|
+
## Testing
|
|
217
|
+
|
|
218
|
+
Schema repo: the existing fixture-based `validate.test.ts`. A verdict carrying
|
|
219
|
+
`criterion_scores` validates; one carrying a score outside `[0,1]` or a non-number
|
|
220
|
+
value does not; one omitting the field entirely still validates, because that is
|
|
221
|
+
every producer today. A gate.blocked with `reason: "criterion_floor"` validates
|
|
222
|
+
with and without `failed_criterion`; an unrecognized reason is still rejected,
|
|
223
|
+
and that test must fail because of the enum rather than
|
|
224
|
+
`additionalProperties`.
|
|
225
|
+
|
|
226
|
+
Ecosystem: bats, isolated temp home.
|
|
227
|
+
|
|
228
|
+
- **`weighted_mean` produces a different result from `mean` when weights are
|
|
229
|
+
unequal.** Today they are identical, so any test that cannot tell them apart
|
|
230
|
+
proves nothing.
|
|
231
|
+
- Weights summing to something other than 1.0 normalize correctly — driven
|
|
232
|
+
through **librarian's** rubric loader, since tribunal's own validator rejects
|
|
233
|
+
such a rubric before the aggregator ever sees it.
|
|
234
|
+
- A criterion below its `min_pass` blocks even when the aggregate clears
|
|
235
|
+
`score_threshold` **and** the gate policy is satisfied — the property that
|
|
236
|
+
does not exist today.
|
|
237
|
+
- A verdict with no `criterion_scores` neither blocks nor scores 0; aggregation
|
|
238
|
+
degrades to `mean`. Distinctly: a verdict scoring a criterion **at** 0.0
|
|
239
|
+
*does* block, proving absence and zero are not conflated.
|
|
240
|
+
- A hyphenated criterion name scores and gates correctly.
|
|
241
|
+
- A rubric whose floor names a criterion **no judge scored** is surfaced rather
|
|
242
|
+
than silently passing — the adversarial-judge gap above, made visible.
|
|
243
|
+
- The public rubric, after the swap, blocks a lesson on a low `disclosure` score
|
|
244
|
+
while a low `generality` score no longer blocks it. Note this is a difference
|
|
245
|
+
from *intended* `unanimous` behavior, not from shipped behavior: at the
|
|
246
|
+
configured panel of two, `unanimous` and `majority` are the same gate, so
|
|
247
|
+
today's public tier blocks on neither.
|
|
248
|
+
|
|
249
|
+
## Out of scope
|
|
250
|
+
|
|
251
|
+
Retiring `criteria_evaluated`, which stays as-is. Any new judge type. The
|
|
252
|
+
`aggregation_method` enum, which already carries `weighted_mean`. And the
|
|
253
|
+
`TODO(ONL-6)` hard-fail on type/schema divergence — worth doing, but it is the
|
|
254
|
+
schema repo's own cleanup, not this thread's.
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Cartographer `undocumented_entity` Phase — Design
|
|
2
|
+
|
|
3
|
+
**Status:** Approved, not started.
|
|
4
|
+
**Tracked by:** `ecosystem-3eu`.
|
|
5
|
+
**Single repository.** Everything lands under `plugins/cartographer/`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What this is
|
|
10
|
+
|
|
11
|
+
Cartographer's four analysis phases all start from the *text* of the instruction
|
|
12
|
+
files. `contradiction` and `dead_rule` compare rules against each other,
|
|
13
|
+
`scope_collision` compares project rules against global ones, and `stale_ref`
|
|
14
|
+
extracts path-like tokens out of the instruction files and tests them on the
|
|
15
|
+
filesystem (`cartographer-analyze.sh:99-125`).
|
|
16
|
+
|
|
17
|
+
Every phase walks doc → disk. Nothing walks disk → doc.
|
|
18
|
+
|
|
19
|
+
So something that exists on disk and is simply not mentioned is structurally
|
|
20
|
+
invisible: it produces no token to extract, no contradiction, no dead rule, and
|
|
21
|
+
no scope collision. This is why cartographer never flagged that `CLAUDE.md`'s
|
|
22
|
+
plugin map documented 12 of 17 plugins, omitting `librarian` — the plugin that
|
|
23
|
+
owns the entire upstream chain the lesson-promotion pipeline hangs off. Found
|
|
24
|
+
while fixing `ecosystem-yp8`.
|
|
25
|
+
|
|
26
|
+
This is a coverage gap, not a defect. An omission is a different class of drift
|
|
27
|
+
from a stale reference, and cartographer was only ever built to catch the
|
|
28
|
+
latter.
|
|
29
|
+
|
|
30
|
+
## Why an absence is not automatically a defect
|
|
31
|
+
|
|
32
|
+
The obvious framing — "enumerate everything on disk, flag whatever no
|
|
33
|
+
instruction file mentions" — produces noise, because most of a repository has no
|
|
34
|
+
business being named in `CLAUDE.md`.
|
|
35
|
+
|
|
36
|
+
`docs/adr/` is the clean counterexample. Five ADRs sit on disk; `CLAUDE.md` names
|
|
37
|
+
ADR-001, ADR-004, and ADR-005 and says nothing about 002 or 003. That is correct.
|
|
38
|
+
`CLAUDE.md` is not an ADR index — `docs/adr/` is its own index, and an ADR earns
|
|
39
|
+
a mention in the instruction files only when an agent needs to know about it.
|
|
40
|
+
|
|
41
|
+
Meanwhile `plugins/librarian/` being absent from the plugin map *was* a real
|
|
42
|
+
defect, and `skills/list-prompt-rules/` is one right now: it appears zero times
|
|
43
|
+
in both `CLAUDE.md` and `AGENTS.md`.
|
|
44
|
+
|
|
45
|
+
So the phase cannot ask "is this mentioned?" in the abstract. It has to be told
|
|
46
|
+
**where completeness is expected**. That is the job of the `globs` config: an
|
|
47
|
+
opt-in list naming the entity classes whose enumeration is supposed to be
|
|
48
|
+
complete. Anything not matched by a glob is never considered, which is what
|
|
49
|
+
keeps `docs/adr/` permanently out of scope without a special case.
|
|
50
|
+
|
|
51
|
+
## Detection
|
|
52
|
+
|
|
53
|
+
Pure bash. No model call.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
for each glob in config.globs:
|
|
57
|
+
for each match under repo_root:
|
|
58
|
+
name = basename(match)
|
|
59
|
+
if not mentioned(name) in DISCOVERED_FILES:
|
|
60
|
+
→ finding
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Three details carry weight:
|
|
64
|
+
|
|
65
|
+
**Corpus is `DISCOVERED_FILES` only, never `GLOBAL_FILES`.** The user's global
|
|
66
|
+
`~/.claude/CLAUDE.md` has no business documenting one project's plugins. Counting
|
|
67
|
+
a global mention as coverage would let an unrelated personal note silence a real
|
|
68
|
+
project-level omission.
|
|
69
|
+
|
|
70
|
+
**`mentioned()` is a word-boundary match**, `grep -qE "\b<escaped>\b"`, not
|
|
71
|
+
`grep -F`. A substring test would let the word "counseling" satisfy an entity
|
|
72
|
+
named `counsel`. The name is regex-escaped before interpolation.
|
|
73
|
+
|
|
74
|
+
**Detection needs no LLM, and deliberately does not use one.** Presence or
|
|
75
|
+
absence of a name is a grep. A model call would only be useful for judging
|
|
76
|
+
whether an omission *matters*, which is a sharper question than the drift that
|
|
77
|
+
motivated this — `librarian` was absent entirely, and the crudest possible check
|
|
78
|
+
catches that. Adding a materiality judge now would spend a model call per audit
|
|
79
|
+
to solve a problem we have not yet observed. If the false-positive rate turns out
|
|
80
|
+
to warrant it, the `stale_ref` classify pre-pass is the shape to copy.
|
|
81
|
+
|
|
82
|
+
## Where it sits in the pipeline
|
|
83
|
+
|
|
84
|
+
A third analysis inside `run_synthesize`, alongside `stale_ref` and
|
|
85
|
+
`scope_collision` — **not** a sixth pipeline phase.
|
|
86
|
+
|
|
87
|
+
The phase list (`discover` / `extract` / `relate` / `synthesize` / `emit`) stays
|
|
88
|
+
as it is, so run records keep their current shape and the `phases_completed` and
|
|
89
|
+
`phases_failed` arrays in `runs/audit-<id>.json` do not gain a new member.
|
|
90
|
+
Consumers reading those records do not need to change.
|
|
91
|
+
|
|
92
|
+
New library: `plugins/cartographer/scripts/lib/cartographer-omission.sh`,
|
|
93
|
+
exporting:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
cartographer_analyze_undocumented_entity <files_json> <repo_root> <globs_json> \
|
|
97
|
+
<exclude_json> <max_findings>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
It prints a JSON array of findings on stdout, matching the shape the other
|
|
101
|
+
analyzers return, so `run_synthesize` merges it into `raw_all` with no special
|
|
102
|
+
handling and the existing hash-enrichment loop covers it unchanged.
|
|
103
|
+
|
|
104
|
+
It is invoked under `$_TIMEOUT_CMD "$_phase_timeout"` like its siblings. The work
|
|
105
|
+
is fast, but a pathological glob over a huge tree should be killed on the same
|
|
106
|
+
terms as everything else rather than being trusted because it is "just bash".
|
|
107
|
+
|
|
108
|
+
## Finding shape
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"type": "undocumented_entity",
|
|
113
|
+
"severity": "warning",
|
|
114
|
+
"file_a": "<absolute path to the entity>",
|
|
115
|
+
"excerpt_a": "<entity name>",
|
|
116
|
+
"file_b": null,
|
|
117
|
+
"excerpt_b": null,
|
|
118
|
+
"description": "<name> exists at <relative path> but is not mentioned in any instruction file.",
|
|
119
|
+
"suggested_fix": "Document <name> in CLAUDE.md, or exclude its path from cartographer.undocumented_entity."
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**`file_a` is the entity, not a document.** There is no single instruction file
|
|
124
|
+
at fault — the entity is missing from the whole corpus, so naming one file would
|
|
125
|
+
be arbitrary. Keying identity on the entity also makes
|
|
126
|
+
`cartographer_finding_hash` stable when someone reorganizes `CLAUDE.md`: a
|
|
127
|
+
finding must not re-fire as new because a heading moved. With `file_b` and
|
|
128
|
+
`excerpt_b` empty, the existing commutative hash degenerates to a stable
|
|
129
|
+
per-entity key, which is exactly the dedup behavior wanted — one finding per
|
|
130
|
+
undocumented entity, once, until it is resolved.
|
|
131
|
+
|
|
132
|
+
**Severity is always `warning`.** The existing definition reserves `error` for
|
|
133
|
+
rules whose violation would compromise safety or produce incorrect output. A
|
|
134
|
+
missing mention is neither.
|
|
135
|
+
|
|
136
|
+
## Configuration
|
|
137
|
+
|
|
138
|
+
Under the `.cartographer` namespace in `config.json`, overridable through the
|
|
139
|
+
standard five-layer settings overlay (ecosystem `docs/adr/004`):
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
"undocumented_entity": {
|
|
143
|
+
"enabled": true,
|
|
144
|
+
"globs": ["plugins/*/", "skills/*/"],
|
|
145
|
+
"exclude": [],
|
|
146
|
+
"max_findings": 20
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- **`globs`** — repo-root-relative. The defaults are Claude Code layout
|
|
151
|
+
conventions rather than ecosystem-specific paths, and a glob that matches
|
|
152
|
+
nothing yields no candidates, so this is silently inert in a repository
|
|
153
|
+
without those directories. That is what makes shipping it enabled defensible:
|
|
154
|
+
it is useful out of the box where the convention holds and invisible where it
|
|
155
|
+
does not.
|
|
156
|
+
- **`exclude`** — substring filter over matched paths, mirroring the semantics
|
|
157
|
+
of the existing top-level `exclude_paths`. That field replaces rather than
|
|
158
|
+
merges when a settings layer overrides it (cartographer
|
|
159
|
+
`docs/adr/004-exclude-paths-replace-semantics.md`); this field follows the
|
|
160
|
+
same rule, and the README must say so, because replace-not-merge is the
|
|
161
|
+
behavior users get wrong.
|
|
162
|
+
- **`max_findings`** — see below.
|
|
163
|
+
- **`enabled: false`** short-circuits to `[]` before any filesystem walk.
|
|
164
|
+
|
|
165
|
+
## Two edge cases that decide correctness
|
|
166
|
+
|
|
167
|
+
**Targeted post-write audits skip this phase entirely.** When
|
|
168
|
+
`CARTOGRAPHER_TARGET_FILE` is set, `run_discover` sets `DISCOVERED_FILES` to that
|
|
169
|
+
one file. Grepping a single file for every entity name in the repository would
|
|
170
|
+
report almost the entire enumeration as undocumented — a burst of false findings
|
|
171
|
+
that would then be dedup-sentineled and never re-evaluated, poisoning the
|
|
172
|
+
findings store permanently. The phase therefore returns `[]` when `TARGET_FILE`
|
|
173
|
+
is non-empty.
|
|
174
|
+
|
|
175
|
+
This follows established precedent rather than inventing one: `scope_collision`
|
|
176
|
+
already no-ops on targeted runs, because `run_discover` sets `GLOBAL_FILES` to
|
|
177
|
+
`[]` and the analyzer returns early on an empty corpus.
|
|
178
|
+
|
|
179
|
+
**First-run noise is capped at `max_findings`.** Enabling this against a
|
|
180
|
+
repository with a thin `CLAUDE.md` could otherwise produce dozens of findings in
|
|
181
|
+
one audit. The count of dropped candidates is written to `audit.log`; a silent
|
|
182
|
+
truncation would read as "this is everything" when it is not.
|
|
183
|
+
|
|
184
|
+
## Event emission is knowingly broken, and not fixed here
|
|
185
|
+
|
|
186
|
+
`cartographer.issue.found` does not validate against the published schema and
|
|
187
|
+
never has. `run-audit.sh` emits `finding_type` / `affected_files` /
|
|
188
|
+
`finding_hash`; `@onlooker-community/schema` 2.12.0 requires `issue_type` and
|
|
189
|
+
`file_path` with `additionalProperties: false`. `cartographer.audit.complete` is
|
|
190
|
+
off-contract too. Verified by piping both payloads through the real
|
|
191
|
+
`scripts/lib/onlooker-event.mjs emit` path — each exits 1.
|
|
192
|
+
|
|
193
|
+
Per ADR-005 the consequence differs by install mode. In a dev or CI checkout the
|
|
194
|
+
schema package resolves, validation runs, `cartographer_emit_event` returns 1,
|
|
195
|
+
and `emit_safe`'s `|| true` swallows it, so nothing reaches the bus. In an
|
|
196
|
+
installed marketplace plugin there is no `node_modules`, the emitter fails open,
|
|
197
|
+
and off-contract payloads are emitted. Findings still reach
|
|
198
|
+
`findings/<hash>.json` in both modes, which is why `/cartographer` looks healthy
|
|
199
|
+
and this has gone unnoticed.
|
|
200
|
+
|
|
201
|
+
**This is tracked separately as `ecosystem-q4d` (P1) and is out of scope here.**
|
|
202
|
+
Findings from this phase land on disk exactly like the other four, and its bus
|
|
203
|
+
event is broken in exactly the same way until `q4d` lands. Folding the fix in
|
|
204
|
+
would mix a bug fix with a feature and pull an upstream schema change into the
|
|
205
|
+
middle of a feature branch — the same reasoning that split `4d3` / `cs8` / `973`.
|
|
206
|
+
|
|
207
|
+
Worth recording: the published schema's `issue_type` enum already contains
|
|
208
|
+
`orphaned_plugin`, and `issue_categories` already contains `orphaned_plugins`.
|
|
209
|
+
The contract anticipated disk → doc detection; the implementation never built
|
|
210
|
+
it. Whoever resolves `q4d` should decide whether `undocumented_entity` maps onto
|
|
211
|
+
that existing name or whether the enum grows a new member.
|
|
212
|
+
|
|
213
|
+
## Testing
|
|
214
|
+
|
|
215
|
+
bats, following `test/helpers/setup.bash` and the repo's `writing-tests` skill —
|
|
216
|
+
isolated temp home, no writes to the real `~/.onlooker/`.
|
|
217
|
+
|
|
218
|
+
| Case | Expectation |
|
|
219
|
+
|------|-------------|
|
|
220
|
+
| Entity on disk, name absent from corpus | one finding, `type=undocumented_entity` |
|
|
221
|
+
| Entity on disk, name present in corpus | no finding |
|
|
222
|
+
| Entity `counsel`, corpus says "counseling" | one finding — word boundary holds |
|
|
223
|
+
| Path matches `exclude` | no finding |
|
|
224
|
+
| `enabled: false` | `[]`, no filesystem walk |
|
|
225
|
+
| Glob matching nothing | `[]` |
|
|
226
|
+
| `TARGET_FILE` set | `[]` |
|
|
227
|
+
| Candidates exceed `max_findings` | capped, drop count in `audit.log` |
|
|
228
|
+
| Same entity across two runs | identical `finding_hash` |
|
|
229
|
+
|
|
230
|
+
## Acceptance
|
|
231
|
+
|
|
232
|
+
Run a full audit against this repository. The phase reports exactly one finding
|
|
233
|
+
— `skills/list-prompt-rules` — and nothing else. All 16 plugins under `plugins/`
|
|
234
|
+
are currently documented in `CLAUDE.md`, and `docs/adr/` is never enumerated.
|
|
235
|
+
|
|
236
|
+
That one-finding result is the acceptance signal in both directions: it proves
|
|
237
|
+
the detection fires on a real gap, and it proves the enumeration is narrow
|
|
238
|
+
enough not to bury that gap in noise.
|
|
239
|
+
|
|
240
|
+
## Documentation to update
|
|
241
|
+
|
|
242
|
+
- `plugins/cartographer/README.md` — phase list and the new config block.
|
|
243
|
+
- `plugins/cartographer/skills/cartographer/SKILL.md` — the `--phase` value list
|
|
244
|
+
at line 112, and the frontmatter `description`, which enumerates what
|
|
245
|
+
cartographer audits for. The finding renderers at lines 65 and 135 read
|
|
246
|
+
`.type` and `.description` generically and need no change.
|
|
247
|
+
- `CLAUDE.md` — the cartographer row in the plugin map describes the hook
|
|
248
|
+
surface, not the phases, so it likely needs no change. Confirm at
|
|
249
|
+
implementation time.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlooker-community/ecosystem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "Agents, skills, hooks, commands, rules, and MCP configurations that power [Onlooker](https://onlooker.dev)",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@biomejs/biome": "2.4.15",
|
|
45
|
-
"@onlooker-community/schema": "^2.
|
|
45
|
+
"@onlooker-community/schema": "^2.15.0",
|
|
46
46
|
"globals": "^17.6.0",
|
|
47
47
|
"markdownlint-cli": "^0.48.0"
|
|
48
48
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assayer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Claim verification. At session end, parses the agent's final message for testable claims (\"I ran the tests, they pass\", \"the build is green\") and checks each against the actual command results in the session transcript, classifying it corroborated, contradicted, or unverifiable. Catches lying-without-malice. Advisory by default. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.3](https://github.com/onlooker-community/ecosystem/compare/assayer-v1.1.2...assayer-v1.1.3) (2026-08-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* make the local bats suite tell the truth :mag: ([#135](https://github.com/onlooker-community/ecosystem/issues/135)) ([f0763e0](https://github.com/onlooker-community/ecosystem/commit/f0763e09f3caf2d39c89f28befd12567af0af845))
|
|
9
|
+
|
|
3
10
|
## [1.1.2](https://github.com/onlooker-community/ecosystem/compare/assayer-v1.1.1...assayer-v1.1.2) (2026-08-02)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -57,3 +57,9 @@ assayer_config_timeout() {
|
|
|
57
57
|
v=$(assayer_config_get '.assayer.evaluation.timeout_seconds')
|
|
58
58
|
printf '%s' "${v:-60}"
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
assayer_config_final_message_chars() {
|
|
62
|
+
local v
|
|
63
|
+
v=$(assayer_config_get '.assayer.final_message_chars')
|
|
64
|
+
printf '%s' "${v:-6000}"
|
|
65
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cartographer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Proactive periodic auditor of the persistent instruction layer (CLAUDE.md, AGENTS.md, .claude/rules/). Discovers all instruction files in the repo, extracts semantic maps, and surfaces contradictions, shadowing, gaps, and drift before they cause expensive agent misbehavior. Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -2,6 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the Cartographer plugin are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.6.0](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.5.1...cartographer-v0.6.0) (2026-08-19)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **cartographer:** announce a finding when its drift is gone :wave: ([#186](https://github.com/onlooker-community/ecosystem/issues/186)) ([f2b57f6](https://github.com/onlooker-community/ecosystem/commit/f2b57f67b3e09f7ec624bce0bb4a306c4e20df8b))
|
|
11
|
+
|
|
12
|
+
## [0.5.1](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.5.0...cartographer-v0.5.1) (2026-08-19)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **cartographer:** refuse a typeless finding out loud :loudspeaker: ([#182](https://github.com/onlooker-community/ecosystem/issues/182)) ([35ee0af](https://github.com/onlooker-community/ecosystem/commit/35ee0af055fd360a70a77d6ef036bbeb91dcc945))
|
|
18
|
+
|
|
19
|
+
## [0.5.0](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.4.3...cartographer-v0.5.0) (2026-08-18)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* **cartographer:** narrow an audit by finding type or subtree :mag: ([#177](https://github.com/onlooker-community/ecosystem/issues/177)) ([94764df](https://github.com/onlooker-community/ecosystem/commit/94764dfc8386521a06c8234bc506cb51a667588d))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* **librarian:** stop stage 5 from holding a session open :hourglass: ([#178](https://github.com/onlooker-community/ecosystem/issues/178)) ([4f12a1e](https://github.com/onlooker-community/ecosystem/commit/4f12a1ee25300e0dc372542814e0922df5dbd335))
|
|
30
|
+
|
|
31
|
+
## [0.4.3](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.4.2...cartographer-v0.4.3) (2026-08-17)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Bug Fixes
|
|
35
|
+
|
|
36
|
+
* **cartographer:** make the audit read the settings you gave it :gear: ([#174](https://github.com/onlooker-community/ecosystem/issues/174)) ([dc57731](https://github.com/onlooker-community/ecosystem/commit/dc5773165fe561f77e87101ed1fbe6e13bb34c34))
|
|
37
|
+
|
|
38
|
+
## [0.4.2](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.4.1...cartographer-v0.4.2) (2026-08-17)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Bug Fixes
|
|
42
|
+
|
|
43
|
+
* **cartographer:** let a finding you fixed finally go away :wastebasket: ([#172](https://github.com/onlooker-community/ecosystem/issues/172)) ([c8fdcf4](https://github.com/onlooker-community/ecosystem/commit/c8fdcf40b711b68ee5d90e2c4b6408ea38926900))
|
|
44
|
+
|
|
45
|
+
## [0.4.1](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.4.0...cartographer-v0.4.1) (2026-08-17)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Bug Fixes
|
|
49
|
+
|
|
50
|
+
* **cartographer:** put its events on the bus for the first time :mega: ([#170](https://github.com/onlooker-community/ecosystem/issues/170)) ([91be8d2](https://github.com/onlooker-community/ecosystem/commit/91be8d2c9f171006db12842f3e582d001f9d4e27))
|
|
51
|
+
|
|
52
|
+
## [0.4.0](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.3.1...cartographer-v0.4.0) (2026-08-16)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
### Features
|
|
56
|
+
|
|
57
|
+
* **cartographer:** detect what no instruction file mentions :eyes: ([#168](https://github.com/onlooker-community/ecosystem/issues/168)) ([31efb7b](https://github.com/onlooker-community/ecosystem/commit/31efb7b035665d5a49a37c1e1af21f519850f7bd))
|
|
58
|
+
|
|
5
59
|
## [0.3.1](https://github.com/onlooker-community/ecosystem/compare/cartographer-v0.3.0...cartographer-v0.3.1) (2026-08-01)
|
|
6
60
|
|
|
7
61
|
|