@stackbilt/cli 0.9.1 → 0.10.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/LICENSE +17 -1
- package/README.md +42 -0
- package/dist/__tests__/bootstrap.test.d.ts +2 -0
- package/dist/__tests__/bootstrap.test.d.ts.map +1 -0
- package/dist/__tests__/bootstrap.test.js +134 -0
- package/dist/__tests__/bootstrap.test.js.map +1 -0
- package/dist/__tests__/integration/precommit-hook.test.js +4 -4
- package/dist/__tests__/score.test.d.ts +2 -0
- package/dist/__tests__/score.test.d.ts.map +1 -0
- package/dist/__tests__/score.test.js +234 -0
- package/dist/__tests__/score.test.js.map +1 -0
- package/dist/commands/adf-tidy.d.ts.map +1 -1
- package/dist/commands/adf-tidy.js +6 -3
- package/dist/commands/adf-tidy.js.map +1 -1
- package/dist/commands/adf.js +20 -13
- package/dist/commands/adf.js.map +1 -1
- package/dist/commands/audit.js +24 -7
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/blast.d.ts +12 -0
- package/dist/commands/blast.d.ts.map +1 -0
- package/dist/commands/blast.js +208 -0
- package/dist/commands/blast.js.map +1 -0
- package/dist/commands/bootstrap.d.ts.map +1 -1
- package/dist/commands/bootstrap.js +245 -101
- package/dist/commands/bootstrap.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +9 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +127 -8
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +22 -18
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/score.d.ts +9 -0
- package/dist/commands/score.d.ts.map +1 -0
- package/dist/commands/score.js +1273 -0
- package/dist/commands/score.js.map +1 -0
- package/dist/commands/setup.js +2 -2
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/surface.d.ts +15 -0
- package/dist/commands/surface.d.ts.map +1 -0
- package/dist/commands/surface.js +112 -0
- package/dist/commands/surface.js.map +1 -0
- package/dist/http-client.d.ts +13 -4
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +1 -1
- package/dist/http-client.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -3
- package/dist/index.js.map +1 -1
- package/dist/types/scaffold-contract-types.d.ts +90 -0
- package/dist/types/scaffold-contract-types.d.ts.map +1 -0
- package/dist/types/scaffold-contract-types.js +22 -0
- package/dist/types/scaffold-contract-types.js.map +1 -0
- package/package.json +12 -9
package/LICENSE
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
Copyright 2026 Stackbilt LLC
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
1
17
|
Apache License
|
|
2
18
|
Version 2.0, January 2004
|
|
3
19
|
http://www.apache.org/licenses/
|
|
@@ -175,7 +191,7 @@
|
|
|
175
191
|
|
|
176
192
|
END OF TERMS AND CONDITIONS
|
|
177
193
|
|
|
178
|
-
Copyright 2026
|
|
194
|
+
Copyright 2026 Stackbilt LLC
|
|
179
195
|
|
|
180
196
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
181
197
|
you may not use this file except in compliance with the License.
|
package/README.md
CHANGED
|
@@ -69,6 +69,8 @@ charter adf migrate --dry-run # preview agent config → ADF migration
|
|
|
69
69
|
charter adf sync --check # verify .adf files match locked hashes
|
|
70
70
|
charter adf evidence --auto-measure --format json # validate metric ceilings
|
|
71
71
|
charter telemetry report --period 24h --format json # passive local usage summary
|
|
72
|
+
charter blast src/foo.ts --depth 3 # reverse dep graph → files affected by changing this seed
|
|
73
|
+
charter surface --markdown # extract routes (Hono/Express) + D1 schema as markdown
|
|
72
74
|
```
|
|
73
75
|
|
|
74
76
|
## Human Onboarding (Copy/Paste)
|
|
@@ -299,6 +301,44 @@ charter adf migrate [--dry-run] [--source <file>] [--no-backup]
|
|
|
299
301
|
- `metrics recalibrate`: Re-measure current LOC from manifest metric sources, propose and apply new ceilings using configurable headroom, and append rationale records to `BUDGET_RATIONALES`. Requires explicit rationale (`--reason`) unless `--auto-rationale` is used.
|
|
300
302
|
- `migrate`: Scan existing agent config files (CLAUDE.md, .cursorrules, agents.md, GEMINI.md, copilot-instructions.md), classify content using the ADX-002 decision tree, and migrate into ADF modules. `--dry-run` previews the migration plan without writing files. `--source <file>` targets a single file. `--no-backup` skips `.pre-adf-migrate.bak` creation. `--merge-strategy` controls deduplication: `dedupe` (default, skip items already in ADF), `append` (always add), or `replace`. Environment-specific rules (WSL, PATH, credential helpers) are retained in the thin pointer.
|
|
301
303
|
|
|
304
|
+
### `charter blast`
|
|
305
|
+
|
|
306
|
+
Compute the blast radius of a change: which files transitively depend on the given seed files? Builds a reverse dependency graph (TS/JS imports — handles ESM `.js → .ts`, tsconfig path aliases including `extends` chains, `src/index.*` fallback, cycles, comments) and BFS-traverses up to a configurable depth.
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
charter blast src/kernel/dispatch.ts # default depth 3
|
|
310
|
+
charter blast src/a.ts src/b.ts --depth 4 # multi-seed
|
|
311
|
+
charter blast src/foo.ts --format json # structured output
|
|
312
|
+
charter blast src/foo.ts --root ./packages/x # scan a subdirectory
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Reports:
|
|
316
|
+
- `affected` — relative paths of transitively dependent files
|
|
317
|
+
- `hotFiles` — top 20 most-imported files in the graph (architectural hubs)
|
|
318
|
+
- `summary.totalAffected`, `summary.depthHistogram`
|
|
319
|
+
|
|
320
|
+
Blast radius ≥20 files triggers a `CROSS_CUTTING` warning — a signal for governance gates to escalate the change. Pure heuristic, no LLM calls, zero runtime dependencies. Backed by `@stackbilt/blast`.
|
|
321
|
+
|
|
322
|
+
### `charter surface`
|
|
323
|
+
|
|
324
|
+
Extract the API surface of a project: HTTP routes and database schema. Designed for Cloudflare Worker projects but works on any Node.js HTTP backend.
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
charter surface # text summary
|
|
328
|
+
charter surface --format json # machine-readable
|
|
329
|
+
charter surface --markdown # for .ai/surface.adf injection
|
|
330
|
+
charter surface --root ./packages/worker # scan a subdirectory
|
|
331
|
+
charter surface --schema db/schema.sql # explicit schema path
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Detects:
|
|
335
|
+
- **Routes** — Hono, Express, itty-router via regex (requires `/` path prefix; strips comments before scanning)
|
|
336
|
+
- **Schema** — D1/SQLite `CREATE TABLE` statements with column flags (pk, unique, nullable, default)
|
|
337
|
+
|
|
338
|
+
Ignores `__tests__/`, `*.test.*`, `*.spec.*` to avoid false positives from test fixtures.
|
|
339
|
+
|
|
340
|
+
Use cases: breaking change detection (diff surfaces before/after a PR), auto-generated AI context maps, deploy pipeline gates, mission brief fingerprinting for autonomous task runners. Backed by `@stackbilt/surface`.
|
|
341
|
+
|
|
302
342
|
### `charter telemetry`
|
|
303
343
|
|
|
304
344
|
Passive local observability for Charter/ADF usage. Every CLI run appends command metadata (timestamp, command path, flags, duration, exit code) to `.charter/telemetry/events.ndjson`.
|
|
@@ -372,6 +412,8 @@ Setup-only options:
|
|
|
372
412
|
- `@stackbilt/drift` -- blessed-stack pattern drift detection
|
|
373
413
|
- `@stackbilt/validate` -- citation validation and intent classification
|
|
374
414
|
- `@stackbilt/adf` -- ADF parser, formatter, patcher, and bundler
|
|
415
|
+
- `@stackbilt/blast` -- reverse dependency graph + blast radius analysis
|
|
416
|
+
- `@stackbilt/surface` -- API surface extraction (routes + D1 schema)
|
|
375
417
|
- `@stackbilt/ci` -- GitHub Actions integration helpers
|
|
376
418
|
|
|
377
419
|
## License
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/bootstrap.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs = __importStar(require("node:fs"));
|
|
37
|
+
const os = __importStar(require("node:os"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const vitest_1 = require("vitest");
|
|
40
|
+
const bootstrap_1 = require("../commands/bootstrap");
|
|
41
|
+
const baseOptions = {
|
|
42
|
+
configPath: '.charter',
|
|
43
|
+
format: 'text',
|
|
44
|
+
ciMode: false,
|
|
45
|
+
yes: false,
|
|
46
|
+
};
|
|
47
|
+
(0, vitest_1.describe)('bootstrapCommand', () => {
|
|
48
|
+
let originalCwd;
|
|
49
|
+
let tempDir;
|
|
50
|
+
let logs;
|
|
51
|
+
(0, vitest_1.beforeEach)(() => {
|
|
52
|
+
originalCwd = process.cwd();
|
|
53
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'charter-bootstrap-test-'));
|
|
54
|
+
process.chdir(tempDir);
|
|
55
|
+
logs = [];
|
|
56
|
+
vitest_1.vi.spyOn(console, 'log').mockImplementation((...args) => {
|
|
57
|
+
logs.push(args.map(String).join(' '));
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
(0, vitest_1.afterEach)(() => {
|
|
61
|
+
process.chdir(originalCwd);
|
|
62
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
63
|
+
vitest_1.vi.restoreAllMocks();
|
|
64
|
+
});
|
|
65
|
+
(0, vitest_1.it)('keeps custom ADF files when run with --yes but without --force', async () => {
|
|
66
|
+
fs.mkdirSync('.ai', { recursive: true });
|
|
67
|
+
const customCore = `ADF: 0.1
|
|
68
|
+
|
|
69
|
+
CONTEXT:
|
|
70
|
+
- Custom core instructions
|
|
71
|
+
`;
|
|
72
|
+
fs.writeFileSync(path.join('.ai', 'core.adf'), customCore);
|
|
73
|
+
const exitCode = await (0, bootstrap_1.bootstrapCommand)({ ...baseOptions, yes: true }, ['--yes', '--preset', 'worker', '--skip-install', '--skip-doctor']);
|
|
74
|
+
(0, vitest_1.expect)(exitCode).toBe(0);
|
|
75
|
+
(0, vitest_1.expect)(fs.readFileSync(path.join('.ai', 'core.adf'), 'utf-8')).toBe(customCore);
|
|
76
|
+
(0, vitest_1.expect)(fs.existsSync(path.join('.ai', '.backup'))).toBe(false);
|
|
77
|
+
(0, vitest_1.expect)(fs.existsSync(path.join('.ai', 'manifest.adf'))).toBe(true);
|
|
78
|
+
(0, vitest_1.expect)(fs.existsSync(path.join('.ai', 'state.adf'))).toBe(true);
|
|
79
|
+
const skipWarning = logs.find(l => l.includes('.ai/core.adf has custom content') && l.includes('skipping scaffold overwrite'));
|
|
80
|
+
(0, vitest_1.expect)(skipWarning).toBeDefined();
|
|
81
|
+
(0, vitest_1.expect)(skipWarning).toMatch(/\d+ bytes/);
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.it)('backs up and overwrites custom ADF files when run with --force', async () => {
|
|
84
|
+
fs.mkdirSync('.ai', { recursive: true });
|
|
85
|
+
const customCore = `ADF: 0.1
|
|
86
|
+
|
|
87
|
+
CONTEXT:
|
|
88
|
+
- Preserve me in backup
|
|
89
|
+
`;
|
|
90
|
+
const customState = `ADF: 0.1
|
|
91
|
+
STATE:
|
|
92
|
+
CURRENT: Custom state
|
|
93
|
+
`;
|
|
94
|
+
fs.writeFileSync(path.join('.ai', 'core.adf'), customCore);
|
|
95
|
+
fs.writeFileSync(path.join('.ai', 'state.adf'), customState);
|
|
96
|
+
const exitCode = await (0, bootstrap_1.bootstrapCommand)(baseOptions, ['--force', '--preset', 'worker', '--skip-install', '--skip-doctor']);
|
|
97
|
+
(0, vitest_1.expect)(exitCode).toBe(0);
|
|
98
|
+
// Backup files should exist with timestamp suffix
|
|
99
|
+
const backupDir = path.join('.ai', '.backup');
|
|
100
|
+
(0, vitest_1.expect)(fs.existsSync(backupDir)).toBe(true);
|
|
101
|
+
const backupFiles = fs.readdirSync(backupDir);
|
|
102
|
+
const coreBackup = backupFiles.find(f => f.startsWith('core.adf.'));
|
|
103
|
+
const stateBackup = backupFiles.find(f => f.startsWith('state.adf.'));
|
|
104
|
+
(0, vitest_1.expect)(coreBackup).toBeDefined();
|
|
105
|
+
(0, vitest_1.expect)(stateBackup).toBeDefined();
|
|
106
|
+
(0, vitest_1.expect)(fs.readFileSync(path.join(backupDir, coreBackup), 'utf-8')).toBe(customCore);
|
|
107
|
+
(0, vitest_1.expect)(fs.readFileSync(path.join(backupDir, stateBackup), 'utf-8')).toBe(customState);
|
|
108
|
+
// Originals should be overwritten with scaffold content
|
|
109
|
+
(0, vitest_1.expect)(fs.readFileSync(path.join('.ai', 'core.adf'), 'utf-8')).not.toBe(customCore);
|
|
110
|
+
(0, vitest_1.expect)(fs.readFileSync(path.join('.ai', 'state.adf'), 'utf-8')).not.toBe(customState);
|
|
111
|
+
(0, vitest_1.expect)(logs).toContain(' Backed up 2 files to .ai/.backup/');
|
|
112
|
+
});
|
|
113
|
+
(0, vitest_1.it)('detects orphaned .adf modules and auto-registers them in --yes mode', async () => {
|
|
114
|
+
// Pre-create .ai/ with extra modules that won't be in the scaffold manifest
|
|
115
|
+
fs.mkdirSync('.ai', { recursive: true });
|
|
116
|
+
fs.writeFileSync(path.join('.ai', 'agent.adf'), 'ADF: 0.1\n\nCONTEXT:\n - Agent rules\n');
|
|
117
|
+
fs.writeFileSync(path.join('.ai', 'persona.adf'), 'ADF: 0.1\n\nCONTEXT:\n - Persona rules\n');
|
|
118
|
+
const exitCode = await (0, bootstrap_1.bootstrapCommand)({ ...baseOptions, yes: true }, ['--yes', '--preset', 'worker', '--skip-install', '--skip-doctor']);
|
|
119
|
+
(0, vitest_1.expect)(exitCode).toBe(0);
|
|
120
|
+
// Should warn about the two unregistered modules
|
|
121
|
+
const orphanWarning = logs.find(l => l.includes('unregistered .adf module'));
|
|
122
|
+
(0, vitest_1.expect)(orphanWarning).toBeDefined();
|
|
123
|
+
(0, vitest_1.expect)(orphanWarning).toContain('agent.adf');
|
|
124
|
+
(0, vitest_1.expect)(orphanWarning).toContain('persona.adf');
|
|
125
|
+
// In --yes mode, orphans should be auto-registered in manifest
|
|
126
|
+
const registerLog = logs.find(l => l.includes('Registered 2 module(s) as ON_DEMAND'));
|
|
127
|
+
(0, vitest_1.expect)(registerLog).toBeDefined();
|
|
128
|
+
// Verify manifest contains the orphan entries
|
|
129
|
+
const manifest = fs.readFileSync(path.join('.ai', 'manifest.adf'), 'utf-8');
|
|
130
|
+
(0, vitest_1.expect)(manifest).toContain('agent.adf');
|
|
131
|
+
(0, vitest_1.expect)(manifest).toContain('persona.adf');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=bootstrap.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.test.js","sourceRoot":"","sources":["../../src/__tests__/bootstrap.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -170,7 +170,7 @@ function writeHookFixtureRepo(tmp) {
|
|
|
170
170
|
// v3 Pre-commit Hook Simulation
|
|
171
171
|
// ============================================================================
|
|
172
172
|
(0, vitest_1.describe)('pre-commit hook auto-tidy (integration)', () => {
|
|
173
|
-
(0, vitest_1.it)('auto-tidies bloated CLAUDE.md during git commit', { timeout:
|
|
173
|
+
(0, vitest_1.it)('auto-tidies bloated CLAUDE.md during git commit', { timeout: 60000 }, () => {
|
|
174
174
|
const tmp = makeTempDir('hook-tidy');
|
|
175
175
|
writeHookFixtureRepo(tmp);
|
|
176
176
|
// Inject bloat into CLAUDE.md
|
|
@@ -197,7 +197,7 @@ function writeHookFixtureRepo(tmp) {
|
|
|
197
197
|
(0, vitest_1.expect)(finalContent).not.toContain('## Database Rules');
|
|
198
198
|
(0, vitest_1.expect)(finalContent).toContain('## Environment');
|
|
199
199
|
});
|
|
200
|
-
(0, vitest_1.it)('passes through cleanly when CLAUDE.md has no bloat', { timeout:
|
|
200
|
+
(0, vitest_1.it)('passes through cleanly when CLAUDE.md has no bloat', { timeout: 60000 }, () => {
|
|
201
201
|
const tmp = makeTempDir('hook-clean');
|
|
202
202
|
writeHookFixtureRepo(tmp);
|
|
203
203
|
// Make a trivial change to CLAUDE.md (add env item, not bloat)
|
|
@@ -210,7 +210,7 @@ function writeHookFixtureRepo(tmp) {
|
|
|
210
210
|
const finalContent = fs.readFileSync(path.join(tmp, 'CLAUDE.md'), 'utf-8');
|
|
211
211
|
(0, vitest_1.expect)(finalContent).toContain('DO NOT add rules');
|
|
212
212
|
});
|
|
213
|
-
(0, vitest_1.it)('skips tidy when CHARTER_SKIP_TIDY=1 is set', { timeout:
|
|
213
|
+
(0, vitest_1.it)('skips tidy when CHARTER_SKIP_TIDY=1 is set', { timeout: 60000 }, () => {
|
|
214
214
|
const tmp = makeTempDir('hook-skip');
|
|
215
215
|
writeHookFixtureRepo(tmp);
|
|
216
216
|
// Inject bloat
|
|
@@ -237,7 +237,7 @@ function writeHookFixtureRepo(tmp) {
|
|
|
237
237
|
const finalContent = fs.readFileSync(path.join(tmp, 'CLAUDE.md'), 'utf-8');
|
|
238
238
|
(0, vitest_1.expect)(finalContent).toContain('## Architecture');
|
|
239
239
|
});
|
|
240
|
-
(0, vitest_1.it)('re-stages modified .adf modules after tidy', { timeout:
|
|
240
|
+
(0, vitest_1.it)('re-stages modified .adf modules after tidy', { timeout: 60000 }, () => {
|
|
241
241
|
const tmp = makeTempDir('hook-restage');
|
|
242
242
|
writeHookFixtureRepo(tmp);
|
|
243
243
|
// Inject bloat with trigger keywords that route to backend.adf
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/score.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs = __importStar(require("node:fs"));
|
|
37
|
+
const os = __importStar(require("node:os"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const vitest_1 = require("vitest");
|
|
40
|
+
const index_1 = require("../index");
|
|
41
|
+
const score_1 = require("../commands/score");
|
|
42
|
+
const baseOptions = {
|
|
43
|
+
configPath: '.charter',
|
|
44
|
+
format: 'json',
|
|
45
|
+
ciMode: false,
|
|
46
|
+
yes: false,
|
|
47
|
+
};
|
|
48
|
+
const originalCwd = process.cwd();
|
|
49
|
+
const tempDirs = [];
|
|
50
|
+
(0, vitest_1.afterEach)(() => {
|
|
51
|
+
process.chdir(originalCwd);
|
|
52
|
+
while (tempDirs.length > 0) {
|
|
53
|
+
const dir = tempDirs.pop();
|
|
54
|
+
if (dir) {
|
|
55
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
vitest_1.vi.restoreAllMocks();
|
|
59
|
+
});
|
|
60
|
+
(0, vitest_1.describe)('scoreCommand', () => {
|
|
61
|
+
(0, vitest_1.it)('gives a strong score to a well-grounded Charter-style repo', async () => {
|
|
62
|
+
const tmp = createTempRepo();
|
|
63
|
+
process.chdir(tmp);
|
|
64
|
+
fs.mkdirSync(path.join(tmp, '.ai'), { recursive: true });
|
|
65
|
+
fs.mkdirSync(path.join(tmp, '.github', 'workflows'), { recursive: true });
|
|
66
|
+
fs.mkdirSync(path.join(tmp, '.githooks'), { recursive: true });
|
|
67
|
+
fs.mkdirSync(path.join(tmp, '.codex', 'skills', 'review'), { recursive: true });
|
|
68
|
+
fs.mkdirSync(path.join(tmp, '.claude'), { recursive: true });
|
|
69
|
+
fs.mkdirSync(path.join(tmp, 'src'), { recursive: true });
|
|
70
|
+
fs.writeFileSync(path.join(tmp, 'package.json'), JSON.stringify({
|
|
71
|
+
name: 'ready-repo',
|
|
72
|
+
version: '1.0.0',
|
|
73
|
+
scripts: {
|
|
74
|
+
test: 'vitest run',
|
|
75
|
+
'test:unit': 'vitest run src/index.ts',
|
|
76
|
+
},
|
|
77
|
+
}, null, 2));
|
|
78
|
+
fs.writeFileSync(path.join(tmp, 'README.md'), `# Ready Repo
|
|
79
|
+
|
|
80
|
+
See \`src/index.ts\` and \`.ai/manifest.adf\`.
|
|
81
|
+
|
|
82
|
+
\`\`\`sh
|
|
83
|
+
npm test
|
|
84
|
+
npm run test:unit
|
|
85
|
+
\`\`\`
|
|
86
|
+
`);
|
|
87
|
+
fs.writeFileSync(path.join(tmp, 'CLAUDE.md'), `# Working Rules
|
|
88
|
+
|
|
89
|
+
Use \`src/index.ts\` as the main entrypoint.
|
|
90
|
+
Review \`.ai/manifest.adf\` before changing architecture.
|
|
91
|
+
|
|
92
|
+
\`\`\`sh
|
|
93
|
+
charter adf evidence --auto-measure
|
|
94
|
+
\`\`\`
|
|
95
|
+
`);
|
|
96
|
+
fs.writeFileSync(path.join(tmp, '.cursorrules'), `Keep edits small.
|
|
97
|
+
Check \`.ai/core.adf\` and \`.ai/state.adf\` before large changes.
|
|
98
|
+
`);
|
|
99
|
+
fs.writeFileSync(path.join(tmp, 'AGENTS.md'), `# Agents
|
|
100
|
+
|
|
101
|
+
Coordinate ownership before parallel changes.
|
|
102
|
+
Use \`npm test\` before handing work back.
|
|
103
|
+
`);
|
|
104
|
+
fs.writeFileSync(path.join(tmp, '.ai', 'manifest.adf'), `ADF: 0.1
|
|
105
|
+
ROLE: Repo context router
|
|
106
|
+
|
|
107
|
+
DEFAULT_LOAD:
|
|
108
|
+
- core.adf
|
|
109
|
+
- state.adf
|
|
110
|
+
|
|
111
|
+
ON_DEMAND:
|
|
112
|
+
- backend.adf (Triggers on: API, Node, DB)
|
|
113
|
+
`);
|
|
114
|
+
fs.writeFileSync(path.join(tmp, '.ai', 'core.adf'), `ADF: 0.1
|
|
115
|
+
|
|
116
|
+
CONTEXT:
|
|
117
|
+
- Service entrypoint: src/index.ts
|
|
118
|
+
|
|
119
|
+
CONSTRAINTS [load-bearing]:
|
|
120
|
+
- Run npm test before merge
|
|
121
|
+
- Keep API handlers pure at the edge
|
|
122
|
+
|
|
123
|
+
METRICS:
|
|
124
|
+
entry_loc: 12 / 200 [lines]
|
|
125
|
+
`);
|
|
126
|
+
fs.writeFileSync(path.join(tmp, '.ai', 'state.adf'), `ADF: 0.1
|
|
127
|
+
STATE:
|
|
128
|
+
CURRENT: scoring implementation is active
|
|
129
|
+
NEXT: keep docs synchronized
|
|
130
|
+
`);
|
|
131
|
+
fs.writeFileSync(path.join(tmp, '.ai', 'backend.adf'), `ADF: 0.1
|
|
132
|
+
CONTEXT:
|
|
133
|
+
- API routes live in src/index.ts
|
|
134
|
+
`);
|
|
135
|
+
fs.writeFileSync(path.join(tmp, 'src', 'index.ts'), 'export const ready = true;\n');
|
|
136
|
+
fs.writeFileSync(path.join(tmp, '.github', 'workflows', 'ci.yml'), 'name: CI\non: [push]\n');
|
|
137
|
+
fs.writeFileSync(path.join(tmp, '.githooks', 'pre-commit'), '#!/usr/bin/env sh\nnpm test\n');
|
|
138
|
+
fs.writeFileSync(path.join(tmp, '.codex', 'skills', 'review', 'SKILL.md'), '# Review Skill\n');
|
|
139
|
+
fs.writeFileSync(path.join(tmp, '.claude', 'settings.json'), JSON.stringify({
|
|
140
|
+
permissions: {
|
|
141
|
+
allow: ['Bash(npm test)'],
|
|
142
|
+
},
|
|
143
|
+
}, null, 2));
|
|
144
|
+
const { exitCode, report } = await captureJson(() => (0, score_1.scoreCommand)(baseOptions, []));
|
|
145
|
+
(0, vitest_1.expect)(exitCode).toBe(0);
|
|
146
|
+
(0, vitest_1.expect)(report.score.grade).toBe('A');
|
|
147
|
+
(0, vitest_1.expect)(report.score.total).toBeGreaterThanOrEqual(95);
|
|
148
|
+
(0, vitest_1.expect)(findCategory(report, 'agent-config')?.score).toBe(25);
|
|
149
|
+
(0, vitest_1.expect)(findCategory(report, 'architecture')?.score).toBe(20);
|
|
150
|
+
(0, vitest_1.expect)(findCategory(report, 'governance')?.score).toBe(10);
|
|
151
|
+
(0, vitest_1.expect)(report.recommendations).not.toContainEqual(vitest_1.expect.stringContaining('charter bootstrap --yes'));
|
|
152
|
+
});
|
|
153
|
+
(0, vitest_1.it)('is registered in the top-level CLI and scores non-Charter repos without crashing', async () => {
|
|
154
|
+
const tmp = createTempRepo();
|
|
155
|
+
process.chdir(tmp);
|
|
156
|
+
fs.mkdirSync(path.join(tmp, 'src'), { recursive: true });
|
|
157
|
+
fs.mkdirSync(path.join(tmp, '.github', 'workflows'), { recursive: true });
|
|
158
|
+
fs.writeFileSync(path.join(tmp, 'package.json'), JSON.stringify({
|
|
159
|
+
name: 'plain-repo',
|
|
160
|
+
version: '1.0.0',
|
|
161
|
+
scripts: {
|
|
162
|
+
test: 'node --test',
|
|
163
|
+
},
|
|
164
|
+
}, null, 2));
|
|
165
|
+
fs.writeFileSync(path.join(tmp, 'README.md'), `# Plain Repo
|
|
166
|
+
|
|
167
|
+
Main module: \`src/app.ts\`
|
|
168
|
+
|
|
169
|
+
\`\`\`sh
|
|
170
|
+
npm test
|
|
171
|
+
\`\`\`
|
|
172
|
+
`);
|
|
173
|
+
fs.writeFileSync(path.join(tmp, 'src', 'app.ts'), 'export const app = 1;\n');
|
|
174
|
+
fs.writeFileSync(path.join(tmp, '.github', 'workflows', 'ci.yml'), 'name: CI\non: [push]\n');
|
|
175
|
+
const { exitCode, report } = await captureJson(() => (0, index_1.run)(['score', '--format', 'json']));
|
|
176
|
+
(0, vitest_1.expect)(exitCode).toBe(0);
|
|
177
|
+
(0, vitest_1.expect)(report.score.total).toBeGreaterThan(0);
|
|
178
|
+
(0, vitest_1.expect)(report.recommendations.some((item) => item.includes('charter bootstrap --yes'))).toBe(true);
|
|
179
|
+
(0, vitest_1.expect)(report.recommendations.some((item) => item.includes('CLAUDE.md'))).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
(0, vitest_1.it)('falls back to mtime freshness and flags stale config relative to code', async () => {
|
|
182
|
+
const tmp = createTempRepo();
|
|
183
|
+
process.chdir(tmp);
|
|
184
|
+
fs.mkdirSync(path.join(tmp, 'src'), { recursive: true });
|
|
185
|
+
fs.writeFileSync(path.join(tmp, 'package.json'), JSON.stringify({
|
|
186
|
+
name: 'stale-repo',
|
|
187
|
+
version: '1.0.0',
|
|
188
|
+
scripts: {
|
|
189
|
+
test: 'node --test',
|
|
190
|
+
},
|
|
191
|
+
}, null, 2));
|
|
192
|
+
fs.writeFileSync(path.join(tmp, 'README.md'), `# Stale Repo
|
|
193
|
+
|
|
194
|
+
\`\`\`sh
|
|
195
|
+
npm test
|
|
196
|
+
\`\`\`
|
|
197
|
+
`);
|
|
198
|
+
fs.writeFileSync(path.join(tmp, 'CLAUDE.md'), '# Repo Rules\n\nRun `npm test` before merge.\n');
|
|
199
|
+
fs.writeFileSync(path.join(tmp, '.cursorrules'), 'Keep changes localized.\n');
|
|
200
|
+
fs.writeFileSync(path.join(tmp, 'AGENTS.md'), '# Agents\n\nCoordinate work.\n');
|
|
201
|
+
fs.writeFileSync(path.join(tmp, 'src', 'app.ts'), 'export const stale = true;\n');
|
|
202
|
+
const oldDate = new Date(Date.now() - (160 * 24 * 60 * 60 * 1000));
|
|
203
|
+
const newDate = new Date(Date.now() - (2 * 24 * 60 * 60 * 1000));
|
|
204
|
+
for (const file of ['CLAUDE.md', '.cursorrules', 'AGENTS.md']) {
|
|
205
|
+
fs.utimesSync(path.join(tmp, file), oldDate, oldDate);
|
|
206
|
+
}
|
|
207
|
+
fs.utimesSync(path.join(tmp, 'src', 'app.ts'), newDate, newDate);
|
|
208
|
+
const { report } = await captureJson(() => (0, score_1.scoreCommand)(baseOptions, []));
|
|
209
|
+
(0, vitest_1.expect)(report.signals.freshness.strategy).toBe('mtime');
|
|
210
|
+
(0, vitest_1.expect)((report.signals.freshness.deltaDays || 0)).toBeGreaterThan(100);
|
|
211
|
+
(0, vitest_1.expect)(findCategory(report, 'freshness')?.score).toBeLessThanOrEqual(2);
|
|
212
|
+
(0, vitest_1.expect)(report.recommendations.some((item) => item.includes('Refresh agent config after recent code changes'))).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
function createTempRepo() {
|
|
216
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'charter-score-test-'));
|
|
217
|
+
tempDirs.push(tmp);
|
|
218
|
+
return tmp;
|
|
219
|
+
}
|
|
220
|
+
async function captureJson(runCommand) {
|
|
221
|
+
const logs = [];
|
|
222
|
+
vitest_1.vi.spyOn(console, 'log').mockImplementation((message) => {
|
|
223
|
+
logs.push(String(message ?? ''));
|
|
224
|
+
});
|
|
225
|
+
const exitCode = await runCommand();
|
|
226
|
+
return {
|
|
227
|
+
exitCode,
|
|
228
|
+
report: JSON.parse(logs[0]),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function findCategory(report, id) {
|
|
232
|
+
return report.categories.find((category) => category.id === id);
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=score.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.test.js","sourceRoot":"","sources":["../../src/__tests__/score.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adf-tidy.d.ts","sourceRoot":"","sources":["../../src/commands/adf-tidy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"adf-tidy.d.ts","sourceRoot":"","sources":["../../src/commands/adf-tidy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAwE3C,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAwHzF"}
|
|
@@ -220,7 +220,9 @@ function extractBeyondPointer(content, fileName) {
|
|
|
220
220
|
const template = getPointerTemplates()[baseName];
|
|
221
221
|
if (!template)
|
|
222
222
|
return '';
|
|
223
|
-
|
|
223
|
+
// Strip charter-managed sentinel blocks before scanning for bloat.
|
|
224
|
+
// Without this, tidy treats the module index table as user-authored content.
|
|
225
|
+
const lines = (0, adf_1.stripCharterSentinels)(content).split('\n');
|
|
224
226
|
const bloatLines = [];
|
|
225
227
|
let inEnvironmentSection = false;
|
|
226
228
|
let inPointerHeader = true; // Start true — skip the pointer preamble
|
|
@@ -254,13 +256,14 @@ function extractBeyondPointer(content, fileName) {
|
|
|
254
256
|
}
|
|
255
257
|
// Section detection
|
|
256
258
|
if (trimmed.startsWith('## ')) {
|
|
257
|
-
|
|
259
|
+
// Environment and Module Index are charter-managed retained sections — not bloat (#71)
|
|
260
|
+
if (trimmed === '## Environment' || trimmed === '## Module Index') {
|
|
258
261
|
inEnvironmentSection = true;
|
|
259
262
|
continue;
|
|
260
263
|
}
|
|
261
264
|
else {
|
|
262
265
|
inEnvironmentSection = false;
|
|
263
|
-
// Non-
|
|
266
|
+
// Non-retained H2 section = bloat
|
|
264
267
|
bloatLines.push(line);
|
|
265
268
|
continue;
|
|
266
269
|
}
|