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.
Files changed (141) hide show
  1. package/bin/create-cmp.mjs +33 -3
  2. package/package.json +10 -4
  3. package/packages/harness/package.json +18 -8
  4. package/packages/harness/src/approve.mjs +10 -11
  5. package/packages/harness/src/console/console-data.mjs +117 -0
  6. package/packages/harness/src/console/console-evidence.mjs +122 -0
  7. package/packages/harness/src/console/console-overview.mjs +642 -0
  8. package/packages/harness/src/console/console-shell.mjs +1139 -0
  9. package/packages/harness/src/console/console-tabs.mjs +2828 -0
  10. package/packages/harness/src/console/contrast.mjs +74 -0
  11. package/packages/harness/src/console/preview-service.mjs +1384 -0
  12. package/packages/harness/src/framework-check.mjs +271 -37
  13. package/packages/harness/src/lib/a11y.mjs +1 -1
  14. package/packages/harness/src/lib/affected-tests.mjs +99 -44
  15. package/packages/harness/src/lib/approvals.mjs +177 -350
  16. package/packages/harness/src/lib/audit-cadence.mjs +1 -1
  17. package/packages/harness/src/lib/comments.mjs +18 -4
  18. package/packages/harness/src/lib/determinism.mjs +275 -14
  19. package/packages/harness/src/lib/evidence-badge.mjs +34 -2
  20. package/packages/harness/src/lib/evidence-ladder.mjs +202 -0
  21. package/packages/harness/src/lib/evidence-level.mjs +144 -59
  22. package/packages/harness/src/lib/feature-brief.mjs +53 -16
  23. package/packages/harness/src/lib/flight-recorder.mjs +72 -8
  24. package/packages/harness/src/lib/framework-check.mjs +434 -53
  25. package/packages/harness/src/lib/harness-lock.mjs +87 -19
  26. package/packages/harness/src/lib/harness-manifest.mjs +208 -0
  27. package/packages/harness/src/lib/harness-region.mjs +106 -2
  28. package/packages/harness/src/lib/harness-source.mjs +142 -0
  29. package/packages/harness/src/lib/inputs-hash.mjs +175 -22
  30. package/packages/harness/src/lib/lane-markers.mjs +78 -0
  31. package/packages/harness/src/lib/lane-narrator.mjs +4 -2
  32. package/packages/harness/src/lib/lane-runner.mjs +72 -6
  33. package/packages/harness/src/lib/plan.mjs +44 -11
  34. package/packages/harness/src/lib/plant-calibration.mjs +135 -0
  35. package/packages/harness/src/lib/profile-loader.mjs +323 -0
  36. package/packages/harness/src/lib/profiles/cmp/affected.mjs +70 -0
  37. package/packages/harness/src/lib/profiles/cmp/android-checks.mjs +56 -0
  38. package/packages/harness/src/lib/profiles/cmp/artifacts.mjs +381 -0
  39. package/packages/harness/src/lib/{component-stories.mjs → profiles/cmp/component-stories.mjs} +5 -2
  40. package/packages/harness/src/lib/profiles/cmp/console-copy.mjs +29 -0
  41. package/packages/harness/src/lib/profiles/cmp/declarations.mjs +181 -0
  42. package/{template/qa/lib → packages/harness/src/lib/profiles/cmp}/e2e-coverage.mjs +13 -3
  43. package/packages/harness/src/lib/profiles/cmp/index.mjs +47 -0
  44. package/packages/harness/src/lib/profiles/cmp/ladder.mjs +67 -0
  45. package/packages/harness/src/lib/profiles/cmp/maestro.mjs +113 -0
  46. package/packages/harness/src/lib/profiles/cmp/plants.mjs +95 -0
  47. package/{template/qa/lib → packages/harness/src/lib/profiles/cmp}/reachability.mjs +1 -1
  48. package/packages/harness/src/lib/{steps-cmp.mjs → profiles/cmp/steps-cmp.mjs} +229 -27
  49. package/packages/harness/src/lib/receipt-validate.mjs +84 -16
  50. package/packages/harness/src/lib/spec-coverage.mjs +179 -92
  51. package/packages/harness/src/lib/spec-model.mjs +262 -0
  52. package/packages/harness/src/lib/step-cache.mjs +11 -2
  53. package/packages/harness/src/lib/step-outcomes.mjs +84 -151
  54. package/packages/harness/src/lib/walk.mjs +1 -1
  55. package/packages/harness/src/preview-gallery.mjs +1 -1
  56. package/packages/harness/src/receipt-check.mjs +80 -24
  57. package/packages/harness/src/record-audit.mjs +1 -1
  58. package/packages/harness/src/scaffold-feature.mjs +10 -2
  59. package/packages/harness/src/verify.mjs +222 -142
  60. package/packages/harness/src/watch.mjs +146 -33
  61. package/packages/receipts/package.json +1 -1
  62. package/packages/receipts/src/index.mjs +2 -2
  63. package/packages/receipts/src/inputs-hash.mjs +175 -22
  64. package/packages/receipts/src/receipt-validate.mjs +84 -16
  65. package/src/commands/attach.mjs +128 -4
  66. package/src/commands/harden.mjs +7 -1
  67. package/src/commands/upgrade.mjs +7 -0
  68. package/src/lib/minimal.mjs +23 -1
  69. package/src/scaffold.mjs +8 -0
  70. package/template/.claude/settings.json +2 -2
  71. package/template/.claude/skills/add-feature/SKILL.md +7 -3
  72. package/template/.claude/skills/add-repository/SKILL.md +2 -2
  73. package/template/.claude/skills/add-screen/SKILL.md +8 -4
  74. package/template/.githooks/pre-push +9 -0
  75. package/template/CLAUDE.md +55 -186
  76. package/template/docs/TESTING.md +1 -1
  77. package/template/gitignore +3 -0
  78. package/template/qa/approve.mjs +10 -11
  79. package/template/qa/comments.json +1 -1
  80. package/template/qa/evidence/schema.json +258 -34
  81. package/template/qa/framework-check.mjs +271 -37
  82. package/template/qa/harness-manifest.json +10 -0
  83. package/template/qa/lib/a11y.mjs +1 -1
  84. package/template/qa/lib/affected-tests.mjs +99 -44
  85. package/template/qa/lib/approvals.mjs +177 -350
  86. package/template/qa/lib/audit-cadence.mjs +1 -1
  87. package/template/qa/lib/comments.mjs +18 -4
  88. package/template/qa/lib/determinism.mjs +275 -14
  89. package/template/qa/lib/evidence-badge.mjs +34 -2
  90. package/template/qa/lib/evidence-ladder.mjs +202 -0
  91. package/template/qa/lib/evidence-level.mjs +144 -59
  92. package/template/qa/lib/feature-brief.mjs +53 -16
  93. package/template/qa/lib/flight-recorder.mjs +72 -8
  94. package/template/qa/lib/framework-check.mjs +434 -53
  95. package/template/qa/lib/harness-lock.mjs +87 -19
  96. package/template/qa/lib/harness-manifest.mjs +208 -0
  97. package/template/qa/lib/harness-region.mjs +106 -2
  98. package/template/qa/lib/harness-source.mjs +142 -0
  99. package/template/qa/lib/inputs-hash.mjs +175 -22
  100. package/template/qa/lib/lane-markers.mjs +78 -0
  101. package/template/qa/lib/lane-narrator.mjs +4 -2
  102. package/template/qa/lib/lane-runner.mjs +72 -6
  103. package/template/qa/lib/plan.mjs +44 -11
  104. package/template/qa/lib/plant-calibration.mjs +135 -0
  105. package/template/qa/lib/profile-loader.mjs +323 -0
  106. package/template/qa/lib/profiles/cmp/affected.mjs +70 -0
  107. package/template/qa/lib/profiles/cmp/android-checks.mjs +56 -0
  108. package/template/qa/lib/profiles/cmp/artifacts.mjs +381 -0
  109. package/template/qa/lib/{component-stories.mjs → profiles/cmp/component-stories.mjs} +5 -2
  110. package/template/qa/lib/profiles/cmp/console-copy.mjs +29 -0
  111. package/template/qa/lib/profiles/cmp/declarations.mjs +181 -0
  112. package/{packages/harness/src/lib → template/qa/lib/profiles/cmp}/e2e-coverage.mjs +13 -3
  113. package/template/qa/lib/profiles/cmp/index.mjs +47 -0
  114. package/template/qa/lib/profiles/cmp/ladder.mjs +67 -0
  115. package/template/qa/lib/profiles/cmp/maestro.mjs +113 -0
  116. package/template/qa/lib/profiles/cmp/plants.mjs +95 -0
  117. package/{packages/harness/src/lib → template/qa/lib/profiles/cmp}/reachability.mjs +1 -1
  118. package/template/qa/lib/{steps-cmp.mjs → profiles/cmp/steps-cmp.mjs} +229 -27
  119. package/template/qa/lib/receipt-validate.mjs +84 -16
  120. package/template/qa/lib/spec-coverage.mjs +179 -92
  121. package/template/qa/lib/spec-model.mjs +262 -0
  122. package/template/qa/lib/step-cache.mjs +11 -2
  123. package/template/qa/lib/step-outcomes.mjs +84 -151
  124. package/template/qa/lib/walk.mjs +1 -1
  125. package/template/qa/preview-gallery.mjs +1 -1
  126. package/template/qa/receipt-check.mjs +80 -24
  127. package/template/qa/record-audit.mjs +1 -1
  128. package/template/qa/scaffold-feature.mjs +10 -2
  129. package/template/qa/verified-surface.json +17 -0
  130. package/template/qa/verify.mjs +222 -142
  131. package/template/qa/watch.mjs +146 -33
  132. /package/packages/harness/src/lib/{device-lease.mjs → profiles/cmp/device-lease.mjs} +0 -0
  133. /package/packages/harness/src/lib/{device-provider.mjs → profiles/cmp/device-provider.mjs} +0 -0
  134. /package/packages/harness/src/lib/{render.mjs → profiles/cmp/render.mjs} +0 -0
  135. /package/packages/harness/src/lib/{token-drift.mjs → profiles/cmp/token-drift.mjs} +0 -0
  136. /package/packages/harness/src/lib/{tree.mjs → profiles/cmp/tree.mjs} +0 -0
  137. /package/template/qa/lib/{device-lease.mjs → profiles/cmp/device-lease.mjs} +0 -0
  138. /package/template/qa/lib/{device-provider.mjs → profiles/cmp/device-provider.mjs} +0 -0
  139. /package/template/qa/lib/{render.mjs → profiles/cmp/render.mjs} +0 -0
  140. /package/template/qa/lib/{token-drift.mjs → profiles/cmp/token-drift.mjs} +0 -0
  141. /package/template/qa/lib/{tree.mjs → profiles/cmp/tree.mjs} +0 -0
