@onedotmint/charterleaf 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.
package/AGENTS_SNIPPET.md CHANGED
@@ -1,29 +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
- After non-trivial implementation or debugging, consider whether durable project knowledge was discovered. If so, record only the durable result:
20
-
21
- - observable behavior, contract, invariant, or non-goal → capability spec;
22
- - long-lived implementation constraint, boundary, recurring pitfall, or required verification practice → engineering spec;
23
- - important architectural choice, rationale, or revisit condition → decision.
24
-
25
- If nothing durable was learned, do not modify specs. Do not record current task status, implementation progress, temporary debugging notes, one-off commands, session history, Todo items, or transient observations. Specs accumulate durable project knowledge, not development activity.
26
-
27
- 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.
28
-
29
- 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
87
+
88
+ Scoped
89
+ No applies_to glob matched this path.
90
+
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
+ ```
49
100
 
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.
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.
51
104
 
52
- The frontmatter convention intentionally supports only top-level scalar values and simple string lists. Runtime dependencies remain zero.
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,53 +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.
76
162
 
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.
163
+ ## changes/ is optional staging
78
164
 
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.
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.
80
171
 
81
- After non-trivial implementation or debugging, record only genuinely durable knowledge: behavior, contracts, invariants, and non-goals in capabilities; long-lived constraints, boundaries, pitfalls, or verification practices in engineering; and important architectural choices in decisions. If nothing durable was learned, do not modify specs. Specs accumulate durable project knowledge, not development activity.
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.
82
175
 
83
- 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
84
177
 
85
- ## 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.
86
182
 
87
- 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).
88
185
 
89
- 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.
90
188
 
91
189
  ## Boundaries
92
190
 
93
191
  Charterleaf has no:
94
192
 
95
- - planning
96
- - task management
97
- - agent orchestration
98
- - implementation or review workflow
99
- - session memory or context compression
100
- - semantic search or embeddings
101
- - code/spec semantic verification
102
- - database, index, or cache
103
- - daemon or background watcher
104
- - plugin/extension runtime
105
- - 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.
106
201
 
107
- The three CLI commands are helpers, not workflow gates.
202
+ The two CLI commands are helpers, not workflow gates.
108
203
 
109
204
  ## Development
110
205
 
111
- 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.
112
208
 
113
209
  ```bash
114
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
-
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
223
 
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;
@@ -454,6 +410,14 @@ function lint(cwd = process.cwd(), streams = {}) {
454
410
  const display = relativeDisplay(filePath, cwd);
455
411
  const { frontmatter: rawFrontmatter, body } = parsed.get(filePath);
456
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
+
457
421
  const affects = frontmatter.affects;
458
422
  if (!Array.isArray(affects) || !affects.every((item) => typeof item === 'string' && item.length > 0)) {
459
423
  continue;
@@ -470,7 +434,6 @@ function lint(cwd = process.cwd(), streams = {}) {
470
434
  }
471
435
  }
472
436
 
473
- const sections = changeSections(body);
474
437
  for (const section of ['ADD', 'MODIFY', 'REMOVE']) {
475
438
  for (const reqId of new Set(allRequirementIds(sections[section] || ''))) {
476
439
  if (!changeTouches.has(reqId)) changeTouches.set(reqId, new Map());
@@ -515,10 +478,10 @@ function lint(cwd = process.cwd(), streams = {}) {
515
478
  });
516
479
  for (const [display, message] of errors) write(stdout, `ERROR ${display}: ${message}`);
517
480
  if (errors.length) {
518
- write(stdout, `${errors.length} errors`);
481
+ write(stdout, `${errors.length} structural errors`);
519
482
  return 1;
520
483
  }
521
- write(stdout, '0 errors');
484
+ write(stdout, '0 structural errors');
522
485
  return 0;
523
486
  }
524
487
 
@@ -527,9 +490,8 @@ function helpText() {
527
490
  'Usage: charterleaf <command> [args]',
528
491
  '',
529
492
  'Commands:',
530
- ' map show the specification map',
531
- ' related <path> find specs matching a repo-relative code path',
532
- ' lint check Charterleaf structural conventions',
493
+ ' related <path> find durable constraints matching a repo-relative path',
494
+ ' lint report structural errors in specs/',
533
495
  ].join('\n');
534
496
  }
535
497
 
@@ -548,14 +510,6 @@ function main(argv = process.argv.slice(2), cwd = process.cwd(), streams = {}) {
548
510
  if (!argv.length) return usageError('a command is required', streams);
549
511
 
550
512
  const [command, ...rest] = argv;
551
- if (command === 'map') {
552
- if (rest.length === 1 && (rest[0] === '--help' || rest[0] === '-h')) {
553
- write(streams.stdout || process.stdout, 'Usage: charterleaf map');
554
- return 0;
555
- }
556
- if (rest.length) return usageError('map takes no arguments', streams);
557
- return map(path.resolve(cwd), streams);
558
- }
559
513
  if (command === 'related') {
560
514
  if (rest.length === 1 && (rest[0] === '--help' || rest[0] === '-h')) {
561
515
  write(streams.stdout || process.stdout, 'Usage: charterleaf related <path>');
@@ -581,7 +535,6 @@ module.exports = {
581
535
  globMatches,
582
536
  lint,
583
537
  main,
584
- map,
585
538
  normalizeRepoPath,
586
539
  parseFrontmatter,
587
540
  related,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onedotmint/charterleaf",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "A tiny specification layer for coding agents.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -5,54 +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
- After non-trivial implementation or debugging, consider whether durable project knowledge was discovered. If so, record only the durable result:
40
+ ## Progressive retrieval
39
41
 
40
- - observable behavior, contract, invariant, or non-goal → capability spec;
41
- - long-lived implementation constraint, boundary, recurring pitfall, or required verification practice → engineering spec;
42
- - important architectural choice, rationale, or revisit condition → decision.
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.
43
61
 
44
- If nothing durable was learned, do not modify specs. Do not record current task status, implementation progress, temporary debugging notes, one-off commands, session history, Todo items, or transient observations. Specs accumulate durable project knowledge, not development activity.
62
+ ## related semantics
45
63
 
46
- 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.
64
+ `charterleaf related <path>`:
47
65
 
48
- After editing specs, run:
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
- ```bash
51
- charterleaf lint
52
- ```
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.
53
77
 
54
- `lint` checks structural conventions and deterministic active-change conflicts only. It does not judge writing, architecture quality, or semantic code/spec drift.
78
+ ## lint semantics
55
79
 
56
- After an accepted change is implemented, merge the durable result into living specs and delete the change file. Git keeps history.
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.
57
85
 
58
- Do not plan, manage tasks, invoke agents, run implementation/review workflows, manage memory, or replace the host agent's code-navigation tools.
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?>