@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,741 @@
|
|
|
1
|
+
# Cartographer `undocumented_entity` Phase Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Give cartographer a disk → doc analysis phase so an entity that exists on disk and is mentioned in no instruction file becomes visible.
|
|
6
|
+
|
|
7
|
+
**Architecture:** A new pure-bash analyzer library, `cartographer-omission.sh`, enumerates configured globs under the repo root and word-boundary-greps each entity's name across the instruction corpus. It is invoked as a third analysis inside `run_synthesize`, alongside `stale_ref` and `scope_collision`, and returns findings in the same shape so the existing hash-enrichment and emit loops handle it unchanged. No model call.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** bash (targeting 3.2 — macOS system bash), `jq`, `grep -E`, bats for tests.
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-08-16-cartographer-undocumented-entity-design.md`
|
|
12
|
+
**Tracked by:** `ecosystem-3eu`
|
|
13
|
+
|
|
14
|
+
## Global Constraints
|
|
15
|
+
|
|
16
|
+
- All hook and library code is bash. No Python, no Node entry points. Shelling out to `node` for event emission is allowed.
|
|
17
|
+
- Event types follow `<plugin>.<noun>.<verb>`. **This plan emits no new event type** — findings ride the existing `cartographer.issue.found`.
|
|
18
|
+
- Runtime artifacts go under `${ONLOOKER_DIR:-$HOME/.onlooker}/`. Never hardcode `~/.onlooker`.
|
|
19
|
+
- American English in all comments, identifiers, and docs.
|
|
20
|
+
- Tests target bash 3.2 semantics: any **non-final** `[[ ]]` assertion in a bats body must be gated with `|| return 1`, or it does not fail the test on macOS.
|
|
21
|
+
- Config defaults live in `plugins/cartographer/config.json` under the `cartographer` namespace key.
|
|
22
|
+
- `npm run test:ci` must pass — it adds shellcheck and the manifest/reference linters on top of the tests.
|
|
23
|
+
|
|
24
|
+
## Out of scope, deliberately
|
|
25
|
+
|
|
26
|
+
Two pre-existing bugs sit adjacent to this work. **Do not fix either one in this branch.**
|
|
27
|
+
|
|
28
|
+
- **`ecosystem-q4d` (P1)** — both cartographer event payloads are off-contract with the published schema, so `cartographer.issue.found` never validates. Findings from this phase land on disk correctly and their bus event is broken in exactly the same way as the existing four phases. That is expected. Do not touch the emit path.
|
|
29
|
+
- **`ecosystem-88v` (P2)** — `run-audit.sh` never calls `cartographer_config_load` at the top level, and does not export `PLUGIN_ROOT`, so the `cartographer_config_load` calls inside the three analysis sub-shells fail too. **Task 3 works around this** by setting `PLUGIN_ROOT` inside its own sub-shell. That inline assignment is intentional, is commented as such, and should be removed once `88v` lands.
|
|
30
|
+
|
|
31
|
+
## File Structure
|
|
32
|
+
|
|
33
|
+
| File | Responsibility |
|
|
34
|
+
|------|----------------|
|
|
35
|
+
| `plugins/cartographer/scripts/lib/cartographer-omission.sh` | **Create.** The analyzer. Enumeration, mention test, finding construction, cap. Nothing else. |
|
|
36
|
+
| `plugins/cartographer/scripts/lib/cartographer-config.sh` | **Modify.** Four accessors for the new config block. |
|
|
37
|
+
| `plugins/cartographer/config.json` | **Modify.** Shipped defaults. |
|
|
38
|
+
| `plugins/cartographer/scripts/run-audit.sh` | **Modify.** Wire the analyzer into `run_synthesize`; guard on targeted audits. |
|
|
39
|
+
| `test/bats/cartographer-omission.bats` | **Create.** Analyzer unit tests. |
|
|
40
|
+
| `test/bats/cartographer-config.bats` | **Modify.** Config accessor tests. |
|
|
41
|
+
| `plugins/cartographer/README.md` | **Modify.** Phase list and config block. |
|
|
42
|
+
| `plugins/cartographer/skills/cartographer/SKILL.md` | **Modify.** `--phase` values and frontmatter description. |
|
|
43
|
+
|
|
44
|
+
The analyzer lives in its own file rather than being appended to `cartographer-analyze.sh` because that file is exclusively LLM-calling analyzers sharing one prompt-and-parse idiom. This one calls no model and shares none of it.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### Task 1: Config surface
|
|
49
|
+
|
|
50
|
+
**Files:**
|
|
51
|
+
|
|
52
|
+
- Modify: `plugins/cartographer/config.json`
|
|
53
|
+
- Modify: `plugins/cartographer/scripts/lib/cartographer-config.sh:66-80`
|
|
54
|
+
- Test: `test/bats/cartographer-config.bats`
|
|
55
|
+
|
|
56
|
+
**Interfaces:**
|
|
57
|
+
|
|
58
|
+
- Consumes: `cartographer_config_get`, `cartographer_config_get_json` (existing, same file).
|
|
59
|
+
- Produces: `cartographer_config_undocumented_enabled` → `"true"`/`"false"`; `cartographer_config_undocumented_globs` → JSON array; `cartographer_config_undocumented_exclude` → JSON array; `cartographer_config_undocumented_max_findings` → integer. Task 3 calls all four.
|
|
60
|
+
|
|
61
|
+
- [ ] **Step 1: Write the failing tests**
|
|
62
|
+
|
|
63
|
+
Append to `test/bats/cartographer-config.bats`:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
@test "undocumented_entity: defaults ship enabled with plugin and skill globs" {
|
|
67
|
+
cartographer_config_load ""
|
|
68
|
+
local enabled globs max
|
|
69
|
+
enabled=$(cartographer_config_undocumented_enabled)
|
|
70
|
+
globs=$(cartographer_config_undocumented_globs)
|
|
71
|
+
max=$(cartographer_config_undocumented_max_findings)
|
|
72
|
+
[ "$enabled" = "true" ]
|
|
73
|
+
[ "$max" = "20" ]
|
|
74
|
+
[ "$(printf '%s' "$globs" | jq -r 'length')" = "2" ]
|
|
75
|
+
[ "$(printf '%s' "$globs" | jq -r '.[0]')" = "plugins/*/" ]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@test "undocumented_entity: user settings can disable the phase" {
|
|
79
|
+
mkdir -p "${HOME}/.claude"
|
|
80
|
+
printf '%s\n' '{"cartographer":{"undocumented_entity":{"enabled":false}}}' \
|
|
81
|
+
> "${HOME}/.claude/settings.json"
|
|
82
|
+
cartographer_config_load ""
|
|
83
|
+
local enabled max
|
|
84
|
+
enabled=$(cartographer_config_undocumented_enabled)
|
|
85
|
+
max=$(cartographer_config_undocumented_max_findings)
|
|
86
|
+
[ "$enabled" = "false" ]
|
|
87
|
+
[ "$max" = "20" ]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@test "undocumented_entity: repo settings replace the glob list wholesale" {
|
|
91
|
+
local repo="${BATS_TEST_TMPDIR}/repo-glob"
|
|
92
|
+
mkdir -p "${repo}/.claude"
|
|
93
|
+
printf '%s\n' '{"cartographer":{"undocumented_entity":{"globs":["agents/*/"]}}}' \
|
|
94
|
+
> "${repo}/.claude/settings.json"
|
|
95
|
+
cartographer_config_load "$repo"
|
|
96
|
+
local globs
|
|
97
|
+
globs=$(cartographer_config_undocumented_globs)
|
|
98
|
+
[ "$(printf '%s' "$globs" | jq -r 'length')" = "1" ]
|
|
99
|
+
[ "$(printf '%s' "$globs" | jq -r '.[0]')" = "agents/*/" ]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@test "undocumented_entity: exclude defaults to empty" {
|
|
103
|
+
cartographer_config_load ""
|
|
104
|
+
local excl
|
|
105
|
+
excl=$(cartographer_config_undocumented_exclude)
|
|
106
|
+
[ "$(printf '%s' "$excl" | jq -r 'length')" = "0" ]
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
111
|
+
|
|
112
|
+
Run: `bats test/bats/cartographer-config.bats`
|
|
113
|
+
Expected: FAIL — `cartographer_config_undocumented_enabled: command not found`.
|
|
114
|
+
|
|
115
|
+
- [ ] **Step 3: Add the config defaults**
|
|
116
|
+
|
|
117
|
+
In `plugins/cartographer/config.json`, add a sibling to `exclude_paths` inside the `cartographer` object (mind the comma on the `exclude_paths` line):
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
"exclude_paths": ["node_modules", ".git", "vendor", ".venv", "dist", ".next", ".nuxt", "build", "__pycache__"],
|
|
121
|
+
"undocumented_entity": {
|
|
122
|
+
"enabled": true,
|
|
123
|
+
"globs": ["plugins/*/", "skills/*/"],
|
|
124
|
+
"exclude": [],
|
|
125
|
+
"max_findings": 20
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- [ ] **Step 4: Add the accessors**
|
|
130
|
+
|
|
131
|
+
Append to `plugins/cartographer/scripts/lib/cartographer-config.sh`:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# ── undocumented_entity phase ──────────────────────────────────────────────────
|
|
135
|
+
# Disk → doc detection. Unlike the other phases these are read inside the
|
|
136
|
+
# analysis sub-shell rather than by the orchestrator; see run-audit.sh and
|
|
137
|
+
# ecosystem-88v for why.
|
|
138
|
+
|
|
139
|
+
cartographer_config_undocumented_enabled() {
|
|
140
|
+
local v
|
|
141
|
+
v=$(cartographer_config_get '.cartographer.undocumented_entity.enabled')
|
|
142
|
+
printf '%s' "${v:-true}"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
cartographer_config_undocumented_globs() {
|
|
146
|
+
cartographer_config_get_json \
|
|
147
|
+
'.cartographer.undocumented_entity.globs // ["plugins/*/","skills/*/"]'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
cartographer_config_undocumented_exclude() {
|
|
151
|
+
cartographer_config_get_json '.cartographer.undocumented_entity.exclude // []'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
cartographer_config_undocumented_max_findings() {
|
|
155
|
+
local v
|
|
156
|
+
v=$(cartographer_config_get '.cartographer.undocumented_entity.max_findings')
|
|
157
|
+
printf '%s' "${v:-20}"
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- [ ] **Step 5: Run tests to verify they pass**
|
|
162
|
+
|
|
163
|
+
Run: `bats test/bats/cartographer-config.bats`
|
|
164
|
+
Expected: PASS, all tests including the four pre-existing ones.
|
|
165
|
+
|
|
166
|
+
- [ ] **Step 6: Break one assertion on purpose**
|
|
167
|
+
|
|
168
|
+
Temporarily change `[ "$enabled" = "false" ]` to `[ "$enabled" = "true" ]` in the disable test, re-run, and confirm it **fails**. Revert. A test that passes either way reports coverage that does not exist.
|
|
169
|
+
|
|
170
|
+
- [ ] **Step 7: Commit**
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
git add plugins/cartographer/config.json \
|
|
174
|
+
plugins/cartographer/scripts/lib/cartographer-config.sh \
|
|
175
|
+
test/bats/cartographer-config.bats
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Then run `/commit` — do not hand-write `git commit -m`. Suggested subject:
|
|
179
|
+
`feat(cartographer): add config for the disk-to-doc phase :gear:`
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### Task 2: Detection library
|
|
184
|
+
|
|
185
|
+
**Files:**
|
|
186
|
+
|
|
187
|
+
- Create: `plugins/cartographer/scripts/lib/cartographer-omission.sh`
|
|
188
|
+
- Test: `test/bats/cartographer-omission.bats` (create)
|
|
189
|
+
|
|
190
|
+
**Interfaces:**
|
|
191
|
+
|
|
192
|
+
- Consumes: nothing from Task 1. This library is standalone and takes every input as a parameter, matching how the analyzers in `cartographer-analyze.sh` are written.
|
|
193
|
+
- Produces: `cartographer_analyze_undocumented_entity <files_json> <repo_root> <globs_json> <exclude_json> <max_findings>` → prints a JSON array on stdout. Task 3 calls it. Also `_cartographer_name_mentioned <name> <files_json>` → exit 0 if mentioned; private, but bats tests it directly.
|
|
194
|
+
|
|
195
|
+
- [ ] **Step 1: Write the failing tests**
|
|
196
|
+
|
|
197
|
+
Create `test/bats/cartographer-omission.bats`:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
#!/usr/bin/env bats
|
|
201
|
+
|
|
202
|
+
setup() {
|
|
203
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
204
|
+
setup_test_env
|
|
205
|
+
|
|
206
|
+
PLUGIN_ROOT="${REPO_ROOT}/plugins/cartographer"
|
|
207
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
208
|
+
# shellcheck disable=SC1091
|
|
209
|
+
source "${PLUGIN_ROOT}/scripts/lib/cartographer-omission.sh"
|
|
210
|
+
|
|
211
|
+
FIXTURE_REPO="${BATS_TEST_TMPDIR}/repo"
|
|
212
|
+
mkdir -p "${FIXTURE_REPO}/plugins/alpha" \
|
|
213
|
+
"${FIXTURE_REPO}/plugins/beta" \
|
|
214
|
+
"${FIXTURE_REPO}/skills/solo"
|
|
215
|
+
DOC="${FIXTURE_REPO}/CLAUDE.md"
|
|
216
|
+
printf '# Doc\nThe alpha plugin does things.\n' > "$DOC"
|
|
217
|
+
CORPUS=$(jq -n --arg f "$DOC" '[$f]')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@test "flags an entity whose name appears nowhere in the corpus" {
|
|
221
|
+
local out
|
|
222
|
+
out=$(cartographer_analyze_undocumented_entity \
|
|
223
|
+
"$CORPUS" "$FIXTURE_REPO" '["plugins/*/"]' '[]' 20)
|
|
224
|
+
[[ "$(printf '%s' "$out" | jq -r 'length')" == "1" ]] || return 1
|
|
225
|
+
[[ "$(printf '%s' "$out" | jq -r '.[0].excerpt_a')" == "beta" ]] || return 1
|
|
226
|
+
[ "$(printf '%s' "$out" | jq -r '.[0].type')" = "undocumented_entity" ]
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@test "does not flag an entity the corpus mentions" {
|
|
230
|
+
local out
|
|
231
|
+
out=$(cartographer_analyze_undocumented_entity \
|
|
232
|
+
"$CORPUS" "$FIXTURE_REPO" '["plugins/*/"]' '[]' 20)
|
|
233
|
+
[ "$(printf '%s' "$out" | jq -r '[.[] | select(.excerpt_a == "alpha")] | length')" = "0" ]
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@test "finding carries the entity as file_a and a null file_b" {
|
|
237
|
+
local out
|
|
238
|
+
out=$(cartographer_analyze_undocumented_entity \
|
|
239
|
+
"$CORPUS" "$FIXTURE_REPO" '["plugins/*/"]' '[]' 20)
|
|
240
|
+
[[ "$(printf '%s' "$out" | jq -r '.[0].file_a')" == "${FIXTURE_REPO}/plugins/beta" ]] || return 1
|
|
241
|
+
[[ "$(printf '%s' "$out" | jq -r '.[0].file_b')" == "null" ]] || return 1
|
|
242
|
+
[ "$(printf '%s' "$out" | jq -r '.[0].severity')" = "warning" ]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@test "word boundary: a longer word containing the name does not count as a mention" {
|
|
246
|
+
printf '# Doc\nWe do a lot of counseling here.\n' > "$DOC"
|
|
247
|
+
mkdir -p "${FIXTURE_REPO}/plugins/counsel"
|
|
248
|
+
local out
|
|
249
|
+
out=$(cartographer_analyze_undocumented_entity \
|
|
250
|
+
"$CORPUS" "$FIXTURE_REPO" '["plugins/*/"]' '[]' 20)
|
|
251
|
+
[ "$(printf '%s' "$out" | jq -r '[.[] | select(.excerpt_a == "counsel")] | length')" = "1" ]
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@test "word boundary: a hyphenated name is not matched inside a longer hyphenated token" {
|
|
255
|
+
printf '# Doc\nSee my-list-prompt-rules-thing for details.\n' > "$DOC"
|
|
256
|
+
mkdir -p "${FIXTURE_REPO}/skills/list-prompt-rules"
|
|
257
|
+
local out
|
|
258
|
+
out=$(cartographer_analyze_undocumented_entity \
|
|
259
|
+
"$CORPUS" "$FIXTURE_REPO" '["skills/*/"]' '[]' 20)
|
|
260
|
+
[ "$(printf '%s' "$out" | jq -r '[.[] | select(.excerpt_a == "list-prompt-rules")] | length')" = "1" ]
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@test "word boundary: a name bounded by slashes counts as a mention" {
|
|
264
|
+
printf '# Doc\nSee plugins/beta/ for details.\n' > "$DOC"
|
|
265
|
+
local out
|
|
266
|
+
out=$(cartographer_analyze_undocumented_entity \
|
|
267
|
+
"$CORPUS" "$FIXTURE_REPO" '["plugins/*/"]' '[]' 20)
|
|
268
|
+
[ "$(printf '%s' "$out" | jq -r '[.[] | select(.excerpt_a == "beta")] | length')" = "0" ]
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@test "exclude filters a matched path by substring" {
|
|
272
|
+
local out
|
|
273
|
+
out=$(cartographer_analyze_undocumented_entity \
|
|
274
|
+
"$CORPUS" "$FIXTURE_REPO" '["plugins/*/","skills/*/"]' '["skills/"]' 20)
|
|
275
|
+
[[ "$(printf '%s' "$out" | jq -r 'length')" == "1" ]] || return 1
|
|
276
|
+
[ "$(printf '%s' "$out" | jq -r '.[0].excerpt_a')" = "beta" ]
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@test "max_findings caps the result and reports the drop count on stderr" {
|
|
280
|
+
local out err
|
|
281
|
+
err="${BATS_TEST_TMPDIR}/err.txt"
|
|
282
|
+
out=$(cartographer_analyze_undocumented_entity \
|
|
283
|
+
"$CORPUS" "$FIXTURE_REPO" '["plugins/*/","skills/*/"]' '[]' 1 2>"$err")
|
|
284
|
+
[[ "$(printf '%s' "$out" | jq -r 'length')" == "1" ]] || return 1
|
|
285
|
+
grep -q "1 candidate" "$err"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@test "a glob matching nothing yields an empty array" {
|
|
289
|
+
local out
|
|
290
|
+
out=$(cartographer_analyze_undocumented_entity \
|
|
291
|
+
"$CORPUS" "$FIXTURE_REPO" '["nonexistent/*/"]' '[]' 20)
|
|
292
|
+
[ "$out" = "[]" ]
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
@test "an empty corpus yields an empty array rather than flagging everything" {
|
|
296
|
+
local out
|
|
297
|
+
out=$(cartographer_analyze_undocumented_entity \
|
|
298
|
+
'[]' "$FIXTURE_REPO" '["plugins/*/"]' '[]' 20)
|
|
299
|
+
[ "$out" = "[]" ]
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@test "the same entity produces an identical finding hash across two runs" {
|
|
303
|
+
# shellcheck disable=SC1091
|
|
304
|
+
source "${PLUGIN_ROOT}/scripts/lib/cartographer-analyze.sh"
|
|
305
|
+
local a b h1 h2
|
|
306
|
+
a=$(cartographer_analyze_undocumented_entity \
|
|
307
|
+
"$CORPUS" "$FIXTURE_REPO" '["plugins/*/"]' '[]' 20)
|
|
308
|
+
b=$(cartographer_analyze_undocumented_entity \
|
|
309
|
+
"$CORPUS" "$FIXTURE_REPO" '["plugins/*/"]' '[]' 20)
|
|
310
|
+
h1=$(cartographer_finding_hash "undocumented_entity" \
|
|
311
|
+
"$(printf '%s' "$a" | jq -r '.[0].file_a')" \
|
|
312
|
+
"$(printf '%s' "$a" | jq -r '.[0].excerpt_a')" "" "")
|
|
313
|
+
h2=$(cartographer_finding_hash "undocumented_entity" \
|
|
314
|
+
"$(printf '%s' "$b" | jq -r '.[0].file_a')" \
|
|
315
|
+
"$(printf '%s' "$b" | jq -r '.[0].excerpt_a')" "" "")
|
|
316
|
+
[[ -n "$h1" ]] || return 1
|
|
317
|
+
[ "$h1" = "$h2" ]
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
322
|
+
|
|
323
|
+
Run: `bats test/bats/cartographer-omission.bats`
|
|
324
|
+
Expected: FAIL — the source in `setup()` errors, no such file.
|
|
325
|
+
|
|
326
|
+
- [ ] **Step 3: Write the implementation**
|
|
327
|
+
|
|
328
|
+
Create `plugins/cartographer/scripts/lib/cartographer-omission.sh`:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
#!/usr/bin/env bash
|
|
332
|
+
# cartographer-omission.sh — disk → doc detection.
|
|
333
|
+
#
|
|
334
|
+
# Every other analysis phase starts from the text of the instruction files and
|
|
335
|
+
# tests what it finds against the filesystem. This one runs the other way: it
|
|
336
|
+
# enumerates entities on disk and checks each is mentioned somewhere in the
|
|
337
|
+
# corpus. An entity nothing names produces no token for stale_ref to extract
|
|
338
|
+
# and no rule for contradiction to compare, which is why omissions were
|
|
339
|
+
# previously invisible to every phase.
|
|
340
|
+
#
|
|
341
|
+
# Detection is a grep — no model call. A model would only help judge whether an
|
|
342
|
+
# omission MATTERS, which is a sharper question than the drift that motivated
|
|
343
|
+
# this. See docs/superpowers/specs/2026-08-16-cartographer-undocumented-entity-design.md
|
|
344
|
+
#
|
|
345
|
+
# Usage:
|
|
346
|
+
# cartographer_analyze_undocumented_entity <files_json> <repo_root> \
|
|
347
|
+
# <globs_json> <exclude_json> <max_findings>
|
|
348
|
+
#
|
|
349
|
+
# Prints a JSON array of findings on stdout in the same shape the analyzers in
|
|
350
|
+
# cartographer-analyze.sh return, so run_synthesize merges it without special
|
|
351
|
+
# handling. Diagnostics go to stderr, which run-audit.sh appends to audit.log.
|
|
352
|
+
|
|
353
|
+
# Word-boundary mention test.
|
|
354
|
+
#
|
|
355
|
+
# Boundaries are hand-rolled rather than \b because entity names contain
|
|
356
|
+
# hyphens, and \b treats '-' as a non-word character: \blist-prompt-rules\b
|
|
357
|
+
# would also match inside "my-list-prompt-rules-thing". Bounding on
|
|
358
|
+
# [^A-Za-z0-9_-] instead means a hyphenated name matches only when genuinely
|
|
359
|
+
# standalone, while a name inside a path ("plugins/beta/") still counts.
|
|
360
|
+
_cartographer_name_mentioned() {
|
|
361
|
+
local name="$1"
|
|
362
|
+
local files_json="$2"
|
|
363
|
+
|
|
364
|
+
local escaped
|
|
365
|
+
escaped=$(printf '%s' "$name" | sed 's/[][\.*^$(){}?+|\\]/\\&/g')
|
|
366
|
+
|
|
367
|
+
local fpath
|
|
368
|
+
while IFS= read -r fpath; do
|
|
369
|
+
[[ -z "$fpath" || ! -f "$fpath" ]] && continue
|
|
370
|
+
if grep -qE "(^|[^A-Za-z0-9_-])${escaped}([^A-Za-z0-9_-]|\$)" "$fpath" 2>/dev/null; then
|
|
371
|
+
return 0
|
|
372
|
+
fi
|
|
373
|
+
done < <(printf '%s' "$files_json" | jq -r '.[]' 2>/dev/null)
|
|
374
|
+
return 1
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
cartographer_analyze_undocumented_entity() {
|
|
378
|
+
local files_json="${1:-[]}"
|
|
379
|
+
local repo_root="${2:?repo_root required}"
|
|
380
|
+
local globs_json="${3:-[]}"
|
|
381
|
+
local exclude_json="${4:-[]}"
|
|
382
|
+
local max_findings="${5:-20}"
|
|
383
|
+
|
|
384
|
+
# An empty corpus cannot document anything, so every entity would look
|
|
385
|
+
# undocumented. Refuse rather than emit a burst of false findings that the
|
|
386
|
+
# emit phase would dedup-sentinel and never re-evaluate.
|
|
387
|
+
local corpus_count
|
|
388
|
+
corpus_count=$(printf '%s' "$files_json" | jq 'length' 2>/dev/null || printf '0')
|
|
389
|
+
[[ "${corpus_count:-0}" -eq 0 ]] && { printf '[]'; return 0; }
|
|
390
|
+
|
|
391
|
+
local findings="[]"
|
|
392
|
+
local emitted=0 dropped=0
|
|
393
|
+
local root="${repo_root%/}"
|
|
394
|
+
|
|
395
|
+
# nullglob so an unmatched pattern expands to nothing rather than to itself.
|
|
396
|
+
# Restore the prior setting — this library is sourced, not run.
|
|
397
|
+
local had_nullglob=0
|
|
398
|
+
shopt -q nullglob && had_nullglob=1
|
|
399
|
+
shopt -s nullglob
|
|
400
|
+
|
|
401
|
+
local glob match
|
|
402
|
+
while IFS= read -r glob; do
|
|
403
|
+
[[ -z "$glob" ]] && continue
|
|
404
|
+
# shellcheck disable=SC2086 # unquoted on purpose: this is the glob expansion
|
|
405
|
+
for match in ${root}/$glob; do
|
|
406
|
+
[[ -e "$match" ]] || continue
|
|
407
|
+
|
|
408
|
+
local trimmed="${match%/}"
|
|
409
|
+
local name relpath
|
|
410
|
+
name=$(basename "$trimmed")
|
|
411
|
+
relpath="${trimmed#"${root}"/}"
|
|
412
|
+
|
|
413
|
+
local excluded=0 excl
|
|
414
|
+
while IFS= read -r excl; do
|
|
415
|
+
[[ -z "$excl" ]] && continue
|
|
416
|
+
[[ "$relpath" == *"$excl"* ]] && { excluded=1; break; }
|
|
417
|
+
done < <(printf '%s' "$exclude_json" | jq -r '.[]' 2>/dev/null)
|
|
418
|
+
[[ "$excluded" -eq 1 ]] && continue
|
|
419
|
+
|
|
420
|
+
_cartographer_name_mentioned "$name" "$files_json" && continue
|
|
421
|
+
|
|
422
|
+
if [[ "$emitted" -ge "$max_findings" ]]; then
|
|
423
|
+
dropped=$(( dropped + 1 ))
|
|
424
|
+
continue
|
|
425
|
+
fi
|
|
426
|
+
|
|
427
|
+
local finding
|
|
428
|
+
finding=$(jq -n \
|
|
429
|
+
--arg fa "$trimmed" \
|
|
430
|
+
--arg n "$name" \
|
|
431
|
+
--arg rp "$relpath" \
|
|
432
|
+
'{
|
|
433
|
+
type: "undocumented_entity",
|
|
434
|
+
severity: "warning",
|
|
435
|
+
file_a: $fa,
|
|
436
|
+
excerpt_a: $n,
|
|
437
|
+
file_b: null,
|
|
438
|
+
excerpt_b: null,
|
|
439
|
+
description: ($n + " exists at " + $rp
|
|
440
|
+
+ " but is not mentioned in any instruction file."),
|
|
441
|
+
suggested_fix: ("Document " + $n
|
|
442
|
+
+ " in CLAUDE.md, or exclude its path from cartographer.undocumented_entity.")
|
|
443
|
+
}')
|
|
444
|
+
findings=$(printf '%s' "$findings" | jq --argjson f "$finding" '. + [$f]')
|
|
445
|
+
emitted=$(( emitted + 1 ))
|
|
446
|
+
done
|
|
447
|
+
done < <(printf '%s' "$globs_json" | jq -r '.[]' 2>/dev/null)
|
|
448
|
+
|
|
449
|
+
[[ "$had_nullglob" -eq 0 ]] && shopt -u nullglob
|
|
450
|
+
|
|
451
|
+
# Say what was dropped. A silent truncation reads as "this is everything".
|
|
452
|
+
if [[ "$dropped" -gt 0 ]]; then
|
|
453
|
+
printf 'undocumented_entity: capped at %s findings, %s candidate(s) dropped\n' \
|
|
454
|
+
"$max_findings" "$dropped" >&2
|
|
455
|
+
fi
|
|
456
|
+
|
|
457
|
+
printf '%s' "$findings"
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
462
|
+
|
|
463
|
+
Run: `bats test/bats/cartographer-omission.bats`
|
|
464
|
+
Expected: PASS, 11 tests.
|
|
465
|
+
|
|
466
|
+
- [ ] **Step 5: Run shellcheck**
|
|
467
|
+
|
|
468
|
+
Run: `npm run test:shellcheck`
|
|
469
|
+
Expected: clean. The only suppression is the deliberate `SC2086` on the glob-expansion line; if shellcheck flags anything else, fix the code rather than adding a suppression.
|
|
470
|
+
|
|
471
|
+
- [ ] **Step 6: Break one assertion on purpose**
|
|
472
|
+
|
|
473
|
+
In the hyphen boundary test, temporarily change the expected `1` to `0`, re-run, confirm it **fails**, revert. That test is the one most likely to silently pass for the wrong reason.
|
|
474
|
+
|
|
475
|
+
- [ ] **Step 7: Commit**
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
git add plugins/cartographer/scripts/lib/cartographer-omission.sh \
|
|
479
|
+
test/bats/cartographer-omission.bats
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Then run `/commit`. Suggested subject:
|
|
483
|
+
`feat(cartographer): detect entities no instruction file mentions :mag:`
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
### Task 3: Pipeline integration and documentation
|
|
488
|
+
|
|
489
|
+
**Files:**
|
|
490
|
+
|
|
491
|
+
- Modify: `plugins/cartographer/scripts/run-audit.sh:142-197` (`run_synthesize`) and the header comment at lines 7-12
|
|
492
|
+
- Modify: `plugins/cartographer/README.md`
|
|
493
|
+
- Modify: `plugins/cartographer/skills/cartographer/SKILL.md:3` and `:112`
|
|
494
|
+
- Test: `test/bats/cartographer-omission.bats` (append integration tests)
|
|
495
|
+
|
|
496
|
+
**Interfaces:**
|
|
497
|
+
|
|
498
|
+
- Consumes: `cartographer_analyze_undocumented_entity` (Task 2); `cartographer_config_undocumented_enabled` / `_globs` / `_exclude` / `_max_findings` (Task 1).
|
|
499
|
+
- Produces: nothing new for later tasks. This is the last task.
|
|
500
|
+
|
|
501
|
+
- [ ] **Step 1: Write the failing integration tests**
|
|
502
|
+
|
|
503
|
+
Append to `test/bats/cartographer-omission.bats`:
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
# Stubs `claude` so the three LLM phases make no real call, then runs a full
|
|
507
|
+
# audit. Any extra env the caller needs is exported before calling this.
|
|
508
|
+
_run_audit() {
|
|
509
|
+
local stub="${BATS_TEST_TMPDIR}/bin"
|
|
510
|
+
mkdir -p "$stub"
|
|
511
|
+
printf '#!/usr/bin/env bash\ncat >/dev/null\nprintf "[]"\n' > "${stub}/claude"
|
|
512
|
+
chmod +x "${stub}/claude"
|
|
513
|
+
|
|
514
|
+
PATH="${stub}:${PATH}" \
|
|
515
|
+
CARTOGRAPHER_DIR="${BATS_TEST_TMPDIR}/state" \
|
|
516
|
+
CARTOGRAPHER_REPO_ROOT="$FIXTURE_REPO" \
|
|
517
|
+
CARTOGRAPHER_TRIGGER="manual" \
|
|
518
|
+
CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" \
|
|
519
|
+
bash "${PLUGIN_ROOT}/scripts/run-audit.sh"
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
# run_emit writes findings with a bare `jq`, which pretty-prints — the file
|
|
523
|
+
# contains `"type": "undocumented_entity"` with a space. Parse rather than grep.
|
|
524
|
+
_findings_of_type() {
|
|
525
|
+
local dir="${BATS_TEST_TMPDIR}/state/findings"
|
|
526
|
+
local count=0 f
|
|
527
|
+
[[ -d "$dir" ]] || { printf '0'; return 0; }
|
|
528
|
+
for f in "$dir"/*.json; do
|
|
529
|
+
[[ -e "$f" ]] || continue
|
|
530
|
+
if [[ "$(jq -r '.type // ""' "$f" 2>/dev/null)" == "undocumented_entity" ]]; then
|
|
531
|
+
count=$(( count + 1 ))
|
|
532
|
+
fi
|
|
533
|
+
done
|
|
534
|
+
printf '%s' "$count"
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
@test "integration: a full audit records the undocumented entity on disk" {
|
|
538
|
+
_run_audit
|
|
539
|
+
[ "$(_findings_of_type)" = "1" ]
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
@test "integration: a targeted post-write audit records no undocumented entity" {
|
|
543
|
+
export CARTOGRAPHER_TARGET_FILE="$DOC"
|
|
544
|
+
_run_audit
|
|
545
|
+
[ "$(_findings_of_type)" = "0" ]
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
@test "integration: enabled=false suppresses the phase" {
|
|
549
|
+
mkdir -p "${FIXTURE_REPO}/.claude"
|
|
550
|
+
printf '%s\n' '{"cartographer":{"undocumented_entity":{"enabled":false}}}' \
|
|
551
|
+
> "${FIXTURE_REPO}/.claude/settings.json"
|
|
552
|
+
_run_audit
|
|
553
|
+
[ "$(_findings_of_type)" = "0" ]
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
558
|
+
|
|
559
|
+
Run: `bats test/bats/cartographer-omission.bats`
|
|
560
|
+
Expected: the first integration test FAILS with `0 != 1`; the other two pass vacuously (nothing runs the phase yet). Only the first proves anything at this point.
|
|
561
|
+
|
|
562
|
+
- [ ] **Step 3: Source the new library in run-audit.sh**
|
|
563
|
+
|
|
564
|
+
After line 32 (`source "$PLUGIN_ROOT/scripts/lib/cartographer-analyze.sh"`), add:
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
source "$PLUGIN_ROOT/scripts/lib/cartographer-omission.sh"
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
- [ ] **Step 4: Wire the phase into run_synthesize**
|
|
571
|
+
|
|
572
|
+
In `run_synthesize`, immediately after the `scope_findings=$(...)` assignment and before the `# Merge all raw findings` comment, insert:
|
|
573
|
+
|
|
574
|
+
```bash
|
|
575
|
+
# Disk → doc. Skipped on targeted post-write audits: DISCOVERED_FILES is a
|
|
576
|
+
# single file there, so grepping it for every entity name would report
|
|
577
|
+
# nearly the whole enumeration as undocumented, and the emit phase would
|
|
578
|
+
# dedup-sentinel those false findings permanently. scope_collision already
|
|
579
|
+
# no-ops on targeted runs for the same reason.
|
|
580
|
+
#
|
|
581
|
+
# Config is read inside the sub-shell rather than passed in, because the
|
|
582
|
+
# orchestrator's own config is never loaded (ecosystem-88v). PLUGIN_ROOT is
|
|
583
|
+
# assigned here because run-audit.sh does not export it, which is why the
|
|
584
|
+
# sibling phases' cartographer_config_load calls fail. Remove both
|
|
585
|
+
# workarounds when 88v lands.
|
|
586
|
+
local omission_findings="[]"
|
|
587
|
+
if [[ -z "$TARGET_FILE" ]]; then
|
|
588
|
+
omission_findings=$($_TIMEOUT_CMD "$_phase_timeout" bash -c \
|
|
589
|
+
"PLUGIN_ROOT='$PLUGIN_ROOT'
|
|
590
|
+
source '$PLUGIN_ROOT/scripts/lib/cartographer-config.sh'
|
|
591
|
+
source '$PLUGIN_ROOT/scripts/lib/cartographer-omission.sh'
|
|
592
|
+
cartographer_config_load '$REPO_ROOT'
|
|
593
|
+
[[ \"\$(cartographer_config_undocumented_enabled)\" == 'true' ]] \
|
|
594
|
+
|| { printf '[]'; exit 0; }
|
|
595
|
+
cartographer_analyze_undocumented_entity '$DISCOVERED_FILES' '$REPO_ROOT' \
|
|
596
|
+
\"\$(cartographer_config_undocumented_globs)\" \
|
|
597
|
+
\"\$(cartographer_config_undocumented_exclude)\" \
|
|
598
|
+
\"\$(cartographer_config_undocumented_max_findings)\"" \
|
|
599
|
+
2>>"$CARTOGRAPHER_DIR/audit.log") || omission_findings="[]"
|
|
600
|
+
fi
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
- [ ] **Step 5: Merge the findings**
|
|
604
|
+
|
|
605
|
+
Replace the `raw_all=$(jq -n ...)` block with:
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
local raw_all
|
|
609
|
+
raw_all=$(jq -n \
|
|
610
|
+
--argjson relate "${RELATE_FINDINGS:-[]}" \
|
|
611
|
+
--argjson stale "${stale_findings:-[]}" \
|
|
612
|
+
--argjson scope "${scope_findings:-[]}" \
|
|
613
|
+
--argjson omission "${omission_findings:-[]}" \
|
|
614
|
+
'$relate + $stale + $scope + $omission')
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
- [ ] **Step 6: Update the file header comment**
|
|
618
|
+
|
|
619
|
+
In `run-audit.sh`, change line 11 from:
|
|
620
|
+
|
|
621
|
+
```text
|
|
622
|
+
# 4. synthesize — stale_ref + scope_collision + finding hash computation
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
to:
|
|
626
|
+
|
|
627
|
+
```text
|
|
628
|
+
# 4. synthesize — stale_ref + scope_collision + undocumented_entity + hash
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
- [ ] **Step 7: Run tests to verify they pass**
|
|
632
|
+
|
|
633
|
+
Run: `bats test/bats/cartographer-omission.bats`
|
|
634
|
+
Expected: PASS, 14 tests.
|
|
635
|
+
|
|
636
|
+
- [ ] **Step 8: Verify against this repository**
|
|
637
|
+
|
|
638
|
+
Run:
|
|
639
|
+
|
|
640
|
+
```bash
|
|
641
|
+
CARTOGRAPHER_DIR="$(mktemp -d)" \
|
|
642
|
+
CARTOGRAPHER_REPO_ROOT="$PWD" \
|
|
643
|
+
CARTOGRAPHER_TRIGGER=manual \
|
|
644
|
+
CLAUDE_PLUGIN_ROOT="$PWD/plugins/cartographer" \
|
|
645
|
+
bash plugins/cartographer/scripts/run-audit.sh
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
Expected: exactly one `undocumented_entity` finding, for `skills/list-prompt-rules`. All 16 plugins are documented and `docs/adr/` is never enumerated. If a second finding appears, the enumeration is too broad — do not proceed until it is one.
|
|
649
|
+
|
|
650
|
+
- [ ] **Step 9: Update the README**
|
|
651
|
+
|
|
652
|
+
In `plugins/cartographer/README.md`, add a row to the "What it detects" table
|
|
653
|
+
after the `scope_collision` row at line 16:
|
|
654
|
+
|
|
655
|
+
```markdown
|
|
656
|
+
| `undocumented_entity` | Something that exists on disk — a plugin, a skill — that no instruction file mentions |
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
Then add to the "Configuration" section, after the `exclude_paths` note at
|
|
660
|
+
line 75:
|
|
661
|
+
|
|
662
|
+
````markdown
|
|
663
|
+
### Detecting omissions
|
|
664
|
+
|
|
665
|
+
Every other check reads the instruction files and tests what it finds against
|
|
666
|
+
the filesystem. `undocumented_entity` runs the other way: it enumerates
|
|
667
|
+
entities on disk and flags any whose name appears in no instruction file. That
|
|
668
|
+
is the one kind of drift the other checks structurally cannot see — something
|
|
669
|
+
absent produces no reference to follow.
|
|
670
|
+
|
|
671
|
+
```json
|
|
672
|
+
{
|
|
673
|
+
"cartographer": {
|
|
674
|
+
"undocumented_entity": {
|
|
675
|
+
"enabled": true,
|
|
676
|
+
"globs": ["plugins/*/", "skills/*/"],
|
|
677
|
+
"exclude": [],
|
|
678
|
+
"max_findings": 20
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
`globs` are relative to the repository root, and a glob matching nothing is
|
|
685
|
+
simply inert — the defaults do nothing in a repository without those
|
|
686
|
+
directories. The list is deliberately opt-in: most of a repository has no
|
|
687
|
+
business being named in `CLAUDE.md`, so only classes where you expect the
|
|
688
|
+
documentation to be *complete* belong here.
|
|
689
|
+
|
|
690
|
+
**Note:** as with `exclude_paths`, overriding `globs` or `exclude` replaces the
|
|
691
|
+
entire list rather than extending it. Repeat the defaults alongside your
|
|
692
|
+
additions if you mean to extend.
|
|
693
|
+
````
|
|
694
|
+
|
|
695
|
+
- [ ] **Step 10: Update the skill**
|
|
696
|
+
|
|
697
|
+
In `plugins/cartographer/skills/cartographer/SKILL.md`:
|
|
698
|
+
|
|
699
|
+
- Line 112: change the `--phase` list to `contradiction`, `stale_ref`, `dead_rule`, `scope_collision`, or `undocumented_entity`.
|
|
700
|
+
- Line 3 frontmatter `description`: add omissions to what it audits for, e.g. `...dead rules, scope collisions, and entities no instruction file mentions.`
|
|
701
|
+
|
|
702
|
+
The finding renderers at lines 65 and 135 read `.type` and `.description` generically — **do not change them.**
|
|
703
|
+
|
|
704
|
+
- [ ] **Step 11: Confirm CLAUDE.md needs no change**
|
|
705
|
+
|
|
706
|
+
The cartographer row in the plugin map describes the hook surface, not the phases. Read it and confirm. If it does mention phases, update it.
|
|
707
|
+
|
|
708
|
+
- [ ] **Step 12: Run the full CI suite**
|
|
709
|
+
|
|
710
|
+
Run: `npm run test:ci`
|
|
711
|
+
Expected: PASS — shellcheck, bats, schema, biome, markdownlint, manifest and reference linters.
|
|
712
|
+
|
|
713
|
+
- [ ] **Step 13: Commit**
|
|
714
|
+
|
|
715
|
+
```bash
|
|
716
|
+
git add plugins/cartographer/scripts/run-audit.sh \
|
|
717
|
+
plugins/cartographer/README.md \
|
|
718
|
+
plugins/cartographer/skills/cartographer/SKILL.md \
|
|
719
|
+
test/bats/cartographer-omission.bats
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
Then run `/commit`. Suggested subject:
|
|
723
|
+
`feat(cartographer): run the omission phase in every full audit :eyes:`
|
|
724
|
+
|
|
725
|
+
- [ ] **Step 14: Close the bead and open the PR**
|
|
726
|
+
|
|
727
|
+
```bash
|
|
728
|
+
bd close ecosystem-3eu --reason="undocumented_entity phase ships; detection, config, wiring, tests, docs"
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
Then open a PR with `/git-workflow:pr`. Never push to `main` — release-please needs every change to travel through a PR. Note in the PR body that `ecosystem-q4d` and `ecosystem-88v` were found during this work and deliberately left out.
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
## Notes for the implementer
|
|
736
|
+
|
|
737
|
+
**Why the sub-shell looks awkward.** The three sibling phases pass config in as positional parameters. This one reads config inside its own sub-shell instead. That is not a style preference — the orchestrator never loads config (`ecosystem-88v`), so a value read at the top of `run-audit.sh` would always be the shipped default and the `enabled: false` off-switch would be inert. The `PLUGIN_ROOT='...'` assignment is there for the same reason. Both go away when `88v` lands; the comment in the code says so.
|
|
738
|
+
|
|
739
|
+
**Do not "fix" the emit path.** You will notice while testing that no `cartographer.issue.found` event reaches `$ONLOOKER_EVENTS_LOG`. That is `ecosystem-q4d`, it predates this work, and it affects all five phases equally. Findings on disk are the correct assertion surface for these tests.
|
|
740
|
+
|
|
741
|
+
**bash 3.2.** `mapfile`/`readarray` are unavailable. Do not reach for them. `shopt -s nullglob` and `while read` are the portable idioms, and both are used above.
|