@onedotmint/charterleaf 0.2.0 → 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.
package/AGENTS_SNIPPET.md CHANGED
@@ -1,21 +1,14 @@
1
- # Charterleaf instructions
1
+ # Project References
2
2
 
3
- Project specifications live under `specs/`. For non-trivial architectural, behavioral, or cross-cutting changes, find the smallest relevant set; never read the whole tree by default.
3
+ Use this file as the project navigation entry.
4
+ Document where authoritative information lives — project state,
5
+ validation, resources, history, and other project-specific sources.
4
6
 
5
- - `specs/capabilities/` — durable behavior, contracts, invariants, non-goals.
6
- - `specs/engineering/` — durable implementation constraints and concrete recurring pitfalls.
7
- - `specs/decisions/` — important architectural choices and revisit conditions.
8
- - `specs/changes/` — lightweight active deltas.
9
- - `specs/constitution.md` — project-wide guardrails only.
7
+ Durable constraints live under `specs/`. For work on a concrete
8
+ repository path:
10
9
 
11
- When a concrete code path is known, use `charterleaf related <path>` to locate matching living specs plus active changes that affect them. When the relevant code path is not yet known, use `charterleaf map` only to discover available specification scopes; do not read every listed specification. Use normal code navigation to locate the smallest relevant area, then use `charterleaf related <path>`. For broad project-level questions, start with normal project entry documents; do not expand into every spec or project-state document merely because no path was provided. Use `charterleaf lint` for structural validation and mechanical active-change conflicts. These commands are helpers, not workflow gates.
10
+ charterleaf related <path>
12
11
 
13
- Authority is `Constitution > Active Change > Living Spec > Current Code`. Do not add authority scores or silently rewrite specs to match code.
14
-
15
- Keep specs compressed and evidence-based. Prefer durable contracts and pitfalls over commentary. Do not invent aspirational standards that are not demonstrated by the repo or explicitly accepted.
16
-
17
- Do not create specs for temporary implementation details, current tasks, debugging notes, routine refactors, or trivial fixes with no durable implication. For a durable bug fix, use `Current` / `Expected` when useful and explicitly state what must `PRESERVE`.
18
-
19
- For meaningful durable behavior/contract changes, use one change file with only the needed `ADD`, `MODIFY`, `REMOVE`, and/or `PRESERVE` sections. After acceptance and implementation, merge durable results into living specs and delete the change file. Git is the history.
20
-
21
- Charterleaf is a knowledge layer, not a workflow layer. It must not manage tasks, planning, agents, execution, review, memory, compression, semantic search, code/spec verification, release lifecycle, or Git history.
12
+ Read authoritative owners progressively. If the current source is
13
+ sufficient, stop. Keep one owner per fact and point to it elsewhere
14
+ instead of copying the same fact into multiple documents.
package/README.md CHANGED
@@ -2,20 +2,28 @@
2
2
 
3
3
  A tiny specification layer for coding agents.
4
4
 
5
+ Given a concrete repository path, Charterleaf answers one question:
6
+
7
+ > Which durable constraints should I read before touching this code?
8
+
5
9
  Durable project intent, deterministic routing, and structural linting —
6
10
  without workflow orchestration.
7
11
 
8
- > **Keep project intent in the repo, without turning specs into a workflow.**
12
+ ## Two layers
9
13
 
10
- Charterleaf keeps durable project knowledge in Markdown and provides exactly three deterministic helpers:
14
+ Charterleaf sits beside a project's own navigation, not on top of it.
11
15
 
12
- ```bash
13
- charterleaf map
14
- charterleaf related <path>
15
- charterleaf lint
16
- ```
16
+ - **A project reference protocol** — a tracked `AGENTS.md` entry plus
17
+ the project's own documents — tells an agent *where* authoritative
18
+ information lives: current state, validation, resources, history,
19
+ source roots.
20
+ - **Charterleaf** tells an agent *which durable constraints* matter when
21
+ work lands on a specific repository path.
17
22
 
18
- It is a knowledge layer, not a workflow framework.
23
+ Charterleaf complements project navigation; it does not replace
24
+ project-owned sources of state, evidence, or workflow. It stores and
25
+ routes durable constraints; it never becomes the owner of a fact that
26
+ belongs to a project file.
19
27
 
20
28
  ## Install
21
29
 
@@ -35,21 +43,87 @@ No Pi extension or Pi runtime dependency is used.
35
43
 
36
44
  ## CLI
37
45
 
