@xhulz/nina 0.45.0 → 0.46.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 (126) hide show
  1. package/package.json +1 -1
  2. package/releases/0.46.0/core/GAPS.md +125 -0
  3. package/releases/0.46.0/core/answered.json +41 -0
  4. package/releases/0.46.0/core/defaults/tree/.claude/agents/architect.md +1 -0
  5. package/releases/0.46.0/core/defaults/tree/.claude/agents/dba.md +3 -0
  6. package/releases/0.46.0/core/defaults/tree/.claude/agents/devops.md +1 -0
  7. package/releases/0.46.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
  8. package/releases/0.46.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  9. package/releases/0.46.0/core/defaults/tree/.claude/agents/qa.md +7 -0
  10. package/releases/0.46.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  11. package/releases/0.46.0/core/defaults/tree/.claude/agents/secops.md +3 -0
  12. package/releases/0.46.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  13. package/releases/0.46.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  14. package/releases/0.46.0/core/tree/.claude/agents/architect.md +127 -0
  15. package/releases/0.46.0/core/tree/.claude/agents/dba.md +110 -0
  16. package/releases/0.46.0/core/tree/.claude/agents/devops.md +115 -0
  17. package/releases/0.46.0/core/tree/.claude/agents/implementer.md +158 -0
  18. package/releases/0.46.0/core/tree/.claude/agents/integration-tester.md +168 -0
  19. package/releases/0.46.0/core/tree/.claude/agents/planner.md +132 -0
  20. package/releases/0.46.0/core/tree/.claude/agents/qa.md +150 -0
  21. package/releases/0.46.0/core/tree/.claude/agents/reviewer.md +183 -0
  22. package/releases/0.46.0/core/tree/.claude/agents/secops.md +120 -0
  23. package/releases/0.46.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
  24. package/releases/0.46.0/core/tree/.claude/agents/solidity-dev.md +123 -0
  25. package/releases/0.46.0/core/tree/.claude/agents-overview.md +64 -0
  26. package/releases/0.46.0/core/tree/.claude/frontend.md +10 -0
  27. package/releases/0.46.0/core/tree/.claude/graph.md +75 -0
  28. package/releases/0.46.0/core/tree/.claude/patterns.md +248 -0
  29. package/releases/0.46.0/core/tree/.claude/pills/README.md +147 -0
  30. package/releases/0.46.0/core/tree/.claude/pipeline.md +101 -0
  31. package/releases/0.46.0/core/tree/.claude/retrieval.md +103 -0
  32. package/releases/0.46.0/core/tree/.claude/router.md +198 -0
  33. package/releases/0.46.0/core/tree/.claude/templates/integration.md +45 -0
  34. package/releases/0.46.0/core/tree/CLAUDE.md +163 -0
  35. package/releases/0.46.0/core/tree/scripts/cost-watch.mjs +25 -0
  36. package/releases/0.46.0/core/tree/scripts/edit-guard.mjs +24 -0
  37. package/releases/0.46.0/core/tree/scripts/harness-check.mjs +71 -0
  38. package/releases/0.46.0/core/tree/scripts/loop-gate.mjs +19 -0
  39. package/releases/0.46.0/core/vocabulary.json +13 -0
  40. package/releases/0.46.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  41. package/releases/0.46.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  42. package/releases/0.46.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  43. package/releases/0.46.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  44. package/releases/0.46.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  45. package/releases/0.46.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  46. package/releases/0.46.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
  47. package/releases/0.46.0/surfaces/blockchain/tree/.claude/router.md +6 -0
  48. package/releases/0.46.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
  49. package/releases/0.46.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
  50. package/releases/0.46.0/surfaces/db/tree/.claude/agents/dba.md +51 -0
  51. package/releases/0.46.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
  52. package/releases/0.46.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  53. package/releases/0.46.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  54. package/releases/0.46.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
  55. package/releases/0.46.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  56. package/releases/0.46.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
  57. package/releases/0.46.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
  58. package/releases/0.46.0/surfaces/db/tree/.claude/graph.md +8 -0
  59. package/releases/0.46.0/surfaces/db/tree/.claude/patterns.md +19 -0
  60. package/releases/0.46.0/surfaces/db/tree/.claude/pipeline.md +13 -0
  61. package/releases/0.46.0/surfaces/db/tree/.claude/retrieval.md +5 -0
  62. package/releases/0.46.0/surfaces/db/tree/.claude/router.md +4 -0
  63. package/releases/0.46.0/surfaces/db/tree/CLAUDE.md +17 -0
  64. package/releases/0.46.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  65. package/releases/0.46.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  66. package/releases/0.46.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
  67. package/releases/0.46.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  68. package/releases/0.46.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  69. package/releases/0.46.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  70. package/releases/0.46.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  71. package/releases/0.46.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  72. package/releases/0.46.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  73. package/releases/0.46.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  74. package/releases/0.46.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  75. package/releases/0.46.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  76. package/releases/0.46.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  77. package/releases/0.46.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  78. package/releases/0.46.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  79. package/releases/0.46.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  80. package/releases/0.46.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
  81. package/releases/0.46.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  82. package/releases/0.46.0/surfaces/frontend/tree/.claude/router.md +14 -0
  83. package/releases/0.46.0/surfaces/frontend/tree/CLAUDE.md +5 -0
  84. package/releases/0.46.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  85. package/releases/0.46.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  86. package/releases/0.46.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  87. package/releases/0.46.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  88. package/releases/0.46.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  89. package/releases/0.46.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  90. package/releases/0.46.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  91. package/releases/0.46.0/surfaces/integrations/tree/.claude/graph.md +10 -0
  92. package/releases/0.46.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
  93. package/releases/0.46.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  94. package/releases/0.46.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  95. package/releases/0.46.0/surfaces/integrations/tree/.claude/router.md +6 -0
  96. package/releases/0.46.0/surfaces/integrations/tree/CLAUDE.md +17 -0
  97. package/releases/0.46.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
  98. package/releases/0.46.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
  99. package/releases/0.46.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  100. package/releases/0.46.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  101. package/releases/0.46.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  102. package/releases/0.46.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
  103. package/releases/0.46.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  104. package/releases/0.46.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  105. package/releases/0.46.0/surfaces/money/tree/.claude/graph.md +2 -0
  106. package/releases/0.46.0/surfaces/money/tree/.claude/patterns.md +49 -0
  107. package/releases/0.46.0/surfaces/money/tree/.claude/pipeline.md +2 -0
  108. package/releases/0.46.0/surfaces/money/tree/.claude/retrieval.md +2 -0
  109. package/releases/0.46.0/surfaces/money/tree/CLAUDE.md +11 -0
  110. package/releases/0.46.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  111. package/releases/0.46.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  112. package/releases/0.46.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  113. package/releases/0.46.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  114. package/releases/0.46.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  115. package/releases/0.46.0/surfaces/pii/tree/CLAUDE.md +5 -0
  116. package/releases/0.46.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  117. package/releases/0.46.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
  118. package/releases/0.46.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  119. package/releases/0.46.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  120. package/releases/0.46.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  121. package/releases/0.46.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  122. package/releases/0.46.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
  123. package/releases/0.46.0/surfaces/prisma/tree/CLAUDE.md +5 -0
  124. package/src/commands/check.mjs +3 -3
  125. package/src/commands/compose.mjs +8 -4
  126. package/src/paths.mjs +15 -0
