create-cmp-cli 0.12.0 → 0.14.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/bin/create-cmp.mjs +3 -0
- package/package.json +6 -2
- package/packages/harness/package.json +38 -0
- package/packages/harness/src/approve.mjs +247 -0
- package/packages/harness/src/arch-doc.mjs +69 -0
- package/packages/harness/src/comment.mjs +76 -0
- package/packages/harness/src/lib/a11y.mjs +113 -0
- package/packages/harness/src/lib/affected-tests.mjs +147 -0
- package/packages/harness/src/lib/approvals.mjs +1403 -0
- package/packages/harness/src/lib/arch-doc.mjs +451 -0
- package/packages/harness/src/lib/audit-cadence.mjs +290 -0
- package/packages/harness/src/lib/comments.mjs +252 -0
- package/packages/harness/src/lib/component-stories.mjs +183 -0
- package/packages/harness/src/lib/determinism.mjs +179 -0
- package/packages/harness/src/lib/device-lease.mjs +249 -0
- package/packages/harness/src/lib/evidence-badge.mjs +158 -0
- package/packages/harness/src/lib/evidence-level.mjs +117 -0
- package/packages/harness/src/lib/feature-brief.mjs +324 -0
- package/packages/harness/src/lib/flight-recorder.mjs +332 -0
- package/packages/harness/src/lib/harness-lock.mjs +147 -0
- package/packages/harness/src/lib/harness-region.mjs +159 -0
- package/packages/harness/src/lib/inputs-hash.mjs +194 -0
- package/packages/harness/src/lib/reachability.mjs +211 -0
- package/packages/harness/src/lib/receipt-validate.mjs +234 -0
- package/packages/harness/src/lib/render.mjs +254 -0
- package/packages/harness/src/lib/spec-coverage.mjs +131 -0
- package/packages/harness/src/lib/step-cache.mjs +221 -0
- package/packages/harness/src/lib/token-drift.mjs +94 -0
- package/packages/harness/src/lib/tree.mjs +108 -0
- package/packages/harness/src/preview-gallery.mjs +122 -0
- package/packages/harness/src/receipt-check.mjs +96 -0
- package/packages/harness/src/record-audit.mjs +83 -0
- package/packages/harness/src/refusal-demo.mjs +498 -0
- package/packages/harness/src/retrospective.mjs +51 -0
- package/packages/harness/src/scaffold-feature.mjs +723 -0
- package/packages/harness/src/setup-hooks.mjs +33 -0
- package/packages/harness/src/verify.mjs +1709 -0
- package/packages/harness/src/walkthrough.mjs +499 -0
- package/packages/harness/src/watch.mjs +622 -0
- package/packages/receipts/package.json +36 -0
- package/packages/receipts/src/index.mjs +16 -0
- package/packages/receipts/src/inputs-hash.mjs +194 -0
- package/packages/receipts/src/receipt-validate.mjs +234 -0
- package/src/commands/upgrade.mjs +383 -0
- package/src/lib/harness-upgrade.mjs +521 -0
- package/src/scaffold.mjs +60 -1
- package/template/AGENTS.md +5 -0
- package/template/CLAUDE.md +34 -1
- package/template/README.md +4 -0
- package/template/gitignore +8 -0
- package/template/qa/lib/audit-cadence.mjs +290 -0
- package/template/qa/lib/determinism.mjs +179 -0
- package/template/qa/lib/evidence-badge.mjs +158 -0
- package/template/qa/lib/flight-recorder.mjs +332 -0
- package/template/qa/lib/harness-lock.mjs +147 -0
- package/template/qa/lib/harness-region.mjs +159 -0
- package/template/qa/lib/inputs-hash.mjs +17 -2
- package/template/qa/lib/receipt-validate.mjs +1 -1
- package/template/qa/preview-gallery.mjs +17 -2
- package/template/qa/record-audit.mjs +83 -0
- package/template/qa/retrospective.mjs +51 -0
- package/template/qa/verify.mjs +400 -10
- package/template/qa/watch.mjs +2 -2
|
@@ -23,6 +23,21 @@ const previewsDir = resolve(
|
|
|
23
23
|
const { renderTreeSvg } = await import(new URL("./lib/render.mjs", import.meta.url));
|
|
24
24
|
const { auditA11y } = await import(new URL("./lib/a11y.mjs", import.meta.url));
|
|
25
25
|
|
|
26
|
+
// The app's display name is read at RUNTIME from create-cmp.json, never stamped in.
|
|
27
|
+
// Every .mjs under qa/ is machine-owned harness code copied byte-identical from the
|
|
28
|
+
// engine — token substitution must not touch it (see qa/lib/harness-region.mjs), so
|
|
29
|
+
// anything app-specific is looked up from the record that already holds that truth.
|
|
30
|
+
function appName() {
|
|
31
|
+
try {
|
|
32
|
+
const rec = JSON.parse(readFileSync(join(HERE, "..", "create-cmp.json"), "utf8"));
|
|
33
|
+
if (typeof rec.name === "string" && rec.name.trim()) return rec.name;
|
|
34
|
+
} catch {
|
|
35
|
+
// Not stamped, or an unreadable record — the gallery is a report, not a gate.
|
|
36
|
+
}
|
|
37
|
+
return "App";
|
|
38
|
+
}
|
|
39
|
+
const APP_NAME = appName();
|
|
40
|
+
|
|
26
41
|
const manifest = JSON.parse(readFileSync(join(previewsDir, "manifest.json"), "utf8"));
|
|
27
42
|
const { width, height, pngScale } = manifest.viewport;
|
|
28
43
|
|
|
@@ -57,7 +72,7 @@ const esc = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replac
|
|
|
57
72
|
|
|
58
73
|
const html = `<!doctype html>
|
|
59
74
|
<meta charset="utf-8">
|
|
60
|
-
<title
|
|
75
|
+
<title>${esc(APP_NAME)} — screen previews (headless)</title>
|
|
61
76
|
<style>
|
|
62
77
|
:root { color-scheme: light; }
|
|
63
78
|
body { font-family: -apple-system, system-ui, sans-serif; margin: 0; background: #F7F9FC; color: #1A1A1A; }
|
|
@@ -77,7 +92,7 @@ const html = `<!doctype html>
|
|
|
77
92
|
.lbl { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: #9CA3AF; margin: 0 0 4px; }
|
|
78
93
|
</style>
|
|
79
94
|
<header>
|
|
80
|
-
<h1
|
|
95
|
+
<h1>${esc(APP_NAME)} — screen previews</h1>
|
|
81
96
|
<p>Rendered headlessly (no device/emulator) by <code>:composeApp:renderScreens</code> —
|
|
82
97
|
${width}×${height}dp, PNG @${pngScale}x · pixels for humans, wireframe+tree for the AI ·
|
|
83
98
|
regenerate: <code>./gradlew :composeApp:renderScreens && node qa/preview-gallery.mjs</code></p>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Record that a cmp-audit of one androidMain subsystem happened — the
|
|
3
|
+
// write half of the audit-cadence report (qa/lib/audit-cadence.mjs).
|
|
4
|
+
//
|
|
5
|
+
// node qa/record-audit.mjs <subsystem> [--by <who-or-what>]
|
|
6
|
+
// node qa/record-audit.mjs --list
|
|
7
|
+
//
|
|
8
|
+
// Recording is a CLAIM — "this subsystem, as of this commit, was audited" —
|
|
9
|
+
// so the entry's sha is derived from HEAD by the library, never passed in,
|
|
10
|
+
// and the write is REFUSED when there is no git history, when the subsystem
|
|
11
|
+
// is not one this app actually has (derived from the tree, printed on
|
|
12
|
+
// refusal), or when the subsystem's files differ from HEAD (the record
|
|
13
|
+
// would name a commit the audited bytes did not match — commit first).
|
|
14
|
+
// Refusal over fabrication, the same stance as qa/approve.mjs.
|
|
15
|
+
//
|
|
16
|
+
// This CLI exists so the audit loop closes mechanically: the release
|
|
17
|
+
// profile's receipt nudges "changed since last audit → cmp-audit <name>",
|
|
18
|
+
// and the auditor's last act is this one command.
|
|
19
|
+
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
|
|
23
|
+
import { AUDITS_REL_PATH, ROOT_SUBSYSTEM, androidMainPackageRoot, evaluateAuditCadence, listSubsystems, recordAudit } from "./lib/audit-cadence.mjs";
|
|
24
|
+
|
|
25
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
26
|
+
|
|
27
|
+
const USAGE = `node qa/record-audit.mjs <subsystem> [--by <who-or-what>]
|
|
28
|
+
|
|
29
|
+
Appends one audit record (subsystem, HEAD sha, ISO timestamp, recorder) to
|
|
30
|
+
${AUDITS_REL_PATH}. The verify lane's release profile reports which
|
|
31
|
+
subsystems changed since their last record. Subsystems are derived from the
|
|
32
|
+
tree: the immediate package directories under the androidMain Kotlin source
|
|
33
|
+
root ("${ROOT_SUBSYSTEM}" for files directly at the package root).
|
|
34
|
+
|
|
35
|
+
--list print the derived subsystems and their audit status
|
|
36
|
+
--by <name> who/what recorded this (default: git user.name)
|
|
37
|
+
--help, -h this usage
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const args = process.argv.slice(2);
|
|
41
|
+
|
|
42
|
+
if (args.includes("--help") || args.includes("-h") || args.length === 0) {
|
|
43
|
+
console.log(USAGE);
|
|
44
|
+
process.exit(args.length === 0 ? 2 : 0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (args.includes("--list")) {
|
|
48
|
+
const report = evaluateAuditCadence(ROOT);
|
|
49
|
+
if (!report.ok) {
|
|
50
|
+
console.log(report.reason);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
console.log(`androidMain subsystems under ${report.packageRoot} (${report.summary}):`);
|
|
54
|
+
for (const s of report.subsystems) {
|
|
55
|
+
const when = s.audit?.at ? ` — last audit ${s.audit.at.slice(0, 10)} (${s.audit.sha.slice(0, 7)}, by ${s.audit.by ?? "unknown"})` : "";
|
|
56
|
+
console.log(` ${s.name}: ${s.status}${when}`);
|
|
57
|
+
}
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const byIdx = args.indexOf("--by");
|
|
62
|
+
const by = byIdx >= 0 ? args[byIdx + 1] : undefined;
|
|
63
|
+
if (byIdx >= 0 && !by) {
|
|
64
|
+
console.error("--by needs a value");
|
|
65
|
+
process.exit(2);
|
|
66
|
+
}
|
|
67
|
+
const positional = args.filter((a, i) => !(byIdx >= 0 && (i === byIdx || i === byIdx + 1)));
|
|
68
|
+
if (positional.length !== 1 || positional[0].startsWith("--")) {
|
|
69
|
+
console.error(`expected exactly one subsystem name — run node qa/record-audit.mjs --help`);
|
|
70
|
+
process.exit(2);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const res = recordAudit(ROOT, { subsystem: positional[0], by });
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
console.error(`refused: ${res.reason}`);
|
|
76
|
+
const pkgRoot = androidMainPackageRoot(ROOT);
|
|
77
|
+
if (pkgRoot.ok) {
|
|
78
|
+
console.error(`derived subsystems: ${listSubsystems(ROOT, pkgRoot.rel).join(", ")}`);
|
|
79
|
+
}
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
console.log(`recorded: audit of ${res.entry.subsystem} against ${res.sha.slice(0, 7)} (by ${res.entry.by}) → ${AUDITS_REL_PATH}`);
|
|
83
|
+
console.log("commit the ledger with your change — the release profile's receipt reads it.");
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The flight recorder's reader — "did this project drift from its tooling?"
|
|
3
|
+
// answered mechanically, from qa/flight-recorder.jsonl alone.
|
|
4
|
+
//
|
|
5
|
+
// node qa/retrospective.mjs [--json]
|
|
6
|
+
//
|
|
7
|
+
// The verify lane appends one journal line per run (qa/lib/flight-recorder.mjs);
|
|
8
|
+
// this CLI turns those lines into the ten-second report a retrospective
|
|
9
|
+
// starts from: fast vs full ratio, which steps SKIP most and why (verbatim
|
|
10
|
+
// reasons, grouped), whether the device tier is ever actually reached, the
|
|
11
|
+
// longest recorded stretch with no full lane, and which degraded paths fired.
|
|
12
|
+
//
|
|
13
|
+
// HONESTY RULES — the reader is only as good as its refusals:
|
|
14
|
+
// - it states only what the journal recorded; a missing journal is
|
|
15
|
+
// "no flight data recorded yet" and exit 0, never an error, never a
|
|
16
|
+
// fabricated baseline;
|
|
17
|
+
// - a short journal says it is short instead of letting two entries read
|
|
18
|
+
// as a trend;
|
|
19
|
+
// - it never editorializes about the developer — every line is a count or
|
|
20
|
+
// a date about lane runs, and the human draws the conclusions.
|
|
21
|
+
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
import { readFlightJournal, renderFlightReport, summarizeFlightJournal } from "./lib/flight-recorder.mjs";
|
|
26
|
+
|
|
27
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
28
|
+
const asJson = process.argv.includes("--json");
|
|
29
|
+
|
|
30
|
+
const journal = readFlightJournal(ROOT);
|
|
31
|
+
|
|
32
|
+
if (!journal.exists) {
|
|
33
|
+
if (asJson) console.log(JSON.stringify({ recorded: false }));
|
|
34
|
+
else console.log("no flight data recorded yet — the journal (qa/flight-recorder.jsonl) appears after the first verify-lane run");
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
if (journal.error) {
|
|
38
|
+
console.error(`qa/flight-recorder.jsonl exists but could not be read: ${journal.error}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const summary = summarizeFlightJournal(journal.entries);
|
|
43
|
+
|
|
44
|
+
if (asJson) {
|
|
45
|
+
console.log(JSON.stringify({ recorded: true, malformed: journal.malformed, summary }, null, 2));
|
|
46
|
+
} else {
|
|
47
|
+
for (const line of renderFlightReport(summary, { malformed: journal.malformed })) {
|
|
48
|
+
console.log(line);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
process.exit(0);
|