@@ -22,6 +22,10 @@ import { fileURLToPath } from "node:url";
22
22
  import { computeInputsHash } from "./lib/inputs-hash.mjs";
23
23
  import { evaluateReceipt, readReceipt } from "./lib/receipt-validate.mjs";
24
24
  import { readHold, assessHold, describeHold, holdExplains } from "./lib/agent-hold.mjs";
25
+ import { LANE_MARKER_STALE_MS, laneMarkerPath } from "./lib/lane-markers.mjs";
26
+ import { resolveHarnessManifest } from "./lib/harness-manifest.mjs";
27
+ import { loadProfileSync } from "./lib/profile-loader.mjs";
28
+ import { evidenceLadderFor } from "./lib/evidence-ladder.mjs";
25
29
 
26
30
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
27
31
 
@@ -30,9 +34,8 @@ const asHook = args.includes("--hook");
30
34
  const asJson = args.includes("--json");
31
35
 
32
36
  // The lane's own in-flight marker (verify.mjs stamps it, rewriting it at each
33
- // step start with the step name and index). Mirrors qa/watch.mjs's bound.
34
- const LANE_MARKER_REL = ["composeApp", "build", ".cmp-lane-in-progress"];
35
- const LANE_MARKER_STALE_MS = 30 * 60 * 1000;
37
+ // step start with the step name and index) qa/lib/lane-markers.mjs, one
38
+ // path and one bound for every reader.
36
39
 
