create-cmp-cli 0.23.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 (145) 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 +46 -18
  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 +747 -0
  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/agent-hold.mjs +234 -0
  16. package/packages/harness/src/lib/approvals.mjs +177 -350
  17. package/packages/harness/src/lib/audit-cadence.mjs +1 -1
  18. package/packages/harness/src/lib/comments.mjs +18 -4
  19. package/packages/harness/src/lib/determinism.mjs +275 -14
  20. package/packages/harness/src/lib/evidence-badge.mjs +38 -6
  21. package/packages/harness/src/lib/evidence-ladder.mjs +202 -0
  22. package/packages/harness/src/lib/evidence-level.mjs +144 -59
  23. package/packages/harness/src/lib/feature-brief.mjs +53 -16
  24. package/packages/harness/src/lib/flight-recorder.mjs +72 -8
  25. package/packages/harness/src/lib/framework-check.mjs +778 -0
  26. package/packages/harness/src/lib/harness-lock.mjs +87 -19
  27. package/packages/harness/src/lib/harness-manifest.mjs +208 -0
  28. package/packages/harness/src/lib/harness-region.mjs +106 -2
  29. package/packages/harness/src/lib/harness-source.mjs +142 -0
  30. package/packages/harness/src/lib/inputs-hash.mjs +180 -22
  31. package/packages/harness/src/lib/lane-markers.mjs +78 -0
  32. package/packages/harness/src/lib/lane-narrator.mjs +4 -2
  33. package/packages/harness/src/lib/lane-runner.mjs +72 -6
  34. package/packages/harness/src/lib/plan.mjs +44 -11
  35. package/packages/harness/src/lib/plant-calibration.mjs +135 -0
  36. package/packages/harness/src/lib/profile-loader.mjs +323 -0
  37. package/packages/harness/src/lib/profiles/cmp/affected.mjs +70 -0
  38. package/packages/harness/src/lib/profiles/cmp/android-checks.mjs +56 -0
  39. package/packages/harness/src/lib/profiles/cmp/artifacts.mjs +381 -0
  40. package/packages/harness/src/lib/{component-stories.mjs → profiles/cmp/component-stories.mjs} +5 -2
  41. package/packages/harness/src/lib/profiles/cmp/console-copy.mjs +29 -0
  42. package/packages/harness/src/lib/profiles/cmp/declarations.mjs +181 -0
  43. package/{template/qa/lib → packages/harness/src/lib/profiles/cmp}/e2e-coverage.mjs +13 -3
  44. package/packages/harness/src/lib/profiles/cmp/index.mjs +47 -0
  45. package/packages/harness/src/lib/profiles/cmp/ladder.mjs +67 -0
  46. package/packages/harness/src/lib/profiles/cmp/maestro.mjs +113 -0
  47. package/packages/harness/src/lib/profiles/cmp/plants.mjs +95 -0
  48. package/{template/qa/lib → packages/harness/src/lib/profiles/cmp}/reachability.mjs +1 -1
  49. package/packages/harness/src/lib/{steps-cmp.mjs → profiles/cmp/steps-cmp.mjs} +230 -28
  50. package/packages/harness/src/lib/receipt-validate.mjs +84 -16
  51. package/packages/harness/src/lib/spec-coverage.mjs +179 -92
  52. package/packages/harness/src/lib/spec-model.mjs +262 -0
  53. package/packages/harness/src/lib/step-cache.mjs +11 -2
  54. package/packages/harness/src/lib/step-outcomes.mjs +84 -151
  55. package/packages/harness/src/lib/walk.mjs +1 -1
  56. package/packages/harness/src/plan.mjs +30 -1
  57. package/packages/harness/src/preview-gallery.mjs +1 -1
  58. package/packages/harness/src/receipt-check.mjs +95 -26
  59. package/packages/harness/src/record-audit.mjs +1 -1
  60. package/packages/harness/src/scaffold-feature.mjs +10 -2
  61. package/packages/harness/src/verify.mjs +222 -142
  62. package/packages/harness/src/watch.mjs +146 -33
  63. package/packages/receipts/package.json +1 -1
  64. package/packages/receipts/src/index.mjs +2 -2
  65. package/packages/receipts/src/inputs-hash.mjs +180 -22
  66. package/packages/receipts/src/receipt-validate.mjs +84 -16
  67. package/src/commands/attach.mjs +128 -4
  68. package/src/commands/harden.mjs +7 -1
  69. package/src/commands/upgrade.mjs +7 -0
  70. package/src/lib/minimal.mjs +23 -1
  71. package/src/scaffold.mjs +8 -0
  72. package/template/.claude/settings.json +2 -2
  73. package/template/.claude/skills/add-feature/SKILL.md +7 -3
  74. package/template/.claude/skills/add-repository/SKILL.md +2 -2
  75. package/template/.claude/skills/add-screen/SKILL.md +8 -4
  76. package/template/.githooks/pre-push +9 -0
  77. package/template/CLAUDE.md +55 -186
  78. package/template/docs/TESTING.md +1 -1
  79. package/template/gitignore +4 -0
  80. package/template/qa/approve.mjs +46 -18
  81. package/template/qa/comments.json +1 -1
  82. package/template/qa/evidence/schema.json +258 -34
  83. package/template/qa/framework-check.mjs +747 -0
  84. package/template/qa/harness-manifest.json +10 -0
  85. package/template/qa/lib/a11y.mjs +1 -1
  86. package/template/qa/lib/affected-tests.mjs +99 -44
  87. package/template/qa/lib/agent-hold.mjs +234 -0
  88. package/template/qa/lib/approvals.mjs +177 -350
  89. package/template/qa/lib/audit-cadence.mjs +1 -1
  90. package/template/qa/lib/comments.mjs +18 -4
  91. package/template/qa/lib/determinism.mjs +275 -14
  92. package/template/qa/lib/evidence-badge.mjs +38 -6
  93. package/template/qa/lib/evidence-ladder.mjs +202 -0
  94. package/template/qa/lib/evidence-level.mjs +144 -59
  95. package/template/qa/lib/feature-brief.mjs +53 -16
  96. package/template/qa/lib/flight-recorder.mjs +72 -8
  97. package/template/qa/lib/framework-check.mjs +778 -0
  98. package/template/qa/lib/harness-lock.mjs +87 -19
  99. package/template/qa/lib/harness-manifest.mjs +208 -0
  100. package/template/qa/lib/harness-region.mjs +106 -2
  101. package/template/qa/lib/harness-source.mjs +142 -0
  102. package/template/qa/lib/inputs-hash.mjs +180 -22
  103. package/template/qa/lib/lane-markers.mjs +78 -0
  104. package/template/qa/lib/lane-narrator.mjs +4 -2
  105. package/template/qa/lib/lane-runner.mjs +72 -6
  106. package/template/qa/lib/plan.mjs +44 -11
  107. package/template/qa/lib/plant-calibration.mjs +135 -0
  108. package/template/qa/lib/profile-loader.mjs +323 -0
  109. package/template/qa/lib/profiles/cmp/affected.mjs +70 -0
  110. package/template/qa/lib/profiles/cmp/android-checks.mjs +56 -0
  111. package/template/qa/lib/profiles/cmp/artifacts.mjs +381 -0
  112. package/template/qa/lib/{component-stories.mjs → profiles/cmp/component-stories.mjs} +5 -2
  113. package/template/qa/lib/profiles/cmp/console-copy.mjs +29 -0
  114. package/template/qa/lib/profiles/cmp/declarations.mjs +181 -0
  115. package/{packages/harness/src/lib → template/qa/lib/profiles/cmp}/e2e-coverage.mjs +13 -3
  116. package/template/qa/lib/profiles/cmp/index.mjs +47 -0
  117. package/template/qa/lib/profiles/cmp/ladder.mjs +67 -0
  118. package/template/qa/lib/profiles/cmp/maestro.mjs +113 -0
  119. package/template/qa/lib/profiles/cmp/plants.mjs +95 -0
  120. package/{packages/harness/src/lib → template/qa/lib/profiles/cmp}/reachability.mjs +1 -1
  121. package/template/qa/lib/{steps-cmp.mjs → profiles/cmp/steps-cmp.mjs} +230 -28
  122. package/template/qa/lib/receipt-validate.mjs +84 -16
  123. package/template/qa/lib/spec-coverage.mjs +179 -92
  124. package/template/qa/lib/spec-model.mjs +262 -0
  125. package/template/qa/lib/step-cache.mjs +11 -2
  126. package/template/qa/lib/step-outcomes.mjs +84 -151
  127. package/template/qa/lib/walk.mjs +1 -1
  128. package/template/qa/plan.mjs +30 -1
  129. package/template/qa/preview-gallery.mjs +1 -1
  130. package/template/qa/receipt-check.mjs +95 -26
  131. package/template/qa/record-audit.mjs +1 -1
  132. package/template/qa/scaffold-feature.mjs +10 -2
  133. package/template/qa/verified-surface.json +17 -0
  134. package/template/qa/verify.mjs +222 -142
  135. package/template/qa/watch.mjs +146 -33
  136. /package/packages/harness/src/lib/{device-lease.mjs → profiles/cmp/device-lease.mjs} +0 -0
  137. /package/packages/harness/src/lib/{device-provider.mjs → profiles/cmp/device-provider.mjs} +0 -0
  138. /package/packages/harness/src/lib/{render.mjs → profiles/cmp/render.mjs} +0 -0
  139. /package/packages/harness/src/lib/{token-drift.mjs → profiles/cmp/token-drift.mjs} +0 -0
  140. /package/packages/harness/src/lib/{tree.mjs → profiles/cmp/tree.mjs} +0 -0
  141. /package/template/qa/lib/{device-lease.mjs → profiles/cmp/device-lease.mjs} +0 -0
  142. /package/template/qa/lib/{device-provider.mjs → profiles/cmp/device-provider.mjs} +0 -0
  143. /package/template/qa/lib/{render.mjs → profiles/cmp/render.mjs} +0 -0
  144. /package/template/qa/lib/{token-drift.mjs → profiles/cmp/token-drift.mjs} +0 -0
  145. /package/template/qa/lib/{tree.mjs → profiles/cmp/tree.mjs} +0 -0
