qfai 2.1.0 → 2.1.2

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 (184) hide show
  1. package/README.md +327 -55
  2. package/assets/defaults/agent-routing/qfai-triage.yml +9 -0
  3. package/assets/defaults/workflows/change-tooling.yml +0 -4
  4. package/assets/defaults/workflows/run-owned-cleanup.yml +12 -0
  5. package/assets/init/.claude/settings.json +27 -9
  6. package/assets/init/.codex/hooks.json +20 -8
  7. package/assets/init/.qfai/assistant/agent/frontend-engineer.md +5 -0
  8. package/assets/init/.qfai/assistant/agent/implementation-reviewer.md +14 -1
  9. package/assets/init/.qfai/assistant/agent/product-experience-architect.md +4 -0
  10. package/assets/init/.qfai/assistant/agent/product-surface-reviewer.md +7 -0
  11. package/assets/init/.qfai/assistant/agent/qa-gatekeeper.md +29 -0
  12. package/assets/init/.qfai/assistant/agent/requirements-analyst.md +8 -0
  13. package/assets/init/.qfai/assistant/rule/communication.md +18 -8
  14. package/assets/init/.qfai/assistant/rule/constitution.md +5 -4
  15. package/assets/init/.qfai/assistant/rule/drift-protocol.md +72 -5
  16. package/assets/init/.qfai/assistant/rule/references/asset-authoring.md +72 -0
  17. package/assets/init/.qfai/assistant/rule/references/context-summary.md +30 -0
  18. package/assets/init/.qfai/assistant/rule/references/gate-failure-autorepair.md +42 -0
  19. package/assets/init/.qfai/assistant/rule/references/griller-recommendations.md +23 -0
  20. package/assets/init/.qfai/assistant/rule/references/host-backstops.md +60 -0
  21. package/assets/init/.qfai/assistant/rule/references/placeholder-scan.md +25 -0
  22. package/assets/init/.qfai/assistant/rule/references/reviewer-findings.md +41 -0
  23. package/assets/init/.qfai/assistant/rule/references/reviewer-remit.md +35 -0
  24. package/assets/init/.qfai/assistant/rule/references/worker-edit-boundary.md +48 -0
  25. package/assets/init/.qfai/assistant/rule/shared-skill-delegation-baseline.md +39 -162
  26. package/assets/init/.qfai/assistant/rule/shared-skill-operating-baseline.md +14 -145
  27. package/assets/init/.qfai/assistant/rule/stage-cost.md +1 -1
  28. package/assets/init/.qfai/assistant/rule/test-layers.md +42 -0
  29. package/assets/init/.qfai/assistant/rule/ui-definition-protocol.md +6 -1
  30. package/assets/init/.qfai/assistant/skill/qfai-configure/SKILL.md +11 -45
  31. package/assets/init/.qfai/assistant/skill/qfai-configure/references/principles.md +25 -0
  32. package/assets/init/.qfai/assistant/skill/qfai-discussion/SKILL.md +1 -1
  33. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/discussion-completion-matrix.md +12 -9
  34. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui-bearing-playbook.md +2 -4
  35. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux/platform_baselines.md +11 -0
  36. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux/review_audit_playbook.md +11 -0
  37. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux_best_practices.md +13 -42
  38. package/assets/init/.qfai/assistant/skill/qfai-discussion/templates/uiux/00_index.md +0 -16
  39. package/assets/init/.qfai/assistant/skill/qfai-grill/SKILL.md +1 -1
  40. package/assets/init/.qfai/assistant/skill/qfai-grilling/SKILL.md +16 -88
  41. package/assets/init/.qfai/assistant/skill/qfai-grilling/references/delegated-session.md +58 -0
  42. package/assets/init/.qfai/assistant/skill/qfai-grilling/references/session-record.md +42 -0
  43. package/assets/init/.qfai/assistant/skill/qfai-implement/SKILL.md +17 -4
  44. package/assets/init/.qfai/assistant/skill/qfai-implement/references/credential-reuse.md +9 -0
  45. package/assets/init/.qfai/assistant/skill/qfai-implement/references/cross-spec-ownership.md +41 -0
  46. package/assets/init/.qfai/assistant/skill/qfai-implement/references/oracle-strength.md +6 -1
  47. package/assets/init/.qfai/assistant/skill/qfai-implement/references/parallelization-policy.md +9 -1
  48. package/assets/init/.qfai/assistant/skill/qfai-implement/references/red-admissibility.md +47 -1
  49. package/assets/init/.qfai/assistant/skill/qfai-implement/references/ui-affecting.md +4 -3
  50. package/assets/init/.qfai/assistant/skill/qfai-maintain/SKILL.md +4 -4
  51. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/SKILL.md +16 -10
  52. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/references/migration-guide.md +10 -260
  53. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/references/migration-placement.md +273 -0
  54. package/assets/init/.qfai/assistant/skill/qfai-prototyping/SKILL.md +5 -4
  55. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/design-md-spec.md +14 -5
  56. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/generator-prompt.md +10 -0
  57. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/handoff.md +1 -1
  58. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/iteration-loop.md +3 -4
  59. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/review-payload-schema.md +12 -5
  60. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/reviewer-prompt.md +33 -4
  61. package/assets/init/.qfai/assistant/skill/qfai-run/SKILL.md +34 -36
  62. package/assets/init/.qfai/assistant/skill/qfai-run/references/evidence-delivery.md +322 -0
  63. package/assets/init/.qfai/assistant/skill/qfai-run/references/extraction.md +67 -24
  64. package/assets/init/.qfai/assistant/skill/qfai-run/references/operator-screens.md +246 -5
  65. package/assets/init/.qfai/assistant/skill/qfai-run/references/plan.md +29 -5
  66. package/assets/init/.qfai/assistant/skill/qfai-run/references/stage-points.md +43 -0
  67. package/assets/init/.qfai/assistant/skill/qfai-sdd/SKILL.md +5 -4
  68. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/concrete-abstract-cycle.md +10 -1
  69. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/contract-artifact-rules.md +22 -9
  70. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-anti-patterns.md +41 -6
  71. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-md-authoring.md +11 -5
  72. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-md-brand-catalog.md +17 -2
  73. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/requirements-decomposition.md +10 -2
  74. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-execution-playbook.md +34 -9
  75. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-phase-checklists.md +4 -4
  76. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-quality-gate.md +2 -2
  77. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-triage.md +177 -7
  78. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/spec-traceability-rules.md +29 -5
  79. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-contract-guide.md +130 -1
  80. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-design-contract-normalization.md +4 -30
  81. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/change-request.md +39 -2
  82. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/contracts/db-contract.sample.sql +2 -2
  83. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/contracts/ui-contract.sample.yaml +52 -5
  84. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/03_contract/cli/cli-NNNN-title.md +2 -2
  85. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/03_contract/tech.md +1 -1
  86. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/decisions.md +7 -0
  87. package/assets/init/.qfai/assistant/skill/qfai-triage/SKILL.md +30 -20
  88. package/assets/init/.qfai/assistant/skill/qfai-verify/SKILL.md +6 -5
  89. package/assets/init/.qfai/assistant/skill/qfai-verify/references/context-load.md +9 -6
  90. package/assets/init/.qfai/assistant/skill/qfai-verify/references/verify-output-contract.md +92 -13
  91. package/assets/init/.qfai/assistant/skill/web-research/SKILL.md +37 -31
  92. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/brave-search/config.toml +2 -4
  93. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/brave-search/mcp-config.json +9 -5
  94. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/.mcp.json +1 -2
  95. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/config.toml +3 -3
  96. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/mcp-config.json +11 -8
  97. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/playwright/config.toml +1 -1
  98. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/playwright/mcp-config.json +7 -3
  99. package/assets/init/.qfai/assistant/step/common-design-md/STEP.md +17 -7
  100. package/assets/init/.qfai/assistant/step/common-gate-run/STEP.md +126 -13
  101. package/assets/init/.qfai/assistant/step/common-review-cycle/STEP.md +3 -2
  102. package/assets/init/.qfai/assistant/step/discussion-pack/STEP.md +1 -0
  103. package/assets/init/.qfai/assistant/step/discussion-research/STEP.md +2 -0
  104. package/assets/init/.qfai/assistant/step/discussion-uiux/STEP.md +12 -4
  105. package/assets/init/.qfai/assistant/step/implement-backport/STEP.md +1 -1
  106. package/assets/init/.qfai/assistant/step/implement-benchmark/STEP.md +7 -4
  107. package/assets/init/.qfai/assistant/step/implement-dep-bump/STEP.md +11 -1
  108. package/assets/init/.qfai/assistant/step/implement-diagnose/STEP.md +2 -0
  109. package/assets/init/.qfai/assistant/step/implement-oracle-parity/STEP.md +2 -2
  110. package/assets/init/.qfai/assistant/step/implement-quarantine/STEP.md +3 -3
  111. package/assets/init/.qfai/assistant/step/implement-refactor/STEP.md +2 -2
  112. package/assets/init/.qfai/assistant/step/implement-regression-fix/STEP.md +4 -3
  113. package/assets/init/.qfai/assistant/step/implement-retire/STEP.md +6 -3
  114. package/assets/init/.qfai/assistant/step/implement-revert/STEP.md +4 -3
  115. package/assets/init/.qfai/assistant/step/implement-scaffold/STEP.md +3 -3
  116. package/assets/init/.qfai/assistant/step/implement-seam/STEP.md +9 -6
  117. package/assets/init/.qfai/assistant/step/implement-stress-harness/STEP.md +1 -1
  118. package/assets/init/.qfai/assistant/step/implement-sweep/STEP.md +4 -3
  119. package/assets/init/.qfai/assistant/step/implement-tdd/STEP.md +42 -6
  120. package/assets/init/.qfai/assistant/step/implement-test-fix/STEP.md +2 -0
  121. package/assets/init/.qfai/assistant/step/implement-tooling/STEP.md +22 -2
  122. package/assets/init/.qfai/assistant/step/maintain-edit/STEP.md +18 -14
  123. package/assets/init/.qfai/assistant/step/prototyping-grill/STEP.md +4 -3
  124. package/assets/init/.qfai/assistant/step/prototyping-loop/STEP.md +4 -0
  125. package/assets/init/.qfai/assistant/step/prototyping-preflight/STEP.md +1 -3
  126. package/assets/init/.qfai/assistant/step/sdd-contract/STEP.md +11 -0
  127. package/assets/init/.qfai/assistant/step/sdd-flow/STEP.md +4 -0
  128. package/assets/init/.qfai/assistant/step/sdd-gate/STEP.md +4 -0
  129. package/assets/init/.qfai/assistant/step/sdd-story/STEP.md +10 -0
  130. package/assets/init/.qfai/assistant/step/sdd-triage/STEP.md +37 -11
  131. package/assets/init/.qfai/assistant/step/triage-answer/STEP.md +5 -4
  132. package/assets/init/.qfai/assistant/step/triage-close/STEP.md +33 -23
  133. package/assets/init/.qfai/assistant/step/triage-cluster/STEP.md +5 -3
  134. package/assets/init/.qfai/assistant/step/triage-decompose/STEP.md +2 -0
  135. package/assets/init/.qfai/assistant/step/triage-dedupe/STEP.md +4 -0
  136. package/assets/init/.qfai/assistant/step/triage-handoff/STEP.md +13 -4
  137. package/assets/init/.qfai/assistant/step/triage-investigate/STEP.md +8 -5
  138. package/assets/init/.qfai/assistant/step/triage-owned-cleanup/STEP.md +86 -0
  139. package/assets/init/.qfai/assistant/step/triage-request-info/STEP.md +2 -0
  140. package/assets/init/.qfai/assistant/step/triage-security-intake/STEP.md +6 -3
  141. package/assets/init/.qfai/assistant/step/verify-change-note/STEP.md +9 -5
  142. package/assets/init/.qfai/assistant/step/verify-commit/STEP.md +39 -6
  143. package/assets/init/.qfai/assistant/step/verify-external/STEP.md +5 -2
  144. package/assets/init/.qfai/assistant/step/verify-qfai-gate/STEP.md +10 -2
  145. package/assets/init/.qfai/assistant/step/verify-release-notes/STEP.md +1 -0
  146. package/assets/init/.qfai/assistant/step/verify-repeat-run/STEP.md +7 -5
  147. package/assets/init/.qfai/assistant/step/verify-repo-gate/STEP.md +136 -28
  148. package/assets/init/.qfai/waivers.yml +1 -1
  149. package/assets/init/root/.agents/rules/ai-readable-markdown.md +98 -0
  150. package/assets/init/root/.agents/rules/api-budget.md +191 -13
  151. package/assets/init/root/.agents/rules/documentation-clarity.md +192 -35
  152. package/assets/init/root/.agents/rules/grilling.md +4 -4
  153. package/assets/init/root/.agents/rules/interface-clarity.md +5 -5
  154. package/assets/init/root/.agents/rules/minimal-implementation.md +11 -3
  155. package/assets/init/root/.agents/rules/reminders.json +27 -14
  156. package/assets/init/root/.agents/rules/session-feedback.md +136 -0
  157. package/assets/init/root/.agents/rules/temporary-files.md +47 -5
  158. package/assets/init/root/.agents/rules/untrusted-content.md +3 -3
  159. package/assets/init/root/.agents/rules/user-questions.md +74 -36
  160. package/assets/init/root/.gitattributes +9 -0
  161. package/assets/init/root/.github/workflows/qfai-docs.yml +44 -12
  162. package/assets/init/root/.github/workflows/qfai-tests.yml +58 -15
  163. package/assets/init/root/.github/workflows/qfai-validate.yml +49 -42
  164. package/assets/init/root/AGENTS.md +23 -21
  165. package/assets/mdschema/story/03_contract/tech.mdschema.yml +7 -3
  166. package/assets/sandbox-templates/default-deny.yaml +9 -5
  167. package/assets/schemas/workflow/extraction.schema.json +85 -5
  168. package/assets/schemas/workflow/plan.schema.json +9 -2
  169. package/assets/scripts/check-mdschema.mjs +172 -5
  170. package/assets/uix-rev/comparison-review.md +1 -1
  171. package/assets/uix-rev/contracts-review.md +1 -2
  172. package/dist/cli/index.cjs +6499 -7688
  173. package/dist/cli/index.cjs.map +1 -1
  174. package/dist/cli/index.mjs +6449 -7641
  175. package/dist/cli/index.mjs.map +1 -1
  176. package/dist/index.cjs +5548 -7823
  177. package/dist/index.cjs.map +1 -1
  178. package/dist/index.d.cts +40 -666
  179. package/dist/index.d.ts +40 -666
  180. package/dist/index.mjs +5397 -7648
  181. package/dist/index.mjs.map +1 -1
  182. package/docs/MIGRATION-2.0.0.md +1 -1
  183. package/package.json +5 -7
  184. package/assets/init/root/CLAUDE.md +0 -45
