simt-dev 0.2.1 → 0.3.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +239 -0
  2. package/bin/simt-dev.mjs +21 -2
  3. package/docs/decisions/ADR-064-the-context-meter-counts-what-the-tool-wrote.md +107 -0
  4. package/docs/decisions/ADR-065-a-pack-is-one-shared-rule-set-pinned-by-digest.md +97 -0
  5. package/docs/decisions/ADR-066-a-monorepo-projects-once-per-package.md +115 -0
  6. package/docs/decisions/ADR-067-which-harnesses-read-agents-md-is-a-column.md +110 -0
  7. package/docs/decisions/ADR-068-the-mcp-revision-list-cites-what-was-read.md +111 -0
  8. package/docs/decisions/ADR-069-the-conformance-lint-checks-the-published-specification.md +90 -0
  9. package/docs/decisions/ADR-070-the-json-payload-carries-its-own-version.md +91 -0
  10. package/docs/decisions/ADR-071-a-rule-that-can-be-narrowed-is-written-narrow.md +106 -0
  11. package/docs/decisions/ADR-072-the-budget-is-a-declaration-beside-the-gates.md +104 -0
  12. package/docs/decisions/ADR-073-a-kernel-is-a-smaller-projection-the-repository-asks-for.md +92 -0
  13. package/docs/decisions/ADR-074-a-rule-and-a-decision-are-readable-by-uri.md +99 -0
  14. package/docs/decisions/ADR-075-nothing-leaves-the-machine.md +56 -0
  15. package/docs/decisions/ADR-076-a-change-to-what-agents-read-is-measured.md +70 -0
  16. package/docs/decisions/ADR-077-the-command-card-leads-the-region-and-reads-committed-evidence.md +91 -0
  17. package/docs/decisions/ADR-078-a-line-the-repository-already-proves-does-not-ship.md +117 -0
  18. package/docs/decisions/ADR-079-the-rules-are-enforced-at-the-tool-call-from-one-policy-table.md +162 -0
  19. package/docs/decisions/ADR-080-the-tools-own-names-are-not-repositories-and-neither-reader-decides-by-shape.md +99 -0
  20. package/docs/decisions/ADR-081-a-tracker-is-read-through-the-repositorys-template.md +36 -0
  21. package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +35 -0
  22. package/package.json +2 -1
  23. package/rules/core.md +1 -0
  24. package/rules/stacks/android.md +2 -0
  25. package/rules/stacks/cloudflare-workers.md +2 -0
  26. package/rules/stacks/connectiq.md +1 -0
  27. package/rules/stacks/container.md +2 -0
  28. package/rules/stacks/dart.md +2 -0
  29. package/rules/stacks/deno.md +2 -0
  30. package/rules/stacks/dotnet.md +2 -0
  31. package/rules/stacks/elixir.md +2 -0
  32. package/rules/stacks/go.md +2 -0
  33. package/rules/stacks/jvm.md +2 -0
  34. package/rules/stacks/node.md +2 -0
  35. package/rules/stacks/python.md +2 -0
  36. package/rules/stacks/react-native.md +2 -0
  37. package/rules/stacks/react-web.md +2 -0
  38. package/rules/stacks/ruby.md +2 -0
  39. package/rules/stacks/rust.md +2 -0
  40. package/rules/stacks/swift.md +2 -0
  41. package/rules/stacks/typescript.md +2 -0
  42. package/rules/stacks/uv.md +2 -0
  43. package/src/agenthooks/decide.mjs +123 -0
  44. package/src/agenthooks/policy.mjs +78 -0
  45. package/src/agenthooks/render.mjs +240 -0
  46. package/src/argv.mjs +55 -1
  47. package/src/bench/ablate.mjs +630 -0
  48. package/src/bench/adapters.json +149 -0
  49. package/src/bench/grade.mjs +229 -0
  50. package/src/bench/mine.mjs +811 -0
  51. package/src/bench/probes.mjs +1350 -0
  52. package/src/bench/report.mjs +899 -0
  53. package/src/bench/run.mjs +1002 -0
  54. package/src/commands/agenthook.mjs +282 -0
  55. package/src/commands/bench.mjs +359 -0
  56. package/src/commands/compare.mjs +15 -0
  57. package/src/commands/graph.mjs +45 -21
  58. package/src/commands/harness.mjs +301 -0
  59. package/src/commands/help.mjs +73 -2
  60. package/src/commands/index.mjs +67 -25
  61. package/src/commands/init.mjs +12 -8
  62. package/src/commands/mcp.mjs +20 -5
  63. package/src/commands/plan.mjs +99 -0
  64. package/src/commands/rules.mjs +42 -57
  65. package/src/commands/skills.mjs +67 -92
  66. package/src/commands/skillsinstall.mjs +17 -2
  67. package/src/commands/sync.mjs +62 -6
  68. package/src/commands/tokens.mjs +169 -0
  69. package/src/compare/row.mjs +73 -2
  70. package/src/decisions/index.mjs +23 -5
  71. package/src/graph/spec.mjs +227 -0
  72. package/src/graph/speccheck.mjs +68 -0
  73. package/src/hooks/dispatcher.mjs +35 -3
  74. package/src/kernel/card.mjs +254 -0
  75. package/src/kernel/inferability.mjs +188 -0
  76. package/src/mcp/resources.mjs +157 -0
  77. package/src/mcp/schema.mjs +139 -0
  78. package/src/mcp/server.mjs +228 -20
  79. package/src/mcp/tools.mjs +372 -1
  80. package/src/measure/context.mjs +542 -0
  81. package/src/measure/loaderView.mjs +435 -0
  82. package/src/measure/session.mjs +267 -0
  83. package/src/monorepo.mjs +339 -0
  84. package/src/output/index.mjs +78 -2
  85. package/src/packs.mjs +182 -0
  86. package/src/plan/index.mjs +650 -0
  87. package/src/plan/template.mjs +331 -0
  88. package/src/projection/loaders.mjs +689 -0
  89. package/src/projection/manifest.mjs +114 -3
  90. package/src/projection/project.mjs +140 -9
  91. package/src/projection/rules.mjs +365 -22
  92. package/src/projection/surfaces.mjs +155 -23
  93. package/src/projection/targets.mjs +37 -0
  94. package/src/toolwords.mjs +69 -0
  95. package/templates/plan/tracker.json +36 -0