@@ -0,0 +1,5 @@
1
+ <!-- nina:slot pii.1 -->
2
+ 8. **Sensitive personal data is classified, and the classification is enforced.** What counts as sensitive here — identity documents, financial identifiers, anything a person would not expect to be readable by whoever can read a log — is declared in `.claude/architecture.md` § *Privacy*, along with the regime that governs it ({{PRIVACY_REGIME}}). Encrypted at rest. **No sensitive value in a log, an error message, a URL, an analytics event, or an agent transcript** — the places nobody thinks about are exactly the ones that persist. Document bytes live only in the object store, referenced by key, never in the database in clear text.
3
+
4
+ <!-- nina:slot pii.2 -->
5
+ - **personal data**: where it is collected, stored, logged or sent
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | `prisma-client-api` | a new or changed Prisma query |
@@ -0,0 +1,42 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | `prisma-cli` | **before ANY `prisma` CLI command** — `validate`, `format`, `migrate dev`, `migrate diff`, `migrate deploy`. Flags differ between versions, and one of them (`migrate diff --shadow-database-url` against a live database) destroys the database it points at |
3
+ | `prisma-client-api` | auditing a new or changed query — filters, operators, `$transaction` semantics. Through a driver adapter, cite the adapter's installed source for what a transaction does: the skill describes the default engine, and an adapter can roll back less |
4
+
5
+ <!-- nina:slot prisma.2 -->
6
+ Neither skill covers a **caching or edge layer sitting on top of the client** — cache strategy,
7
+ edge-client behaviour and the rule about never caching a read that authorizes a write belong to that
8
+ layer's own integration doc, declared like any other integration. A skill does not replace it.
9
+
10
+ <!-- nina:slot prisma.3 -->
11
+ - **With Prisma, both offline.** Run `pnpm --filter {{DB_PKG_NAME}} exec prisma validate`; it must pass. Then run `prisma migrate diff` in its **offline, file-only form** and read the generated SQL, not just the Prisma delta. Its flags changed in Prisma 7, so read the installed major (`prisma --version`) before writing the command:
12
+
13
+ ```
14
+ # Prisma 6. Compare the PREVIOUS schema against the working one — pure file-to-file, no database.
15
+ git show HEAD:{{DB_PKG}}/prisma/schema.prisma > /tmp/schema-head.prisma
16
+ pnpm --filter {{DB_PKG_NAME}} exec prisma migrate diff \
17
+ --from-schema-datamodel /tmp/schema-head.prisma \
18
+ --to-schema-datamodel prisma/schema.prisma \
19
+ --script
20
+ ```
21
+
22
+ From Prisma 7 the two flags are `--from-schema` and `--to-schema`, and the schema engine starts from the
23
+ datasource in `prisma.config.ts` even for this diff: with none there, it prints nothing and exits 0. For
24
+ SQLite a placeholder the diff never opens is enough, a `file:` URL under a gitignored directory; for any
25
+ other provider, find out what the diff opens before you give it one. Never a real database's URL. A first schema has nothing at `HEAD`: diff it `--from-empty`. Compare the output against the
26
+ hand-written `migration.sql` to confirm the declarative parts match.
27
+
28
+ ⚠️ **Do NOT reach for `--from-migrations`.** It looks like the natural choice and it is a trap:
29
+ Prisma refuses it without a shadow database (`--shadow-database-url`, or from Prisma 7 a
30
+ `shadowDatabaseUrl` in `prisma.config.ts`), which pushes you toward supplying a database URL — the exact move that destroys databases (below). The file-to-file form above answers the
31
+ same question with no database involved.
32
+
33
+ ⛔ **NEVER point `--shadow-database-url`, `--from-url`, `--to-url`, `--from-schema-datasource` — or, from Prisma 7, a `shadowDatabaseUrl` or the config datasource behind `--from-config-datasource` — at a real database, most of all not the one `{{SECRETS_LOCAL}}` names.** Prisma **executes** the entire migration history against whatever database those flags name, which **DESTROYS ALL DATA** in it. A shadow database must be a disposable, empty database and nothing else. It does not announce itself: the command completes with a reassuring "empty migration" result and no error, having already destroyed everything in the database it was pointed at. If the offline form above cannot answer your question, **STOP and report that to the orchestrator** instead of reaching for a database URL.
34
+
35
+ <!-- nina:slot prisma.4 -->
36
+ - **Where reads are cached through Prisma Accelerate,** every `findUnique` / `findMany` on a hot read path names its `cacheStrategy` (`ttl` / `swr`), and one that feeds a correctness-critical write names none.
37
+
38
+ <!-- nina:slot prisma.5 -->
39
+ - **On an edge runtime,** new Prisma code paths use the edge client of the installed major: `@prisma/client/edge` up to Prisma 6; from 7, the client the `prisma-client` generator writes with the platform's `runtime` set, imported from where it is generated — `@prisma/client/edge` fails there with `MODULE_NOT_FOUND`.
40
+
41
+ <!-- nina:slot prisma.6 -->
42
+ - Run a Prisma command that writes to a real database — `prisma migrate dev`, `migrate deploy`, `migrate reset`, `migrate resolve`, `db push`, `db execute`, `$executeRaw*` — or **any `prisma migrate diff` variant carrying a database URL**, which silently wipes the database it points at.
@@ -0,0 +1,11 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ - **`prisma-cli`** — before `migrate deploy` or any other Prisma CLI command against a real database.
3
+
4
+ <!-- nina:slot prisma.2 -->
5
+ - **Prisma migrations go out with `prisma migrate deploy`**, and nothing else changes a shared database's schema.
6
+
7
+ <!-- nina:slot prisma.3 -->
8
+ - Run `prisma migrate diff --shadow-database-url <url>` against any live database. It **executes** the migration history against that database, destroying everything in it, and reports success. Use a disposable shadow database or don't run it.
9
+
10
+ <!-- nina:slot prisma.4 -->
11
+ - Run `prisma migrate reset` against a shared database, ever.
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | `prisma-client-api` | a new or changed Prisma query |
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | `prisma-cli` | a Prisma CLI command |
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | `prisma-client-api` | a new or changed Prisma query |
@@ -0,0 +1,15 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ - **Database table/column names** (Prisma `@map` / `@@map`): `snake_case` in SQL, `camelCase` in TS.
3
+
4
+ <!-- nina:slot prisma.2 -->
5
+ - Where Prisma reads are cached through Accelerate, `cacheStrategy` lives in `{{DB_PKG}}`: services do not add, strip, or override cache policies.
6
+
7
+ <!-- nina:slot prisma.3 -->
8
+
9
+ ### Prisma
10
+
11
+ - Schema lives in `{{DB_PKG}}/prisma/schema.prisma`. Nowhere else.
12
+ - **Never write raw SQL** unless justified in a code comment with perf data.
13
+ - N+1 is solved with `include` / `select`, chosen deliberately.
14
+ - Where reads are cached through Accelerate, every `findUnique` / `findMany` on a hot path defines its `cacheStrategy`, and no direct connection string is embedded in code.
15
+ - Tests run a real Prisma client against a real database; the client is never mocked.
@@ -0,0 +1,5 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | Run any `prisma` CLI command (the database gate, devops, and the integration gate where there is one) | `prisma-cli` | flags differ between versions; `migrate diff --shadow-database-url` against a live DB wiped dev/staging once |
3
+
4
+ <!-- nina:slot prisma.2 -->
5
+ | Write or audit a Prisma query (architect, implementer, the database gate, reviewer) | `prisma-client-api` | filters, operators, `$transaction` semantics. a caching or edge layer on top of the client is **NOT covered** — its cache strategy belongs to that layer's own integration doc |
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import { readFile, readdir } from 'node:fs/promises';
12
- import { describeMove, HARNESS, interruptedMove, readProfile, surfacesIn } from '../paths.mjs';
12
+ import { deferredWhy, describeMove, HARNESS, interruptedMove, readProfile, surfacesIn } from '../paths.mjs';
13
13
  import { expectedUnfilled } from '../expected.mjs';
