@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 +1 -1
- package/README.md +6 -2
- package/lib/charterleaf.js +88 -0
- package/package.json +1 -1
- package/skills/charterleaf/SKILL.md +11 -1
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.
|
|
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
|
|
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
|
|
105
|
+
The three CLI commands are helpers, not workflow gates.
|
|
102
106
|
|
|
103
107
|
## Development
|
|
104
108
|
|
package/lib/charterleaf.js
CHANGED
|
@@ -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
|
@@ -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.
|
|
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
|
|