package/dist/index.d.ts CHANGED
@@ -129,42 +129,14 @@ type ValidationResult = {
129
129
  timings?: ValidationTimings;
130
130
  };
131
131
 
132
- type RenderEvidenceConfig = {
133
- enabled?: boolean;
134
- viewports?: string[];
135
- out?: string;
136
- baseUrl?: string;
137
- failOpen?: boolean;
138
- };
139
-
140
132
  type FailOn = "never" | "warning" | "error";
141
133
  type OutputFormat = "text" | "github";
142
- type TraceabilitySeverity = "warning" | "error";
143
- /**
144
- * The value set of the retired orphanContractsPolicy. Compatibility fields
145
- * annotate their type with this one: referencing the public alias would trigger
146
- * `@typescript-eslint/no-deprecated` and force a lint-suppression comment. Delete
147
- * this internal type when the compatibility acceptance ends.
148
- */
149
- type RetiredOrphanContractsPolicy = "error" | "warning" | "allow";
150
- /**
151
- * @deprecated validation.traceability.orphanContractsPolicy is retired.
152
- * No check reads it, so setting it changes nothing. It stays for the
153
- * compatibility period only, so existing TypeScript consumers that import it
154
- * keep type-checking.
155
- */
156
- type OrphanContractsPolicy = RetiredOrphanContractsPolicy;
157
134
  type QfaiPaths = {
158
135
  contractsDir: string;
159
136
  specsDir: string;
160
137
  discussionDir: string;
161
138
  outDir: string;
162
139
  skillsDir: string;
163
- /**
164
- * @deprecated Use paths.skillsDir.
165
- * It is still read for compatibility and is not used on the main validation path.
166
- */
167
- promptsDir: string;
168
140
  srcDir: string;
169
141
  testsDir: string;
170
142
  /**
@@ -178,8 +150,20 @@ type QfaiPaths = {
178
150
  */
179
151
  migrationsDir?: string;
180
152
  };