@@ -21,6 +21,11 @@ import { fileURLToPath } from "node:url";
21
21
 
22
22
  import { computeInputsHash } from "./lib/inputs-hash.mjs";
23
23
  import { evaluateReceipt, readReceipt } from "./lib/receipt-validate.mjs";
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";
24
29
 
25
30
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
26
31
 
@@ -29,9 +34,8 @@ const asHook = args.includes("--hook");
29
34
  const asJson = args.includes("--json");
30
35
 
31
36
  // The lane's own in-flight marker (verify.mjs stamps it, rewriting it at each
32
- // step start with the step name and index). Mirrors qa/watch.mjs's bound.
33
- const LANE_MARKER_REL = ["composeApp", "build", ".cmp-lane-in-progress"];
34
- 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.
35
39
 
36
40
  /**
37
41
  * The full check running RIGHT NOW, or null. The gate must still refuse — a
@@ -43,7 +47,7 @@ const LANE_MARKER_STALE_MS = 30 * 60 * 1000;
43
47
  */
44
48
  function laneInFlight() {
45
49
  try {
46
- const p = path.join(ROOT, ...LANE_MARKER_REL);
50
+ const p = laneMarkerPath(ROOT);
47
51
  const st = fs.statSync(p);
48
52
  if (Date.now() - st.mtimeMs >= LANE_MARKER_STALE_MS) return null;
49
53
  // Content is a bonus, never a requirement: legacy markers hold "pid iso".
@@ -72,7 +76,7 @@ function readStdinJson() {
72
76
  }
73
77
 
74
78
  // The predicate itself lives in qa/lib/receipt-validate.mjs (vendored from the
75
- // @create-cmp/receipts package — one definition everywhere a receipt is judged); this
79
+ // prooflane-receipts package — one definition everywhere a receipt is judged); this
76
80
  // CLI only reads the receipt and frames the exit codes.
77
81
  function evaluate() {
78
82
  const receipt = readReceipt(ROOT);
@@ -108,28 +112,61 @@ function evaluate() {
108
112
  profile: receipt.profile,
109
113
  };
110
114
  }
111
- // The device tier must have RUN (2026-09-03). The lane boots a headless
112
- // emulator itself, so e2eSmoke/androidChecks only SKIP for two kinds of
113
- // reason: the project's own structure (no qa/e2e harness, no instrumented
114
- // sourceshonest, allowed) or the ENVIRONMENT (CMP_DEVICE=none, maestro
115
- // not installed, a lease held elsewhere, an ambiguous serial). The second
116
- // kind is a gap a human can close, and a change is not done while it stands.
117
- // Receipts predating `skipKind` are read by their reason text.
118
- const DEVICE_TIER = ["e2eSmoke", "androidChecks"];
119
- const envSkipped = (Array.isArray(receipt.steps) ? receipt.steps : []).filter(
120
- (s) =>
121
- s &&
122
- DEVICE_TIER.includes(s.name) &&
123
- s.verdict === "SKIP" &&
124
- (s.skipKind === "environment" ||
125
- (!s.skipKind && /no Android device|maestro CLI not installed|is held by|devices attached|CMP_DEVICE=none/.test(String(s.reason ?? "")))),
126
- );
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
+ });
127
164
  if (envSkipped.length) {
128
165
  return {
129
166
  valid: false,
130
167
  reason:
131
- `the device tier did not run — ${envSkipped.map((s) => `${s.name}: ${String(s.reason ?? "").split("\n")[0]}`).join("; ")}. ` +
132
- "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",
133
170
  profile: receipt.profile,
134
171
  };
135
172
  }
