create-cmp-cli 0.14.0 → 0.15.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/README.md +51 -353
- package/bin/create-cmp.mjs +19 -3
- package/llms.txt +3 -3
- package/options.schema.json +4 -0
- package/package.json +2 -2
- package/packages/harness/package.json +11 -3
- package/packages/harness/src/lib/harness-lock.mjs +2 -2
- package/packages/harness/src/lib/inputs-hash.mjs +1 -1
- package/packages/harness/src/lib/receipt-validate.mjs +1 -1
- package/packages/harness/src/receipt-check.mjs +1 -1
- package/packages/harness/src/verify.mjs +15 -1
- package/packages/receipts/package.json +11 -3
- package/packages/receipts/src/index.mjs +1 -1
- package/packages/receipts/src/inputs-hash.mjs +1 -1
- package/packages/receipts/src/receipt-validate.mjs +1 -1
- package/src/commands/attach.mjs +250 -0
- package/src/commands/create.mjs +13 -2
- package/src/commands/harden.mjs +263 -0
- package/src/commands/upgrade.mjs +20 -2
- package/src/lib/adr-seed.mjs +27 -0
- package/src/lib/harness-upgrade.mjs +40 -5
- package/src/lib/hooks.mjs +140 -0
- package/src/lib/minimal.mjs +130 -0
- package/src/lib/toggle.mjs +4 -1
- package/src/lib/verify.mjs +6 -1
- package/src/scaffold.mjs +18 -1
- package/template/.github/workflows/verify.yml +15 -0
- package/template/AGENTS.md +57 -8
- package/template/CLAUDE.md +49 -0
- package/template/CONTRIBUTING.md +13 -0
- package/template/README.md +33 -0
- package/template/docs/ARCHITECTURE.md +18 -1
- package/template/docs/TESTING.md +10 -0
- package/template/manifest.json +13 -0
- package/template/qa/lib/harness-lock.mjs +2 -2
- package/template/qa/lib/inputs-hash.mjs +1 -1
- package/template/qa/lib/receipt-validate.mjs +1 -1
- package/template/qa/receipt-check.mjs +1 -1
- package/template/qa/verify.mjs +15 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// there is exactly one definition of the surface and the algorithm.
|
|
6
6
|
//
|
|
7
7
|
// SINGLE SOURCE OF TRUTH: packages/receipts/src/inputs-hash.mjs in the
|
|
8
|
-
// create-cmp repo (the
|
|
8
|
+
// create-cmp repo (the `@create-cmp/receipts` package). The copy in a generated
|
|
9
9
|
// project's qa/lib/ is vendored byte-identical at scaffold time and pinned by
|
|
10
10
|
// test/receipts-parity.test.mjs — edit the package source, then run
|
|
11
11
|
// `node scripts/sync-harness.mjs`.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// tarball rather than the working tree.
|
|
7
7
|
//
|
|
8
8
|
// SINGLE SOURCE OF TRUTH: packages/receipts/src/receipt-validate.mjs in the
|
|
9
|
-
// create-cmp repo (the
|
|
9
|
+
// create-cmp repo (the `@create-cmp/receipts` package). The copy in a generated
|
|
10
10
|
// project's qa/lib/ is vendored byte-identical at scaffold time and pinned by
|
|
11
11
|
// test/receipts-parity.test.mjs — edit the package source, then run
|
|
12
12
|
// `node scripts/sync-harness.mjs`.
|
|
@@ -39,7 +39,7 @@ function readStdinJson() {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// The predicate itself lives in qa/lib/receipt-validate.mjs (vendored from the
|
|
42
|
-
// cmp
|
|
42
|
+
// @create-cmp/receipts package — one definition everywhere a receipt is judged); this
|
|
43
43
|
// CLI only reads the receipt and frames the exit codes.
|
|
44
44
|
function evaluate() {
|
|
45
45
|
const receipt = readReceipt(ROOT);
|
|
@@ -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") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "cmp
|
|
2
|
+
"name": "@create-cmp/receipts",
|
|
3
3
|
"version": "0.1.0",
|
|
4
|
-
"description": "Validate create-cmp evidence receipts: inputs-hash recompute, receipt-vs-tree binding, freshness, and execution-plausibility. Dependency-free ESM
|
|
4
|
+
"description": "Validate create-cmp evidence receipts: inputs-hash recompute, receipt-vs-tree binding, freshness, and execution-plausibility. Dependency-free ESM \u2014 the single source of truth vendored into every generated project's qa/lib/ and consumed by the hosted receipt check.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.mjs",
|
|
7
7
|
"exports": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"test": "node --test"
|
|
17
|
+
"test": "node --test",
|
|
18
|
+
"prepublishOnly": "npm test"
|
|
18
19
|
},
|
|
19
20
|
"engines": {
|
|
20
21
|
"node": ">=18"
|
|
@@ -32,5 +33,12 @@
|
|
|
32
33
|
"type": "git",
|
|
33
34
|
"url": "git+https://github.com/kvdm-co-pilot/create-cmp.git",
|
|
34
35
|
"directory": "packages/receipts"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/kvdm-co-pilot/create-cmp/tree/main/packages/receipts#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/kvdm-co-pilot/create-cmp/issues"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
35
43
|
}
|
|
36
44
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// cmp
|
|
1
|
+
// @create-cmp/receipts — validate create-cmp evidence receipts.
|
|
2
2
|
// Single source of truth for the inputs-hash algorithm and the receipt
|
|
3
3
|
// predicate; vendored byte-identical into every generated project's qa/lib/
|
|
4
4
|
// and consumed by hosted validators. Dependency-free ESM.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// there is exactly one definition of the surface and the algorithm.
|
|
6
6
|
//
|
|
7
7
|
// SINGLE SOURCE OF TRUTH: packages/receipts/src/inputs-hash.mjs in the
|
|
8
|
-
// create-cmp repo (the
|
|
8
|
+
// create-cmp repo (the `@create-cmp/receipts` package). The copy in a generated
|
|
9
9
|
// project's qa/lib/ is vendored byte-identical at scaffold time and pinned by
|
|
10
10
|
// test/receipts-parity.test.mjs — edit the package source, then run
|
|
11
11
|
// `node scripts/sync-harness.mjs`.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// tarball rather than the working tree.
|
|
7
7
|
//
|
|
8
8
|
// SINGLE SOURCE OF TRUTH: packages/receipts/src/receipt-validate.mjs in the
|
|
9
|
-
// create-cmp repo (the
|
|
9
|
+
// create-cmp repo (the `@create-cmp/receipts` package). The copy in a generated
|
|
10
10
|
// project's qa/lib/ is vendored byte-identical at scaffold time and pinned by
|
|
11
11
|
// test/receipts-parity.test.mjs — edit the package source, then run
|
|
12
12
|
// `node scripts/sync-harness.mjs`.
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// `create-cmp attach` — M0a: the agent contract for an EXISTING Compose/KMP
|
|
2
|
+
// repo that create-cmp never scaffolded (docs/features/attach-mode.md is the
|
|
3
|
+
// design of record; LADDER §R5).
|
|
4
|
+
//
|
|
5
|
+
// The honesty constraint IS the design: a foreign repo carries none of the
|
|
6
|
+
// stamped machinery our discovery surfaces normally point at, so attach
|
|
7
|
+
// writes ONLY surfaces that are true in any Compose/KMP repo — the engine's
|
|
8
|
+
// own doctor/upgrade at the toolchain walls, and an advisory SessionStart
|
|
9
|
+
// context that says plainly what is and is not wired here. Previews, the
|
|
10
|
+
// lane, and enforcement are named as NOT wired (M0b's staged scope), never
|
|
11
|
+
// faked. Files are never clobbered: an existing differing file keeps its
|
|
12
|
+
// bytes and our content lands beside it as `*.cmp-new` (the same sidecar
|
|
13
|
+
// convention as `upgrade --harness` and `harden`).
|
|
14
|
+
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
|
|
18
|
+
import { colors, ok, warn, fail } from "../lib/log.mjs";
|
|
19
|
+
import { consent } from "../bootstrap/exec.mjs";
|
|
20
|
+
import { sessionStartCommand } from "../lib/hooks.mjs";
|
|
21
|
+
import { SIDECAR_SUFFIX } from "../lib/harness-upgrade.mjs";
|
|
22
|
+
|
|
23
|
+
const COMPOSE_SIGNALS = [
|
|
24
|
+
"org.jetbrains.compose",
|
|
25
|
+
'kotlin("multiplatform")',
|
|
26
|
+
"org.jetbrains.kotlin.multiplatform",
|
|
27
|
+
"org.jetbrains.kotlin.plugin.compose",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/** SessionStart context for an attached repo. No apostrophes (shell-quoted). */
|
|
31
|
+
export const ATTACH_SESSION_CONTEXT =
|
|
32
|
+
"This Compose/KMP repo carries the create-cmp attach surfaces: AGENTS.md maps " +
|
|
33
|
+
"toolchain walls to zero-consent commands (doctor diagnoses machine AND project, " +
|
|
34
|
+
"upgrade diffs against a proven-green version set). No verify lane, previews, or " +
|
|
35
|
+
"inspector are wired here — do not assume create-cmp scaffold machinery exists in " +
|
|
36
|
+
"this repo. A new app scaffolded with npx create-cmp-cli would carry all of it.";
|
|
37
|
+
|
|
38
|
+
/** The attach rendering of the symptom table — only rows true in ANY Compose/KMP repo. */
|
|
39
|
+
export function attachAgentsMd() {
|
|
40
|
+
return `# Agent instructions
|
|
41
|
+
|
|
42
|
+
This is an existing Compose/KMP repository with the create-cmp **attach surfaces**
|
|
43
|
+
installed: discovery for the walls every KMP build eventually hits. It was NOT
|
|
44
|
+
scaffolded by create-cmp — none of the generated-project machinery (verify lane,
|
|
45
|
+
headless previews, live inspector, generators) is wired here.
|
|
46
|
+
|
|
47
|
+
## Stuck? Symptom → command
|
|
48
|
+
|
|
49
|
+
Both commands run from the repo root with nothing to install — \`npx\` fetches on demand.
|
|
50
|
+
|
|
51
|
+
| Symptom | Run |
|
|
52
|
+
|---|---|
|
|
53
|
+
| Build broken, toolchain suspect | \`npx create-cmp-cli doctor --fix\` — diagnoses machine AND project (kotlin↔ksp lockstep, catalog drift); asks before any repair |
|
|
54
|
+
| Dependency versions stale or mismatched | \`npx create-cmp-cli upgrade --dry-run\` — diff against the next proven-green set before touching anything |
|
|
55
|
+
|
|
56
|
+
Famous build failures (kotlin↔KSP mismatch, the KSP2/iOS catch-22, \`SDK location not
|
|
57
|
+
found\`, \`No space left on device\`): \`doctor\` diagnoses all of them offline; the worked
|
|
58
|
+
write-ups live upstream at
|
|
59
|
+
<https://github.com/kvdm-co-pilot/create-cmp/tree/main/docs/errors>.
|
|
60
|
+
|
|
61
|
+
## What is NOT wired here
|
|
62
|
+
|
|
63
|
+
A create-cmp-scaffolded app additionally carries device-free screen previews (structure
|
|
64
|
+
for the agent, pixels for the human), a live on-device inspector, feature generators,
|
|
65
|
+
and a verify lane with evidence receipts and a machine-checked definition of done. Those
|
|
66
|
+
surfaces presuppose scaffold machinery this repo does not have; attach does not fake
|
|
67
|
+
them. To see the full contract, scaffold with \`npx create-cmp-cli@latest\`.
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function attachSettings() {
|
|
72
|
+
return (
|
|
73
|
+
JSON.stringify(
|
|
74
|
+
{
|
|
75
|
+
hooks: {
|
|
76
|
+
SessionStart: [
|
|
77
|
+
{
|
|
78
|
+
matcher: "",
|
|
79
|
+
hooks: [{ type: "command", command: sessionStartCommand(ATTACH_SESSION_CONTEXT) }],
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
null,
|
|
85
|
+
2
|
|
86
|
+
) + "\n"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Classify the target directory. Pure; returns {ok:true} or {ok:false, reason}.
|
|
92
|
+
*/
|
|
93
|
+
export function classifyTarget(projectDir) {
|
|
94
|
+
if (fs.existsSync(path.join(projectDir, "create-cmp.json"))) {
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
reason:
|
|
98
|
+
"this is a create-cmp-stamped app (create-cmp.json present). Attach is for repos " +
|
|
99
|
+
"create-cmp never scaffolded — use `create-cmp harden` (minimal → full) or " +
|
|
100
|
+
"`create-cmp upgrade --harness` (refresh) here instead.",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const hasGradleSettings =
|
|
104
|
+
fs.existsSync(path.join(projectDir, "settings.gradle.kts")) ||
|
|
105
|
+
fs.existsSync(path.join(projectDir, "settings.gradle"));
|
|
106
|
+
if (!hasGradleSettings) {
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
reason: "no settings.gradle(.kts) — attach targets an existing Gradle Compose/KMP project.",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// A Compose/KMP signal anywhere in the repo's build files (two levels deep
|
|
113
|
+
// covers the conventional module layout without a full tree walk).
|
|
114
|
+
const buildFiles = [];
|
|
115
|
+
const collect = (dir, depth) => {
|
|
116
|
+
let entries;
|
|
117
|
+
try {
|
|
118
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
119
|
+
} catch {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
for (const e of entries) {
|
|
123
|
+
if (e.name === "build" || e.name.startsWith(".")) continue;
|
|
124
|
+
const abs = path.join(dir, e.name);
|
|
125
|
+
if (e.isDirectory() && depth > 0) collect(abs, depth - 1);
|
|
126
|
+
else if (/^build\.gradle(\.kts)?$|^libs\.versions\.toml$/.test(e.name)) buildFiles.push(abs);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
collect(projectDir, 2);
|
|
130
|
+
collect(path.join(projectDir, "gradle"), 1);
|
|
131
|
+
const signals = buildFiles.some((f) => {
|
|
132
|
+
try {
|
|
133
|
+
const src = fs.readFileSync(f, "utf8");
|
|
134
|
+
return COMPOSE_SIGNALS.some((s) => src.includes(s));
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
if (!signals) {
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
reason:
|
|
143
|
+
"no Compose/KMP signal found in the build files (looked for the Compose or Kotlin " +
|
|
144
|
+
"Multiplatform plugins). Attach only claims to help Compose/KMP repos.",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return { ok: true };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Plan (and optionally write) the attach surfaces. Testable core: no exit, no
|
|
152
|
+
* prompt. Each unit lands as one of: current | written | sidecar.
|
|
153
|
+
* @param {object} params
|
|
154
|
+
* @param {string} params.projectDir
|
|
155
|
+
* @param {boolean} [params.apply=false]
|
|
156
|
+
* @returns {{units: Array<{relPath:string, action:string}>, notWired: string[]}}
|
|
157
|
+
*/
|
|
158
|
+
export function attachProject({ projectDir, apply = false }) {
|
|
159
|
+
const target = classifyTarget(projectDir);
|
|
160
|
+
if (!target.ok) throw new Error(target.reason);
|
|
161
|
+
|
|
162
|
+
const files = [
|
|
163
|
+
{ relPath: "AGENTS.md", content: attachAgentsMd() },
|
|
164
|
+
{ relPath: ".claude/settings.json", content: attachSettings() },
|
|
165
|
+
];
|
|
166
|
+
const units = [];
|
|
167
|
+
for (const { relPath, content } of files) {
|
|
168
|
+
const abs = path.join(projectDir, relPath);
|
|
169
|
+
if (fs.existsSync(abs)) {
|
|
170
|
+
const current = fs.readFileSync(abs, "utf8");
|
|
171
|
+
if (current === content) {
|
|
172
|
+
units.push({ relPath, action: "current" });
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (apply) {
|
|
176
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
177
|
+
fs.writeFileSync(abs + SIDECAR_SUFFIX, content);
|
|
178
|
+
}
|
|
179
|
+
units.push({ relPath, action: "sidecar" });
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (apply) {
|
|
183
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
184
|
+
fs.writeFileSync(abs, content);
|
|
185
|
+
}
|
|
186
|
+
units.push({ relPath, action: "written" });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
units,
|
|
191
|
+
notWired: [
|
|
192
|
+
"headless screen previews (PreviewRegistry + renderScreens) — staged M0b, needs a per-repo Gradle wiring design",
|
|
193
|
+
"live on-device inspector — ships with scaffolded apps; not injectable into a foreign build yet",
|
|
194
|
+
"verify lane / evidence receipts / enforcement — the lane addresses the stamped layout by name",
|
|
195
|
+
],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* `create-cmp attach [dir] [--dry-run] [--yes]`
|
|
201
|
+
*/
|
|
202
|
+
export async function runAttach(flags, positional) {
|
|
203
|
+
const targetDir =
|
|
204
|
+
(typeof flags["target-dir"] === "string" && flags["target-dir"]) || positional || ".";
|
|
205
|
+
const projectDir = path.resolve(targetDir);
|
|
206
|
+
|
|
207
|
+
process.stdout.write(
|
|
208
|
+
`\n${colors.bold("create-cmp attach")} — agent contract for an existing Compose/KMP repo\n` +
|
|
209
|
+
` project: ${colors.cyan(projectDir)}\n\n`
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
let plan;
|
|
213
|
+
try {
|
|
214
|
+
plan = attachProject({ projectDir, apply: false });
|
|
215
|
+
} catch (e) {
|
|
216
|
+
fail(e.message);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (const u of plan.units) {
|
|
221
|
+
if (u.action === "current") ok(`${u.relPath} — already current`);
|
|
222
|
+
if (u.action === "written") process.stdout.write(` will write ${u.relPath}\n`);
|
|
223
|
+
if (u.action === "sidecar")
|
|
224
|
+
warn(`${u.relPath} exists and differs — ours will land as ${u.relPath}${SIDECAR_SUFFIX}`);
|
|
225
|
+
}
|
|
226
|
+
process.stdout.write(`\n${colors.bold("Not wired")} (staged, never faked):\n`);
|
|
227
|
+
for (const n of plan.notWired) process.stdout.write(` · ${n}\n`);
|
|
228
|
+
|
|
229
|
+
const actionable = plan.units.filter((u) => u.action !== "current");
|
|
230
|
+
if (actionable.length === 0) {
|
|
231
|
+
ok("\nNothing to do — attach surfaces are current.");
|
|
232
|
+
process.exit(0);
|
|
233
|
+
}
|
|
234
|
+
if (flags["dry-run"] === true) {
|
|
235
|
+
process.stdout.write(`\n${colors.yellow("Dry run")} — nothing written.\n`);
|
|
236
|
+
process.exit(0);
|
|
237
|
+
}
|
|
238
|
+
const approved = await consent("\nWrite the attach surfaces?", { assumeYes: flags.yes === true });
|
|
239
|
+
if (!approved) {
|
|
240
|
+
process.stdout.write(`${colors.yellow("Not applied")} — re-run with --yes to skip the prompt.\n`);
|
|
241
|
+
process.exit(0);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const applied = attachProject({ projectDir, apply: true });
|
|
245
|
+
for (const u of applied.units) {
|
|
246
|
+
if (u.action === "written") ok(`wrote ${u.relPath}`);
|
|
247
|
+
if (u.action === "sidecar") warn(`wrote ${u.relPath}${SIDECAR_SUFFIX} (yours untouched)`);
|
|
248
|
+
}
|
|
249
|
+
process.exit(0);
|
|
250
|
+
}
|
package/src/commands/create.mjs
CHANGED
|
@@ -74,6 +74,10 @@ function buildConfigFromFlags(flags, positional) {
|
|
|
74
74
|
region: typeof flags.region === "string" ? flags.region : "us-central1",
|
|
75
75
|
themePrefix:
|
|
76
76
|
typeof flags["theme-prefix"] === "string" ? flags["theme-prefix"] : pascalFromName(name),
|
|
77
|
+
// `--minimal` is the Act 1 door (LADDER §2): same app, tests, previews and
|
|
78
|
+
// advisory hooks, without the verify lane / receipts / governance.
|
|
79
|
+
// `create-cmp harden` installs the subtraction back.
|
|
80
|
+
harness: !flagBool(flags, "minimal", false),
|
|
77
81
|
platforms: { android: true, ios },
|
|
78
82
|
firebase: {
|
|
79
83
|
enabled: firebase,
|
|
@@ -95,7 +99,7 @@ function buildConfigFromFlags(flags, positional) {
|
|
|
95
99
|
};
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
async function interactiveConfig(positional) {
|
|
102
|
+
async function interactiveConfig(positional, flags = {}) {
|
|
99
103
|
let prompts;
|
|
100
104
|
try {
|
|
101
105
|
prompts = (await import("prompts")).default;
|
|
@@ -157,6 +161,12 @@ async function interactiveConfig(positional) {
|
|
|
157
161
|
|
|
158
162
|
const extras = await prompts(
|
|
159
163
|
[
|
|
164
|
+
{
|
|
165
|
+
type: "confirm",
|
|
166
|
+
name: "harness",
|
|
167
|
+
message: "Verification harness (verify lane, evidence receipts, machine-checked done)?",
|
|
168
|
+
initial: flags.minimal !== true, // --minimal pre-answers the interview question
|
|
169
|
+
},
|
|
160
170
|
{ type: "confirm", name: "room", message: "Room local cache?", initial: true },
|
|
161
171
|
{ type: "confirm", name: "e2e", message: "E2E test harness (Maestro)?", initial: true },
|
|
162
172
|
{
|
|
@@ -193,6 +203,7 @@ async function interactiveConfig(positional) {
|
|
|
193
203
|
iosBundleId: base.package,
|
|
194
204
|
region: base.region,
|
|
195
205
|
themePrefix: pascalFromName(base.appName),
|
|
206
|
+
harness: extras.harness,
|
|
196
207
|
platforms: { android: true, ios: base.ios },
|
|
197
208
|
firebase: { enabled: base.firebase, auth, firestore, storage, functions, fcm },
|
|
198
209
|
room: extras.room,
|
|
@@ -222,7 +233,7 @@ export async function runCreate(flags, positional) {
|
|
|
222
233
|
|
|
223
234
|
const config = nonInteractive
|
|
224
235
|
? buildConfigFromFlags(flags, positional)
|
|
225
|
-
: await interactiveConfig(positional);
|
|
236
|
+
: await interactiveConfig(positional, flags);
|
|
226
237
|
|
|
227
238
|
const verify = flagBool(flags, "verify", true);
|
|
228
239
|
|