38
- Run from a repository root containing `specs/`:
46
+ Charterleaf exposes exactly two deterministic commands. Run them from a
47
+ repository root containing `specs/`:
39
48
 
40
49
  ```bash
41
- charterleaf map
42
- charterleaf related src/auth/session.ts
50
+ charterleaf related <path>
43
51
  charterleaf lint
44
52
  ```
45
53
 
46
- `map` shows a deterministic, lightweight index of the `specs/` layer when a relevant code path is not yet known. It lists specification scopes and metadata only; it does not summarize specs or recommend what to read. Use normal code navigation to find the smallest relevant area, then use `related <path>`.
54
+ There is no `map` command. Discovery is handled by a scoped miss (below)
55
+ and by the project's own reference protocol.
56
+
57
+ ### related \<path\>
58
+
59
+ Routes the supplied repo-relative path (`/` or `\` separators) to durable
60
+ constraints:
61
+
62
+ - `specs/constitution.md` is **Global**: returned whenever it exists,
63
+ regardless of frontmatter. It needs no `id` and no `applies_to`.
64
+ - `capabilities/` and `engineering/` specs are **Scoped**: returned when
65
+ any `applies_to` glob matches.
66
+ - Active changes whose `affects` reference a matched spec id are appended
67
+ under `Active changes`.
68
+
69
+ ```text
70
+ Global
71
+ specs/constitution.md
72
+
73
+ Scoped
74
+ specs/capabilities/auth.md
75
+ ```
76
+
77
+ `applies_to` is a deterministic routing hint, not a completeness proof.
78
+ "No match" only ever means "no living spec's `applies_to` glob matched
79
+ this path" — never "no constraints apply". When nothing matches, the
80
+ command states that explicitly and lists available living specs by `id`,
81
+ path, and `applies_to` only, so discovery stays possible without reading
82
+ every spec:
47
83
 
48
- `related <path>` performs deterministic path routing. It matches `applies_to` with `*` and `**`, normalizes repo-relative paths to POSIX form, then appends active changes whose `affects` reference a matched Spec ID. Output is stable; no match is success. It is not semantic search.
84
+ ```text
85
+ Global
86
+ specs/constitution.md
49
87
 
50
- `lint` performs mechanical checks only: frontmatter shape, living Spec IDs, duplicate requirement IDs, change references, static `ADD` / `MODIFY` / `REMOVE` references, and conflicting active changes that touch the same requirement ID. It does not judge wording, architecture quality, or semantic code/spec drift.
88
+ Scoped
89
+ No applies_to glob matched this path.
51
90
 
52
- The frontmatter convention intentionally supports only top-level scalar values and simple string lists. Runtime dependencies remain zero.
91
+ Available living specs
92
+ auth.session
93
+ specs/capabilities/auth.md
94
+ applies_to: src/auth/**
95
+
96
+ engineering.backend
97
+ specs/engineering/backend.md
98
+ applies_to: src/**/*.ts
99
+ ```
100
+
101
+ `related` never inspects the filesystem for the supplied path; it accepts
102
+ the caller's repo-relative string. A scoped miss is success (exit code
103
+ 0), not an error.
104
+
105
+ ### lint
106
+
107
+ ```bash
108
+ charterleaf lint
109
+ ```
110
+
111
+ `lint` performs mechanical structural checks only: frontmatter shape,
112
+ living Spec IDs, required `applies_to` on living specs, duplicate spec
113
+ and requirement IDs, change references, static `ADD` / `MODIFY` /
114
+ `REMOVE` references, changes whose `ADD` / `MODIFY` / `REMOVE` sections
115
+ are all empty (HTML comments do not count), and conflicting active
116
+ changes that touch the same requirement ID. Success prints
117
+ `0 structural errors`.
118
+
119
+ `lint` does **not** verify:
120
+
121
+ - semantic correctness;
122
+ - code/spec agreement;
123
+ - routing coverage;
124
+ - project compliance;
125
+ - whether a change is actually complete;
126
+ - whether an Agent read the specs.
53
127
 
54
128
  ## Project knowledge
55
129
 
@@ -62,51 +136,75 @@ specs/
62
136
  └── changes/
63
137
  ```
64
138
 
65
- - `constitution.md` — rare project-wide guardrails.
66
- - `capabilities/` — durable behavior, contracts, invariants, non-goals.
67
- - `engineering/` — durable implementation constraints and, when useful, concrete pitfalls.
68
- - `decisions/` — why an important architectural choice exists.
69
- - `changes/` — a lightweight active delta for an intentional durable change.
139
+ - `constitution.md` — rare project-wide durable boundaries. Global
140
+ constraint; not routed by `applies_to`.
141
+ - `capabilities/` — durable observable behavior, contracts, invariants,
142
+ non-goals.
143
+ - `engineering/` — durable implementation boundaries, long-lived
144
+ constraints, recurring pitfalls, verification practice.
145
+ - `decisions/` — important architectural rationale and revisit
146
+ conditions.
147
+ - `changes/` — optional staging for an accepted but not-yet-implemented
148
+ contract. See below.
70
149
 
71
- Authority is structural, not scored metadata:
150
+ ## Writing specs
72
151
 
73
- ```text
74
- Constitution > Active Change > Living Spec > Current Code
75
- ```
152
+ A spec records a constraint whose absence would plausibly cause a future
153
+ developer or agent to make a wrong decision. It should be non-obvious,
154
+ durable, **already true**, and mistake-preventing. Good fits: contracts,
155
+ invariants, architecture boundaries, non-goals, recurring pitfalls,
156
+ accepted rationale, verification constraints.
157
+
158
+ Do not write specs for current state, future design, aspirations, todos,
159
+ implementation plans, session notes, progress, temporary debugging,
160
+ navigation, or history. What you hope to become is a plan, not a living
161
+ spec.
162
+
163
+ ## changes/ is optional staging
76
164
 
77
- Keep specs compressed and evidence-based. Prefer contracts over commentary and record durable pitfalls that code alone does not reveal. Do not invent aspirational standards merely to make a spec look complete.
165
+ Use `specs/changes/<name>.md` only when an accepted future contract must
166
+ temporarily coexist with current living reality — for example when
167
+ implementation happens in another session or by another agent, human
168
+ review comes first, or a multi-step migration is underway. A change must
169
+ contain at least one of `ADD`, `MODIFY`, `REMOVE`; `PRESERVE` alone is
170
+ not a change.
78
171
 
79
- For bug fixes with durable behavioral implications, clarify `Current`, `Expected`, and `PRESERVE` when that reduces accidental scope. Do not create a spec for trivial fixes with no durable implication.
172
+ Otherwise, implement, verify, and update the living spec if durable truth
173
+ changed. After an accepted change is implemented, merge the durable
174
+ result into living specs and delete the change file. Git keeps history.
80
175
 
81
- Load the smallest relevant set; do not read the whole tree by default. After an accepted change is implemented, merge the durable result into living specs and delete the change file. Git is the history.
176
+ ## Agent conventions
82
177
 
83
- ## Other agents
178
+ The complete Agent convention lives in
179
+ [`skills/charterleaf/SKILL.md`](skills/charterleaf/SKILL.md): adoption
180
+ gate, progressive retrieval, `related` and `lint` semantics, spec
181
+ writing discipline, and durable learning.
84
182
 
85
- Charterleaf core does not depend on Pi. Any coding agent capable of reading Markdown and executing shell commands can use it.
183
+ For agents that read project instructions, the minimal reference
184
+ bootstrap is [`AGENTS_SNIPPET.md`](AGENTS_SNIPPET.md).
86
185
 
87
- Use `AGENTS_SNIPPET.md` with agents that support project instructions. Agent Skills-compatible harnesses can use the bundled `skills/charterleaf/SKILL.md`.
186
+ Charterleaf core does not depend on Pi. Any coding agent that can read
187
+ Markdown and run shell commands can use it.
88
188
 
89
189
  ## Boundaries
90
190
 
91
191
  Charterleaf has no:
92
192
 
93
- - planning
94
- - task management
95
- - agent orchestration
96
- - implementation or review workflow
97
- - session memory or context compression
98
- - semantic search or embeddings
99
- - code/spec semantic verification
100
- - database, index, or cache
101
- - daemon or background watcher
102
- - plugin/extension runtime
103
- - Git/history abstraction
193
+ - project state, tasks, planning, or workflow;
194
+ - memory, session lifecycle, evidence, or history management;
195
+ - scope registry, owner registry, or project bootstrap command;
196
+ - semantic search, embeddings, or code/spec semantic verification;
197
+ - repository coverage analysis or filesystem awareness in `related`;
198
+ - database, index, cache, daemon, or watcher;
199
+ - plugin/extension runtime;
200
+ - Git abstraction.
104
201
 
105
- The three CLI commands are helpers, not workflow gates.
202
+ The two CLI commands are helpers, not workflow gates.
106
203
 
107
204
  ## Development
108
205
 
109
- Requires Node.js 20 or newer. There are no runtime or development dependencies and no build step.
206
+ Requires Node.js 20 or newer. There are no runtime or development
207
+ dependencies and no build step.
110
208
 
111
209
  ```bash
112
210
  npm test
File without changes
@@ -163,8 +163,6 @@ function related(repoPath, cwd = process.cwd(), streams = {}) {
163
163
 
164
164
  const target = normalizeRepoPath(repoPath);
165
165
  const documents = [];
166
- const matches = [];
167
- const matchedIds = new Set();
168
166
  const errors = [];
169
167
 
170
168
  for (const filePath of walkMarkdown(specRoot)) {
@@ -176,23 +174,24 @@ function related(repoPath, cwd = process.cwd(), streams = {}) {
176
174
  errors.push(`ERROR ${display}: ${error.message}`);
177
175
  continue;
178
176
  }
179
- documents.push({ filePath, display, frontmatter: parsed.frontmatter || {} });
177
+ documents.push({
178
+ filePath,
179
+ display,
180
+ relative: path.relative(specRoot, filePath).replaceAll('\\', '/'),
181
+ frontmatter: parsed.frontmatter || {},
182
+ });
180
183
  }
181
184
 
182
- for (const document of documents) {
183
- const { display, frontmatter } = document;
184
- if (!Object.hasOwn(frontmatter, 'applies_to')) continue;
185
- const patterns = frontmatter.applies_to;
185
+ const livingSpecs = documents.filter((document) => isLivingSpec(document.filePath, specRoot));
186
+ const scoped = [];
187
+ for (const document of livingSpecs) {
188
+ const patterns = document.frontmatter.applies_to;
189
+ if (patterns === undefined) continue;
186
190
  if (!Array.isArray(patterns) || !patterns.every((item) => typeof item === 'string')) {
187
- errors.push(`ERROR ${display}: applies_to must be a list of paths`);
191
+ errors.push(`ERROR ${document.display}: applies_to must be a list of paths`);
188
192
  continue;
189
193
  }
190
- if (!patterns.some((pattern) => globMatches(pattern, target))) continue;
191
-
192
- matches.push(display);
193
- if (typeof frontmatter.id === 'string' && frontmatter.id.trim()) {
194
- matchedIds.add(frontmatter.id.trim());
195
- }
194
+ if (patterns.some((pattern) => globMatches(pattern, target))) scoped.push(document);
196
195
  }
197
196
 
198
197
  if (errors.length) {
@@ -200,6 +199,12 @@ function related(repoPath, cwd = process.cwd(), streams = {}) {
200
199
  return 2;
201
200
  }
202
201
 
202
+ const matchedIds = new Set();
203
+ for (const document of scoped) {
204
+ const id = typeof document.frontmatter.id === 'string' ? document.frontmatter.id.trim() : '';
205
+ if (id) matchedIds.add(id);
206
+ }
207
+
203
208
  const activeChanges = [];
204
209
  if (matchedIds.size) {
205
210
  const changeRoot = path.join(specRoot, 'changes');
@@ -213,93 +218,40 @@ function related(repoPath, cwd = process.cwd(), streams = {}) {
213
218
  }
214
219
  }
215
220
 
216
- matches.sort();
221
+ scoped.sort((left, right) => (left.display < right.display ? -1 : left.display > right.display ? 1 : 0));
217
222
  activeChanges.sort();
218
- const output = [...matches];
219
- for (const change of activeChanges) {
220
- if (!output.includes(change)) output.push(change);
221
- }
222
-
223
- for (const match of output) write(stdout, match);
224
- if (!output.length) write(stdout, 'No related specs.');
225
- return 0;
226
- }
227
223
 
228
- function writeLivingSpecs(stdout, documents) {
229
- for (const document of documents) {
230
- const id = typeof document.frontmatter.id === 'string' ? document.frontmatter.id.trim() : '';
231
- const appliesTo = Array.isArray(document.frontmatter.applies_to)
232
- ? document.frontmatter.applies_to.join(', ')
233
- : String(document.frontmatter.applies_to || '');
234
- write(stdout, ` ${id || '(missing id)'}`);
235
- write(stdout, ` ${document.display}`);
236
- write(stdout, ` applies_to: ${appliesTo}`);
237
- }
238
- }
239
-
240
- function map(cwd = process.cwd(), streams = {}) {
241
- const stdout = streams.stdout || process.stdout;
242
- const stderr = streams.stderr || process.stderr;
243
- const specRoot = path.join(cwd, 'specs');
244
- if (!fs.existsSync(specRoot) || !fs.statSync(specRoot).isDirectory()) {
245
- write(stderr, 'ERROR specs/: not found');
246
- return 2;
247
- }
248
-
249
- const documents = [];
250
- const errors = [];
251
- for (const filePath of walkMarkdown(specRoot)) {
252
- const display = relativeDisplay(filePath, cwd);
253
- try {
254
- const { frontmatter } = parseFrontmatter(fs.readFileSync(filePath, 'utf8'));
255
- documents.push({
256
- filePath,
257
- display,
258
- frontmatter: frontmatter || {},
259
- relative: path.relative(specRoot, filePath).replaceAll('\\', '/'),
260
- });
261
- } catch (error) {
262
- errors.push(`ERROR ${display}: ${error.message}`);
263
- }
224
+ const blocks = [];
225
+ const constitution = documents.find((document) => document.relative === 'constitution.md');
226
+ if (constitution) blocks.push(['Global', [` ${constitution.display}`]]);
227
+ if (scoped.length) {
228
+ blocks.push(['Scoped', scoped.map((document) => ` ${document.display}`)]);
229
+ } else {
230
+ blocks.push(['Scoped', [' No applies_to glob matched this path.']]);
264
231
  }
265
- if (errors.length) {
266
- for (const error of errors) write(stderr, error);
267
- return 2;
232
+ if (activeChanges.length) {
233
+ blocks.push(['Active changes', activeChanges.map((change) => ` ${change}`)]);
268
234
  }
269
-
270
- const constitution = [];
271
- const capabilities = [];
272
- const engineering = [];
273
- const decisions = [];
274
- const changes = [];
275
- for (const document of documents) {
276
- if (document.relative === 'constitution.md') constitution.push(document);
277
- else if (document.relative.startsWith('capabilities/')) capabilities.push(document);
278
- else if (document.relative.startsWith('engineering/')) engineering.push(document);
279
- else if (document.relative.startsWith('decisions/')) decisions.push(document);
280
- else if (document.relative.startsWith('changes/')) changes.push(document);
235
+ if (!scoped.length && livingSpecs.length) {
236
+ const lines = [];
237
+ livingSpecs.forEach((document, index) => {
238
+ if (index) lines.push('');
239
+ const id = typeof document.frontmatter.id === 'string' ? document.frontmatter.id.trim() : '';
240
+ const appliesTo = Array.isArray(document.frontmatter.applies_to)
241
+ ? document.frontmatter.applies_to.join(', ')
242
+ : String(document.frontmatter.applies_to ?? '');
243
+ lines.push(` ${id || '(missing id)'}`);
244
+ lines.push(` ${document.display}`);
245
+ lines.push(` applies_to: ${appliesTo}`);
246
+ });
247
+ blocks.push(['Available living specs', lines]);
281
248
  }
282
249
 
283
- write(stdout, 'Constitution');
284
- for (const document of constitution) write(stdout, ` ${document.display}`);
285
- write(stdout, '');
286
- write(stdout, 'Capabilities');
287
- writeLivingSpecs(stdout, capabilities);
288
- write(stdout, '');
289
- write(stdout, 'Engineering');
290
- writeLivingSpecs(stdout, engineering);
291
- write(stdout, '');
292
- write(stdout, 'Decisions');
293
- for (const document of decisions) write(stdout, ` ${document.display}`);
294
- write(stdout, '');
295
- write(stdout, 'Active changes');
296
- for (const document of changes) {
297
- const affects = Array.isArray(document.frontmatter.affects)
298
- ? document.frontmatter.affects.join(', ')
299
- : String(document.frontmatter.affects || '');
300
- write(stdout, ` ${document.display}`);
301
- write(stdout, ` affects: ${affects}`);
302
- }
250
+ blocks.forEach(([title, lines], index) => {
251
+ if (index) write(stdout, '');
252
+ write(stdout, title);
253
+ for (const line of lines) write(stdout, line);
254
+ });
303
255
  return 0;
304
256
  }
305
257
 
@@ -317,6 +269,10 @@ function requirementDefinitions(text) {
317
269
  return found;
318
270
  }
319
271
 
272
+ function stripHtmlComments(text) {
273
+ return String(text).replace(/<!--[\s\S]*?(?:-->|$)/g, '');
274
+ }
275
+
320
276
  function changeSections(body) {
321
277
  const sections = new Map();
322
278
  let current = null;
@@ -384,6 +340,12 @@ function lint(cwd = process.cwd(), streams = {}) {
384
340
  ids.set(specId, filePath);
385
341
  }
386
342
  }
343
+
344
+ if (!Object.hasOwn(frontmatter, 'applies_to')) {
345
+ errors.push([display, 'missing applies_to']);
346
+ } else if (Array.isArray(frontmatter.applies_to) && frontmatter.applies_to.length === 0) {
347
+ errors.push([display, 'applies_to must contain at least one path']);
348
+ }
387
349
  }
388
350
 
389
351
  for (const field of ['applies_to', 'tags']) {
@@ -448,6 +410,14 @@ function lint(cwd = process.cwd(), streams = {}) {
448
410
  const display = relativeDisplay(filePath, cwd);
449
411
  const { frontmatter: rawFrontmatter, body } = parsed.get(filePath);
450
412
  const frontmatter = rawFrontmatter || {};
413
+ const sections = changeSections(body);
414
+ const hasDelta = ['ADD', 'MODIFY', 'REMOVE'].some(
415
+ (section) => stripHtmlComments(sections[section] || '').trim().length > 0,
416
+ );
417
+ if (!hasDelta) {
418
+ errors.push([display, 'change must contain at least one ADD, MODIFY, or REMOVE section']);
419
+ }
420
+
451
421
  const affects = frontmatter.affects;
452
422
  if (!Array.isArray(affects) || !affects.every((item) => typeof item === 'string' && item.length > 0)) {
453
423
  continue;
@@ -464,7 +434,6 @@ function lint(cwd = process.cwd(), streams = {}) {
464
434
  }
465
435
  }
466
436
 
467
- const sections = changeSections(body);
468
437
  for (const section of ['ADD', 'MODIFY', 'REMOVE']) {
469
438
  for (const reqId of new Set(allRequirementIds(sections[section] || ''))) {
470
439
  if (!changeTouches.has(reqId)) changeTouches.set(reqId, new Map());
@@ -509,10 +478,10 @@ function lint(cwd = process.cwd(), streams = {}) {
509
478
  });
510
479
  for (const [display, message] of errors) write(stdout, `ERROR ${display}: ${message}`);
511
480
  if (errors.length) {
512
- write(stdout, `${errors.length} errors`);
481
+ write(stdout, `${errors.length} structural errors`);
513
482
  return 1;
514
483
  }
515
- write(stdout, '0 errors');
484
+ write(stdout, '0 structural errors');
516
485
  return 0;
517
486
  }
518
487
 
@@ -521,9 +490,8 @@ function helpText() {
521
490
  'Usage: charterleaf <command> [args]',
522
491
  '',
523
492
  'Commands:',
524
- ' map show the specification map',
525
- ' related <path> find specs matching a repo-relative code path',
526
- ' lint check Charterleaf structural conventions',
493
+ ' related <path> find durable constraints matching a repo-relative path',
494
+ ' lint report structural errors in specs/',
527
495
  ].join('\n');
528
496
  }
529
497
 
@@ -542,14 +510,6 @@ function main(argv = process.argv.slice(2), cwd = process.cwd(), streams = {}) {
542
510
  if (!argv.length) return usageError('a command is required', streams);
543
511
 
544
512
  const [command, ...rest] = argv;
545
- if (command === 'map') {
546
- if (rest.length === 1 && (rest[0] === '--help' || rest[0] === '-h')) {
547
- write(streams.stdout || process.stdout, 'Usage: charterleaf map');
548
- return 0;
549
- }
550
- if (rest.length) return usageError('map takes no arguments', streams);
551
- return map(path.resolve(cwd), streams);
552
- }
553
513
  if (command === 'related') {
554
514
  if (rest.length === 1 && (rest[0] === '--help' || rest[0] === '-h')) {
555
515
  write(streams.stdout || process.stdout, 'Usage: charterleaf related <path>');
@@ -575,7 +535,6 @@ module.exports = {
575
535
  globMatches,
576
536
  lint,
577
537
  main,
578
- map,
579
538
  normalizeRepoPath,
580
539
  parseFrontmatter,
581
540
  related,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onedotmint/charterleaf",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A tiny specification layer for coding agents.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -5,46 +5,145 @@ description: A tiny specification layer for durable project intent and constrain
5
5
 
6
6
  # Charterleaf
7
7
 
8
- Treat Charterleaf as a small knowledge layer, not a workflow system.
8
+ ## Adoption gate
9
9
 
10
- When a concrete code path is known, run:
10
+ Before using Charterleaf in a repository, check whether `specs/` exists.
11
11
 
12
- ```bash
13
- charterleaf related <path>
14
- ```
12
+ If no `specs/` directory exists:
15
13
 
16
- Read only the returned living specs and active changes.
14
+ - do not run Charterleaf;
15
+ - do not create `specs/`;
16
+ - do not recommend adoption automatically;
17
+ - continue using the repository's normal instructions.
17
18
 
18
- When the relevant code path is not yet known, run:
19
+ Charterleaf is opt-in.
19
20
 
20
- ```bash
21
- charterleaf map
22
- ```
21
+ ## Boundary
23
22
 
24
- Use the map only to discover available specification scopes. Do not read every listed specification by default. Use normal code navigation to locate the smallest relevant area. Once a concrete path is known, run `charterleaf related <path>`.
23
+ Charterleaf answers one question:
25
24
 
26
- For broad project-level questions, start with normal project entry documents. Do not expand into every spec or project-state document merely because no path was provided. `map` is not semantic search.
25
+ > For this repository path, which durable constraints should I read?
27
26
 
28
- Use authority in this order:
27
+ A project's own reference protocol — its `AGENTS.md` entry and the
28
+ project-owned documents it points to — answers a different question:
29
29
 
30
- ```text
31
- Constitution > Active Change > Living Spec > Current Code
32
- ```
30
+ > Where do this project's authoritative facts live?
33
31
 
34
- Keep specs compressed and evidence-based. Prefer contracts over commentary and durable pitfalls over patterns obvious from code. Never invent aspirational standards; record only demonstrated or explicitly accepted intent.
32
+ Charterleaf owns only: spec taxonomy, durable constraint routing
33
+ (`related`), and structural lint (`lint`).
35
34
 
36
- Do not touch specs for renames, lint cleanup, routine refactors, temporary debugging, Todo/status changes, or trivial fixes with no durable implication.
35
+ Charterleaf does not own: current project state, tasks, workflow,
36
+ planning, memory, evidence, history, session lifecycle, project-wide
37
+ consistency, semantic code/spec verification, or repository state
38
+ synchronization.
37
39
 
38
- For a durable bug fix, use `Current` and `Expected` when useful and state what must `PRESERVE`. For other durable behavior/contract changes, keep one `specs/changes/<name>.md` delta with only needed `ADD`, `MODIFY`, `REMOVE`, and/or `PRESERVE` sections.
40
+ ## Progressive retrieval
39
41
 
40
- After editing specs, run:
42
+ 1. **Identify the need.** Orientation, current state, evidence,
43
+ validation, resources, implementation detail, or durable constraint?
44
+ 2. **Locate the owner.** If the owner is known, read it directly. If not,
45
+ read the project `AGENTS.md` / project instructions. Do not scan the
46
+ whole repository's Markdown first.
47
+ 3. **Enough?** If the authoritative information you have is sufficient,
48
+ stop reading. Do not keep reading for completeness.
49
+ 4. **Expand progressively.** Only when information is insufficient, the
50
+ owner is unclear, sources conflict, the work area is unknown, or
51
+ verification is needed — and only one layer at a time.
52
+ 5. **Concrete path.** Once work lands on a concrete repository path, run
53
+ `charterleaf related <path>` and read the returned constraints.
54
+ 6. **Work.** Normal implementation, learning, design, or debugging. The
55
+ reference protocol does not manage workflow.
56
+ 7. **Write back.** Changed current fact → its single owner. New evidence
57
+ → the evidence owner. New or changed durable constraint → `specs/`.
58
+ Historical event → project history or Git. Future plan → planning
59
+ docs. Never copy one fact into several files: one owner per fact,
60
+ other documents point to it.
41
61
 
42
- ```bash
43
- charterleaf lint
44
- ```
62
+ ## related semantics
45
63
 
46
- `lint` checks structural conventions and deterministic active-change conflicts only. It does not judge writing, architecture quality, or semantic code/spec drift.
64
+ `charterleaf related <path>`:
47
65
 
48
- After an accepted change is implemented, merge the durable result into living specs and delete the change file. Git keeps history.
66
+ - always returns `specs/constitution.md` as Global when it exists;
67
+ - routes `capabilities/` and `engineering/` specs whose `applies_to`
68
+ glob matches the path;
69
+ - appends active changes whose `affects` reference a returned spec id.
49
70
 
50
- Do not plan, manage tasks, invoke agents, run implementation/review workflows, manage memory, or replace the host agent's code-navigation tools.
71
+ `applies_to` is a **routing hint**, not a completeness proof. The message
72
+ `No applies_to glob matched this path.` means exactly that — it never
73
+ means "no constraints apply". On a scoped miss the command lists
74
+ available living specs by `id`, path, and `applies_to` only. Use that
75
+ list to find the right area, then rerun with the concrete path. The
76
+ command never prints spec bodies, ranks relevance, or guesses.
77
+
78
+ ## lint semantics
79
+
80
+ `charterleaf lint` reports mechanical structural problems only:
81
+ frontmatter shape, living-spec ids and `applies_to`, duplicate spec and
82
+ requirement ids, change references, changes whose `ADD` / `MODIFY` /
83
+ `REMOVE` sections are all empty (HTML comments do not count), and
84
+ conflicting active changes.
85
+
86
+ It does **not** verify semantic correctness, code/spec agreement,
87
+ routing coverage, project compliance, whether a change is complete, or
88
+ whether an Agent read the specs. Success prints `0 structural errors`.
89
+ Run it after editing specs.
90
+
91
+ ## Spec writing discipline
92
+
93
+ A spec records a constraint whose absence would plausibly cause a future
94
+ developer or agent to make a wrong decision. It should be non-obvious,
95
+ durable, **already true**, and mistake-preventing.
96
+
97
+ Good fits: contracts, invariants, architecture boundaries, non-goals,
98
+ recurring pitfalls, accepted architectural rationale, verification
99
+ constraints.
100
+
101
+ Not specs: current state, future design, aspirations, todos,
102
+ implementation plans, session notes, progress, temporary debugging,
103
+ navigation, history. "Already true" matters — what you hope to become is
104
+ a plan, not a living spec.
105
+
106
+ Place a constraint where it belongs:
107
+
108
+ - rare project-wide durable boundary → `specs/constitution.md`;
109
+ - observable behavior, contract, invariant, or non-goal →
110
+ `specs/capabilities/`;
111
+ - implementation boundary, long-lived constraint, recurring pitfall, or
112
+ verification practice → `specs/engineering/`;
113
+ - important architectural rationale and revisit condition →
114
+ `specs/decisions/`.
115
+
116
+ Keep specs compressed. Prefer durable contracts and pitfalls over
117
+ commentary, and never invent aspirational standards.
118
+
119
+ ## changes/ is optional staging
120
+
121
+ Use `specs/changes/<name>.md` only when an accepted future contract must
122
+ temporarily coexist with current living reality — for example when
123
+ implementation happens in another session or by another agent, human
124
+ review comes first, or a multi-step migration is underway. A change must
125
+ contain at least one of `ADD`, `MODIFY`, `REMOVE`; `PRESERVE` alone is
126
+ not a change.
127
+
128
+ Otherwise: implement, verify, and update the living spec if durable truth
129
+ changed. After an accepted change is implemented, merge the durable
130
+ result into living specs and delete the change file. Git keeps history.
131
+
132
+ ## Durable learning
133
+
134
+ After non-trivial implementation or debugging, ask once: did this work
135
+ reveal durable knowledge whose absence could cause a future mistake?
136
+
137
+ If not, change nothing. If yes, record only the durable constraint in
138
+ the matching category above.
139
+
140
+ Never record current progress, temporary debug notes, one-off commands,
141
+ session history, todos, or transient observations. Specs accumulate
142
+ durable constraints, not development activity.
143
+
144
+ ## Non-goals
145
+
146
+ Do not use Charterleaf to manage tasks, planning, workflow, memory,
147
+ evidence, history, repository state, or scope registries. Do not ask it
148
+ to verify semantics, coverage, or project compliance. It finds
149
+ constraints; it does not manage the project.
@@ -5,6 +5,16 @@ affects:
5
5
 
6
6
  # <Change name>
7
7
 
8
+ <!-- Optional staging. Use only when an accepted future contract must
9
+ coexist with current living reality — implementation in another session or
10
+ by another agent, human review first, or a multi-step migration.
11
+
12
+ A change must contain at least one of ADD, MODIFY, or REMOVE.
13
+ PRESERVE alone is not a change.
14
+
15
+ After implementation, merge the durable result into living specs and
16
+ delete this file. Git keeps history. -->
17
+
8
18
  ## Intent
9
19
 
10
20
  <What intentional behavior/contract change is being made, and why?>