37
40
  /**
38
41
  * The full check running RIGHT NOW, or null. The gate must still refuse — a
@@ -44,7 +47,7 @@ const LANE_MARKER_STALE_MS = 30 * 60 * 1000;
44
47
  */
45
48
  function laneInFlight() {
46
49
  try {
47
- const p = path.join(ROOT, ...LANE_MARKER_REL);
50
+ const p = laneMarkerPath(ROOT);
48
51
  const st = fs.statSync(p);
49
52
  if (Date.now() - st.mtimeMs >= LANE_MARKER_STALE_MS) return null;
50
53
  // Content is a bonus, never a requirement: legacy markers hold "pid iso".
@@ -73,7 +76,7 @@ function readStdinJson() {
73
76
  }
74
77
 
75
78
  // The predicate itself lives in qa/lib/receipt-validate.mjs (vendored from the
76
- // @create-cmp/receipts package — one definition everywhere a receipt is judged); this
79
+ // prooflane-receipts package — one definition everywhere a receipt is judged); this
77
80
  // CLI only reads the receipt and frames the exit codes.
78
81
  function evaluate() {
79
82
  const receipt = readReceipt(ROOT);
@@ -109,28 +112,61 @@ function evaluate() {
109
112
  profile: receipt.profile,
110
113
  };
111
114
  }
112
- // The device tier must have RUN (2026-09-03). The lane boots a headless
113
- // emulator itself, so e2eSmoke/androidChecks only SKIP for two kinds of
114
- // reason: the project's own structure (no qa/e2e harness, no instrumented
115
- // sourceshonest, allowed) or the ENVIRONMENT (CMP_DEVICE=none, maestro
116
- // not installed, a lease held elsewhere, an ambiguous serial). The second
117
- // kind is a gap a human can close, and a change is not done while it stands.
118
- // Receipts predating `skipKind` are read by their reason text.
119
- const DEVICE_TIER = ["e2eSmoke", "androidChecks"];
120
- const envSkipped = (Array.isArray(receipt.steps) ? receipt.steps : []).filter(
121
- (s) =>
122
- s &&
123
- DEVICE_TIER.includes(s.name) &&
124
- s.verdict === "SKIP" &&
125
- (s.skipKind === "environment" ||
126
- (!s.skipKind && /no Android device|maestro CLI not installed|is held by|devices attached|CMP_DEVICE=none/.test(String(s.reason ?? "")))),
127
- );
115
+ // A step that COULD have run and did not is a gap a human can close, and a
116
+ // change is not done while it stands (2026-09-03). The lane boots its own
117
+ // device, so an environment SKIP no device, a tool not installed, a lease
118
+ // held elsewhere is never the project's honest structure; a `structure`
119
+ // SKIP (this project has no such tier at all) is, and is allowed.
120
+ //
121
+ // Stage 0 PR 6c: this used to test `["e2eSmoke", "androidChecks"]` by name
122
+ // and match Android reason text. On any other stack it therefore checked
123
+ // nothing the one gate that refuses "done" over a tier that never ran,
124
+ // silently inert. `skipKind` is the stack-free signal and every current
125
+ // receipt carries it, so the rule is now: ANY step that skipped for an
126
+ // environmental reason blocks done, whatever it is called.
127
+ //
128
+ // Receipts predating `skipKind` (0.19.0 and earlier) are still read by their
129
+ // reason text, and THAT list is the profile's read from the ladder's
130
+ // execution tier when the profile is loadable, and simply not applied when
131
+ // it is not. A legacy fallback that guessed would be worse than none.
132
+ //
133
+ // THE LADDER IS RESOLVED, NOT SPELLED. This used to read `profile.ladder`
134
+ // directly, which is one of the two places a profile may declare its rungs;
135
+ // qa/verify.mjs read the other, and nothing reconciled them. It goes
136
+ // through qa/lib/evidence-ladder.mjs now, so this reader and the lane can
137
+ // never be looking at different declarations of the same thing.
138
+ //
139
+ // NO PACK IS PASSED, AND THAT IS THE POINT. Getting the pack's spelling means
140
+ // calling `steps(ctx)`, and this file is the Stop hook: a done-gate that
141
+ // starts a lane to answer a legacy-compat question is not a gate, it is a
142
+ // second lane. So a profile that declares its ladder ONLY on the pack gives
143
+ // this reader nothing and gets no legacy fallback — the same honest silence
144
+ // as a profile with no ladder at all, and the reason the top-level spelling
145
+ // is the one `harness init` seeds and the one the resolver prefers.
146
+ const legacyPatterns = /no Android device|maestro CLI not installed|is held by|devices attached|CMP_DEVICE=none/;
147
+ let legacyNames = null;
148
+ try {
149
+ const manifest = resolveHarnessManifest(ROOT);
150
+ if (manifest.ok) {
151
+ const loaded = loadProfileSync(ROOT, manifest.manifest.profile);
152
+ const resolved = evidenceLadderFor(loaded.ok ? loaded.profile : null);
153
+ const ladder = resolved.ok ? resolved.ladder : null;
154
+ if (ladder && Array.isArray(ladder.deviceExecution)) legacyNames = ladder.deviceExecution;
155
+ }
156
+ } catch {
157
+ // The Stop hook never crashes over a legacy-compat lookup.
158
+ }
159
+ const envSkipped = (Array.isArray(receipt.steps) ? receipt.steps : []).filter((s) => {
160
+ if (!s || s.verdict !== "SKIP") return false;
161
+ if (s.skipKind) return s.skipKind === "environment";
162
+ return Boolean(legacyNames && legacyNames.includes(s.name) && legacyPatterns.test(String(s.reason ?? "")));
163
+ });
128
164
  if (envSkipped.length) {
129
165
  return {
130
166
  valid: false,
131
167
  reason:
132
- `the device tier did not run — ${envSkipped.map((s) => `${s.name}: ${String(s.reason ?? "").split("\n")[0]}`).join("; ")}. ` +
133
- "The lane boots a headless emulator itself (set CMP_AVD if it cannot choose one); fix the cause and run `node qa/verify.mjs` again before finishing",
168
+ `a tier did not run — ${envSkipped.map((s) => `${s.name}: ${String(s.reason ?? "").split("\n")[0]}`).join("; ")}. ` +
169
+ "Those steps skipped for an environmental reason, not because this project lacks them; fix the cause and run `node qa/verify.mjs` again before finishing",
134
170
  profile: receipt.profile,
135
171
  };
136
172
  }
@@ -152,9 +188,21 @@ function evaluate() {
152
188
  // Surface the receipt's evidence rung (the ladder — qa/lib/evidence-level.mjs)
153
189
  // alongside the verdict: the rung is the receipt's own derived field, read
154
190
  // verbatim, never recomputed here. Older receipts without it stay valid.
191
+ //
192
+ // AND THE PACK WITH IT, ALWAYS — the two are set in the same breath because
193
+ // NORTH-STAR.md §6.5 requires that every surface showing a rung shows the
194
+ // pack, and §8.9 is why: one pack's L2 and another pack's L2 are different
195
+ // claims. This CLI printed the rung alone, which left every reader of the
196
+ // done-gate exactly where that rule says they must not be. Only the pack's
197
+ // ID is carried: it is the part that carries the meaning, and `pack.version`
198
+ // on a receipt is today the harness lock's number rather than the profile's
199
+ // (docs/adr/0008), so a surface that leaned on it would be repeating a
200
+ // borrowed fact.
155
201
  const level = receipt.evidenceLevel;
156
202
  if (level && typeof level === "object" && typeof level.rung === "string") {
157
203
  result.evidenceLevel = level;
204
+ const id = receipt.pack && typeof receipt.pack.id === "string" ? receipt.pack.id.trim() : "";
205
+ result.packId = id || null;
158
206
  }
159
207
  return result;
160
208
  }
@@ -202,7 +250,15 @@ if (asHook) {
202
250
  process.exit(0);
203
251
  }
204
252
 
205
- const rungSuffix = result.evidenceLevel ? ` evidence ${result.evidenceLevel.rung} · ${result.evidenceLevel.name}` : "";
253
+ // The rung NEVER travels alone (see evaluate() above): a rung is a claim in one
254
+ // pack's vocabulary, so the pack is named beside it, and a receipt that names no
255
+ // pack is SAID to name none rather than being rendered as if the omission did
256
+ // not matter. A rung whose pack is unknown is comparable to nothing, and that is
257
+ // a fact about the evidence, not a formatting detail to be tidied away.
258
+ const rungSuffix = result.evidenceLevel
259
+ ? ` — evidence ${result.evidenceLevel.rung} · ${result.evidenceLevel.name}` +
260
+ (result.packId ? ` · pack ${result.packId}` : " · pack UNNAMED (this rung is comparable to nothing)")
261
+ : "";
206
262
 
207
263
  if (asJson) {
208
264
  console.log(JSON.stringify(result, null, 2));
@@ -29,7 +29,7 @@ const USAGE = `node qa/record-audit.mjs <subsystem> [--by <who-or-what>]
29
29
  Appends one audit record (subsystem, HEAD sha, ISO timestamp, recorder) to
30
30
  ${AUDITS_REL_PATH}. The verify lane's release profile reports which
31
31
  subsystems changed since their last record. Subsystems are derived from the
32
- tree: the immediate package directories under the androidMain Kotlin source
32
+ tree: the immediate package directories under the platform source set
33
33
  root ("${ROOT_SUBSYSTEM}" for files directly at the package root).
34
34
 
35
35
  --list print the derived subsystems and their audit status
@@ -181,10 +181,18 @@ try {
181
181
  } catch {
182
182
  approvalsLib = null;
183
183
  }
184
+ // The exemplar's shape and names are the cmp profile's (Stage 0 PR 5 —
185
+ // qa/lib/profiles/cmp/artifacts.mjs); this stamper is that profile's tool.
186
+ let profileLib = null;
187
+ try {
188
+ profileLib = await import("./lib/profiles/cmp/artifacts.mjs");
189
+ } catch {
190
+ profileLib = null;
191
+ }
184
192
 
185
193
  function resolveSourceNames() {
186
- if (approvalsLib && typeof approvalsLib.resolveExemplarNames === "function") {
187
- return approvalsLib.resolveExemplarNames(ROOT);
194
+ if (profileLib && typeof profileLib.resolveExemplarNames === "function") {
195
+ return profileLib.resolveExemplarNames(ROOT);
188
196
  }
189
197
  return { f: "home", F: "Home", F_UPPER: "HOME", E: "Item" };
190
198
  }
@@ -0,0 +1,17 @@
1
+ {
2
+ "surface": [
3
+ "composeApp",
4
+ "specs",
5
+ "qa",
6
+ "gradle/libs.versions.toml",
7
+ "build.gradle.kts",
8
+ "settings.gradle.kts",
9
+ "gradle.properties"
10
+ ],
11
+ "ignore": [
12
+ "composeApp/build",
13
+ ".gradle",
14
+ ".kotlin",
15
+ ".idea"
16
+ ]
17
+ }