asdlc-cli 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.
@@ -6,16 +6,37 @@ import { runAllGates } from '../runner.js';
6
6
  import { renderMarkdownReport, renderResults } from '../report.js';
7
7
  import { writeProposedBaseline } from '../baseline.js';
8
8
  import { ASDLC_DIR, readState, writeState } from '../state.js';
9
+ import { loadWaivers } from '../waivers.js';
10
+ import { countGovernedConcepts } from '../gates/registry.js';
11
+ import { computeDriftScore, badgeJson } from '../score.js';
9
12
  // "asdlc audit" — read-only diagnosis (S3 Stage 1 / Stage 4).
10
- // Writes only reports and a PROPOSED baseline under .asdlc/; applying anything
11
- // is a separate GOVERN operation with human approval (`asdlc baseline accept`).
12
- export async function auditCommand(root) {
13
+ // Writes only reports, a PROPOSED baseline, and the drift-score badge under
14
+ // .asdlc/; applying anything is a separate GOVERN operation with human
15
+ // approval (`asdlc baseline accept`).
16
+ export async function auditCommand(root, opts = {}) {
13
17
  const config = loadConfig(root);
14
18
  const date = new Date().toISOString().slice(0, 10);
15
19
  const results = await runAllGates(root, config);
20
+ const drift = computeDriftScore(results, loadWaivers(root), countGovernedConcepts(root, config));
21
+ if (opts.score) {
22
+ // Script-friendly: exactly one line on stdout.
23
+ console.log(`${drift.band} ${drift.score}`);
24
+ return;
25
+ }
16
26
  const dir = join(root, ASDLC_DIR, 'reports', date);
17
27
  mkdirSync(dir, { recursive: true });
18
- writeFileSync(join(dir, 'audit.md'), renderMarkdownReport(results, date));
28
+ const scoreLine = [
29
+ '',
30
+ `## Drift score: ${drift.band} (${drift.score}/100)`,
31
+ '',
32
+ `open ${drift.components.open} · baseline ${drift.components.baseline} · waived ${drift.components.waived}` +
33
+ ` · expired waivers ${drift.components.expired_waivers} · governed concepts ${drift.components.governed}` +
34
+ ` · gate coverage ${drift.components.coverage}/${results.length}`,
35
+ '',
36
+ '_The score measures governance activity, not correctness — see docs/DRIFT_SCORE.md._',
37
+ ].join('\n');
38
+ writeFileSync(join(dir, 'audit.md'), renderMarkdownReport(results, date) + scoreLine);
39
+ writeFileSync(join(root, ASDLC_DIR, 'badge.json'), badgeJson(drift));
19
40
  const allFindings = results.flatMap((r) => r.findings);
20
41
  const proposedPath = join(dir, 'proposed-baseline.json');
21
42
  writeProposedBaseline(root, allFindings, proposedPath);
@@ -24,7 +45,11 @@ export async function auditCommand(root) {
24
45
  writeState(root, { ...state, last_audit: date });
25
46
  console.log(renderResults(results, false));
26
47
  console.log('');
27
- console.log(pc.bold(`Audit written: ${join('.asdlc/reports', date, 'audit.md')}`));
48
+ const bandColor = drift.band === 'A' || drift.band === 'B' ? pc.green : drift.band === 'C' ? pc.yellow : pc.red;
49
+ console.log(pc.bold(`Drift score: ${bandColor(`${drift.band} (${drift.score}/100)`)}`) +
50
+ pc.dim(` — open ${drift.components.open}, baseline ${drift.components.baseline}, coverage ${drift.components.coverage}/${results.length}, governed ${drift.components.governed}`));
51
+ console.log(pc.bold(`Audit written: ${join('.asdlc/reports', date, 'audit.md')}`) +
52
+ pc.dim(` · badge: .asdlc/badge.json (shields.io endpoint format)`));
28
53
  console.log(`Proposed baseline (${allFindings.length} findings, PROPOSED_NOT_ACCEPTED): ${join('.asdlc/reports', date, 'proposed-baseline.json')}`);
29
54
  console.log(pc.dim('Accepting it is a GOVERN operation: asdlc baseline accept --from <file> --approved-by <human> --approval-ref <pointer>'));
30
55
  }
@@ -8,6 +8,15 @@ import { loadWaivers } from '../waivers.js';
8
8
  // - registry file exists and has at least one concept section
9
9
  // - every `backtick/path` reference under a concept resolves in the repo
10
10
  // - waivers are well-formed; expired waivers surface as findings (suppression ended)
11
+ // Concepts with a real definition: `## <name>` sections, excluding templates.
12
+ export function countGovernedConcepts(root, config) {
13
+ const regPath = join(root, config.registry_path);
14
+ if (!existsSync(regPath))
15
+ return 0;
16
+ const text = readFileSync(regPath, 'utf8');
17
+ return (text.match(/^##\s+.+$/gm) ?? [])
18
+ .filter((h) => !/example/i.test(h)).length;
19
+ }
11
20
  export function runRegistryGate(root, config) {
12
21
  const findings = [];
13
22
  const regPath = join(root, config.registry_path);
package/dist/index.js CHANGED
@@ -22,8 +22,9 @@ program.command('check')
22
22
  .option('-v, --verbose', 'show all new findings')
23
23
  .action((opts) => checkCommand(process.cwd(), opts));
24
24
  program.command('audit')
25
- .description('Read-only diagnosis: full gate sweep, markdown report, PROPOSED baseline.')
26
- .action(() => auditCommand(process.cwd()));
25
+ .description('Read-only diagnosis: full gate sweep, markdown report, drift score + badge, PROPOSED baseline.')
26
+ .option('--score', 'print only the drift score (e.g. "B 78") and exit')
27
+ .action((opts) => auditCommand(process.cwd(), opts));
27
28
  const baseline = program.command('baseline').description('Baseline governance (GOVERN operations).');
28
29
  baseline.command('accept')
29
30
  .description('Accept a proposed baseline. Requires a human approval pointer.')
package/dist/score.js ADDED
@@ -0,0 +1,26 @@
1
+ export function computeDriftScore(results, waivers, governedConcepts) {
2
+ const open = results.reduce((n, r) => n + r.newFindings.length, 0);
3
+ const baseline = results.reduce((n, r) => n + r.baselined, 0);
4
+ const waived = waivers.valid.length;
5
+ const expired = waivers.expired.length;
6
+ const coverage = results.filter((r) => r.status !== 'SKIPPED' && r.status !== 'ERROR').length;
7
+ const score = Math.max(0, Math.round(100
8
+ - 25 * Math.min(1, open / 10)
9
+ - 15 * Math.min(1, baseline / 200)
10
+ - 5 * Math.min(1, expired)
11
+ - 25 * (1 - coverage / results.length)
12
+ - 30 * (governedConcepts === 0 ? 1 : 0)));
13
+ const band = score >= 90 ? 'A' : score >= 75 ? 'B' : score >= 60 ? 'C' : score >= 40 ? 'D' : 'F';
14
+ return {
15
+ score, band,
16
+ components: { open, baseline, waived, expired_waivers: expired, governed: governedConcepts, coverage },
17
+ };
18
+ }
19
+ // Shields.io endpoint-badge JSON — serve this file raw and point
20
+ // https://img.shields.io/endpoint?url=<raw-url> at it.
21
+ export function badgeJson(s) {
22
+ const color = { A: 'brightgreen', B: 'green', C: 'yellow', D: 'orange', F: 'red' }[s.band];
23
+ return JSON.stringify({
24
+ schemaVersion: 1, label: 'drift', message: `${s.band} ${s.score}`, color,
25
+ }, null, 2);
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asdlc-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "ASDLC — the anti-drift governance layer for AI-built codebases. Implements the Anti-Drift Playbook v3.1.1 protocol: plan, init, check, audit.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",