153
+ type QfaiForbiddenIdentifier = {
154
+ sha256: string;
155
+ byteLength: number;
156
+ };
181
157
  type QfaiValidationConfig = {
182
158
  failOn: FailOn;
159
+ /**
160
+ * Terms the specs must no longer state, such as an option a decision rejected.
161
+ * `qfai validate` warns on each line of a spec document that still holds one
162
+ * (QFAI-STORY-016). Unset means no term is checked.
163
+ */
164
+ staleTerms?: string[];
165
+ /** Hash-only identifiers checked against tracked names and current file bytes. */
166
+ forbiddenIdentifiers?: QfaiForbiddenIdentifier[];
183
167
  testStrategy: {
184
168
  /**
185
169
  * When true (default), `qfai validate` reports the silent-placeholder
@@ -189,42 +173,10 @@ type QfaiValidationConfig = {
189
173
  * Set to false to opt out while migrating an existing project.
190
174
  */
191
175
  forbidTestTodoStubs: boolean;
192
- /**
193
- * @deprecated A dead knob: no validation reads it, so setting it to true
194
- * raises no diagnostic. It is already removed from the shipped
195
- * `qfai.config.yaml` and will be removed from the type in the next major
196
- * release. As with paths.promptsDir, the default and the parsing stay as
197
- * they were until then (this is a public type, so existing TypeScript
198
- * consumers can keep referring to it as a `boolean`).
199
- */
200
- requireLayerTags: boolean;
201
- /**
202
- * @deprecated Treated the same as `testStrategy.requireLayerTags`.
203
- */
204
- requireSizeTags: boolean;
205
176
  };
206
177
  traceability: {
207
178
  testFileGlobs: string[];
208
179
  testFileExcludeGlobs: string[];
209
- /**
210
- * @deprecated Retired. No check reads it, so setting it changes nothing,
211
- * and loading it reports QFAI-CFG-001 as an error. It stays as an optional
212
- * field for the compatibility period only, so existing config object
213
- * literals keep type-checking.
214
- */
215
- brMustHaveSc?: boolean;
216
- /**
217
- * @deprecated Retired. The finding for a missing SC test reference has a
218
- * fixed severity, so this value is not read. It stays as an optional field
219
- * for the compatibility period only.
220
- */
221
- scNoTestSeverity?: TraceabilitySeverity;
222
- /**
223
- * @deprecated Retired. The orphan-contract finding no longer exists, so
224
- * this value is not read. It stays as an optional field for the
225
- * compatibility period only.
226
- */
227
- orphanContractsPolicy?: RetiredOrphanContractsPolicy;
228
180
  };
229
181
  };