@@ -0,0 +1,689 @@
1
+ // What each harness actually reads at session start — one row per reader, with the
2
+ // document every claim came from.
3
+ //
4
+ // `surfaces.mjs` answers "where do we WRITE a rule". That is half of a reader, and the
5
+ // half that is convenient: writing a file needs a path and nothing else. Reading one
6
+ // needs more. Claude Code concatenates every `CLAUDE.md` from the filesystem root down
7
+ // and expands `@path` imports four hops deep. Codex walks project root to cwd, takes at
8
+ // most one file per directory, and *stops adding files* once the merged chain reaches
9
+ // `project_doc_max_bytes` — so a repository can be inside the limit on every single file
10
+ // and past it as a chain. Antigravity scans `.agents/rules/` flat and ignores nested
11
+ // files. Three of those facts change what a byte count means, and none of them is in
12
+ // the surface table, which is why R06 had to be blocked on this one.
13
+ //
14
+ // WHAT THIS TABLE IS NOT. It is not a second opinion about any harness, and it does not
15
+ // re-verify `surfaces.mjs`: a path that appears here and there is one fact written twice
16
+ // because two consumers read it, not because either was guessed. `verified` is on every
17
+ // row and cites the page the row was read from, because a loader row with no provenance
18
+ // is worse than no row: it would be a number in a report that nobody can check, and
19
+ // context cost is exactly the number people will not check twice.
20
+ //
21
+ // FOUR DECISIONS, each with a cost.
22
+ //
23
+ // 1. A ROW IS A READER, NOT A PRODUCT. `claude` is Claude Code's loader; Codex and
24
+ // opencode are separate rows even though all three read `AGENTS.md`, because they
25
+ // disagree about what to do with a second one of anything. Keying by product would
26
+ // force those disagreements into a field called "notes".
27
+ //
28
+ // 2. `atStart` IS A BOOLEAN, NOT A RANK. A file either is loaded before the first
29
+ // message or it is not. `cursor` is the case that makes the alternative untenable:
30
+ // a `.mdc` with `alwaysApply: false` costs nothing until a matching file is in
31
+ // context, and a meter that counted it would report a cost the reader does not pay.
32
+ // Ordering the always-on files among themselves is `order`, and it is a sentence
33
+ // because that is what the vendor docs are.
34
+ //
35
+ // 3. A CAP IS NAMED WITH ITS SCOPE. Codex's 32 KiB applies to the *merged chain* and
36
+ // Antigravity's 24,000 B applies to *one rule file* — a difference that decides
37
+ // whether a finding belongs to a file or to a repository, so `cap.scope` is a
38
+ // required field rather than a comment. A reader that publishes no cap has
39
+ // `cap: null`, which means "checked, none found", not "not checked".
40
+ //
41
+ // 4. UNDOCUMENTED IS ITS OWN ANSWER. Copilot has no documented hooks file, and Gemini
42
+ // CLI's own configuration reference 404s today. Those cells are `null` with a
43
+ // `note` saying what was looked for. An empty string would read as "no hooks" and
44
+ // an invented path would read as a fact.
45
+
46
+ /**
47
+ * One row per reader. Ordered by how widely the reader is deployed, so a truncated
48
+ * report loses the rows nobody could have acted on.
49
+ *
50
+ * `start` entries, each one a file the reader may load:
51
+ *
52
+ * scope `project` (in the repository) or `user` (in the reader's home)
53
+ * path the path, relative to the repository root; `dir` entries carry the
54
+ * directory instead and are enumerated by `tree`
55
+ * tree `single` the named path only
56
+ * `chain` root to cwd; `chainMerge: 'first'` keeps one file per
57
+ * directory, `chainMerge: 'all'` keeps every candidate
58
+ * `flat` immediate children of `dir` only
59
+ * `nested` recursive under `dir`
60
+ * atStart loaded before the first message; `false` costs nothing until a
61
+ * matching file or a mention pulls it in
62
+ * absent the entry is skipped when any of these paths exists at the root —
63
+ * the documented precondition, evaluated where the vendor documents it
64
+ * why one line naming the document the entry came from
65
+ *
66
+ * `order` is concatenation order as the vendor states it, in words.
67
+ */
68
+ export const LOADERS = [
69
+ {
70
+ id: 'opencode',
71
+ label: 'OpenCode v2',
72
+ readers: 'OpenCode v2',
73
+ surface: 'opencode',
74
+ start: [
75
+ {
76
+ scope: 'project',
77
+ path: 'AGENTS.md',
78
+ tree: 'chain',
79
+ atStart: true,
80
+ why: 'OpenCode v2 instructions docs: every AGENTS.md from the workspace directory up to the project root is loaded and combined; V2 recognizes AGENTS.md only and does not fall back to CLAUDE.md',
81
+ },
82
+ {
83
+ scope: 'user',
84
+ path: '~/.config/opencode/AGENTS.md',
85
+ tree: 'single',
86
+ atStart: true,
87
+ why: 'OpenCode v2 instructions docs: the global file is loaded first, before the project chain',
88
+ },
89
+ {
90
+ scope: 'project',
91
+ path: '.claude/skills/{name}/SKILL.md',
92
+ tree: 'nested',
93
+ dir: '.claude/skills',
94
+ suffix: '/SKILL.md',
95
+ atStart: true,
96
+ metadata: true,
97
+ why: 'OpenCode v2 skills docs: .claude/skills is a project compatibility source, registered before .agents/skills',
98
+ },
99
+ {
100
+ scope: 'project',
101
+ path: '.agents/skills/{name}/SKILL.md',
102
+ tree: 'nested',
103
+ dir: '.agents/skills',
104
+ suffix: '/SKILL.md',
105
+ atStart: true,
106
+ // Frontmatter only, which is why the entry carries `metadata: true`: a skill's
107
+ // body is opened when a reader decides it is relevant, and billing the body to
108
+ // the session-start cost is the error this makes impossible to repeat.
109
+ metadata: true,
110
+ why: 'OpenCode v2 skills docs: .agents/skills is a project compatibility source; the model is shown the id, name and description, not the body',
111
+ },
112
+ {
113
+ scope: 'project',
114
+ path: '.opencode/skills/{name}/SKILL.md',
115
+ tree: 'nested',
116
+ dir: '.opencode/skills',
117
+ suffix: '/SKILL.md',
118
+ atStart: true,
119
+ metadata: true,
120
+ why: 'OpenCode v2 skills docs: .opencode/skills is the project source and outranks .agents/skills and .claude/skills for the same id',
121
+ },
122
+ ],
123
+ alsoReads: [
124
+ {
125
+ what: 'AGENTS.md below the workspace',
126
+ atStart: false,
127
+ why: 'OpenCode v2 instructions docs: instruction files below the workspace load when the agent reads or lists that area, not at session start',
128
+ },
129
+ {
130
+ what: 'opencode.json `instructions` array',
131
+ atStart: false,
132
+ why: 'OpenCode v2 instructions docs: the config schema accepts an instructions array, but V2 does not resolve its files, globs or URLs',
133
+ },
134
+ ],
135
+ order: 'global ~/.config/opencode/AGENTS.md, then project AGENTS.md files from the workspace directory up to the project root (nearest first); files are combined, not overridden',
136
+ cap: null,
137
+ capNote:
138
+ 'no byte cap published for AGENTS.md; V2 accepts an instructions array and does not load it, so there is no fetch timeout to apply',
139
+ imports: {
140
+ syntax: null,
141
+ depth: 0,
142
+ why: 'OpenCode v2 instructions docs: an AGENTS.md reference to another file is not an include; V2 loads AGENTS.md files by discovery, not by @path expansion',
143
+ },
144
+ nested: 'cumulative up to the project root at session start; files below the workspace load when that area is read',
145
+ skills: ['.agents/skills', '.opencode/skills', '.claude/skills'],
146
+ hooks: {
147
+ path: '.opencode/plugins/',
148
+ verified:
149
+ 'OpenCode v2 plugins docs: direct .ts and .js files and immediate plugin packages are loaded from every discovered .opencode/plugins/ directory, and the plugins array in opencode.json(c)',
150
+ },
151
+ mcp: {
152
+ path: 'opencode.json',
153
+ key: 'mcp.servers',
154
+ verified:
155
+ 'OpenCode v2 MCP servers docs: servers are named under mcp.servers; V2 does not place server names directly under mcp',
156
+ },
157
+ verified:
158
+ 'OpenCode v2 instructions, skills, plugins and MCP docs (opencode.ai/v2/docs, read 2026-10-14) — V2 loads AGENTS.md only, combining the global file with every project AGENTS.md from the workspace up to the project root; CLAUDE.md is not a fallback; the instructions array is accepted and not loaded; skills advertise name and description from .opencode/skills, .claude/skills and .agents/skills',
159
+ note: 'This row is OpenCode v2. V1 read CLAUDE.md when AGENTS.md was absent and loaded the instructions array; V2 does neither, so a meter that still did would bill bytes this reader does not pay.',
160
+ },
161
+ {
162
+ id: 'claude',
163
+ label: 'Claude Code',
164
+ readers: 'Claude Code',
165
+ surface: 'claude',
166
+ start: [
167
+ {
168
+ scope: 'project',
169
+ path: 'CLAUDE.md',
170
+ tree: 'chain',
171
+ atStart: true,
172
+ why: 'Claude Code memory docs: CLAUDE.md and CLAUDE.local.md are discovered from the working directory and every directory above it, and all discovered files are concatenated',
173
+ },
174
+ {
175
+ scope: 'project',
176
+ path: 'CLAUDE.local.md',
177
+ tree: 'chain',
178
+ atStart: true,
179
+ why: 'Claude Code memory docs: CLAUDE.local.md is loaded at each level, after CLAUDE.md from that level',
180
+ },
181
+ {
182
+ scope: 'project',
183
+ path: 'AGENTS.md',
184
+ tree: 'chain',
185
+ atStart: true,
186
+ absent: ['CLAUDE.md', 'CLAUDE.local.md'],
187
+ why: 'Claude Code memory docs: AGENTS.md is read directly, but only when there is no CLAUDE.md or CLAUDE.local.md in the working directory or above it',
188
+ },
189
+ {
190
+ scope: 'project',
191
+ path: '.claude/rules/{name}.md',
192
+ tree: 'nested',
193
+ dir: '.claude/rules',
194
+ suffix: '.md',
195
+ atStart: true,
196
+ why: 'Claude Code memory docs: project rules under .claude/rules/ are discovered recursively and loaded at session start when unscoped',
197
+ },
198
+ {
199
+ scope: 'user',
200
+ path: '~/.claude/rules/*.md',
201
+ tree: 'flat',
202
+ dir: '.claude/rules',
203
+ atStart: true,
204
+ why: 'Claude Code memory docs: user-level rules in ~/.claude/rules/ load before project rules',
205
+ },
206
+ {
207
+ scope: 'project',
208
+ path: '.claude/skills/{name}/SKILL.md',
209
+ tree: 'nested',
210
+ dir: '.claude/skills',
211
+ suffix: '/SKILL.md',
212
+ atStart: true,
213
+ metadata: true,
214
+ why: 'Claude Code memory docs: skills are discovered under .claude/skills',
215
+ },
216
+ ],
217
+ alsoReads: [
218
+ {
219
+ what: 'MEMORY.md (auto memory)',
220
+ atStart: true,
221
+ why: 'Claude Code memory docs: auto memory loads the first 200 lines or 25 KB, whichever comes first',
222
+ },
223
+ ],
224
+ order: 'filesystem root down to the working directory, CLAUDE.md before CLAUDE.local.md at each level',
225
+ cap: {
226
+ bytes: 4 * 1024 * 1024,
227
+ scope: 'file',
228
+ config: 'none — a fixed limit',
229
+ why: 'Claude Code memory docs: "Claude Code loads a CLAUDE.md file of up to 4 MiB in full and skips a larger file"',
230
+ },
231
+ imports: {
232
+ syntax: '@path',
233
+ depth: 4,
234
+ why: 'Claude Code memory docs: `@path/to/import` is expanded at launch, resolved relative to the importing file, to a maximum depth of four hops; parsing skips code spans and fenced blocks, `@` inside backticks is literal, quoted paths are not imported, and an import outside the working directory prompts for approval',
235
+ },
236
+ nested: 'on demand — a subdirectory CLAUDE.md loads when Claude reads, writes or edits a file in that directory',
237
+ skills: ['.claude/skills'],
238
+ hooks: {
239
+ path: '.claude/settings.json',
240
+ verified:
241
+ 'Claude Code hooks docs, hook locations: project settings at .claude/settings.json and .claude/settings.local.json, user settings at ~/.claude/settings.json',
242
+ },
243
+ mcp: {
244
+ path: '.mcp.json',
245
+ key: null,
246
+ verified:
247
+ 'Claude Code MCP docs: project-scoped servers live in .mcp.json in the project root; local and user scope live in ~/.claude.json',
248
+ },
249
+ verified:
250
+ 'Claude Code memory docs (docs.claude.com/en/docs/claude-code/memory, read 2026-10-14) — CLAUDE.md and CLAUDE.local.md are concatenated from every directory above the working directory, subdirectory files load on demand, `@path` imports expand to four hops, a CLAUDE.md larger than 4 MiB is skipped, and AGENTS.md is read only when no CLAUDE.md exists above the working directory',
251
+ note:
252
+ 'The AGENTS.md row carries an `absent` condition because the vendor documents it as one. A repository holding both files is read by Claude Code for the CLAUDE.md alone, which is why the pointer surface exists.',
253
+ },
254
+ {
255
+ id: 'codex',
256
+ label: 'Codex CLI',
257
+ readers: 'Codex CLI, Codex IDE extension',
258
+ surface: null,
259
+ start: [
260
+ {
261
+ scope: 'project',
262
+ path: 'AGENTS.override.md',
263
+ tree: 'chain',
264
+ atStart: true,
265
+ why: 'Codex AGENTS.md docs: each directory from the project root to the working directory is checked for AGENTS.override.md first, then AGENTS.md, then the configured fallback filenames, and at most one file per directory is used',
266
+ },
267
+ {
268
+ scope: 'project',
269
+ path: 'AGENTS.md',
270
+ tree: 'chain',
271
+ atStart: true,
272
+ why: 'Codex AGENTS.md docs: AGENTS.md is the second precedence in the same per-directory search',
273
+ },
274
+ {
275
+ scope: 'user',
276
+ path: '~/.codex/AGENTS.override.md',
277
+ tree: 'single',
278
+ atStart: true,
279
+ why: 'Codex AGENTS.md docs: the global file is AGENTS.override.md if non-empty, otherwise AGENTS.md; CODEX_HOME overrides the home directory',
280
+ },
281
+ {
282
+ scope: 'user',
283
+ path: '~/.codex/AGENTS.md',
284
+ tree: 'single',
285
+ atStart: true,
286
+ why: 'Codex AGENTS.md docs: AGENTS.md is the global fallback when AGENTS.override.md is empty or absent',
287
+ },
288
+ ],
289
+ alsoReads: [
290
+ {
291
+ what: 'project_doc_fallback_filenames',
292
+ atStart: true,
293
+ why: 'Codex config reference: an array of additional filenames checked per directory after AGENTS.override.md and AGENTS.md',
294
+ },
295
+ {
296
+ what: 'model_instructions_file',
297
+ atStart: true,
298
+ why: 'Codex config reference: a file that replaces AGENTS.md as the project instructions source',
299
+ },
300
+ ],
301
+ order: 'project root down to the working directory, merged in that order and joined with blank lines',
302
+ cap: {
303
+ bytes: 32768,
304
+ scope: 'chain',
305
+ config: 'project_doc_max_bytes',
306
+ why: 'Codex AGENTS.md docs: empty files are skipped and Codex stops adding files once the combined size reaches project_doc_max_bytes, which defaults to 32 KiB',
307
+ },
308
+ imports: {
309
+ syntax: null,
310
+ depth: 0,
311
+ why: 'no import syntax documented for AGENTS.md; a reference to another file is an instruction to the model, not an include',
312
+ },
313
+ nested: 'cumulative — every directory from the project root to the working directory is merged, and the cap applies to the merge',
314
+ skills: [],
315
+ skillsNote: 'no skill directory is documented for Codex, so the cell is empty rather than borrowed from another reader',
316
+ hooks: {
317
+ path: '.codex/hooks.json',
318
+ verified:
319
+ 'Codex hooks docs: <repo>/.codex/hooks.json or ~/.codex/hooks.json, or inline [hooks] tables in config.toml',
320
+ },
321
+ mcp: {
322
+ path: '.codex/config.toml',
323
+ key: 'mcp_servers',
324
+ verified: 'Codex MCP docs: MCP configuration is stored in config.toml, ~/.codex/config.toml by default',
325
+ },
326
+ verified:
327
+ 'Codex AGENTS.md documentation and configuration reference (developers.openai.com/codex, read 2026-10-14) — the project scope walks from the project root to the working directory, taking at most one file per directory in the order AGENTS.override.md, AGENTS.md, project_doc_fallback_filenames; the merge runs root to cwd and stops adding files at project_doc_max_bytes, 32 KiB by default',
328
+ note:
329
+ 'This is the row that made the table worth building: `cap.scope` is `chain`, so a repository can be inside 32 KiB in every file and still lose the tail of its deepest instruction file to the merge.',
330
+ },
331
+ {
332
+ id: 'cursor',
333
+ label: 'Cursor',
334
+ readers: 'Cursor',
335
+ surface: 'cursor',
336
+ start: [
337
+ {
338
+ scope: 'project',
339
+ path: '.cursor/rules/{name}.mdc',
340
+ tree: 'nested',
341
+ dir: '.cursor/rules',
342
+ suffix: '.mdc',
343
+ // Only the always-apply rows. A `.mdc` with `alwaysApply: false` loads on a
344
+ // matching file or a description match, which is a turn, not a session-start
345
+ // cost — and Cursor publishes no byte cap, so the file's size is not the thing
346
+ // being limited.
347
+ frontmatter: { alwaysApply: 'true' },
348
+ atStart: true,
349
+ why: 'Cursor rules docs: alwaysApply: true alone means the rule is always included; alwaysApply: false attaches on a matching file, on a description match, or only on an @-mention',
350
+ },
351
+ {
352
+ scope: 'project',
353
+ path: 'AGENTS.md',
354
+ tree: 'chain',
355
+ atStart: true,
356
+ why: 'Cursor rules docs: AGENTS.md is supported in the project root and in subdirectories, combined with parent files, with the more specific one winning',
357
+ },
358
+ ],
359
+ alsoReads: [
360
+ {
361
+ what: '.cursor/rules/*.mdc with alwaysApply: false',
362
+ atStart: false,
363
+ why: 'Cursor rules docs: globs auto-attach on a matching file, a description lets the model decide, and a rule with neither loads only on an @-mention',
364
+ },
365
+ {
366
+ what: 'team and user rules',
367
+ atStart: true,
368
+ why: 'Cursor rules docs: precedence is Team Rules, then Project Rules, then User Rules; the latter two are configured in the app, not in a file this tool can project',
369
+ },
370
+ ],
371
+ order: 'team rules, then project rules, then user rules',
372
+ cap: null,
373
+ capNote:
374
+ 'no byte cap published; the docs recommend keeping rules under 500 lines, which is guidance and not a limit the reader enforces',
375
+ imports: {
376
+ syntax: null,
377
+ depth: 0,
378
+ why: 'Cursor rules docs: an @file in a rule body is not inlined — "the file\'s contents are not inlined into the prompt"',
379
+ },
380
+ nested: 'cumulative — nested AGENTS.md files are combined with their parents and the more specific one wins; .cursor/rules/ may hold folders',
381
+ skills: [],
382
+ skillsNote: 'no project skill directory is documented for Cursor',
383
+ hooks: {
384
+ path: '.cursor/hooks.json',
385
+ verified: 'Cursor agent hooks docs: <project-root>/.cursor/hooks.json and ~/.cursor/hooks.json',
386
+ },
387
+ mcp: {
388
+ path: '.cursor/mcp.json',
389
+ key: null,
390
+ verified: 'Cursor MCP docs: "Create .cursor/mcp.json in your project"',
391
+ },
392
+ verified:
393
+ 'Cursor rules documentation (cursor.com/docs/context/rules, read 2026-10-14) — only .cursor/rules/*.mdc is read, a plain .md there is ignored; alwaysApply decides whether a rule costs bytes at start; @file references in a rule body are not inlined; AGENTS.md is read in the project root and in subdirectories, combined with parents',
394
+ note: 'A rule with alwaysApply: false is listed under alsoReads rather than start, because it costs nothing until something matches.',
395
+ },
396
+ {
397
+ id: 'antigravity',
398
+ label: 'Antigravity',
399
+ readers: 'Antigravity IDE, Antigravity CLI',
400
+ surface: 'antigravity',
401
+ start: [
402
+ {
403
+ scope: 'project',
404
+ path: '.agents/rules/{name}.md',
405
+ tree: 'flat',
406
+ dir: '.agents/rules',
407
+ suffix: '.md',
408
+ // `trigger: always_on` is the value that costs bytes before the first message.
409
+ // `model_decision` injects a path and a description only, and `glob` waits for
410
+ // a matching file — all three are real files in the same directory, and only
411
+ // one of them is start cost.
412
+ frontmatter: { trigger: 'always_on' },
413
+ atStart: true,
414
+ why: 'Antigravity rules docs: trigger is one of always_on, model_decision, glob, manual; model_decision injects only the path and description',
415
+ },
416
+ {
417
+ scope: 'project',
418
+ path: 'AGENTS.md',
419
+ tree: 'chain',
420
+ atStart: true,
421
+ why: 'Antigravity rules docs: AGENTS.md, GEMINI.md and .agents/AGENTS.md in the root or any subdirectory are always-on workspace rules, and they take no frontmatter',
422
+ },
423
+ {
424
+ scope: 'user',
425
+ path: '~/.gemini/config/rules/*.md',
426
+ tree: 'flat',
427
+ dir: '.gemini/config/rules',
428
+ atStart: true,
429
+ why: 'Antigravity rules docs: global modular rules at ~/.gemini/config/rules/*.md, alongside ~/.gemini/AGENTS.md and ~/.gemini/config/AGENTS.md',
430
+ },
431
+ ],
432
+ alsoReads: [
433
+ {
434
+ what: '.agents/rules/*.md with trigger: model_decision',
435
+ atStart: false,
436
+ why: 'Antigravity rules docs: progressive disclosure — the path and description are injected and the body is opened on demand',
437
+ },
438
+ {
439
+ what: '.agents/rules/*.md in nested directories',
440
+ atStart: false,
441
+ why: 'Antigravity rules docs: .agents/rules/ is scanned flat — nested files are ignored unless registered in .agents/rules.json',
442
+ },
443
+ ],
444
+ order: 'rules are cumulative across the global, workspace and directory scopes; directory scope is resolved by walking up from the edited file to the workspace root',
445
+ cap: {
446
+ bytes: 24000,
447
+ scope: 'file',
448
+ config: 'none — a fixed limit',
449
+ why: 'Antigravity rules docs: a rule file is truncated at 24,000 bytes, counted after @[label](path) includes are expanded',
450
+ },
451
+ alsoCaps: [
452
+ {
453
+ bytes: 20000,
454
+ scope: 'tokens',
455
+ appliesTo: 'global plus always_on rules combined',
456
+ why: 'Antigravity rules docs: past a 20,000-token budget the largest rules are demoted to a pointer line, "- <path>: <description>"',
457
+ },
458
+ ],
459
+ imports: {
460
+ syntax: '@[label](path)',
461
+ depth: 0,
462
+ why: 'Antigravity rules docs: @[label](path) inlines the file before the size limits are checked, with its frontmatter stripped; a bare @filename only rewrites to a canonical absolute path',
463
+ },
464
+ nested: 'cumulative — a rule applies to its directory and every subdirectory; the .agents/rules/ scan itself is flat',
465
+ skills: [],
466
+ skillsNote: 'no skill directory is documented for Antigravity',
467
+ hooks: {
468
+ path: '.agents/hooks.json',
469
+ verified: 'Antigravity hooks docs: workspace level .agents/hooks.json, global ~/.gemini/config/hooks.json',
470
+ },
471
+ mcp: {
472
+ path: '.agents/mcp_config.json',
473
+ key: null,
474
+ verified: 'Antigravity MCP docs: "Configured in your workspace under .agents/mcp_config.json"',
475
+ },
476
+ verified:
477
+ 'Google Antigravity rules documentation (antigravity.google/docs/rules/, read 2026-10-14) — rules are cumulative across global, workspace and directory scope; .agents/rules/*.md requires frontmatter with trigger, and an invalid block silently discards the rule; the directory is scanned flat and nested files are ignored unless registered in .agents/rules.json; each rule file is truncated at 24,000 bytes after includes are expanded, and global plus always_on rules share a 20,000-token budget past which the largest are demoted to pointers',
478
+ note:
479
+ 'The flat scan is the fact that made the meter wrong before this table existed: a recursive walk of .agents/rules/ counts bytes for files Antigravity never reads.',
480
+ },
481
+ {
482
+ id: 'copilot',
483
+ label: 'GitHub Copilot',
484
+ readers: 'Copilot CLI, Copilot chat, Copilot code review, Copilot cloud agent',
485
+ surface: 'copilot',
486
+ start: [
487
+ {
488
+ scope: 'project',
489
+ path: '.github/copilot-instructions.md',
490
+ tree: 'single',
491
+ atStart: true,
492
+ why: 'GitHub Copilot docs, adding repository instructions: .github/copilot-instructions.md applies to every request in the repository',
493
+ },
494
+ {
495
+ scope: 'project',
496
+ path: '.github/instructions/**/*.instructions.md',
497
+ tree: 'nested',
498
+ dir: '.github/instructions',
499
+ suffix: '.instructions.md',
500
+ frontmatter: { applyTo: '**' },
501
+ atStart: true,
502
+ why: 'GitHub Copilot docs: a path-specific file is applied when a file matching applyTo is in context, and applyTo: "**" matches every file at session start',
503
+ },
504
+ {
505
+ scope: 'project',
506
+ path: 'AGENTS.md',
507
+ tree: 'chain',
508
+ atStart: true,
509
+ why: 'GitHub Copilot docs, agent instructions: one or more AGENTS.md anywhere in the repository, the nearest to the file in play wins, or a single CLAUDE.md or GEMINI.md at the root',
510
+ },
511
+ ],
512
+ alsoReads: [
513
+ {
514
+ what: '.github/instructions/**/*.instructions.md',
515
+ atStart: false,
516
+ why: 'GitHub Copilot docs: applyTo globs select the files, and excludeAgent keeps the file away from code review or the cloud agent',
517
+ },
518
+ ],
519
+ order: 'personal instructions, then repository instructions, then organization instructions',
520
+ cap: null,
521
+ capNote: 'no byte cap published for Copilot instructions',
522
+ imports: {
523
+ syntax: null,
524
+ depth: 0,
525
+ why: 'no import syntax documented for Copilot instruction files',
526
+ },
527
+ nested: 'nearest wins — AGENTS.md files anywhere in the repository are candidates and the nearest one to the file in play is used; .github/instructions/ may hold subdirectories',
528
+ skills: [],
529
+ skillsNote: 'no project skill directory is documented for Copilot',
530
+ hooks: null,
531
+ hooksNote: 'no hooks file is documented for Copilot in any first-party page checked on 2026-10-14',
532
+ mcp: {
533
+ path: '.vscode/mcp.json',
534
+ key: null,
535
+ verified:
536
+ 'GitHub Copilot MCP docs: the VS Code extension reads .vscode/mcp.json; Copilot CLI reads .mcp.json, .github/mcp.json or ~/.copilot/mcp-config.json; a repository-level configuration entered on github.com is not a file at all',
537
+ },
538
+ verified:
539
+ 'GitHub Copilot documentation (docs.github.com/en/copilot, read 2026-10-14) — repository-wide instructions live at .github/copilot-instructions.md; path-specific instructions live at .github/instructions/**/NAME.instructions.md with applyTo globs and an optional excludeAgent; agent instructions are one or more AGENTS.md anywhere in the repository, nearest wins, or a single root CLAUDE.md or GEMINI.md; no byte cap is published',
540
+ note:
541
+ 'Which Copilot reader consumes these differs — the cloud agent and code review are documented as reading repository and path-specific instructions — so the row is one reader with a split, the same split `surfaces.mjs` records on the copilot row.',
542
+ },
543
+ {
544
+ id: 'deepseek',
545
+ label: 'DeepSeek harness',
546
+ readers: 'DeepSeek harness',
547
+ // No projection surface. It reads AGENTS.md, so the standard region is its channel
548
+ // (ADR-067). This row exists because its chain is not Codex's: both AGENTS.md and
549
+ // CLAUDE.md load unless their trimmed bytes match, and the budget drops the broader
550
+ // file before it truncates the most specific one.
551
+ surface: null,
552
+ chainMerge: 'all',
553
+ dedupe: 'trim',
554
+ start: [
555
+ {
556
+ scope: 'project',
557
+ path: 'AGENTS.md',
558
+ tree: 'chain',
559
+ atStart: true,
560
+ why: 'DeepSeek harness agent-instructions docs: AGENTS.md is the first base candidate in each directory from the project root down to the session working directory',
561
+ },
562
+ {
563
+ scope: 'project',
564
+ path: 'CLAUDE.md',
565
+ tree: 'chain',
566
+ atStart: true,
567
+ why: 'DeepSeek harness agent-instructions docs: CLAUDE.md is the second base candidate and loads beside AGENTS.md unless the two are identical after trimming',
568
+ },
569
+ {
570
+ scope: 'project',
571
+ path: 'AGENTS.local.md',
572
+ tree: 'chain',
573
+ atStart: true,
574
+ why: 'DeepSeek harness agent-instructions docs: AGENTS.local.md is a local overlay loaded after the base candidates in the same directory',
575
+ },
576
+ {
577
+ scope: 'project',
578
+ path: 'CLAUDE.local.md',
579
+ tree: 'chain',
580
+ atStart: true,
581
+ why: 'DeepSeek harness agent-instructions docs: CLAUDE.local.md is the second local overlay, under the same per-directory trim dedup',
582
+ },
583
+ {
584
+ scope: 'user',
585
+ path: '~/.dsh/AGENTS.md',
586
+ tree: 'single',
587
+ atStart: true,
588
+ why: 'DeepSeek harness agent-instructions docs: the user-global file is $DSH_HOME/AGENTS.md, defaulting to ~/.dsh/AGENTS.md',
589
+ },
590
+ {
591
+ scope: 'user',
592
+ path: '~/.agents/AGENTS.md',
593
+ tree: 'single',
594
+ atStart: true,
595
+ why: 'DeepSeek harness agent-instructions docs: the shared agents root is $DSH_AGENTS_HOME or ~/.agents, and a copy that matches the harness-home file collapses behind it',
596
+ },
597
+ ],
598
+ alsoReads: [
599
+ {
600
+ what: 'AGENTS.md below the session working directory',
601
+ atStart: false,
602
+ why: 'DeepSeek harness agent-instructions docs: a successful read, write or edit that reaches a deeper directory loads that directory\'s instruction file on the next request; shell navigation does not',
603
+ },
604
+ ],
605
+ order:
606
+ 'user-global ~/.dsh/AGENTS.md, then ~/.agents/AGENTS.md, then every project candidate from the project root down to the working directory, broad to specific; within one directory, base files then local overlays',
607
+ cap: {
608
+ bytes: 65536,
609
+ scope: 'chain',
610
+ config: 'maxBytes',
611
+ drop: 'broader',
612
+ why: 'DeepSeek harness agent-instructions docs: dsh-base sets maxBytes to 65536, and rendering drops whole broader files before truncating the most specific one',
613
+ },
614
+ alsoCaps: [
615
+ {
616
+ bytes: 1048576,
617
+ scope: 'file',
618
+ appliesTo: 'one source file before rendering',
619
+ config: 'maxSourceBytes',
620
+ why: 'DeepSeek harness agent-instructions docs: maxSourceBytes defaults to 1048576, and a larger source file is ignored rather than truncated into the chain',
621
+ },
622
+ ],
623
+ imports: {
624
+ syntax: null,
625
+ depth: 0,
626
+ why: 'DeepSeek harness agent-instructions docs: @path imports are not interpreted; a reference inside an instruction file is not an include',
627
+ },
628
+ nested:
629
+ 'on touch — the baseline is the root-to-cwd chain; a deeper AGENTS.md loads after a successful read, write or edit in that directory, not because the shell changed directory',
630
+ skills: [],
631
+ skillsNote:
632
+ 'the agent-instructions package does not document a project skill directory; shared agent configuration lives at ~/.agents, which is user scope and is not measured here',
633
+ hooks: null,
634
+ hooksNote:
635
+ 'DeepSeek harness hooks docs: the Claude Code and Codex bridges take an explicit configPath, and project-local hooks.json discovery is still marked TODO, so no repository path is loaded by default',
636
+ mcp: {
637
+ path: null,
638
+ key: '@deepseek-ai/dsh-mcp-client',
639
+ verified:
640
+ 'DeepSeek harness MCP docs: a server is a profile plugin row (@deepseek-ai/dsh-mcp-client), not a project file this tool can project',
641
+ },
642
+ verified:
643
+ 'DeepSeek harness agent-instructions package (deepseek-ai/deepseek-harness packages/context/agent-instructions/README.md, read 2026-10-14) — the baseline loads ~/.dsh/AGENTS.md, ~/.agents/AGENTS.md, then AGENTS.md and CLAUDE.md plus the local overlays from the project root down to the working directory; trimmed duplicates in one directory collapse; dsh-base caps the rendered baseline at 65536 bytes and ignores a source file over 1048576 bytes',
644
+ note:
645
+ 'A drifted CLAUDE.md beside AGENTS.md is loaded in full. A symlink or a byte-identical copy is not. That is why this reader does not share Codex\'s one-file-per-directory rule.',
646
+ },
647
+ ];
648
+
649
+ export const LOADER_IDS = LOADERS.map((l) => l.id);
650
+
651
+ /** Loader row `id` or null. */
652
+ export function loader(id) {
653
+ return LOADERS.find((l) => l.id === id) ?? null;
654
+ }
655
+
656
+ /**
657
+ * The cap that applies to one row, or null — with `alsoCaps` folded in.
658
+ *
659
+ * Callers ask for "the limits this reader enforces", not for the shape of the cell. A
660
+ * cap stated as prose in `capNote` and enforced in code somewhere else is the drift this
661
+ * table was built to stop, so the enumeration is one function both the meter and the
662
+ * report call.
663
+ */
664
+ export function caps(row) {
665
+ const out = [];
666
+ if (row.cap) {
667
+ out.push({
668
+ bytes: row.cap.bytes,
669
+ scope: row.cap.scope,
670
+ config: row.cap.config ?? null,
671
+ // Named from the scope rather than written per row, because the two answer
672
+ // different questions and a reader who saw only one of them would misattribute
673
+ // the finding: a chain cap fails on the repository, a file cap fails on the file.
674
+ appliesTo: row.cap.scope === 'chain' ? 'the merged chain' : 'a single file',
675
+ drop: row.cap.drop ?? null,
676
+ why: row.cap.why,
677
+ });
678
+ }
679
+ for (const extra of row.alsoCaps ?? []) {
680
+ out.push({
681
+ bytes: extra.bytes,
682
+ scope: extra.scope,
683
+ config: extra.config ?? null,
684
+ appliesTo: extra.appliesTo,
685
+ why: extra.why,
686
+ });
687
+ }
688
+ return out;
689
+ }