create-cmp-cli 0.14.0 → 0.14.1
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/package.json +1 -1
- package/packages/harness/package.json +2 -2
- package/packages/harness/src/verify.mjs +15 -1
- package/src/commands/upgrade.mjs +20 -2
- package/src/lib/harness-upgrade.mjs +36 -5
- package/template/qa/verify.mjs +15 -1
- package/template/.gradle/8.11.1/checksums/checksums.lock +0 -0
- package/template/.gradle/8.11.1/fileChanges/last-build.bin +0 -0
- package/template/.gradle/8.11.1/fileHashes/fileHashes.lock +0 -0
- package/template/.gradle/8.11.1/gc.properties +0 -0
- package/template/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/template/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/template/.gradle/vcs-1/gc.properties +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-cmp-cli",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
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": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-cmp-harness",
|
|
3
|
-
"version": "0.14.
|
|
4
|
-
"description": "The create-cmp verify lane
|
|
3
|
+
"version": "0.14.1",
|
|
4
|
+
"description": "The create-cmp verify lane \u2014 the machine-owned harness code every stamped app carries byte-identical: evidence receipts, spec coverage, approvals, conformance reporting, golden trees, a11y, and the preview/inspector libs. Dependency-free ESM, vendored into each generated project so the lane runs offline with no install step, and content-hashed so a receipt can name the exact lane that issued it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/verify.mjs",
|
|
7
7
|
"exports": {
|
|
@@ -93,6 +93,14 @@ Flags:
|
|
|
93
93
|
with --profile ci (or release) it runs
|
|
94
94
|
inside the lane and lands on the receipt.
|
|
95
95
|
Never combinable with --fast
|
|
96
|
+
--no-journal skip the qa/flight-recorder.jsonl append.
|
|
97
|
+
qa/watch.mjs passes this on every
|
|
98
|
+
save-triggered run: journalling each save
|
|
99
|
+
would add hundreds of committed lines a day
|
|
100
|
+
and leave a permanently dirty tree inside
|
|
101
|
+
the loop the recorder exists to observe. The
|
|
102
|
+
gap is disclosed in the retrospective's own
|
|
103
|
+
output
|
|
96
104
|
--json print the receipt as JSON instead of the
|
|
97
105
|
human-readable step-by-step log
|
|
98
106
|
--help, -h print this usage and exit 0 without
|
|
@@ -116,7 +124,13 @@ if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
|
116
124
|
process.exit(0);
|
|
117
125
|
}
|
|
118
126
|
|
|
119
|
-
|
|
127
|
+
// Every flag this file CONSUMES must be listed here, or the strict check below
|
|
128
|
+
// rejects it. `--no-journal` was consumed but unlisted in 0.13.0, which meant
|
|
129
|
+
// qa/watch.mjs — whose spawn passes exactly these flags — exited 2 on every
|
|
130
|
+
// save without ever running the lane. test/verify-flags.test.mjs now pins
|
|
131
|
+
// consumed ⊆ recognized and watch's spawn ⊆ recognized so the class cannot
|
|
132
|
+
// recur.
|
|
133
|
+
const RECOGNIZED_FLAGS = new Set(["--profile", "--json", "--fast", "--determinism", "--no-journal"]);
|
|
120
134
|
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
121
135
|
const arg = rawArgs[i];
|
|
122
136
|
if (arg === "--profile") {
|
package/src/commands/upgrade.mjs
CHANGED
|
@@ -43,7 +43,8 @@ import { consent } from "../bootstrap/exec.mjs";
|
|
|
43
43
|
import { loadRegistry, latestSet, getSet } from "../lib/registry.mjs";
|
|
44
44
|
import { planUpgrade, BACKUP_SUFFIX } from "../lib/upgrade.mjs";
|
|
45
45
|
import { writeHarnessLock, checkHarnessIntegrity, describeIntegrity } from "../../packages/harness/src/lib/harness-lock.mjs";
|
|
46
|
-
import { LOCAL_PATCH_PATH } from "../lib/harness-upgrade.mjs";
|
|
46
|
+
import { LOCAL_PATCH_PATH, stampBaseWith } from "../lib/harness-upgrade.mjs";
|
|
47
|
+
import { buildTokenMap } from "../lib/tokens.mjs";
|
|
47
48
|
import {
|
|
48
49
|
planHarnessUpgrade,
|
|
49
50
|
applyHarnessPlan,
|
|
@@ -341,11 +342,28 @@ async function harnessPlanAndApply({ flags, record, projectDir, targetDir, tmpRo
|
|
|
341
342
|
templateDir: baseTemplateDir,
|
|
342
343
|
verify: false,
|
|
343
344
|
});
|
|
344
|
-
|
|
345
|
+
// Engines before 0.14.0 ran lane code through the token stamper, so an app
|
|
346
|
+
// stamped by one carries `Fuelled` where the base template says
|
|
347
|
+
// `__APP_NAME__`. Without this the migration would report the engine's own
|
|
348
|
+
// substitution as if the app had forked the lane.
|
|
349
|
+
const plan = planHarnessUpgrade({
|
|
350
|
+
baseDir,
|
|
351
|
+
newDir,
|
|
352
|
+
projectDir,
|
|
353
|
+
stampBase: stampBaseWith(buildTokenMap(configFromSpecRecord(record, projectDir))),
|
|
354
|
+
});
|
|
345
355
|
|
|
346
356
|
const anythingToDo = printHarnessReport(plan);
|
|
347
357
|
if (!anythingToDo) {
|
|
348
358
|
ok("Engine-owned files are fully up to date — nothing to apply.");
|
|
359
|
+
// Still advance the record. An app that is ALREADY at the new engine is as
|
|
360
|
+
// upgraded as it can be, and leaving engineVersion stale here would keep
|
|
361
|
+
// the very defect this write-back exists to fix: the next run would fetch
|
|
362
|
+
// an obsolete merge base and re-litigate changes that already landed. Not
|
|
363
|
+
// on a dry run — that promises to write nothing.
|
|
364
|
+
if (flags["dry-run"] !== true && writeBackEngineVersion(projectDir, currentVersion)) {
|
|
365
|
+
ok(`create-cmp.json engineVersion → ${colors.bold(currentVersion)}`);
|
|
366
|
+
}
|
|
349
367
|
return 0;
|
|
350
368
|
}
|
|
351
369
|
|
|
@@ -40,7 +40,7 @@ import path from "node:path";
|
|
|
40
40
|
import { spawnSync } from "node:child_process";
|
|
41
41
|
|
|
42
42
|
import { listFiles } from "./fsutil.mjs";
|
|
43
|
-
import { isBinaryPath } from "./tokens.mjs";
|
|
43
|
+
import { isBinaryPath, replaceTokens } from "./tokens.mjs";
|
|
44
44
|
import { BACKUP_SUFFIX } from "./upgrade.mjs";
|
|
45
45
|
import { isHarnessFile } from "../../packages/harness/src/lib/harness-region.mjs";
|
|
46
46
|
|
|
@@ -178,6 +178,23 @@ export function mergeThreeWay(theirs, base, next) {
|
|
|
178
178
|
* null when the path is in neither base nor new (app-authored — invisible)
|
|
179
179
|
*/
|
|
180
180
|
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A `stampBase` function for an app's recorded config — reproduces what a
|
|
184
|
+
* pre-0.14.0 engine would have written into that app's lane, so a file the
|
|
185
|
+
* stamper touched is not mistaken for a local edit. Binary-safe: content that
|
|
186
|
+
* does not round-trip through UTF-8 is returned unchanged.
|
|
187
|
+
* @param {Array<[string,string]>} tokenMap from buildTokenMap(config)
|
|
188
|
+
* @returns {(base: Buffer) => Buffer}
|
|
189
|
+
*/
|
|
190
|
+
export function stampBaseWith(tokenMap) {
|
|
191
|
+
return (base) => {
|
|
192
|
+
const text = base.toString("utf8");
|
|
193
|
+
if (!Buffer.from(text, "utf8").equals(base)) return base;
|
|
194
|
+
return Buffer.from(replaceTokens(text, tokenMap), "utf8");
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
181
198
|
/** Where a preserved local patch to machine-owned lane code is written. */
|
|
182
199
|
export const LOCAL_PATCH_PATH = "qa/harness-local.patch";
|
|
183
200
|
|
|
@@ -253,7 +270,7 @@ export function diffPatch(base, theirs, relPath) {
|
|
|
253
270
|
* @param {Function} [params.merge] injectable three-way merge (classifier only)
|
|
254
271
|
* @returns {{bucket:string, write:Buffer|null, sidecar:null, remove:boolean, patch?:string}|null}
|
|
255
272
|
*/
|
|
256
|
-
export function decideRegionFile({ relPath, base, next, theirs, merge = mergeThreeWay }) {
|
|
273
|
+
export function decideRegionFile({ relPath, base, next, theirs, merge = mergeThreeWay, stampBase = null }) {
|
|
257
274
|
const eq = (a, b) => a !== null && b !== null && a.equals(b);
|
|
258
275
|
const replace = (bucket, extra = {}) => ({
|
|
259
276
|
bucket,
|
|
@@ -274,6 +291,19 @@ export function decideRegionFile({ relPath, base, next, theirs, merge = mergeThr
|
|
|
274
291
|
if (base === null) return replace("region-patched", { patch: "" }); // no base to diff against
|
|
275
292
|
if (eq(theirs, base)) return replace("region-clean");
|
|
276
293
|
|
|
294
|
+
// The app's copy may be what its OWN engine produced rather than what the
|
|
295
|
+
// base tree holds, because engines before 0.14.0 ran lane code through the
|
|
296
|
+
// token stamper. Such an app carries `Fuelled` where the base template says
|
|
297
|
+
// `__APP_NAME__` — a difference the engine created, not the app. Asking
|
|
298
|
+
// "does this match the base as THIS app would have had it stamped?" is the
|
|
299
|
+
// accurate form of "did the app never touch it", and it needs no version
|
|
300
|
+
// comparison: for a 0.14.0+ base there is nothing left to stamp, so the
|
|
301
|
+
// check simply never fires.
|
|
302
|
+
if (stampBase !== null) {
|
|
303
|
+
const asStamped = stampBase(base);
|
|
304
|
+
if (eq(theirs, asStamped)) return replace("region-clean");
|
|
305
|
+
}
|
|
306
|
+
|
|
277
307
|
// The app edited lane code. Is that edit already carried by the new engine?
|
|
278
308
|
// A clean three-way merge landing exactly on `next` means the local change
|
|
279
309
|
// contributed nothing beyond it — the hand-mirror case.
|
|
@@ -283,10 +313,10 @@ export function decideRegionFile({ relPath, base, next, theirs, merge = mergeThr
|
|
|
283
313
|
return replace("region-patched", { patch: diffPatch(base, theirs, relPath) });
|
|
284
314
|
}
|
|
285
315
|
|
|
286
|
-
export function decideFile({ relPath, base, next, theirs, merge = mergeThreeWay }) {
|
|
316
|
+
export function decideFile({ relPath, base, next, theirs, merge = mergeThreeWay, stampBase = null }) {
|
|
287
317
|
// Machine-owned lane files answer a different question — see decideRegionFile.
|
|
288
318
|
if (isHarnessFile(relPath) && !(base === null && next === null)) {
|
|
289
|
-
return decideRegionFile({ relPath, base, next, theirs, merge });
|
|
319
|
+
return decideRegionFile({ relPath, base, next, theirs, merge, stampBase });
|
|
290
320
|
}
|
|
291
321
|
const none = (bucket) => ({ bucket, write: null, sidecar: null, remove: false });
|
|
292
322
|
const write = (bucket, content) => ({ bucket, write: content, sidecar: null, remove: false });
|
|
@@ -355,7 +385,7 @@ function toRel(root, abs) {
|
|
|
355
385
|
* sidecar?:Buffer|null, remove?:boolean}>,
|
|
356
386
|
* counts: Record<string, number>}}
|
|
357
387
|
*/
|
|
358
|
-
export function planHarnessUpgrade({ baseDir, newDir, projectDir, merge = mergeThreeWay }) {
|
|
388
|
+
export function planHarnessUpgrade({ baseDir, newDir, projectDir, merge = mergeThreeWay, stampBase = null }) {
|
|
359
389
|
const rels = new Set();
|
|
360
390
|
for (const f of listFiles(baseDir)) rels.add(toRel(baseDir, f));
|
|
361
391
|
for (const f of listFiles(newDir)) rels.add(toRel(newDir, f));
|
|
@@ -390,6 +420,7 @@ export function planHarnessUpgrade({ baseDir, newDir, projectDir, merge = mergeT
|
|
|
390
420
|
next: readIfPresent(newDir, relPath),
|
|
391
421
|
theirs: readIfPresent(projectDir, relPath),
|
|
392
422
|
merge,
|
|
423
|
+
stampBase,
|
|
393
424
|
});
|
|
394
425
|
if (decision === null) continue;
|
|
395
426
|
counts[decision.bucket] = (counts[decision.bucket] ?? 0) + 1;
|
package/template/qa/verify.mjs
CHANGED
|
@@ -93,6 +93,14 @@ Flags:
|
|
|
93
93
|
with --profile ci (or release) it runs
|
|
94
94
|
inside the lane and lands on the receipt.
|
|
95
95
|
Never combinable with --fast
|
|
96
|
+
--no-journal skip the qa/flight-recorder.jsonl append.
|
|
97
|
+
qa/watch.mjs passes this on every
|
|
98
|
+
save-triggered run: journalling each save
|
|
99
|
+
would add hundreds of committed lines a day
|
|
100
|
+
and leave a permanently dirty tree inside
|
|
101
|
+
the loop the recorder exists to observe. The
|
|
102
|
+
gap is disclosed in the retrospective's own
|
|
103
|
+
output
|
|
96
104
|
--json print the receipt as JSON instead of the
|
|
97
105
|
human-readable step-by-step log
|
|
98
106
|
--help, -h print this usage and exit 0 without
|
|
@@ -116,7 +124,13 @@ if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
|
116
124
|
process.exit(0);
|
|
117
125
|
}
|
|
118
126
|
|
|
119
|
-
|
|
127
|
+
// Every flag this file CONSUMES must be listed here, or the strict check below
|
|
128
|
+
// rejects it. `--no-journal` was consumed but unlisted in 0.13.0, which meant
|
|
129
|
+
// qa/watch.mjs — whose spawn passes exactly these flags — exited 2 on every
|
|
130
|
+
// save without ever running the lane. test/verify-flags.test.mjs now pins
|
|
131
|
+
// consumed ⊆ recognized and watch's spawn ⊆ recognized so the class cannot
|
|
132
|
+
// recur.
|
|
133
|
+
const RECOGNIZED_FLAGS = new Set(["--profile", "--json", "--fast", "--determinism", "--no-journal"]);
|
|
120
134
|
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
121
135
|
const arg = rawArgs[i];
|
|
122
136
|
if (arg === "--profile") {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
File without changes
|