230
182
  type QfaiOutputConfig = {
@@ -266,19 +218,15 @@ type QfaiUiuxConfig = {
266
218
  * key declares nothing.
267
219
  */
268
220
  surfacePaths?: string[];
269
- renderEvidence?: RenderEvidenceConfig;
270
221
  audit?: QfaiUiuxAuditConfig;
271
222
  };
272
223
  type QfaiPrototypingExecutionConfig = {
273
224
  targetUrl?: string | null;
274
225
  /**
275
- * Browser tool handed to the AI evaluator sub-agent.
276
- *
277
- * Only `"playwright"` is accepted. `"playwright-cli"` is retired:
278
- * `normalizePrototypingExecution` rejects the value, and the doctor probe
279
- * reports `D-DEPRECATED-PROBE` at `error`.
226
+ * Browser tool handed to the AI evaluator sub-agent. Only `"playwright"` is
227
+ * accepted.
280
228
  */
281
- browserTool: "playwright" | "playwright-cli";
229
+ browserTool: "playwright";
282
230
  };
283
231
  type QfaiPrototypingConfig = {
284
232
  execution?: QfaiPrototypingExecutionConfig;
@@ -382,6 +330,13 @@ type WorkflowMode = (typeof WORKFLOW_MODES)[number];
382
330
  * none of the three, and no mode is guessed in its place.
383
331
  */
384
332
  declare function readWorkflowMode(document: unknown): WorkflowMode | null;
333
+ /** The config issue an invalid `workflow.skipShipped` raises. */
334
+ declare const SKIPPED_WORKFLOWS_MESSAGE: string;
335
+ /**
336
+ * The shipped workflow names `workflow.skipShipped` lists, read from the parsed config document.
337
+ * `null` means the value is not a list of shipped names, and no entry is guessed in its place.
338
+ */
339
+ declare function readSkippedWorkflows(document: unknown): ReadonlySet<string> | null;
385
340
  declare function resolvePath(root: string, config: QfaiConfig, key: ConfigPathKey): string;
386
341
  /**
387
342
  * Why the file's `prototyping.primaryUiContract` was rejected, or `undefined` when the file sets
@@ -391,449 +346,20 @@ declare function resolvePath(root: string, config: QfaiConfig, key: ConfigPathKe
391
346
  declare function readRejectedPrimaryUiContract(loaded: ConfigLoadResult): string | undefined;
392
347
 
393
348
  type AtddTestKind = "e2e" | "api" | "integration";
394
- type AtddUnknownRefKind = "us" | "tc" | "conApi" | "conDb";
395
- type AtddUnknownRef = {
396
- file: string;
397
- token: string;
398
- kind: AtddUnknownRefKind;
399
- };
400
- type AtddForbiddenRef = {
401
- file: string;
402
- ids: string[];
403
- };
404
- type AtddSpecRefs = Map<string, Map<string, Set<string>>>;
405
- type AtddTraceabilityScan = {
406
- globs: string[];
407
- /**
408
- * Files collected, which is also the number an acceptance layer owns.
409
- *
410
- * The collector drops a file in no acceptance layer before it is counted,
411
- * so this is the count the coverage rules are computed from rather than a
412
- * match total a reader has to discount.
413
- */
414
- matchedFileCount: number;
415
- truncated: boolean;
416
- limit: number;
417
- /**
418
- * The selecting patterns whose files could not all be read, each with the
419
- * reason. The others were still scanned, so a reference missing from the
420
- * tests only these select is not evidence those tests hold none.
421
- */
422
- unreadable: string[];
423
- /**
424
- * Directories the scan could not read and read past, in POSIX form: relative
425
- * to the repository, or absolute where a scanned root lies outside it. The
426
- * rest of each pattern reaching one was still read, but no test inside the
427
- * directory is counted.
428
- */
429
- unreadableDirectories: string[];
430
- };
431
- /**
432
- * Obligation refs bucketed by ID kind.
433
- *
434
- * Shared by the two partitions the coverage result reports: `missing` (owed and
435
- * referenced nowhere) and `coveredByCarrierOnly` (owed, referenced, but from no
436
- * carrier that declares a runnable test).
437
- */
438
- type AtddObligationRefs = {
439
- us: string[];
440
- tc: string[];
441
- conApi: string[];
442
- conDb: string[];
443
- };
444
- type AtddTraceabilityMissing = AtddObligationRefs;
445
- type AtddCodeTraceabilityResult = {
446
- specsRoot: string;
447
- /**
448
- * The configured tests directory, resolved.
449
- *
450
- * The bound on the per-spec owner scan. Test paths are absolute, so a
451
- * checkout that itself lives under `/srv/integration/spec-0002/repo` has an
452
- * ancestor pair that reads exactly like the canonical layout, and a flat
453
- * `tests/integration/a.test.ts` inside it was attributed to `0002`.
454
- */
455
- testsRoot: string;
456
- contractsApiRoot: string;
457
- /**
458
- * Spec number -> the directory `collectSpecEntries` enumerated for it.
459
- *
460
- * Two questions, one map. **Existence** is not the same as `specUsIds.has(n)`:
461
- * a spec created moments ago has a directory and empty catalogues, and scope
462
- * logic that asks the id maps treats it as nonexistent — so a typo naming it
463
- * is handled as repo-wide when that spec's own gate would in fact own it.
464
- * **Its path** is the enumerated one, never `spec-${n}` rebuilt from the
465
- * number: on a case-sensitive filesystem a `SPEC-0001/` directory is valid and
466
- * kept verbatim by `listSpecDirs`, and a synthesised lower-case path would
467
- * send the CLI report and the GitHub annotation at a file that is not there.
468
- */
469
- declaredSpecDirs: Map<string, string>;
470
- /**
471
- * Scanned test file, normalised as the findings name it -> the spec whose
472
- * `<layer>/spec-NNNN/` directory holds it, at `paths.testsDir` or at a
473
- * package's own test root. A file in no such directory has no entry.
474
- */
475
- testFileSpecOwners: Map<string, string>;
476
- /**
477
- * Every declared `US-*`, active and deferred alike — the same
478
- * declared-not-owed distinction the contract sets draw. A deferred story is
479
- * still a known id, so an E2E test written ahead of its slice must not become
480
- * a `QFAI-ATDD-101` unknown reference.
481
- */
482
- specUsIds: Map<string, Set<string>>;
483
- /**
484
- * `US-*` refs excluded from the `QFAI-ATDD-111` obligation because their
485
- * story declares `- x-qfai-status: planned`, formatted as `SPEC-NNNN:US-…`.
486
- * Reported at `info` (`QFAI-ATDD-118`) so the deferral stays visible rather
487
- * than silently shrinking the gate — the same treatment `QFAI-ATDD-114` and
488
- * `-116` give the two contract kinds.
489
- *
490
- * Restricted to specs that owe `QFAI-ATDD-111` at all: under surface typing a
491
- * non-UI-bearing spec's stories are already outside the E2E gate, so nothing
492
- * there is deferred by the marker.
493
- */
494
- deferredUsIds: string[];
495
- specTcIds: Map<string, Set<string>>;
496
- /**
497
- * Every declared `API-NNNN`, active and deferred alike. This is the public
498
- * meaning the field has always had — adding `x-qfai-status: planned` defers
499
- * the test obligation, it does not un-declare the contract, so an external
500
- * consumer using this set for "is this ID declared?" must keep seeing it.
501
- */
502
- apiContractIds: Set<string>;
503
- /** The subset that carries the `QFAI-ATDD-113` obligation. */
504
- activeApiContractIds: Set<string>;
505
- /**
506
- * `API-NNNN` IDs excluded from the `QFAI-ATDD-113` obligation because their
507
- * contract declares `x-qfai-status: planned`. Reported as `info` so the
508
- * deferral stays visible instead of silently shrinking the gate.
509
- */
510
- deferredApiContractIds: Set<string>;
511
- contractsDbRoot: string;
512
- /** Every declared `DB-NNNN`, active and deferred alike. */
513
- dbContractIds: Set<string>;
514
- /** The subset that carries the `QFAI-ATDD-115` obligation. */
515
- activeDbContractIds: Set<string>;
516
- /** `DB-NNNN` IDs deferred by `-- x-qfai-status: planned`; reported at `info`. */
517
- deferredDbContractIds: Set<string>;
518
- refs: {
519
- us: AtddSpecRefs;
520
- tc: AtddSpecRefs;
521
- api: Map<string, Set<string>>;
522
- /**
523
- * `DB-NNNN` references found in integration tests. L3 Integration is the
524
- * layer whose declared scope is real-infrastructure integration including
525
- * the database, so it is the one that can actually exercise a DB contract.
526
- */
527
- db: Map<string, Set<string>>;
528
- };
529
- unknown: AtddUnknownRef[];
530
- forbidden: {
531
- tcInApi: AtddForbiddenRef[];
532
- tcInE2e: AtddForbiddenRef[];
533
- /** TC annotated under integration although its declared Level routes elsewhere. */
534
- tcInIntegration: AtddForbiddenRef[];
535
- };
536
- missing: AtddTraceabilityMissing;
537
- /**
538
- * Owed obligations whose every carrier declares no test a runner collects.
539
- *
540
- * Satisfaction is a text match, and the scan deliberately reads past test
541
- * code so a Gherkin feature or a markdown ledger can carry annotations. The
542
- * consequence is that a bullet list of IDs discharges `QFAI-ATDD-111` /
543
- * `-112` / `-113` / `-115` exactly as an executable acceptance test does, and
544
- * `missing: []` cannot tell the two apart. Reported at `info`
545
- * (`QFAI-ATDD-119`) and persisted into the summary artifact for the same
546
- * reason `unitComponentTcIds` is: a coverage scan must never leave "covered
547
- * by a test" and "covered by an ID written down" indistinguishable.
548
- *
549
- * Membership is decided per carrier by {@link hasRunnableTestStructure}, not
550
- * by extension: markdown never qualifies, a `.feature` with a `Scenario:`
551
- * does, and a `.test.ts` holding only an annotation comment does not — so
552
- * renaming the ledger cannot clear the obligation. Skip state is out of
553
- * scope, so this asserts a test is *declared*, never that it is enabled.
554
- *
555
- * Empty whenever `scan.truncated` is set or `scan.unreadable` or
556
- * `scan.unreadableDirectories` is not empty: an executable carrier may sit
557
- * past the file limit or in what was not read, so the claim is unproven and
558
- * suppressed rather than guessed.
559
- */
560
- coveredByCarrierOnly: AtddObligationRefs;
561
- /**
562
- * Formatted missing TC ref -> the test kind its declared `Level` routes to.
563
- * Lets the CLI phrase `QFAI-ATDD-112` (message, `suggested_action`, report)
564
- * per declared layer instead of naming `tests/integration/**` for every TC.
565
- */
566
- missingTcHomes: Map<string, AtddTestKind>;
567
- /**
568
- * `TC-*` refs excluded from the `QFAI-ATDD-112` obligation because their
569
- * declared `Level` is Unit or Component. Reported at `info`
570
- * (`QFAI-ATDD-117`) so the exclusion is visible rather than silent — the
571
- * shape a coverage scan must never take, since "nothing owed" and "nothing
572
- * scanned" are otherwise indistinguishable. No validate rule demands a test
573
- * for them.
574
- */
575
- unitComponentTcIds: string[];
576
- /**
577
- * `TC-*` refs declaring a `Level` of L4/API or L5/E2E.
578
- *
579
- * `catalog/test-layers.md` states that a `TC-*` row's `Level` stays within
580
- * L1-L3: L4's goal is a `API-NNNN` and L5's is a `US-*`, so an oracle that
581
- * derives to either means the obligation is filed under the wrong ID type —
582
- * not that the test case is an L4/L5 test.
583
- *
584
- * The routing table sends such a row to `tests/api/**` or `tests/e2e/**`
585
- * rather than rejecting it, so that the row is reported once by the rule
586
- * naming the real cause (`QFAI-ATDD-128`) instead of twice as "uncovered in
587
- * integration" and "forbidden in api".
588
- *
589
- * Every declared row, not only the ones missing an annotation: the defect is
590
- * the row's own `Level`, and covering it changes nothing about that.
591
- */
592
- misfiledLevelTcIds: string[];
593
- /**
594
- * Carrier files whose suite is bound through a variable, so nothing static —
595
- * including this scan — can say whether their tests run.
596
- *
597
- * Reported rather than counted, for the same reason `unitComponentTcIds` is:
598
- * the coverage gate's evidence here is a string in a file, and "nothing owed"
599
- * must not look like "nothing checked".
600
- */
601
- computedSuiteCarriers: string[];
602
- /**
603
- * `TC-*` refs a status marker in their own block suspends, each with the
604
- * status and — for `external` — where the obligation is really verified.
605
- *
606
- * Reported at `info` for the same reason `QFAI-ATDD-118` reports a deferred
607
- * story: an exit nobody can see is one nobody reviews.
608
- */
609
- deferredTcIds: DeferredTc[];
610
- /**
611
- * `TC-*` refs that claim `external` and name no verifier.
612
- *
613
- * They keep their obligation. The marker is not a way to say "not here": it
614
- * is a way to say where the obligation is met instead, and without that it
615
- * says nothing a reader can act on.
616
- */
617
- unsupportedTcStatusIds: string[];
618
- /** Test files outside the scanned roots; surfaced instead of dropped. */
619
- skippedTestFiles: string[];
620
- scan: AtddTraceabilityScan;
621
- };
622
349
  /** Test directory each `AtddTestKind` owns, for user-facing messages. */
623
350
  declare const ATDD_TEST_KIND_DIRS: Record<AtddTestKind, string>;
624
351
  /**
625
352
  * The same map rendered against the project's configured `paths.testsDir`.
626
353
  *
627
354
  * The scan already follows `testsDir`, so a project that renamed it to e.g.
628
- * `spec-tests` would otherwise be told by `QFAI-ATDD-112` to annotate
629
- * `tests/api/**` — a directory the scan never reads, leaving the finding
630
- * unclearable. A root-level layout (`testsDir: "."`) is the same trap in the
631
- * other direction: the scan reads `api/**` at the repository root, so the
355
+ * `acceptance` would otherwise be told to annotate `tests/api/**` — a directory
356
+ * the scan never reads. A root-level layout (`testsDir: "."`) is the same trap in
357
+ * the other direction: the scan reads `api/**` at the repository root, so the
632
358
  * `tests/` segment must be dropped rather than kept.
633
359
  * {@link ATDD_TEST_KIND_DIRS} stays as the default-shaped fallback for callers
634
360
  * with no config in hand.
635
361
  */
636
362
  declare function atddTestKindDirs(testsDirRelative: string): Record<AtddTestKind, string>;
637
- declare function evaluateAtddCodeTraceability(root: string, config: QfaiConfig): Promise<AtddCodeTraceabilityResult>;
638
- /**
639
- * Reads the declared `Level` of every TC in `06_Test-Cases.md` so the TC
640
- * obligation can be routed by declared layer instead of being hard-pinned to
641
- * `tests/integration/**`.
642
- *
643
- * Both shipped shapes are read, matching `parseTestCases` in
644
- * `core/atdd/scaffold.ts`: the heading form (`## TC-NNNN` plus `- Level: L4`
645
- * meta lines) and the table form. Heading form wins on duplicates; within
646
- * either form the first declaration wins. Every `TC-ID` table is scanned, not
647
- * just the first — a spec may split its catalogue across several tables, and
648
- * only scanning the first silently dropped the later tables' layers.
649
- */
650
- declare function collectTcLevels(rawTcText: string): Map<string, string>;
651
- declare function collectDeclaredTcIds(rawTcText: string): Set<string>;
652
- /**
653
- * Every heading-form TC id, whether or not the block declares a `Level`.
654
- *
655
- * The level collector yields a pair only when a `- Level:` line follows the
656
- * heading, so it cannot answer "does this spec declare this TC?" — a
657
- * level-less TC is still declared. {@link collectDeclaredTcIds} reads this
658
- * for that reason.
659
- */
660
- declare function collectHeadingTcIdsFrom(rawTcText: string): string[];
661
- /**
662
- * Where a declared `Level` routes its ATDD annotation obligation, or `null`
663
- * when it owes none at all (Unit / Component).
664
- *
665
- * **The single answer to "what does this `Level` mean for ATDD".** One
666
- * question answered in several places is several chances for the routing rule
667
- * and the exclusion rule to disagree about the same cell.
668
- *
669
- * `undefined` means the spec declares no `Level` for the TC (no column, no
670
- * row): that is not "no obligation", it is the default home.
671
- */
672
- declare function resolveAtddHomeKind(level: string | undefined): AtddTestKind | null;
673
- /**
674
- * Splits the declared `US-*` ids into those that still owe an E2E reference and
675
- * those a `- x-qfai-status: planned` marker defers.
676
- *
677
- * The deferred half is returned as formatted `SPEC-NNNN:US-…` refs — the shape
678
- * `narrowToScope` filters and the CLI prints — so a `--spec` run reports its own
679
- * deferrals only, exactly as it does for `QFAI-ATDD-117`.
680
- *
681
- * Only a spec inside `usObligationScope` can defer anything. A non-UI-bearing
682
- * spec's stories are already outside `QFAI-ATDD-111` under surface typing, so
683
- * reporting them at `QFAI-ATDD-118` would claim an obligation was suspended
684
- * that never existed — and its remediation would tell the implementer to add an
685
- * E2E annotation there, the annotation-only E2E that the surface-scope rule
686
- * exists to prevent.
687
- */
688
- /** A suspended test case, carried with what suspended it. */
689
- type DeferredTc = {
690
- /** `SPEC-NNNN:TC-NNNN`, the form every ref list here uses. */
691
- ref: string;
692
- status: TcVerificationStatus;
693
- /** Where the obligation is verified. Present whenever `status` is `external`. */
694
- verifiedBy?: string;
695
- };
696
- declare const PLANNED_CONTRACT_KEY = "x-qfai-status";
697
- /**
698
- * True when the contract declares itself not yet implemented.
699
- *
700
- * `/qfai-sdd` authors contracts in Phase 0 (Contracts-first) but slices them in
701
- * Phase 2, so between the second contract and the last slice every declared
702
- * `API-NNNN` would otherwise be a `QFAI-ATDD-113` error. The marker makes the
703
- * deferral explicit and reviewable in the contract itself.
704
- *
705
- * The marker is only honoured at the **document root**. A text scan that
706
- * accepted any indentation would let an `x-qfai-status: planned` on a single
707
- * OpenAPI operation defer the whole file, silently dropping the API-test
708
- * obligation for every other `API-NNNN` it declares. The document is therefore
709
- * parsed (YAML is a superset of JSON, so both contract formats go through the
710
- * same path) and only a top-level key counts. An unparseable document falls
711
- * back to a column-0 text match, which cannot see a nested key either.
712
- */
713
- declare function isPlannedApiContract(text: string): boolean;
714
- /**
715
- * What a test case says about where it is verified.
716
- *
717
- * `planned` is the `US-*` deferral, one layer down: the test is not written
718
- * yet, the obligation is suspended, and the marker keeps it visible rather than
719
- * silent.
720
- *
721
- * `external` is the state the story marker has no counterpart for. Some
722
- * acceptance criteria are true of the deployment rather than of the code — a
723
- * TLS floor, an HTTPS redirect terminated by the platform — and no layer the
724
- * annotation gate routes to can observe them. Before this, every exit was
725
- * closed: annotating anyway makes the gate green over a test that checks
726
- * something else, a waiver may not cover an error, and retiring the row walks
727
- * up the coverage rules until the requirement itself is deleted.
728
- */
729
- type TcVerificationStatus = "planned" | "external";
730
- /** A test case's declared status, with the pointer `external` requires. */
731
- type TcStatusDeclaration = {
732
- status: TcVerificationStatus;
733
- /** Where the obligation is verified. Required for `external`. */
734
- verifiedBy?: string;
735
- };
736
- /** The key an `external` test case names its real verifier with. */
737
- declare const TC_VERIFIED_BY_KEY = "x-qfai-verified-by";
738
- /**
739
- * Status markers declared in `06_Test-Cases.md`, by test case id.
740
- *
741
- * **Read from a `##`-or-deeper `TC-NNNN` block only, never from the table.**
742
- * That is the design, not a limitation. The field evidence behind this marker
743
- * is that two test cases looked identical from the spec's side — same level,
744
- * same note calling them deployment-bound — and only one of them actually was;
745
- * a marker cheap enough to write in a table cell would have been applied to
746
- * both, and the one that was observable in-process would have lost its only
747
- * real test.
748
- *
749
- * So deferring a test case costs a block of its own. The author has to lift the
750
- * row out of the table, write the status, and — for `external` — name where the
751
- * obligation is really checked. That is the price of the exit, and it is meant
752
- * to be paid deliberately.
753
- *
754
- * Fenced samples and HTML comments are masked first, on the same terms as
755
- * {@link collectTcLevels}: a block in a format example must not defer a real
756
- * test case.
757
- */
758
- declare function collectTcStatuses(rawTcText: string): Map<string, TcStatusDeclaration>;
759
- /**
760
- * Whether a declaration actually suspends the annotation obligation.
761
- *
762
- * `external` without a pointer does not. The pointer is the whole cost of the
763
- * marker: without it the line says only "not here", which is the blanket
764
- * silencer this exit exists to avoid being. Such a test case keeps its
765
- * obligation and is reported, so the marker cannot be written and forgotten.
766
- */
767
- declare function suspendsObligation(declaration: TcStatusDeclaration): boolean;
768
- /**
769
- * The `US-*` ids a spec pack defers from the `QFAI-ATDD-111` obligation.
770
- *
771
- * A story whose acceptance cannot be observed at E2E in this slice had no
772
- * in-band way to say so: `API-NNNN` and `DB-NNNN` both defer with
773
- * `x-qfai-status: planned`, while a `US-*` could only be left uncovered (a hard
774
- * `QFAI-ATDD-111` error), covered by a test asserting nothing, or erased by
775
- * declaring the whole spec non-user-facing. This is the per-story counterpart.
776
- *
777
- * Fenced samples and HTML comments are masked first, on the same terms as
778
- * {@link collectTcLevels}: an illustrative block showing the marker must not
779
- * silently drop a real story's obligation.
780
- *
781
- * A block opens at either shape {@link collectDeclaredUsIds} declares an id from — a
782
- * `##`-or-deeper heading ({@link US_HEADING_RE}) or a catalog list entry
783
- * ({@link US_LIST_ITEM_RE}) — and closes at the next opener or any heading, so
784
- * every declared story can carry the marker and none inherits a neighbour's.
785
- *
786
- * A catalog entry closes on its Markdown item boundary as well: its block ends
787
- * at the first non-blank line that does not reach the entry's *content* column —
788
- * the column its marker plus the whitespace after it ends at, which is where
789
- * Markdown puts the item's own children. Without that, prose or a blank line
790
- * after `- US-0001: …` left the entry open to the next heading, and a
791
- * document-level `- x-qfai-status: planned` written well outside it deferred
792
- * US-0001 — the very leak the document-root rule closes on the heading side.
793
- *
794
- * Comparing against the *marker* column instead would leave a narrower version
795
- * of the same leak: under a column-0 `- US-0001: …` a one-space-indented
796
- * ` - x-qfai-status: planned` clears the marker column while falling short of
797
- * the two columns a child needs, so Markdown reads it as a separate list item
798
- * and the entry must already be closed by then. The marker has to sit *inside*
799
- * the entry, indented to its content.
800
- */
801
- declare function collectPlannedUsIds(rawUsText: string): Set<string>;
802
- /**
803
- * The `US-*` ids a spec pack declares, read from the entries that declare them.
804
- *
805
- * An entry is a `##`-or-deeper heading ({@link US_HEADING_RE}) or a catalog
806
- * list item ({@link US_LIST_ITEM_RE}) — the two shapes
807
- * {@link collectPlannedUsIds} already opens a block at. A mention anywhere else
808
- * is prose about a story, not a declaration of one.
809
- *
810
- * What this replaces took the union of those entries and every loose
811
- * `US-NNNN` in the file, so any sentence naming an id declared it. A note
812
- * reserving a deleted story's id — the id is retired, do not reuse it — made
813
- * the story live again, and `QFAI-ATDD-111` then demanded an E2E reference
814
- * for something with no entry, no acceptance criteria and no behaviour. The
815
- * three ways out all cost something: an annotation with nothing behind it, the
816
- * note hidden in an HTML comment where the people it warns cannot read it, or
817
- * the id spelled so the scan misses it.
818
- *
819
- * This is the rule {@link collectDeclaredTcIds} already applies one document
820
- * over, for the same reason: an id read from somewhere other than its
821
- * declaration carries an obligation nobody wrote.
822
- *
823
- * Fenced samples and HTML comments are masked first, so an illustrative block
824
- * showing the entry shape declares nothing.
825
- */
826
- declare function collectDeclaredUsIds(rawUsText: string): Set<string>;
827
- /**
828
- * True when a DB contract declares itself not yet implemented.
829
- *
830
- * The counterpart of `isPlannedApiContract`, and deliberately a different rule:
831
- * the API marker counts at the document root only, while this one counts on any
832
- * line of the file. Exported so the shipped estimator procedure, which has to
833
- * describe both forms, can be pinned against the behaviour rather than restate
834
- * it and drift.
835
- */
836
- declare function isPlannedDbContract(text: string): boolean;
837
363
  /**
838
364
  * The extensions a set of globs names, and whether any of them was too large to
839
365
  * write out.
@@ -892,11 +418,6 @@ type TestLayerRoots = {
892
418
  /** Shared directory crosswalk for acceptance checks and migration. */
893
419
  declare function createTestLayerRoots(root: string, config: QfaiConfig): TestLayerRoots;
894
420
  declare function resolveTestKind(filePath: string, roots: TestLayerRoots): AtddTestKind | null;
895
- /**
896
- * The owning spec of a test file, read as {@link layerSpecNumber} reads it, for
897
- * callers outside the scan. `null` for a file in no acceptance layer.
898
- */
899
- declare function atddTestOwnerProbe(root: string, config: QfaiConfig): (absolutePath: string) => string | null;
900
421
  /**
901
422
  * Whether a directory carries a package manifest, memoised per scan.
902
423
  *
@@ -1004,22 +525,6 @@ declare function extractAcSpecNumber(acId: string): string | null;
1004
525
  declare function extractCaseSpecNumber(caseId: string): string | null;
1005
526
  declare function extractScSpecNumber(scId: string): string | null;
1006
527
 
1007
- declare const CANONICAL_PROTOTYPING_SURFACES: readonly ["web", "mobile", "desktop", "cli", "mixed"];
1008
- type CanonicalPrototypingSurface = (typeof CANONICAL_PROTOTYPING_SURFACES)[number];
1009
-
1010
- /**
1011
- * Prototyping surface helpers.
1012
- *
1013
- * Kept here: surface-type helpers and a `playwright-cli` constant used by
1014
- * the capture wiring.
1015
- */
1016
-
1017
- declare const PROTOTYPING_SUPPORTED_SURFACES: readonly ["web", "mobile", "desktop", "mixed"];
1018
- type PrototypingSurface = CanonicalPrototypingSurface;
1019
- type PrototypingBrowserTool = "playwright-cli";
1020
- declare function isValidPrototypingSurface(value: unknown): value is PrototypingSurface;
1021
- declare function isSupportedPrototypingSurface(surface: string): surface is PrototypingSurface;
1022
-
1023
528
  /**
1024
529
  * The input source preflight selected: the discussion pack, or an imported
1025
530
  * specification the caller names with `importPath`. An explicit user
@@ -1170,7 +675,7 @@ declare function locateToolAgainstProject(root: string): Promise<{
1170
675
  * top-level `node_modules` is linked to another checkout's is covered by the
1171
676
  * second.
1172
677
  */
1173
- declare function reachedThroughLinkedNodeModules(root: string, packageDir: string): Promise<boolean>;
678
+ declare function reachedThroughLinkedNodeModules(root: string, packageDir: string, walkFrom?: string): Promise<boolean>;
1174
679
  /**
1175
680
  * Whether `packageDir` sits under what `<dir>/node_modules` really points at.
1176
681
  *
@@ -1202,7 +707,8 @@ declare function resolvesThroughOwnNodeModules(dir: string, packageDir: string):
1202
707
  declare function classifyAgainstDeclaration(declaringDir: string, packageDir: string): boolean;
1203
708
  /**
1204
709
  * The nearest directory at or above `root` whose `package.json` declares
1205
- * `qfai`, or `null` when none does.
710
+ * `qfai`, or `null` when none does. The walk follows `root` as given, and the
711
+ * directory returned is a real path.
1206
712
  *
1207
713
  * Every dependency field counts. A tool named in `devDependencies` is as
1208
714
  * declared as one in `dependencies` — the project said which copy it wants
@@ -1241,8 +747,9 @@ declare function classifyToolLocation(root: string, packageDir: string, link?: {
1241
747
  * and Node reports the link's target. Only the nearest entry counts, because it
1242
748
  * is the one `npx` runs; a farther link to the same checkout means the checkout
1243
749
  * was run by its path. The `node_modules` directory's own real path is returned
1244
- * because it can be a link as well, to another checkout's. Both operands must
1245
- * already be real paths.
750
+ * because it can be a link as well, to another checkout's. `root` is walked as
751
+ * given, so a junction on the way up is followed the way `npx` follows it;
752
+ * `packageDir` must already be a real path.
1246
753
  */
1247
754
  declare function findLinkingNodeModules(root: string, packageDir: string): Promise<string | null>;
1248
755
  /**
@@ -1272,6 +779,9 @@ declare function validateContracts(root: string, config: QfaiConfig): Promise<Is
1272
779
  declare function validateUiContractParse(root: string, config: QfaiConfig): Promise<Issue[]>;
1273
780
  declare function lintSql(text: string, file: string): Issue[];
1274
781
 
782
+ declare const CANONICAL_PROTOTYPING_SURFACES: readonly ["web", "mobile", "desktop", "cli", "mixed"];
783
+ type CanonicalPrototypingSurface = (typeof CANONICAL_PROTOTYPING_SURFACES)[number];
784
+
1275
785
  type SurfaceType = CanonicalPrototypingSurface | "non-ui";
1276
786
  declare const DISCUSSION_UI_BEARING_SURFACES: Set<SurfaceType>;
1277
787
  declare const DISCUSSION_NON_UI_SURFACES: Set<SurfaceType>;
@@ -1280,137 +790,6 @@ declare function isDiscussionUiBearingSurfaceType(surface: SurfaceType): boolean
1280
790
  declare function isNonUiDiscussionSurface(surface: SurfaceType): boolean;
1281
791
  declare function requiresVisualBrowserEvidence(surface: SurfaceType): boolean;
1282
792
 
1283
- type BrowserQaPhase = "smoke" | "interaction" | "visual" | "accessibility";
1284
- declare const BROWSER_QA_PHASES: readonly BrowserQaPhase[];
1285
- type BrowserQaPhaseStatus = "passed" | "executed" | "failed" | "skipped";
1286
- type BrowserQaFindingSeverity = "info" | "warn" | "warning" | "error";
1287
- interface BrowserQaFinding {
1288
- phase: BrowserQaPhase;
1289
- severity: BrowserQaFindingSeverity;
1290
- summary: string;
1291
- detail: string;
1292
- screen_id?: string;
1293
- selector?: string;
1294
- evidence_refs: string[];
1295
- repair_suggestions: string[];
1296
- category?: BrowserQaPhase;
1297
- message?: string;
1298
- route?: string;
1299
- repair_hint?: string;
1300
- }
1301
- type BrowserQaScreenContractRef = {
1302
- screen_id: string;
1303
- route?: string;
1304
- primary_tasks?: string[];
1305
- };
1306
- type BrowserQaPhaseResult = {
1307
- phase: BrowserQaPhase;
1308
- status: BrowserQaPhaseStatus;
1309
- findings: BrowserQaFinding[];
1310
- repair_suggestions: string[];
1311
- evidence_refs: string[];
1312
- checks_performed: string[];
1313
- skippedReason?: string;
1314
- screen_id?: string;
1315
- route?: string;
1316
- };
1317
- type BrowserQaInput = {
1318
- htmlContent?: string;
1319
- targetUrl?: string;
1320
- routes?: string[];
1321
- surface: SurfaceType;
1322
- required?: boolean;
1323
- executionSource?: "html" | "url" | "none";
1324
- screenContracts?: BrowserQaScreenContractRef[];
1325
- };
1326
- type BrowserQaRunResult = {
1327
- phases: BrowserQaPhaseResult[];
1328
- provider: string;
1329
- timestamp: string;
1330
- };
1331
- type BrowserQaSummary = {
1332
- providerId: string;
1333
- phases: Record<BrowserQaPhase, {
1334
- status: BrowserQaPhaseStatus;
1335
- findingsCount: number;
1336
- checksCount: number;
1337
- passed?: number;
1338
- failed?: number;
1339
- }>;
1340
- totalFindings: number;
1341
- totalRepairs: number;
1342
- skippedReasons: string[];
1343
- };
1344
- interface BrowserQaProvider {
1345
- providerId: string;
1346
- canRun(surface: SurfaceType): boolean;
1347
- runSmoke(input: BrowserQaInput): Promise<BrowserQaPhaseResult>;
1348
- runInteraction(input: BrowserQaInput): Promise<BrowserQaPhaseResult>;
1349
- runVisual(input: BrowserQaInput): Promise<BrowserQaPhaseResult>;
1350
- runAccessibility(input: BrowserQaInput): Promise<BrowserQaPhaseResult>;
1351
- }
1352
-
1353
- declare function runBrowserQaOrchestrated(input: BrowserQaInput, provider?: BrowserQaProvider): Promise<BrowserQaRunResult>;
1354
- declare function summarizeBrowserQaResult(result: BrowserQaRunResult): BrowserQaSummary;
1355
-
1356
- /**
1357
- * Render evidence types
1358
- *
1359
- * Canonical types for the render evidence capture pipeline.
1360
- */
1361
- type RenderCaptureTarget = {
1362
- targetId: string;
1363
- route?: string;
1364
- descriptor?: string;
1365
- viewport: string;
1366
- width?: number;
1367
- height?: number;
1368
- };
1369
- type RenderCaptureAdapter = {
1370
- captureScreenshot(target: RenderCaptureTarget, outputDir: string): Promise<string>;
1371
- captureHtml?(target: RenderCaptureTarget, outputDir: string): Promise<string>;
1372
- };
1373
- type RenderEvidenceEntryResult = {
1374
- capture_id: string;
1375
- target: string;
1376
- status: "captured" | "skipped" | "failed";
1377
- screenshot_path?: string;
1378
- html_path?: string;
1379
- viewport: string;
1380
- reason?: string;
1381
- };
1382
- type RenderRunnerResult = {
1383
- entries: RenderEvidenceEntryResult[];
1384
- filesWritten: string[];
1385
- surface?: string;
1386
- mode?: string;
1387
- generatedAt?: string;
1388
- coverageSummary?: {
1389
- total: number;
1390
- captured: number;
1391
- failed: number;
1392
- skipped: number;
1393
- };
1394
- };
1395
-
1396
- /**
1397
- * Render runner
1398
- *
1399
- * Executes render capture for a list of targets, producing truthful
1400
- * evidence entries. No placeholder paths are emitted.
1401
- */
1402
-
1403
- /**
1404
- * Run render capture for all targets.
1405
- *
1406
- * - adapter present + capture succeeds → `captured` with real file paths
1407
- * - adapter present + capture throws → `failed` with reason
1408
- * - adapter absent → `skipped` with reason
1409
- */
1410
- declare function runRenderCapture(targets: RenderCaptureTarget[], outputDir: string, adapter?: RenderCaptureAdapter, options?: {
1411
- required?: boolean;
1412
- }): Promise<RenderRunnerResult>;
1413
-
1414
793
  declare const ARCHETYPES: readonly ["minimal", "bold", "corporate", "playful", "organic", "tech", "elegant", "casual"];
1415
794
  type Archetype = (typeof ARCHETYPES)[number];
1416
795
  declare const COLOR_KEYS: readonly ["primary", "secondary", "accent", "surface", "surface_muted", "text", "text_muted", "danger", "warning", "success", "border", "overlay"];
@@ -1442,9 +821,8 @@ type DesignMd = {
1442
821
  * The published theme the token values were taken from, named so a
1443
822
  * reader can install it.
1444
823
  *
1445
- * Everything downstream is exact about those values — the prototyping loop hashes
1446
- * them, `certify` re-scans them, every literal in every capture is
1447
- * checked against them — and until this field existed there was nothing
824
+ * Everything downstream is exact about those values — `certify` re-scans them,
825
+ * every literal in every capture is checked against them — and until this field existed there was nothing
1448
826
  * under the exactness: twelve colours, three families, four radii and
1449
827
  * three shadows were written from a sentence of prose about the
1450
828
  * archetype.
@@ -1517,16 +895,12 @@ declare function validateDesignMd(d: DesignMd): ValidationIssue[];
1517
895
  declare const DESIGN_MD_SAMPLE_MARKER = "QFAI-SAMPLE-DESIGN-MD";
1518
896
  /**
1519
897
  * True when `text` is still (or is derived from) a DESIGN.md the package
1520
- * ships as a sample. Used to stop `/qfai-sdd` Phase 0 from sha256-freezing
898
+ * ships as a sample. Used to stop `/qfai-sdd` Phase 0 from adopting
1521
899
  * an unauthored brand as the project's brand contract, and to surface the
1522
900
  * same condition from `qfai validate` (QFAI-DCON-034).
1523
901
  *
1524
- * Two detectors, because the marker only covers samples seeded by releases
1525
- * that carry it:
1526
- * 1. `DESIGN_MD_SAMPLE_MARKER` — current samples, survives any edit that
1527
- * leaves the comment in place.
1528
- * 2. Legacy content fingerprint — samples seeded before the marker
1529
- * existed, which `qfai init` will never overwrite.
902
+ * The test is `DESIGN_MD_SAMPLE_MARKER`, which survives any edit that leaves
903
+ * the comment in place.
1530
904
  *
1531
905
  * `text` is `unknown` rather than `string` because this is re-exported from
1532
906
  * `core/index.ts` and so is reachable from untyped JavaScript, where a caller
@@ -1540,4 +914,4 @@ declare function isUnreplacedDesignMdSample(text: unknown): boolean;
1540
914
  /** Run one ordered spec-to-story migration step from the current project root. */
1541
915
  declare function runMigrationStep(step: number, argv: string[]): Promise<number>;
1542
916
 
1543
- export { AC_GHERKIN_SECTION, ARCHETYPES, ATDD_TEST_KIND_DIRS, type Archetype, type AtddCodeTraceabilityResult, type AtddForbiddenRef, type AtddObligationRefs, type AtddSpecRefs, type AtddTestKind, type AtddTraceabilityMissing, type AtddTraceabilityScan, type AtddUnknownRef, type AtddUnknownRefKind, BROWSER_QA_PHASES, COLOR_KEYS, type ColorKey, type ConfigLoadResult, type ConfigPathKey, type ConfigSearchResult, DESIGN_MD_SAMPLE_MARKER, DISCUSSION_NON_UI_SURFACES, DISCUSSION_UI_BEARING_SURFACES, type DeferredTc, type DesignMd, type DesignMdColors, type DesignMdFonts, type DesignMdRadius, type DesignMdShadow, FONT_KEYS, type FailOn, type FenceMaskOptions, type FontKey, ID_PREFIXES, type IdFormatPrefix, type IdPrefix, type InvalidIdOccurrence, type Issue, type IssueCategory, type IssueLocation, type IssueSeverity, type OrphanContractsPolicy, type OutputFormat, PLANNED_CONTRACT_KEY, PROTOTYPING_SUPPORTED_SURFACES, type ParseError, type ParseResult, type PrototypingBrowserTool, type PrototypingSurface, type QfaiConfig, type QfaiOutputConfig, type QfaiPaths, type QfaiPrototypingConfig, type QfaiPrototypingExecutionConfig, type QfaiReportConfig, type QfaiReviewProfile, type QfaiRoutingEntry, type QfaiUiuxAuditConfig, type QfaiUiuxConfig, type QfaiValidationConfig, RADIUS_KEYS, type RadiusKey, type ReportData, type RunSddPreflightOptions, SHADOW_KEYS, type SddPreflightResult, type SddPreflightSource, type SddPreflightStatus, type ShadowKey, TC_VERIFIED_BY_KEY, type TcStatusDeclaration, type TcVerificationStatus, type TestFileExtensions, type TestLayerRoots, type TraceabilitySeverity, VISUAL_BROWSER_SURFACES, type ValidationCounts, type ValidationIssue, type ValidationOptions, type ValidationProfile, type ValidationResult, type ValidationTimings, type ValidationWaiverAction, type ValidationWaiverDowngradeTo, type ValidationWaiverEntry, type ValidationWaiverMatch, type ValidationWaiverScope, type ValidationWaiverSeverity, type ValidationWaiverSuppressed, type ValidationWaivers, WORKFLOW_MODE_MESSAGE, type WorkflowMode, atddAcceptanceLayerFilter, atddAcceptanceTestGlobs, atddTestKindDirs, atddTestOwnerProbe, classifyAgainstDeclaration, classifyToolLocation, collectDeclaredTcIds, collectDeclaredUsIds, collectHeadingTcIdsFrom, collectPlannedUsIds, collectTcLevels, collectTcStatuses, countIssues, createReportData, createTestLayerRoots, defaultConfig, deriveAtddFilePattern, deriveTestFileExtensions, evaluateAtddCodeTraceability, extractAcSpecNumber, extractAllIds, extractBrSpecNumber, extractCapSpecNumber, extractCaseSpecNumber, extractIds, extractInvalidIdOccurrences, extractInvalidIds, extractScSpecNumber, extractSpecNumber, extractUsSpecNumber, findConfigRoot, findDeclaringDir, findLinkingNodeModules, findPackageJsonUpward, formatReportJson, formatReportMarkdown, getConfigPath, isDiscussionUiBearingSurfaceType, isNonUiDiscussionSurface, isPlannedApiContract, isPlannedDbContract, isSupportedPrototypingSurface, isUnreplacedDesignMdSample, isValidPrototypingSurface, lintSql, loadConfig, locateToolAgainstProject, maskFencedCodeBlocks, packageRootProbe, parseDesignMd, reachedThroughLinkedNodeModules, readRejectedPrimaryUiContract, readTestFileExtensions, readWorkflowMode, requiresVisualBrowserEvidence, resolveAtddHomeKind, resolvePath, resolveTestKind, resolveToolPackageDir, resolveToolVersion, resolvesThroughOwnNodeModules, routingEntryName, runBrowserQaOrchestrated, runMigrationStep, runRenderCapture, runSddPreflight, summarizeBrowserQaResult, suspendsObligation, validateContracts, validateDesignMd, validateProject, validateUiContractParse };
917
+ export { AC_GHERKIN_SECTION, ARCHETYPES, ATDD_TEST_KIND_DIRS, type Archetype, type AtddTestKind, COLOR_KEYS, type ColorKey, type ConfigLoadResult, type ConfigPathKey, type ConfigSearchResult, DESIGN_MD_SAMPLE_MARKER, DISCUSSION_NON_UI_SURFACES, DISCUSSION_UI_BEARING_SURFACES, type DesignMd, type DesignMdColors, type DesignMdFonts, type DesignMdRadius, type DesignMdShadow, FONT_KEYS, type FailOn, type FenceMaskOptions, type FontKey, ID_PREFIXES, type IdFormatPrefix, type IdPrefix, type InvalidIdOccurrence, type Issue, type IssueCategory, type IssueLocation, type IssueSeverity, type OutputFormat, type ParseError, type ParseResult, type QfaiConfig, type QfaiForbiddenIdentifier, type QfaiOutputConfig, type QfaiPaths, type QfaiPrototypingConfig, type QfaiPrototypingExecutionConfig, type QfaiReportConfig, type QfaiReviewProfile, type QfaiRoutingEntry, type QfaiUiuxAuditConfig, type QfaiUiuxConfig, type QfaiValidationConfig, RADIUS_KEYS, type RadiusKey, type ReportData, type RunSddPreflightOptions, SHADOW_KEYS, SKIPPED_WORKFLOWS_MESSAGE, type SddPreflightResult, type SddPreflightSource, type SddPreflightStatus, type ShadowKey, type TestFileExtensions, type TestLayerRoots, VISUAL_BROWSER_SURFACES, type ValidationCounts, type ValidationIssue, type ValidationOptions, type ValidationProfile, type ValidationResult, type ValidationTimings, type ValidationWaiverAction, type ValidationWaiverDowngradeTo, type ValidationWaiverEntry, type ValidationWaiverMatch, type ValidationWaiverScope, type ValidationWaiverSeverity, type ValidationWaiverSuppressed, type ValidationWaivers, WORKFLOW_MODE_MESSAGE, type WorkflowMode, atddAcceptanceLayerFilter, atddAcceptanceTestGlobs, atddTestKindDirs, classifyAgainstDeclaration, classifyToolLocation, countIssues, createReportData, createTestLayerRoots, defaultConfig, deriveAtddFilePattern, deriveTestFileExtensions, extractAcSpecNumber, extractAllIds, extractBrSpecNumber, extractCapSpecNumber, extractCaseSpecNumber, extractIds, extractInvalidIdOccurrences, extractInvalidIds, extractScSpecNumber, extractSpecNumber, extractUsSpecNumber, findConfigRoot, findDeclaringDir, findLinkingNodeModules, findPackageJsonUpward, formatReportJson, formatReportMarkdown, getConfigPath, isDiscussionUiBearingSurfaceType, isNonUiDiscussionSurface, isUnreplacedDesignMdSample, lintSql, loadConfig, locateToolAgainstProject, maskFencedCodeBlocks, packageRootProbe, parseDesignMd, reachedThroughLinkedNodeModules, readRejectedPrimaryUiContract, readSkippedWorkflows, readTestFileExtensions, readWorkflowMode, requiresVisualBrowserEvidence, resolvePath, resolveTestKind, resolveToolPackageDir, resolveToolVersion, resolvesThroughOwnNodeModules, routingEntryName, runMigrationStep, runSddPreflight, validateContracts, validateDesignMd, validateProject, validateUiContractParse };