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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// profiles/cmp — the Compose Multiplatform stack profile. Mobile, by definition.
|
|
2
|
+
//
|
|
3
|
+
// This is the first profile and, for now, the only one — and it is loaded the
|
|
4
|
+
// same way every other profile ever will be: by id, from qa/harness-manifest.json,
|
|
5
|
+
// through qa/lib/profile-loader.mjs. The runner does not know this file exists.
|
|
6
|
+
//
|
|
7
|
+
// Stage 0 (docs/proposals/AGNOSTIC-HARNESS-ARCHITECTURE.md §11.3): PR 2 made
|
|
8
|
+
// this module re-export the step pack; PR 4 added `layout` and `tiers`
|
|
9
|
+
// (./declarations.mjs) — the paths and tier names the spec scanner used to
|
|
10
|
+
// hardcode. The other declarations — artifacts, architecture, ladder, review,
|
|
11
|
+
// plants, providers (§4.2) — still live where they always did and move here
|
|
12
|
+
// in the PRs that follow, one at a time, with the mobile fleet-check green
|
|
13
|
+
// after each. A profile that claimed all nine today would be claiming code it
|
|
14
|
+
// does not own yet.
|
|
15
|
+
|
|
16
|
+
import { createCmpSteps } from "./steps-cmp.mjs";
|
|
17
|
+
|
|
18
|
+
export const id = "cmp";
|
|
19
|
+
export const protocol = 1;
|
|
20
|
+
|
|
21
|
+
/** Where specs, sources, tests and flows live; which tiers observe what. */
|
|
22
|
+
export { layout, tiers, grammar, reports, detect, tools } from "./declarations.mjs";
|
|
23
|
+
export { copy as console } from "./console-copy.mjs";
|
|
24
|
+
|
|
25
|
+
/** What a human signs, in definition order; and whether this tree may record signatures at all. */
|
|
26
|
+
export { artifacts, governable } from "./artifacts.mjs";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The evidence ladder: which of THIS profile's steps earn which rung, and what
|
|
30
|
+
* the rungs are called. Exported at the top level (not only on the pack's
|
|
31
|
+
* return) so a reader that must not start a lane — the Stop hook deciding
|
|
32
|
+
* whether a tier that could have run did — can ask without instantiating
|
|
33
|
+
* anything. AGNOSTIC-HARNESS-ARCHITECTURE.md §4.2 #6.
|
|
34
|
+
*/
|
|
35
|
+
export { CMP_LADDER as ladder } from "./ladder.mjs";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The source this profile's Rule 0 instrument plants — a citation must sit on
|
|
39
|
+
* a test, so the instrument has to write one in this stack's language
|
|
40
|
+
* (AGNOSTIC-HARNESS-ARCHITECTURE.md §4.2 #8, §7.2).
|
|
41
|
+
*/
|
|
42
|
+
export { plants } from "./plants.mjs";
|
|
43
|
+
|
|
44
|
+
/** The step pack — today's createCmpSteps, unchanged. */
|
|
45
|
+
export function steps(ctx) {
|
|
46
|
+
return createCmpSteps(ctx);
|
|
47
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// profiles/cmp/ladder.mjs — the Compose Multiplatform profile's evidence
|
|
2
|
+
// ladder: which of ITS steps earn which rung. Mobile's, by definition.
|
|
3
|
+
//
|
|
4
|
+
// L0 "scaffold" — the scaffold profile's checks passed (stamp-time green
|
|
5
|
+
// build: build + unit tests + the pure-Node gates).
|
|
6
|
+
// L1 "desktop" — full static + JVM evidence: everything L0 proves PLUS
|
|
7
|
+
// conformance, golden trees, a11y, and the release COMPILE
|
|
8
|
+
// (releaseBuild) — a green lane with no on-device step run.
|
|
9
|
+
// L2 "device" — L1 plus at least one on-device EXECUTION step PASSed
|
|
10
|
+
// (e2eSmoke, androidChecks, or the live tokenDrift tier).
|
|
11
|
+
// L3 "release" — L2 plus releaseSmoke PASSed (the release APK installed
|
|
12
|
+
// and driven on a device).
|
|
13
|
+
//
|
|
14
|
+
// THE LADDER IS THE PROFILE'S, NOT THE SPINE'S (2026-09-03): vendored into a
|
|
15
|
+
// Kotlin backend, these names graded its strongest run — detekt, Konsist,
|
|
16
|
+
// gitleaks — as L0 "scaffold" and made L1 unreachable by construction. A
|
|
17
|
+
// fixed-amount understatement is not conservative, it is wrong, and receipts
|
|
18
|
+
// are where labels get quoted. So the spine's rung derivation
|
|
19
|
+
// (qa/lib/evidence-level.mjs) takes a ladder from the profile's step pack
|
|
20
|
+
// (`evidenceLadder` on steps()' return) and defaults to NOTHING: a profile that
|
|
21
|
+
// declares no ladder earns no rung, which is the honest grade for a ladder
|
|
22
|
+
// nobody has calibrated. Stage 0 PR 3 moved this constant out of the spine so
|
|
23
|
+
// the spine carries no Compose step names at all.
|
|
24
|
+
//
|
|
25
|
+
// Every field is a list of step names except `release`, one name. `names`
|
|
26
|
+
// maps rung → label. A `cmp` L2 and another profile's L2 are different claims
|
|
27
|
+
// (receipt.pack says which); see AGNOSTIC-HARNESS-ARCHITECTURE.md §8.2.
|
|
28
|
+
|
|
29
|
+
/** The scaffold profile's step set (steps-cmp.mjs stepsForProfile.scaffold). */
|
|
30
|
+
const SCAFFOLD_CORE = [
|
|
31
|
+
"specCoverage",
|
|
32
|
+
"approvals",
|
|
33
|
+
"componentStories",
|
|
34
|
+
"reachability",
|
|
35
|
+
"e2eCoverage",
|
|
36
|
+
"archDoc",
|
|
37
|
+
"schemaHistory",
|
|
38
|
+
"build",
|
|
39
|
+
"unitTests",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/** Steps every PASS must carry to claim even L0 — they run in every profile and never SKIP. */
|
|
43
|
+
const L0_REQUIRED = ["build", "unitTests"];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The steps that distinguish full desktop evidence (L1) from the scaffold
|
|
47
|
+
* checks. None of these can SKIP — they PASS or FAIL — so "PASSed" is exactly
|
|
48
|
+
* "ran green".
|
|
49
|
+
*/
|
|
50
|
+
const L1_REQUIRED = ["releaseBuild", "conformance", "goldenTrees", "a11y"];
|
|
51
|
+
|
|
52
|
+
/** On-device EXECUTION steps — the only steps that can earn L2. */
|
|
53
|
+
const DEVICE_EXECUTION = ["e2eSmoke", "tokenDrift", "androidChecks"];
|
|
54
|
+
|
|
55
|
+
/** The one step that can lift L2 to L3. */
|
|
56
|
+
const RELEASE_EXECUTION = "releaseSmoke";
|
|
57
|
+
|
|
58
|
+
const RUNG_NAMES = { L0: "scaffold", L1: "desktop", L2: "device", L3: "release" };
|
|
59
|
+
|
|
60
|
+
export const CMP_LADDER = Object.freeze({
|
|
61
|
+
scaffoldCore: Object.freeze(SCAFFOLD_CORE),
|
|
62
|
+
l0Required: Object.freeze(L0_REQUIRED),
|
|
63
|
+
l1Required: Object.freeze(L1_REQUIRED),
|
|
64
|
+
deviceExecution: Object.freeze(DEVICE_EXECUTION),
|
|
65
|
+
release: RELEASE_EXECUTION,
|
|
66
|
+
names: Object.freeze(RUNG_NAMES),
|
|
67
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// profiles/cmp/maestro.mjs — the Maestro directory run's verdict, and the
|
|
2
|
+
// device-log sweep scoped to the app under test. Mobile's, by definition.
|
|
3
|
+
//
|
|
4
|
+
// These three lived in qa/lib/step-outcomes.mjs beside the neutral verdict
|
|
5
|
+
// helpers (StepTimeout, stepErrorResult, spawnTimedOut). They are not neutral:
|
|
6
|
+
// a JUnit report from `maestro test <dir>` and an `adb logcat` crash buffer are
|
|
7
|
+
// facts about an Android device, and a backend profile has neither. Stage 0
|
|
8
|
+
// PR 3 moves them here so step-outcomes.mjs is the spine's again. Bodies are
|
|
9
|
+
// verbatim — the tests that pinned them moved with them.
|
|
10
|
+
|
|
11
|
+
// ── Maestro directory run → per-flow outcome ────────────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* Parse Maestro's JUnit report (`maestro test <dir> --format junit --output f`)
|
|
14
|
+
* into one row per flow. Tolerant: a report that is missing or unparsable
|
|
15
|
+
* returns null and the caller falls back to the exit code — never a fabricated
|
|
16
|
+
* per-flow list.
|
|
17
|
+
* @param {string|null} xml
|
|
18
|
+
* @returns {Array<{flow: string, ok: boolean, message?: string}>|null}
|
|
19
|
+
*/
|
|
20
|
+
export function parseMaestroJunit(xml) {
|
|
21
|
+
if (typeof xml !== "string" || !/<testcase\b/.test(xml)) return null;
|
|
22
|
+
const rows = [];
|
|
23
|
+
const caseRe = /<testcase\b([^>]*?)(?:\/>|>([\s\S]*?)<\/testcase>)/g;
|
|
24
|
+
let m;
|
|
25
|
+
while ((m = caseRe.exec(xml))) {
|
|
26
|
+
const attrs = m[1] || "";
|
|
27
|
+
const body = m[2] || "";
|
|
28
|
+
const name = (attrs.match(/\bname="([^"]*)"/) || [])[1] ?? (attrs.match(/\bid="([^"]*)"/) || [])[1] ?? "?";
|
|
29
|
+
const status = (attrs.match(/\bstatus="([^"]*)"/) || [])[1];
|
|
30
|
+
const failed = /<(failure|error)\b/.test(body) || (status && !/^(SUCCESS|PASSED?|OK)$/i.test(status));
|
|
31
|
+
const message = failed ? ((body.match(/<(?:failure|error)\b[^>]*message="([^"]*)"/) || [])[1] ?? body.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 300)) : undefined;
|
|
32
|
+
rows.push(failed ? { flow: name, ok: false, message } : { flow: name, ok: true });
|
|
33
|
+
}
|
|
34
|
+
return rows.length ? rows : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The e2e step's verdict from the Maestro run: exit code + per-flow report +
|
|
39
|
+
* the list of flows the directory held. FAIL names every failing flow; a run
|
|
40
|
+
* whose report lists fewer flows than the directory holds is ERROR — the lane
|
|
41
|
+
* cannot claim flows it has no row for.
|
|
42
|
+
* @param {{ok: boolean, out: string}} res
|
|
43
|
+
* @param {Array<{flow: string, ok: boolean, message?: string}>|null} perFlow
|
|
44
|
+
* @param {string[]} flows root-relative flow files the directory holds
|
|
45
|
+
* @returns {{verdict: "PASS"|"FAIL"|"ERROR", reason?: string, details: object}}
|
|
46
|
+
*/
|
|
47
|
+
export function maestroOutcome(res, perFlow, flows) {
|
|
48
|
+
const details = { flows, results: perFlow ?? undefined };
|
|
49
|
+
if (perFlow) {
|
|
50
|
+
const failed = perFlow.filter((r) => !r.ok);
|
|
51
|
+
if (failed.length) {
|
|
52
|
+
return {
|
|
53
|
+
verdict: "FAIL",
|
|
54
|
+
reason: `Maestro: ${failed.length} of ${perFlow.length} flow${perFlow.length === 1 ? "" : "s"} failed — ${failed.map((r) => `${r.flow}${r.message ? ` (${r.message.split("\n")[0].slice(0, 120)})` : ""}`).join("; ")}`,
|
|
55
|
+
details,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (perFlow.length < flows.length) {
|
|
59
|
+
return {
|
|
60
|
+
verdict: "ERROR",
|
|
61
|
+
reason: `Maestro reported ${perFlow.length} flow${perFlow.length === 1 ? "" : "s"} but ${E2E_DIR_LABEL} holds ${flows.length} — the run did not cover every flow, so no verdict can be claimed for the rest`,
|
|
62
|
+
details,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
return { verdict: "FAIL", reason: `Maestro exited non-zero with every flow reported green — treat as a run failure:\n${String(res.out).split("\n").slice(-10).join("\n")}`, details };
|
|
67
|
+
}
|
|
68
|
+
return { verdict: "PASS", details };
|
|
69
|
+
}
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
return { verdict: "FAIL", reason: `Maestro failed (no per-flow report was written):\n${String(res.out).split("\n").slice(-15).join("\n")}`, details };
|
|
72
|
+
}
|
|
73
|
+
return { verdict: "PASS", reason: "Maestro exited 0 but wrote no per-flow report — verdict from the exit code only", details };
|
|
74
|
+
}
|
|
75
|
+
const E2E_DIR_LABEL = "qa/e2e";
|
|
76
|
+
|
|
77
|
+
// ── Device-log incidents, scoped to the app under test ──────────────────────
|
|
78
|
+
/**
|
|
79
|
+
* ANR / fatal-exception lines from `adb logcat -d -b system,crash,main` that
|
|
80
|
+
* belong to one of `appIds` (an app's process may be `pkg` or `pkg:remote`).
|
|
81
|
+
* An emulator carries other apps — on 2026-09-03 the first self-booted lane
|
|
82
|
+
* went red on `ANR in com.karel.bratometer` while driving com.fleet.check —
|
|
83
|
+
* so an incident in another package is NOT this lane's failure. With no
|
|
84
|
+
* appIds known the sweep stays unscoped (every incident counts) and says so.
|
|
85
|
+
* @param {string} log
|
|
86
|
+
* @param {string[]} appIds
|
|
87
|
+
* @returns {{lines: string[], scoped: boolean}}
|
|
88
|
+
*/
|
|
89
|
+
export function deviceLogIncidents(log, appIds = []) {
|
|
90
|
+
const lines = String(log ?? "").split("\n");
|
|
91
|
+
const ids = appIds.filter((id) => typeof id === "string" && id.trim() && id !== "__PACKAGE__");
|
|
92
|
+
const scoped = ids.length > 0;
|
|
93
|
+
const ours = (proc) => !scoped || ids.some((id) => proc === id || proc.startsWith(`${id}:`));
|
|
94
|
+
const out = [];
|
|
95
|
+
lines.forEach((line, i) => {
|
|
96
|
+
const anr = line.match(/ANR in (\S+?)(?:\s|,|$)/);
|
|
97
|
+
if (anr) {
|
|
98
|
+
if (ours(anr[1].replace(/[,)]$/, ""))) out.push(line);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (/FATAL EXCEPTION/i.test(line)) {
|
|
102
|
+
if (!scoped) {
|
|
103
|
+
out.push(line);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// The crash buffer names the process on one of the next few lines.
|
|
107
|
+
const window = lines.slice(i, i + 8).join("\n");
|
|
108
|
+
const proc = window.match(/Process:\s*(\S+?)(?:,|\s|$)/);
|
|
109
|
+
if (proc && ours(proc[1])) out.push(line);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return { lines: out, scoped };
|
|
113
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// profiles/cmp/plants.mjs — the SOURCE this profile's Rule 0 instrument plants
|
|
2
|
+
// when it proves specCoverage can still read a citation. Mobile's, by
|
|
3
|
+
// definition (docs/NORTH-STAR.md §6; GATE-RULES.md Rule 0/Rule 1).
|
|
4
|
+
//
|
|
5
|
+
// Stage 0 PR 7. Most of qa/framework-check.mjs is already stack-free: which
|
|
6
|
+
// plants a tree can support is derived from the tree through the spec model
|
|
7
|
+
// (PR 4), the clause grammar and the citation marker are the core scanner's,
|
|
8
|
+
// and stripping a flow's citations uses the core's own tag regex. What is NOT
|
|
9
|
+
// stack-free is the file a planted citation has to live in: a citation must sit
|
|
10
|
+
// on a test, so the instrument has to write a test — in this stack's language,
|
|
11
|
+
// with this stack's test-declaration syntax, at a path this stack compiles.
|
|
12
|
+
//
|
|
13
|
+
// Two plants need it, and they are the two that matter most, because each
|
|
14
|
+
// closes an escape that turns a red gate green with no assertions:
|
|
15
|
+
//
|
|
16
|
+
// unbound a `// SPEC:` tag on a CLASS with no test inside the binding
|
|
17
|
+
// window. This is payment-blueprint's real drift: the tag sat on
|
|
18
|
+
// `class PaymentWorkerTest`, three properties above a genuine
|
|
19
|
+
// @Test, and vouched for the whole file. The class body here is
|
|
20
|
+
// deliberately longer than BINDING_WINDOW so a real test cannot
|
|
21
|
+
// wander into range and launder it.
|
|
22
|
+
// tierUnmet a clause only the device tier can observe, cited from a
|
|
23
|
+
// host-tier test that compiles and runs and can never see it.
|
|
24
|
+
//
|
|
25
|
+
// A profile that declares no plants ships without those two, and the
|
|
26
|
+
// instrument says so per plant rather than reporting a quieter green — the
|
|
27
|
+
// §5.2 rule: no plants, no badge.
|
|
28
|
+
|
|
29
|
+
import { PLANT_KINDS } from "../../framework-check.mjs";
|
|
30
|
+
|
|
31
|
+
/** The file a planted citation lives in, relative to the test directory the instrument found. */
|
|
32
|
+
export const testFileBasename = "CmpFrameworkCheckPlanted.kt";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A citation on a type declaration with no test under it. The body must be
|
|
36
|
+
* longer than the core's BINDING_WINDOW so proximity alone cannot bind it.
|
|
37
|
+
* @param {string} clause the planted clause id
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
export function unboundCitationSource(clause) {
|
|
41
|
+
return `// Planted by qa/framework-check.mjs — reverted automatically.\n\n// SPEC: ${clause}\nclass CmpFrameworkCheckPlanted {\n val a = 1\n val b = 2\n val c = 3\n val d = 4\n val e = 5\n val f = 6\n fun helper() {}\n}\n`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A real, compiling, running host-tier test that cites a clause only the
|
|
46
|
+
* device tier could observe.
|
|
47
|
+
* @param {string} clause the planted clause id
|
|
48
|
+
* @returns {string}
|
|
49
|
+
*/
|
|
50
|
+
export function tierUnmetCitationSource(clause) {
|
|
51
|
+
return `// Planted by qa/framework-check.mjs — reverted automatically.\n\nimport kotlin.test.Test\n\nclass CmpFrameworkCheckPlanted {\n // SPEC: ${clause}\n @Test\n fun planted() {}\n}\n`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The tier requirement the tierUnmet plant declares on its clause. It must be
|
|
56
|
+
* one this profile's `tiers.satisfying` defines and that a host-tier test
|
|
57
|
+
* cannot satisfy — otherwise the plant would not plant anything.
|
|
58
|
+
*/
|
|
59
|
+
export const unmeetableTier = "e2e";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* WHICH OF THIS PACK'S STEPS CATCHES EACH KIND OF PLANTED VIOLATION.
|
|
63
|
+
*
|
|
64
|
+
* `specCoverage` and `e2eCoverage` are OUR words. They used to be written into
|
|
65
|
+
* the core's plant selector as literals and asserted against every profile
|
|
66
|
+
* there will ever be — the same defect NORTH-STAR §9.1 records for
|
|
67
|
+
* `harnessIntegrity`, in the same file, one fix later. A pack spelling its
|
|
68
|
+
* coverage step `spec_coverage` had this instrument stop at the first plant
|
|
69
|
+
* with "specCoverage: no row — the guard did not FAIL BY NAME", about a gate
|
|
70
|
+
* that had just failed by name on the row beside it, and the two floor plants
|
|
71
|
+
* behind it never ran at all.
|
|
72
|
+
*
|
|
73
|
+
* So the names live here, where they are true, keyed by the CORE's plant kinds
|
|
74
|
+
* so neither side has to learn the other's vocabulary. A pack that omits this
|
|
75
|
+
* loses nothing it had: its plants assert over the lane instead of over one row
|
|
76
|
+
* (lib/framework-check.mjs `observingStep`). Declaring is how a pack asks for
|
|
77
|
+
* the sharper assertion — that THIS gate, not merely some gate, is the one that
|
|
78
|
+
* caught it.
|
|
79
|
+
*/
|
|
80
|
+
export const observedBy = Object.freeze({
|
|
81
|
+
[PLANT_KINDS.ORPHANED_CITATION]: "specCoverage",
|
|
82
|
+
[PLANT_KINDS.UNBOUND_CITATION]: "specCoverage",
|
|
83
|
+
[PLANT_KINDS.TIER_UNMET]: "specCoverage",
|
|
84
|
+
[PLANT_KINDS.FEATURE_WITHOUT_FLOW]: "e2eCoverage",
|
|
85
|
+
[PLANT_KINDS.NESTED_FLOW]: "e2eCoverage",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/** What the instrument reads off the profile. */
|
|
89
|
+
export const plants = Object.freeze({
|
|
90
|
+
testFileBasename,
|
|
91
|
+
unboundCitationSource,
|
|
92
|
+
tierUnmetCitationSource,
|
|
93
|
+
unmeetableTier,
|
|
94
|
+
observedBy,
|
|
95
|
+
});
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
import fs from "node:fs";
|
|
35
35
|
import path from "node:path";
|
|
36
36
|
|
|
37
|
-
import { parseFeatureBlock } from "
|
|
37
|
+
import { parseFeatureBlock } from "../../feature-brief.mjs";
|
|
38
38
|
|
|
39
39
|
// Mirrors component-stories.mjs's walkDirs: every directory named `wanted`
|
|
40
40
|
// anywhere under `root`, recursive.
|