@@ -151,9 +188,21 @@ function evaluate() {
151
188
  // Surface the receipt's evidence rung (the ladder — qa/lib/evidence-level.mjs)
152
189
  // alongside the verdict: the rung is the receipt's own derived field, read
153
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.
154
201
  const level = receipt.evidenceLevel;
155
202
  if (level && typeof level === "object" && typeof level.rung === "string") {
156
203
  result.evidenceLevel = level;
204
+ const id = receipt.pack && typeof receipt.pack.id === "string" ? receipt.pack.id.trim() : "";
205
+ result.packId = id || null;
157
206
  }
158
207
  return result;
159
208
  }
@@ -175,12 +224,24 @@ if (asHook) {
175
224
  // INSTRUCTION: "run the lane" is wrong advice when one is already running,
176
225
  // and a gate that tells you to do the thing you are doing trains you to
177
226
  // stop reading it.
227
+ //
228
+ // An agent HOLDING the tree is the same shape of correction one level out:
229
+ // a lane in flight explains a receipt that is about to arrive, a hold
230
+ // explains a tree that is mid-edit and would not compile if you ran one.
231
+ // Reported ~15 false alarms in one evening at payment-blueprint, every one
232
+ // of which should have said "wait". It only ever changes the instruction —
233
+ // and only for the two refusals a working agent legitimately causes
234
+ // (holdExplains), never for a FAIL, a forgery or a skipped device tier,
235
+ // where the hold is not the cause and offering it would misdirect.
178
236
  const flight = laneInFlight();
237
+ const hold = holdExplains(result, readReceipt(ROOT)) ? assessHold(readHold(ROOT)) : null;
179
238
  const act = flight
180
239
  ? `A full check is ALREADY RUNNING${flight.step ? ` (${flight.step}${flight.index && flight.total ? `, step ${flight.index} of ${flight.total}` : ""})` : ""} — ` +
181
240
  `wait for it to finish and commit its receipt. Do NOT start a second one; two lanes fight over the same build directory.`
182
- : "Run `node qa/verify.mjs` (it checks every promise and writes the receipt), " +
183
- "commit the receipt, or see README §Verification enforcement to bypass.";
241
+ : hold?.held
242
+ ? describeHold(hold)
243
+ : "Run `node qa/verify.mjs` (it checks every promise and writes the receipt), " +
244
+ "commit the receipt, or see README §Verification enforcement to bypass.";
184
245
  process.stderr.write(
185
246
  `■ Prove — not done: the promises are not yet checked against this tree. ${result.reason}. ${act}\n`,
186
247
  );
@@ -189,7 +250,15 @@ if (asHook) {
189
250
  process.exit(0);
190
251
  }
191
252
 
192
- 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
+ : "";
193
262
 
194
263
  if (asJson) {
195
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
+ }