@onedotmint/charterleaf 0.1.0 → 0.2.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
@@ -8,7 +8,7 @@ Project specifications live under `specs/`. For non-trivial architectural, behav
8
8
  - `specs/changes/` — lightweight active deltas.
9
9
  - `specs/constitution.md` — project-wide guardrails only.
10
10
 
11
- When a concrete code path is known, use `charterleaf related <path>` to locate matching living specs plus active changes that affect them. Otherwise use normal code/text search. Use `charterleaf lint` for structural validation and mechanical active-change conflicts. These commands are helpers, not workflow gates.
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.
12
12
 
13
13
  Authority is `Constitution > Active Change > Living Spec > Current Code`. Do not add authority scores or silently rewrite specs to match code.
14
14
 
package/README.md CHANGED
@@ -7,9 +7,10 @@ without workflow orchestration.
7
7
 
8
8
  > **Keep project intent in the repo, without turning specs into a workflow.**
9
9
 
10
- Charterleaf keeps durable project knowledge in Markdown and provides exactly two deterministic helpers:
10
+ Charterleaf keeps durable project knowledge in Markdown and provides exactly three deterministic helpers:
11
11
 
12
12
  ```bash
13
+ charterleaf map
13
14
  charterleaf related <path>
14
15
  charterleaf lint
15
16
  ```
@@ -37,10 +38,13 @@ No Pi extension or Pi runtime dependency is used.
37
38
  Run from a repository root containing `specs/`:
38
39
 
39
40
  ```bash
41
+ charterleaf map
40
42
  charterleaf related src/auth/session.ts
41
43
  charterleaf lint
42
44
  ```
43
45
 
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>`.
47
+
44
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.
45
49
 
46
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.
@@ -98,7 +102,7 @@ Charterleaf has no:
98
102
  - plugin/extension runtime
99
103
  - Git/history abstraction
100
104
 
101
- The two CLI commands are helpers, not workflow gates.
105
+ The three CLI commands are helpers, not workflow gates.
102
106
 
103
107
  ## Development
104
108
 
@@ -225,6 +225,84 @@ function related(repoPath, cwd = process.cwd(), streams = {}) {
225
225
  return 0;
226
226
  }
227
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
+
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
+ }
264
+ }
265
+ if (errors.length) {
266
+ for (const error of errors) write(stderr, error);
267
+ return 2;
268
+ }
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);
281
+ }
282
+
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
+ }
303
+ return 0;
304
+ }
305
+
228
306
  function isLivingSpec(filePath, specRoot) {
229
307
  const relative = path.relative(specRoot, filePath).replaceAll('\\', '/').split('/');
230
308
  return relative.length >= 2 && (relative[0] === 'capabilities' || relative[0] === 'engineering');
@@ -443,6 +521,7 @@ function helpText() {
443
521
  'Usage: charterleaf <command> [args]',
444
522
  '',
445
523
  'Commands:',
524
+ ' map show the specification map',
446
525
  ' related <path> find specs matching a repo-relative code path',
447
526
  ' lint check Charterleaf structural conventions',
448
527
  ].join('\n');
@@ -463,6 +542,14 @@ function main(argv = process.argv.slice(2), cwd = process.cwd(), streams = {}) {
463
542
  if (!argv.length) return usageError('a command is required', streams);
464
543
 
465
544
  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
+ }
466
553
  if (command === 'related') {
467
554
  if (rest.length === 1 && (rest[0] === '--help' || rest[0] === '-h')) {
468
555
  write(streams.stdout || process.stdout, 'Usage: charterleaf related <path>');
@@ -488,6 +575,7 @@ module.exports = {
488
575
  globMatches,
489
576
  lint,
490
577
  main,
578
+ map,
491
579
  normalizeRepoPath,
492
580
  parseFrontmatter,
493
581
  related,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onedotmint/charterleaf",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A tiny specification layer for coding agents.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -13,7 +13,17 @@ When a concrete code path is known, run:
13
13
  charterleaf related <path>
14
14
  ```
15
15
 
16
- Read only the returned living specs and active changes. If the location is unknown, use the host agent's normal search tools; Charterleaf does not provide semantic search.
16
+ Read only the returned living specs and active changes.
17
+
18
+ When the relevant code path is not yet known, run:
19
+
20
+ ```bash
21
+ charterleaf map
22
+ ```
23
+
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>`.
25
+
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.
17
27
 
18
28
  Use authority in this order:
19
29