create-cmp-cli 0.24.0 → 0.25.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 +33 -3
- package/package.json +10 -4
- package/packages/harness/package.json +18 -8
- package/packages/harness/src/approve.mjs +10 -11
- package/packages/harness/src/console/console-data.mjs +117 -0
- package/packages/harness/src/console/console-evidence.mjs +122 -0
- package/packages/harness/src/console/console-overview.mjs +642 -0
- package/packages/harness/src/console/console-shell.mjs +1139 -0
- package/packages/harness/src/console/console-tabs.mjs +2828 -0
- package/packages/harness/src/console/contrast.mjs +74 -0
- package/packages/harness/src/console/preview-service.mjs +1384 -0
- package/packages/harness/src/framework-check.mjs +271 -37
- package/packages/harness/src/lib/a11y.mjs +1 -1
- package/packages/harness/src/lib/affected-tests.mjs +99 -44
- package/packages/harness/src/lib/approvals.mjs +177 -350
- package/packages/harness/src/lib/audit-cadence.mjs +1 -1
- package/packages/harness/src/lib/comments.mjs +18 -4
- package/packages/harness/src/lib/determinism.mjs +275 -14
- package/packages/harness/src/lib/evidence-badge.mjs +34 -2
- package/packages/harness/src/lib/evidence-ladder.mjs +202 -0
- package/packages/harness/src/lib/evidence-level.mjs +144 -59
- package/packages/harness/src/lib/feature-brief.mjs +53 -16
- package/packages/harness/src/lib/flight-recorder.mjs +72 -8
- package/packages/harness/src/lib/framework-check.mjs +434 -53
- package/packages/harness/src/lib/harness-lock.mjs +87 -19
- package/packages/harness/src/lib/harness-manifest.mjs +208 -0
- package/packages/harness/src/lib/harness-region.mjs +106 -2
- package/packages/harness/src/lib/harness-source.mjs +142 -0
- package/packages/harness/src/lib/inputs-hash.mjs +175 -22
- package/packages/harness/src/lib/lane-markers.mjs +78 -0
- package/packages/harness/src/lib/lane-narrator.mjs +4 -2
- package/packages/harness/src/lib/lane-runner.mjs +72 -6
- package/packages/harness/src/lib/plan.mjs +44 -11
- package/packages/harness/src/lib/plant-calibration.mjs +135 -0
- package/packages/harness/src/lib/profile-loader.mjs +323 -0
- package/packages/harness/src/lib/profiles/cmp/affected.mjs +70 -0
- package/packages/harness/src/lib/profiles/cmp/android-checks.mjs +56 -0
- package/packages/harness/src/lib/profiles/cmp/artifacts.mjs +381 -0
- package/packages/harness/src/lib/{component-stories.mjs → profiles/cmp/component-stories.mjs} +5 -2
- package/packages/harness/src/lib/profiles/cmp/console-copy.mjs +29 -0
- package/packages/harness/src/lib/profiles/cmp/declarations.mjs +181 -0
- package/{template/qa/lib → packages/harness/src/lib/profiles/cmp}/e2e-coverage.mjs +13 -3
- package/packages/harness/src/lib/profiles/cmp/index.mjs +47 -0
- package/packages/harness/src/lib/profiles/cmp/ladder.mjs +67 -0
- package/packages/harness/src/lib/profiles/cmp/maestro.mjs +113 -0
- package/packages/harness/src/lib/profiles/cmp/plants.mjs +95 -0
- package/{template/qa/lib → packages/harness/src/lib/profiles/cmp}/reachability.mjs +1 -1
- package/packages/harness/src/lib/{steps-cmp.mjs → profiles/cmp/steps-cmp.mjs} +229 -27
- package/packages/harness/src/lib/receipt-validate.mjs +84 -16
- package/packages/harness/src/lib/spec-coverage.mjs +179 -92
- package/packages/harness/src/lib/spec-model.mjs +262 -0
- package/packages/harness/src/lib/step-cache.mjs +11 -2
- package/packages/harness/src/lib/step-outcomes.mjs +84 -151
- package/packages/harness/src/lib/walk.mjs +1 -1
- package/packages/harness/src/preview-gallery.mjs +1 -1
- package/packages/harness/src/receipt-check.mjs +80 -24
- package/packages/harness/src/record-audit.mjs +1 -1
- package/packages/harness/src/scaffold-feature.mjs +10 -2
- package/packages/harness/src/verify.mjs +222 -142
- package/packages/harness/src/watch.mjs +146 -33
- package/packages/receipts/package.json +1 -1
- package/packages/receipts/src/index.mjs +2 -2
- package/packages/receipts/src/inputs-hash.mjs +175 -22
- package/packages/receipts/src/receipt-validate.mjs +84 -16
- package/src/commands/attach.mjs +128 -4
- package/src/commands/harden.mjs +7 -1
- package/src/commands/upgrade.mjs +7 -0
- package/src/lib/minimal.mjs +23 -1
- package/src/scaffold.mjs +8 -0
- package/template/.claude/settings.json +2 -2
- package/template/.claude/skills/add-feature/SKILL.md +7 -3
- package/template/.claude/skills/add-repository/SKILL.md +2 -2
- package/template/.claude/skills/add-screen/SKILL.md +8 -4
- package/template/.githooks/pre-push +9 -0
- package/template/CLAUDE.md +55 -186
- package/template/docs/TESTING.md +1 -1
- package/template/gitignore +3 -0
- package/template/qa/approve.mjs +10 -11
- package/template/qa/comments.json +1 -1
- package/template/qa/evidence/schema.json +258 -34
- package/template/qa/framework-check.mjs +271 -37
- package/template/qa/harness-manifest.json +10 -0
- package/template/qa/lib/a11y.mjs +1 -1
- package/template/qa/lib/affected-tests.mjs +99 -44
- package/template/qa/lib/approvals.mjs +177 -350
- package/template/qa/lib/audit-cadence.mjs +1 -1
- package/template/qa/lib/comments.mjs +18 -4
- package/template/qa/lib/determinism.mjs +275 -14
- package/template/qa/lib/evidence-badge.mjs +34 -2
- package/template/qa/lib/evidence-ladder.mjs +202 -0
- package/template/qa/lib/evidence-level.mjs +144 -59
- package/template/qa/lib/feature-brief.mjs +53 -16
- package/template/qa/lib/flight-recorder.mjs +72 -8
- package/template/qa/lib/framework-check.mjs +434 -53
- package/template/qa/lib/harness-lock.mjs +87 -19
- package/template/qa/lib/harness-manifest.mjs +208 -0
- package/template/qa/lib/harness-region.mjs +106 -2
- package/template/qa/lib/harness-source.mjs +142 -0
- package/template/qa/lib/inputs-hash.mjs +175 -22
- package/template/qa/lib/lane-markers.mjs +78 -0
- package/template/qa/lib/lane-narrator.mjs +4 -2
- package/template/qa/lib/lane-runner.mjs +72 -6
- package/template/qa/lib/plan.mjs +44 -11
- package/template/qa/lib/plant-calibration.mjs +135 -0
- package/template/qa/lib/profile-loader.mjs +323 -0
- package/template/qa/lib/profiles/cmp/affected.mjs +70 -0
- package/template/qa/lib/profiles/cmp/android-checks.mjs +56 -0
- package/template/qa/lib/profiles/cmp/artifacts.mjs +381 -0
- package/template/qa/lib/{component-stories.mjs → profiles/cmp/component-stories.mjs} +5 -2
- package/template/qa/lib/profiles/cmp/console-copy.mjs +29 -0
- package/template/qa/lib/profiles/cmp/declarations.mjs +181 -0
- package/{packages/harness/src/lib → template/qa/lib/profiles/cmp}/e2e-coverage.mjs +13 -3
- package/template/qa/lib/profiles/cmp/index.mjs +47 -0
- package/template/qa/lib/profiles/cmp/ladder.mjs +67 -0
- package/template/qa/lib/profiles/cmp/maestro.mjs +113 -0
- package/template/qa/lib/profiles/cmp/plants.mjs +95 -0
- package/{packages/harness/src/lib → template/qa/lib/profiles/cmp}/reachability.mjs +1 -1
- package/template/qa/lib/{steps-cmp.mjs → profiles/cmp/steps-cmp.mjs} +229 -27
- package/template/qa/lib/receipt-validate.mjs +84 -16
- package/template/qa/lib/spec-coverage.mjs +179 -92
- package/template/qa/lib/spec-model.mjs +262 -0
- package/template/qa/lib/step-cache.mjs +11 -2
- package/template/qa/lib/step-outcomes.mjs +84 -151
- package/template/qa/lib/walk.mjs +1 -1
- package/template/qa/preview-gallery.mjs +1 -1
- package/template/qa/receipt-check.mjs +80 -24
- package/template/qa/record-audit.mjs +1 -1
- package/template/qa/scaffold-feature.mjs +10 -2
- package/template/qa/verified-surface.json +17 -0
- package/template/qa/verify.mjs +222 -142
- package/template/qa/watch.mjs +146 -33
- /package/packages/harness/src/lib/{device-lease.mjs → profiles/cmp/device-lease.mjs} +0 -0
- /package/packages/harness/src/lib/{device-provider.mjs → profiles/cmp/device-provider.mjs} +0 -0
- /package/packages/harness/src/lib/{render.mjs → profiles/cmp/render.mjs} +0 -0
- /package/packages/harness/src/lib/{token-drift.mjs → profiles/cmp/token-drift.mjs} +0 -0
- /package/packages/harness/src/lib/{tree.mjs → profiles/cmp/tree.mjs} +0 -0
- /package/template/qa/lib/{device-lease.mjs → profiles/cmp/device-lease.mjs} +0 -0
- /package/template/qa/lib/{device-provider.mjs → profiles/cmp/device-provider.mjs} +0 -0
- /package/template/qa/lib/{render.mjs → profiles/cmp/render.mjs} +0 -0
- /package/template/qa/lib/{token-drift.mjs → profiles/cmp/token-drift.mjs} +0 -0
- /package/template/qa/lib/{tree.mjs → profiles/cmp/tree.mjs} +0 -0
package/bin/create-cmp.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { parseArgs } from "../src/lib/args.mjs";
|
|
17
17
|
|
|
18
|
-
const COMMANDS = new Set(["create", "doctor", "upgrade", "clean", "verify", "harden", "attach", "help"]);
|
|
18
|
+
const COMMANDS = new Set(["create", "doctor", "upgrade", "clean", "verify", "harden", "attach", "harness", "help"]);
|
|
19
19
|
|
|
20
20
|
async function main() {
|
|
21
21
|
const argv = process.argv.slice(2);
|
|
@@ -63,6 +63,30 @@ async function main() {
|
|
|
63
63
|
await runAttach(flags, rest[0]);
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
+
// `harness` takes a subcommand because it is stack-neutral and will grow
|
|
67
|
+
// siblings (`upgrade`, `doctor`) that must not collide with the Compose
|
|
68
|
+
// verbs above. It is also the form that survived the rename it predicted:
|
|
69
|
+
// since 2026-09-08 the implementation lives in the harness package and its
|
|
70
|
+
// own binary spells these `prooflane init` / `prooflane relock`. This
|
|
71
|
+
// dispatch DELEGATES rather than duplicating — `invocation` is the only
|
|
72
|
+
// difference, and it decides which command names the output prints back.
|
|
73
|
+
case "harness": {
|
|
74
|
+
const sub = rest[0];
|
|
75
|
+
if (sub === "init") {
|
|
76
|
+
const { runHarnessInit } = await import("../packages/harness/install/init.mjs");
|
|
77
|
+
process.exit((await runHarnessInit(flags, rest[1], { invocation: "create-cmp" })) ?? 0);
|
|
78
|
+
}
|
|
79
|
+
if (sub === "relock") {
|
|
80
|
+
const { runHarnessRelock } = await import("../packages/harness/install/relock.mjs");
|
|
81
|
+
process.exit((await runHarnessRelock(flags, rest[1], { invocation: "create-cmp" })) ?? 0);
|
|
82
|
+
}
|
|
83
|
+
process.stderr.write(
|
|
84
|
+
`create-cmp harness: unknown subcommand ${JSON.stringify(sub ?? "")}\n` +
|
|
85
|
+
` usage: create-cmp harness init [--profile <id>] [--target-dir <dir>] [--dry-run]\n` +
|
|
86
|
+
` create-cmp harness relock [--target-dir <dir>] [--dry-run]\n`
|
|
87
|
+
);
|
|
88
|
+
process.exit(2);
|
|
89
|
+
}
|
|
66
90
|
case "create":
|
|
67
91
|
default: {
|
|
68
92
|
const { runCreate } = await import("../src/commands/create.mjs");
|
|
@@ -85,7 +109,9 @@ function printHelp() {
|
|
|
85
109
|
` npx create-cmp clean ~/.konan + Gradle build-output hygiene (consent-gated)\n` +
|
|
86
110
|
` npx create-cmp verify run the green-build gate on an existing project\n` +
|
|
87
111
|
` npx create-cmp harden install the full harness into a --minimal scaffold\n` +
|
|
88
|
-
` npx create-cmp attach wire the agent contract into an EXISTING Compose/KMP repo\n
|
|
112
|
+
` npx create-cmp attach wire the agent contract into an EXISTING Compose/KMP repo\n` +
|
|
113
|
+
` npx create-cmp harness init install the verify lane into a repo of ANY stack\n` +
|
|
114
|
+
` npx create-cmp harness relock re-take the lock after editing YOUR profile or declarations\n\n` +
|
|
89
115
|
`create (scaffold) flags:\n` +
|
|
90
116
|
` --name --package --bundle-id --region --theme-prefix\n` +
|
|
91
117
|
` --minimal (light scaffold: app + tests + previews, no verify lane/receipts —\n` +
|
|
@@ -103,7 +129,11 @@ function printHelp() {
|
|
|
103
129
|
`clean flags: --target-dir <dir> --dry-run --yes\n` +
|
|
104
130
|
`verify flags: --target-dir <dir> --no-ios --dry-run\n` +
|
|
105
131
|
`harden flags: --target-dir <dir> --dry-run --yes --verify (run the lane after install)\n` +
|
|
106
|
-
`attach flags: --target-dir <dir> --dry-run --yes\n`
|
|
132
|
+
`attach flags: --target-dir <dir> --dry-run --yes\n` +
|
|
133
|
+
`harness init flags: --profile <id> --target-dir <dir> --dry-run\n` +
|
|
134
|
+
`harness relock flags: --target-dir <dir> --dry-run\n` +
|
|
135
|
+
` (relock covers qa/lib/profiles/<id>/** and qa/{verified-surface,harness-manifest}.json only —\n` +
|
|
136
|
+
` a machine-owned edit is a fork and is refused by name; \`upgrade --harness\` restores it)\n`
|
|
107
137
|
);
|
|
108
138
|
}
|
|
109
139
|
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-cmp-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Create production mobile apps (Android + iOS, one Kotlin codebase) with AI
|
|
3
|
+
"version": "0.25.0",
|
|
4
|
+
"description": "Create production mobile apps (Android + iOS, one Kotlin codebase) with AI \u2014 the delivery harness for Compose Multiplatform, the current generation of cross-platform (Google-backed KMP, iOS stable since May 2025). A deterministic, non-interactive generator that scaffolds a green-building app in minutes, then holds AI-driven changes to a machine-enforced verify lane with a committed evidence receipt. Every app carries a device-free UI preview loop (real screens rendered headlessly on save; changed-screen attribution and compile-error surfacing for coding agents, a live gallery for humans) plus agent-first docs (CLAUDE.md + AGENTS.md). Installs the `create-cmp` command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-cmp": "bin/create-cmp.mjs",
|
|
8
8
|
"create-cmp-cli": "bin/create-cmp.mjs"
|
|
9
9
|
},
|
|
10
10
|
"engines": {
|
|
11
|
-
"node": ">=
|
|
11
|
+
"node": ">=20.19.0"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"bin",
|
|
@@ -74,5 +74,11 @@
|
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"prompts": "^2.4.2",
|
|
76
76
|
"picocolors": "^1.1.1"
|
|
77
|
-
}
|
|
77
|
+
},
|
|
78
|
+
"workspaces": [
|
|
79
|
+
"packages/harness",
|
|
80
|
+
"packages/receipts",
|
|
81
|
+
"packages/aliases/*",
|
|
82
|
+
"inspector/mcp"
|
|
83
|
+
]
|
|
78
84
|
}
|
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The
|
|
2
|
+
"name": "prooflane-harness",
|
|
3
|
+
"version": "0.21.0",
|
|
4
|
+
"description": "The prooflane verify lane: a stack-agnostic evidence harness that derives done from evidence rather than an agent's word. Profile loader, lane runner, receipts, hash-locked region, Stop hook, spec-to-citation binding and the Rule 0/1/2 instruments. Dependency-free ESM, vendored into each project so a receipt is checkable offline from the repo alone, and content-hashed so a receipt names the exact lane that issued it. NOTE: this release still carries the `cmp` (Compose Multiplatform) stack profile as its source of truth; it moves to prooflane-profile-cmp in the package split.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/verify.mjs",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/verify.mjs",
|
|
9
9
|
"./harness-region": "./src/lib/harness-region.mjs",
|
|
10
10
|
"./lib/*.mjs": "./src/lib/*.mjs",
|
|
11
|
-
"./lib/*": "./src/lib/*.mjs"
|
|
11
|
+
"./lib/*": "./src/lib/*.mjs",
|
|
12
|
+
"./console": "./src/console/preview-service.mjs",
|
|
13
|
+
"./console/*.mjs": "./src/console/*.mjs",
|
|
14
|
+
"./console/*": "./src/console/*.mjs"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"prooflane": "bin/prooflane.mjs"
|
|
12
18
|
},
|
|
13
19
|
"files": [
|
|
14
20
|
"src",
|
|
21
|
+
"install",
|
|
22
|
+
"bin",
|
|
23
|
+
"evidence",
|
|
15
24
|
"README.md"
|
|
16
25
|
],
|
|
17
26
|
"scripts": {
|
|
18
27
|
"test": "node --test"
|
|
19
28
|
},
|
|
20
29
|
"engines": {
|
|
21
|
-
"node": ">=
|
|
30
|
+
"node": ">=20.19.0"
|
|
22
31
|
},
|
|
23
32
|
"keywords": [
|
|
24
|
-
"
|
|
33
|
+
"prooflane",
|
|
25
34
|
"verify",
|
|
26
35
|
"evidence",
|
|
27
36
|
"receipt",
|
|
28
37
|
"harness",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
38
|
+
"stack-agnostic",
|
|
39
|
+
"ci",
|
|
40
|
+
"agents"
|
|
31
41
|
],
|
|
32
42
|
"license": "MIT",
|
|
33
43
|
"author": "Karel van der Merwe",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// The approvals CLI — thin shell over qa/lib/approvals.mjs.
|
|
3
3
|
//
|
|
4
|
-
// node qa/approve.mjs <artifact> [<artifact> …]
|
|
4
|
+
// node qa/approve.mjs <artifact> [<artifact> …] --as "Name <email>"
|
|
5
5
|
// records approval (recomputes each artifact's
|
|
6
6
|
// hash now, stamps the time, writes qa/approvals.json).
|
|
7
7
|
// Sign every pending artifact in ONE command: each
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
approveArtifact,
|
|
49
49
|
getApprovalStatuses,
|
|
50
50
|
getFeatureBoard,
|
|
51
|
-
|
|
51
|
+
isProjectGovernable,
|
|
52
52
|
listGovernedArtifacts,
|
|
53
53
|
readJournal,
|
|
54
54
|
reopenArtifact,
|
|
@@ -122,17 +122,16 @@ if (args.includes("--status")) {
|
|
|
122
122
|
process.exit(0);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
// Write guard: refuse to RECORD approvals in a tree
|
|
126
|
-
//
|
|
127
|
-
//
|
|
125
|
+
// Write guard: refuse to RECORD approvals in a tree that cannot be governed —
|
|
126
|
+
// no manifest, no loadable profile, or a tree the profile itself refuses (the
|
|
127
|
+
// raw template, whose package is still a placeholder). Approvals belong to a
|
|
128
|
+
// real project; writing qa/approvals.json into the template pollutes the
|
|
128
129
|
// template itself. Read-only --status (above) stays available anywhere. Applies
|
|
129
130
|
// to every write operation below (single approve, express lane, reopen).
|
|
130
131
|
function refuseIfUnresolvable() {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"this looks like the raw template or a pre-stamp tree. Approvals are recorded in a generated project; refusing to write qa/approvals.json here.",
|
|
135
|
-
);
|
|
132
|
+
const v = isProjectGovernable(ROOT);
|
|
133
|
+
if (v.ok) return;
|
|
134
|
+
console.error(`error: ${v.reason}`);
|
|
136
135
|
process.exit(1);
|
|
137
136
|
}
|
|
138
137
|
|
|
@@ -247,7 +246,7 @@ if (acceptFlagIdx !== -1) {
|
|
|
247
246
|
if (args.length === 0) {
|
|
248
247
|
const ids = listGovernedArtifacts(ROOT).map((a) => a.id);
|
|
249
248
|
console.error(
|
|
250
|
-
'usage: node qa/approve.mjs <artifact> | --status | --log | --accept-defaults | --reopen <artifact> --reason "…" | --reopen-feature <name> --reason "…" | --accept <name>\n' +
|
|
249
|
+
'usage: node qa/approve.mjs <artifact> --as "Name <email>" | --status | --log | --accept-defaults | --reopen <artifact> --reason "…" | --reopen-feature <name> --reason "…" | --accept <name>\n' +
|
|
251
250
|
` valid artifacts: ${ids.length > 0 ? ids.join(", ") : "(none resolved in this project)"}`,
|
|
252
251
|
);
|
|
253
252
|
process.exit(1);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// console-data.mjs — the pure derivations the console's own section renderers
|
|
2
|
+
// need, and nothing else.
|
|
3
|
+
//
|
|
4
|
+
// MOVED HERE from inspector/mcp/src/lib/ (design-language.mjs and
|
|
5
|
+
// components.mjs) when the console moved into this package — NORTH-STAR §9,
|
|
6
|
+
// stage 0.5, "the console into the harness". A reader coming from either of
|
|
7
|
+
// those files should know why these four functions left them.
|
|
8
|
+
//
|
|
9
|
+
// The reason is the package boundary, not tidiness. `packages/harness` is
|
|
10
|
+
// `prooflane-harness`, whose `files` list ships `src/` and whose whole claim
|
|
11
|
+
// (PACKAGE-SPLIT.md §3) is "the console ... Knows no stack. THIS IS THE
|
|
12
|
+
// PRODUCT." A console module that imported back into inspector/mcp would ship
|
|
13
|
+
// with a dangling relative path — the sibling package is not in the tarball —
|
|
14
|
+
// so the console cannot have a single edge pointing that way, and these were
|
|
15
|
+
// the only three it had.
|
|
16
|
+
//
|
|
17
|
+
// What stayed behind is exactly the stack-coupled half. `getTokenUsage` scans
|
|
18
|
+
// Kotlin source for a declaring `object`; `walkKtFiles`/`getComponentsData`
|
|
19
|
+
// parse `.kt` signatures. Those are the eyes (PACKAGE-SPLIT.md §3,
|
|
20
|
+
// `prooflane-studio-cmp`) and they stay in inspector/mcp. What moved is what a
|
|
21
|
+
// console for ANY stack could use: bucket a dimens catalog by name, compute a
|
|
22
|
+
// WCAG pair table from a colour catalog, and spell a component's story id.
|
|
23
|
+
// None of them reads a file or names a path.
|
|
24
|
+
//
|
|
25
|
+
// The originals are re-exports now, so every existing import site and both
|
|
26
|
+
// unit tests still resolve — one definition, two doors.
|
|
27
|
+
|
|
28
|
+
import { contrastRatio } from "./contrast.mjs";
|
|
29
|
+
|
|
30
|
+
// --- component story ids (was components.mjs) ---------------------------------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* PascalCase/camelCase → kebab-case: AppHeader → app-header, ListItemCard →
|
|
34
|
+
* list-item-card. Mirrors the template's qa/lib/component-stories.mjs (the
|
|
35
|
+
* lane-side parity gate) — keep the two in sync, same contract as navSlug.
|
|
36
|
+
*/
|
|
37
|
+
export function kebabCase(name) {
|
|
38
|
+
return String(name)
|
|
39
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
40
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
|
|
41
|
+
.toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The preview-registry story id a component of this name registers (§3.3): `component.<kebab>`. */
|
|
45
|
+
export function componentStoryId(name) {
|
|
46
|
+
return `component.${kebabCase(name)}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// --- dimens classification ----------------------------------------------------
|
|
50
|
+
|
|
51
|
+
const DP_RE = /^(\d+(?:\.\d+)?)\s*dp$/;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {Record<string, string>} dimens catalog dimens (name -> "16dp"-style value)
|
|
55
|
+
* @returns {{spacing: Array<{name:string,value:string,dp:number}>,
|
|
56
|
+
* radius: Array<{name:string,value:string,dp:(number|null)}>,
|
|
57
|
+
* elevation: Array<{name:string,value:string,dp:(number|null)}>,
|
|
58
|
+
* other: Array<{name:string,value:string,dp:(number|null)}>}}
|
|
59
|
+
* spacing sorted ascending by dp (it is rendered as a scale); the rest in
|
|
60
|
+
* catalog order. A `Padding`/`Gap` token whose value doesn't parse as dp
|
|
61
|
+
* cannot be drawn to scale — it goes to `other` (stated, not stretched).
|
|
62
|
+
*/
|
|
63
|
+
export function classifyDimens(dimens = {}) {
|
|
64
|
+
const spacing = [];
|
|
65
|
+
const radius = [];
|
|
66
|
+
const elevation = [];
|
|
67
|
+
const other = [];
|
|
68
|
+
for (const [name, value] of Object.entries(dimens)) {
|
|
69
|
+
const m = DP_RE.exec(String(value).trim());
|
|
70
|
+
const dp = m ? Number(m[1]) : null;
|
|
71
|
+
if (/^(Padding|Gap|Spacing)/.test(name) && dp !== null) spacing.push({ name, value, dp });
|
|
72
|
+
else if (/^Radius/.test(name)) radius.push({ name, value, dp });
|
|
73
|
+
else if (/^Elevation/.test(name)) elevation.push({ name, value, dp });
|
|
74
|
+
else other.push({ name, value, dp });
|
|
75
|
+
}
|
|
76
|
+
spacing.sort((a, b) => a.dp - b.dp || a.name.localeCompare(b.name));
|
|
77
|
+
return { spacing, radius, elevation, other };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --- WCAG contrast pairs ------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
// Normal-text thresholds, WCAG 2.2 SC 1.4.3 (AA) / SC 1.4.6 (AAA).
|
|
83
|
+
export const WCAG_AA_NORMAL = 4.5;
|
|
84
|
+
export const WCAG_AAA_NORMAL = 7;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {Record<string, string>} colors catalog colors (name -> hex)
|
|
88
|
+
* @returns {Array<{fg:string,bg:string,fgHex:string,bgHex:string,ratio:number,aa:boolean,aaa:boolean,role:string}>}
|
|
89
|
+
*/
|
|
90
|
+
export function deriveContrastPairs(colors = {}) {
|
|
91
|
+
const pairs = [];
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
const add = (fg, bg, role) => {
|
|
94
|
+
if (!(fg in colors) || !(bg in colors)) return;
|
|
95
|
+
const key = `${fg}/${bg}`;
|
|
96
|
+
if (seen.has(key)) return;
|
|
97
|
+
const ratio = contrastRatio(colors[fg], colors[bg]);
|
|
98
|
+
if (ratio === null) return; // unparseable hex -> the pair is absent, not guessed
|
|
99
|
+
seen.add(key);
|
|
100
|
+
pairs.push({
|
|
101
|
+
fg,
|
|
102
|
+
bg,
|
|
103
|
+
fgHex: colors[fg],
|
|
104
|
+
bgHex: colors[bg],
|
|
105
|
+
ratio,
|
|
106
|
+
aa: ratio >= WCAG_AA_NORMAL,
|
|
107
|
+
aaa: ratio >= WCAG_AAA_NORMAL,
|
|
108
|
+
role,
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
for (const name of Object.keys(colors)) {
|
|
112
|
+
if (/^On.+/.test(name)) add(name, name.slice(2), "text on its own surface");
|
|
113
|
+
}
|
|
114
|
+
add("OnSurfaceVariant", "Surface", "secondary text on Surface");
|
|
115
|
+
add("OnSurface", "Background", "body text on Background");
|
|
116
|
+
return pairs;
|
|
117
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// console-evidence.mjs — THE RUNG'S ONE SPELLING IN THIS CONSOLE.
|
|
2
|
+
//
|
|
3
|
+
// NORTH-STAR.md §6.5 ends with the rule this file exists to keep: "every
|
|
4
|
+
// surface that shows a rung shows the pack". §8.9 says why it is a guarantee
|
|
5
|
+
// and not a preference — "a `cmp` L2 and any other pack's L2 are different
|
|
6
|
+
// claims and are shown as such". L2 means "a phone drove the app" under one
|
|
7
|
+
// pack and "proven against a real database" under another, and the letter alone
|
|
8
|
+
// does not say which. A rung with no pack beside it is therefore a claim its
|
|
9
|
+
// reader cannot check, which is the one thing this product refuses everywhere
|
|
10
|
+
// else; §3 already promises Gatekeeper "shows the pack beside the rung".
|
|
11
|
+
//
|
|
12
|
+
// WHY A MODULE AND NOT SEVEN EDITS. The console renders a rung in SEVEN places:
|
|
13
|
+
// the rail foot, the front door's standing line, the recent-requests rows, the
|
|
14
|
+
// Evidence section's status line, its headline chip, the committed-receipt
|
|
15
|
+
// timeline, and the digest's lane-run table. NORTH-STAR.md §9.2 says "five",
|
|
16
|
+
// lists six locations, and the count under the scan is seven — the miss being
|
|
17
|
+
// the recent-requests rows, whose rung OUTLIVES the run that earned it. That
|
|
18
|
+
// arithmetic is the argument: §9.2 closes by naming what a list costs — "a fix
|
|
19
|
+
// applied to the instances rather than to the class comes back", a price that
|
|
20
|
+
// section records this repository paying twice in one file. So the console does
|
|
21
|
+
// not hold seven spellings of a rung. It holds one function, every surface
|
|
22
|
+
// calls it, and test/evidence-ladder.test.mjs refuses any console module that
|
|
23
|
+
// reads a rung another way — so an EIGHTH surface is covered the day it is
|
|
24
|
+
// written, by the same inversion the agnostic lint uses, and by nobody
|
|
25
|
+
// remembering a list.
|
|
26
|
+
//
|
|
27
|
+
// ONLY THE ID IS EVER SHOWN. `pack.version` on a receipt is today the harness
|
|
28
|
+
// LOCK's version rather than the profile's — the code that writes it says so
|
|
29
|
+
// ("its version is the lock's until the profile loader gives it its own") and
|
|
30
|
+
// docs/adr/0008-a-resolved-harness-is-still-a-vendored-one.md decided that
|
|
31
|
+
// number "must become null, not inherited". Until it does, a surface printing
|
|
32
|
+
// it would be naming a version of the wrong thing, so nothing here reads it.
|
|
33
|
+
// The id is the part that carries the meaning, and the id is the profile's own.
|
|
34
|
+
//
|
|
35
|
+
// Pure text, never HTML: every caller already owns an `esc` and its own markup,
|
|
36
|
+
// and a derivation that returned tags could not be shared by a rail line, a
|
|
37
|
+
// badge span and a table cell without one of them wearing another's chrome —
|
|
38
|
+
// which console-shell.mjs's design rules forbid outright.
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The pack id out of whatever the caller is holding, or null.
|
|
42
|
+
*
|
|
43
|
+
* Three shapes reach the console and all three are honoured, because forcing
|
|
44
|
+
* one on the callers would mean a bridge, a git-history row and a plan-trail
|
|
45
|
+
* entry each having to remember the same normalisation:
|
|
46
|
+
* - `"cmp"` — the flat id, how the receipt bridge and the plan trail carry it;
|
|
47
|
+
* - `{id: "cmp", version: …}` — the receipt's own `pack` object, verbatim,
|
|
48
|
+
* for any caller that holds the parsed receipt rather than a summary of it;
|
|
49
|
+
* - null/undefined/`{}`/`{id: " "}` — all of them mean the same thing, that
|
|
50
|
+
* nothing here names a pack, and all of them are rendered as that rather
|
|
51
|
+
* than as an absence the reader has to notice for themselves.
|
|
52
|
+
*/
|
|
53
|
+
function packIdOf(pack) {
|
|
54
|
+
if (typeof pack === "string") return pack.trim() || null;
|
|
55
|
+
if (pack && typeof pack === "object" && typeof pack.id === "string") return pack.id.trim() || null;
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The rung as a reader may safely quote it: the grade, its name when there is
|
|
61
|
+
* one, and the pack that graded it — or the statement that no pack is named.
|
|
62
|
+
*
|
|
63
|
+
* `null` when there is no rung to show. That is the honest absence and it has
|
|
64
|
+
* three causes the console must not tell apart: a FAILed lane (the lane itself
|
|
65
|
+
* records no level), a receipt written before the ladder existed, and a
|
|
66
|
+
* malformed field. None of them may be rendered as a rung, so none of them is.
|
|
67
|
+
*
|
|
68
|
+
* @param {{rung?: string, name?: string}|string|null|undefined} level the
|
|
69
|
+
* receipt's own derived `evidenceLevel`, read verbatim and never re-derived
|
|
70
|
+
* here — the lane is the law. A bare string is accepted for the two trails
|
|
71
|
+
* that store only the grade (the plan history's receipt glance, the digest's
|
|
72
|
+
* lane runs), so those surfaces get the pack without inventing a name.
|
|
73
|
+
* @param {{id?: string}|string|null|undefined} pack the pack id, or the
|
|
74
|
+
* receipt's `pack` object.
|
|
75
|
+
* @returns {string|null} e.g. `"L2 device · pack cmp"`, or
|
|
76
|
+
* `"L2 device · pack unnamed"` when the receipt named none. The grade and its
|
|
77
|
+
* name are joined by a SPACE and the pack by a middle dot, which is the
|
|
78
|
+
* console's existing spelling of a rung with one clause added rather than a
|
|
79
|
+
* new grammar: `L2 device` is what the rail foot, the front door and the
|
|
80
|
+
* Evidence status line already read, and the dot is what the done-gate CLI
|
|
81
|
+
* already puts before `pack`. A shared derivation is worth having; a shared
|
|
82
|
+
* derivation that also restyles six pages is two changes wearing one commit.
|
|
83
|
+
*/
|
|
84
|
+
export function rungWithPack(level, pack) {
|
|
85
|
+
const grade =
|
|
86
|
+
typeof level === "string"
|
|
87
|
+
? level.trim()
|
|
88
|
+
: level && typeof level === "object" && typeof level.rung === "string"
|
|
89
|
+
? level.rung.trim()
|
|
90
|
+
: "";
|
|
91
|
+
if (!grade) return null;
|
|
92
|
+
const name =
|
|
93
|
+
level && typeof level === "object" && typeof level.name === "string" && level.name.trim() ? level.name.trim() : "";
|
|
94
|
+
const id = packIdOf(pack);
|
|
95
|
+
// "pack unnamed" is the wording the README badge already uses for this exact
|
|
96
|
+
// state (packages/harness/src/lib/evidence-badge.mjs) and the done-gate CLI
|
|
97
|
+
// says the same thing at greater length. One vocabulary across the surfaces
|
|
98
|
+
// matters more than each one's best phrasing: a reader who learns it on the
|
|
99
|
+
// badge reads it on the console without being taught twice.
|
|
100
|
+
return `${grade}${name ? ` ${name}` : ""} · ${id ? `pack ${id}` : "pack unnamed"}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The sentence that belongs in a `title=` beside a rung — why the pack is
|
|
105
|
+
* there, in the words the badge and the done-gate CLI already use.
|
|
106
|
+
*
|
|
107
|
+
* Two states, and the second is the load-bearing one. A receipt that names no
|
|
108
|
+
* pack is SAID to name none, because an unattributed rung is comparable to
|
|
109
|
+
* nothing and hiding that fact would be the overclaim the whole ladder exists
|
|
110
|
+
* to refuse. `null` when there is no rung, so a caller can build its tooltip
|
|
111
|
+
* without a second absence check.
|
|
112
|
+
*
|
|
113
|
+
* @returns {string|null}
|
|
114
|
+
*/
|
|
115
|
+
export function rungPackNote(level, pack) {
|
|
116
|
+
if (!rungWithPack(level, pack)) return null;
|
|
117
|
+
const grade = typeof level === "string" ? level.trim() : String(level.rung).trim();
|
|
118
|
+
const id = packIdOf(pack);
|
|
119
|
+
return id
|
|
120
|
+
? `this rung is pack ${id}'s: a ${id} ${grade} and another pack's ${grade} are different claims`
|
|
121
|
+
: "the receipt names no pack, so this rung is comparable to nothing";
|
|
122
|
+
}
|