14
14
  import { parseGraph, validateGraph } from '../graph.mjs';
15
15
  import { frontmatterFindings, installedSkills, modelFindings, toolFindings } from '../tools.mjs';
@@ -243,7 +243,7 @@ export async function check(argv, ctx) {
243
243
  const deferredUsed = new Set();
244
244
  let deferrable = false;
245
245
  const owe = (key, problem) => {
246
- const why = typeof deferred[key] === 'string' ? deferred[key].trim() : '';
246
+ const why = deferredWhy(profile, key);
247
247
  if (why) {
248
248
  deferredUsed.add(key);
249
249
  notes.push(`deferred: ${key} — ${why}`);
@@ -298,7 +298,7 @@ export async function check(argv, ctx) {
298
298
  // one only to tailor it.
299
299
  const defaulted = await defaultedSlots(resolved.dir);
300
300
  const open = [...slots].filter((s) => !filled.has(s) && !defaulted.has(s));
301
- const missing = open.filter((s) => !expected.has(s) && !(typeof deferred[s] === 'string' && deferred[s].trim()));
301
+ const missing = open.filter((s) => !expected.has(s) && !deferredWhy(profile, s));
302
302
  for (const s of open.filter((s) => !expected.has(s) && !missing.includes(s))) owe(s, `project slot ${s} has no fragment`);
303
303
  const awaited = open.filter((s) => expected.has(s));
304
304
  const orphan = [...filled].filter((s) => !slots.has(s));
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
20
- import { HARNESS, layerRootFor, readProfile } from '../paths.mjs';
20
+ import { HARNESS, deferredWhy, layerRootFor, readProfile } from '../paths.mjs';
21
21
  import { expectedUnfilled } from '../expected.mjs';
22
22
  import { existsSync, lstatSync, readFileSync } from 'node:fs';
23
23
  import { dirname, join, relative, resolve } from 'node:path';
@@ -553,8 +553,8 @@ export async function composeProject(target, ctx, options = {}) {
553
553
  }
554
554
  // A name the project deferred, with its reason, reads as not decided: a stage that meets it asks
555
555
  // instead of guessing, and the raw placeholder it would otherwise read looks like an editing slip.
556
- for (const [name, why] of Object.entries(profile.deferred ?? {})) {
557
- if (typeof why === 'string' && why.trim() && NAME.test(name)) text = text.split(`{{${name}}}`).join(`[${name}: not decided yet]`);
556
+ for (const name of Object.keys(profile.deferred ?? {})) {
557
+ if (deferredWhy(profile, name) && NAME.test(name)) text = text.split(`{{${name}}}`).join(`[${name}: not decided yet]`);
558
558
  }
559
559
  // What is still a placeholder is owed: no default answers it, and the project has not declared it, or has
560
560
  // declared it null. Left raw, it reached a stage as `{{TENANT_KEY}}` — once inside a shell block it was told to
@@ -611,8 +611,12 @@ export async function compose(argv, ctx) {
611
611
  // A surface's own slot left empty is a bug in the harness, not the project's to fill, so drift mode
612
612
  // still reports it; only the project's slots are left to the declaration detector.
613
613
  const reported = driftOnly ? result.unfilled.filter((slot) => !/ project\.\d+$/.test(slot)) : result.unfilled;
614
- const owed = reported.filter((slot) => !expected.has(slot));
614
+ // A project slot the profile defers, with its reason, is a decision not made yet, here as in `check`.
615
+ const { profile } = readProfile(target);
616
+ const deferred = reported.filter((slot) => !expected.has(slot) && deferredWhy(profile, slot));
617
+ const owed = reported.filter((slot) => !expected.has(slot) && !deferred.includes(slot));
615
618
  const awaited = reported.filter((slot) => expected.has(slot));
619
+ if (deferred.length > 0) console.log(` ${deferred.length} slot(s) deferred in ${HARNESS}/profile.json — \`nina check\` says why each waits`);
616
620
  if (owed.length > 0) {
617
621
  console.log(` ${owed.length} slot(s) have no fragment in a declared layer:`);
618
622
  for (const slot of owed.slice(0, 12)) console.log(` ${slot}`);
package/src/paths.mjs CHANGED
@@ -125,6 +125,21 @@ export function readProfile(target) {
125
125
  }
126
126
  }
127
127
 
128
+ /**
129
+ * Why a profile defers a name — a vocabulary name, a document, or a project slot as `<path> <slot id>` — or
130
+ * `''` when it does not. A deferral with no reason counts for nothing: `check` says so, and every other reader
131
+ * treats the name as owed. `compose --check` once read the profile's deferrals not at all, and failed on six
132
+ * slots a new project had deferred while `check` was clean.
133
+ *
134
+ * @param {object | undefined} profile - The parsed profile.
135
+ * @param {string} key - The deferred name.
136
+ * @returns {string} The reason, trimmed; empty when the name is not deferred.
137
+ */
138
+ export function deferredWhy(profile, key) {
139
+ const why = profile?.deferred?.[key];
140
+ return typeof why === 'string' ? why.trim() : '';
141
+ }
142
+
128
143
  /**
129
144
  * Where a project's layers are read from.
130
145
  *