qfai 2.1.1 → 2.2.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 (185) hide show
  1. package/README.md +56 -36
  2. package/assets/defaults/agent-routing/qfai-implement.yml +9 -0
  3. package/assets/defaults/agent-routing/qfai-triage.yml +18 -0
  4. package/assets/defaults/workflows/answer-in-session.yml +8 -0
  5. package/assets/defaults/workflows/quarantine-flaky.yml +3 -0
  6. package/assets/defaults/workflows/repair-consistency.yml +1 -0
  7. package/assets/defaults/workflows/run-operation.yml +11 -0
  8. package/assets/defaults/workflows/run-owned-cleanup.yml +12 -0
  9. package/assets/defaults/workflows/write-procedure.yml +18 -0
  10. package/assets/graph/codegraph-native.mjs +174 -0
  11. package/assets/graph/graphify-native.py +72 -0
  12. package/assets/init/.claude/settings.json +9 -9
  13. package/assets/init/.codex/hooks.json +8 -8
  14. package/assets/init/.qfai/assistant/agent/frontend-engineer.md +5 -0
  15. package/assets/init/.qfai/assistant/agent/product-experience-architect.md +4 -0
  16. package/assets/init/.qfai/assistant/agent/product-surface-reviewer.md +7 -0
  17. package/assets/init/.qfai/assistant/agent/requirements-analyst.md +8 -0
  18. package/assets/init/.qfai/assistant/rule/communication.md +18 -8
  19. package/assets/init/.qfai/assistant/rule/constitution.md +31 -15
  20. package/assets/init/.qfai/assistant/rule/drift-protocol.md +119 -2
  21. package/assets/init/.qfai/assistant/rule/references/asset-authoring.md +6 -4
  22. package/assets/init/.qfai/assistant/rule/references/codex-delegation.md +27 -0
  23. package/assets/init/.qfai/assistant/rule/references/griller-recommendations.md +21 -9
  24. package/assets/init/.qfai/assistant/rule/references/worker-edit-boundary.md +48 -0
  25. package/assets/init/.qfai/assistant/rule/review-convergence.md +25 -12
  26. package/assets/init/.qfai/assistant/rule/shared-skill-delegation-baseline.md +16 -25
  27. package/assets/init/.qfai/assistant/rule/shared-skill-operating-baseline.md +16 -11
  28. package/assets/init/.qfai/assistant/rule/ui-definition-protocol.md +6 -1
  29. package/assets/init/.qfai/assistant/rule/workflow.md +2 -1
  30. package/assets/init/.qfai/assistant/skill/qfai-configure/SKILL.md +3 -3
  31. package/assets/init/.qfai/assistant/skill/qfai-discussion/SKILL.md +6 -0
  32. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/discussion-completion-matrix.md +20 -15
  33. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui-bearing-playbook.md +2 -4
  34. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux_best_practices.md +1 -6
  35. package/assets/init/.qfai/assistant/skill/qfai-discussion/templates/uiux/00_index.md +0 -16
  36. package/assets/init/.qfai/assistant/skill/qfai-grilling/SKILL.md +30 -20
  37. package/assets/init/.qfai/assistant/skill/qfai-grilling/references/delegated-session.md +4 -3
  38. package/assets/init/.qfai/assistant/skill/qfai-grilling/references/session-record.md +8 -2
  39. package/assets/init/.qfai/assistant/skill/qfai-implement/SKILL.md +16 -3
  40. package/assets/init/.qfai/assistant/skill/qfai-implement/references/parallelization-policy.md +9 -1
  41. package/assets/init/.qfai/assistant/skill/qfai-implement/references/red-admissibility.md +46 -0
  42. package/assets/init/.qfai/assistant/skill/qfai-implement/references/ui-affecting.md +5 -2
  43. package/assets/init/.qfai/assistant/skill/qfai-maintain/SKILL.md +3 -3
  44. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/SKILL.md +12 -6
  45. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/references/migration-guide.md +1 -265
  46. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/references/migration-placement.md +273 -0
  47. package/assets/init/.qfai/assistant/skill/qfai-prototyping/SKILL.md +4 -3
  48. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/review-payload-schema.md +2 -3
  49. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/reviewer-prompt.md +17 -1
  50. package/assets/init/.qfai/assistant/skill/qfai-run/SKILL.md +52 -37
  51. package/assets/init/.qfai/assistant/skill/qfai-run/references/codex-cloud-install.md +80 -0
  52. package/assets/init/.qfai/assistant/skill/qfai-run/references/evidence-delivery.md +322 -0
  53. package/assets/init/.qfai/assistant/skill/qfai-run/references/extraction.md +83 -49
  54. package/assets/init/.qfai/assistant/skill/qfai-run/references/operator-screens.md +226 -11
  55. package/assets/init/.qfai/assistant/skill/qfai-run/references/plan.md +6 -3
  56. package/assets/init/.qfai/assistant/skill/qfai-run/references/reached-step-observations.md +132 -0
  57. package/assets/init/.qfai/assistant/skill/qfai-run/references/stage-points.md +14 -0
  58. package/assets/init/.qfai/assistant/skill/qfai-run/scripts/codex-cloud-install.mjs +117 -0
  59. package/assets/init/.qfai/assistant/skill/qfai-sdd/SKILL.md +4 -3
  60. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/contract-artifact-rules.md +1 -0
  61. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-anti-patterns.md +23 -5
  62. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-md-authoring.md +20 -7
  63. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/requirements-decomposition.md +8 -0
  64. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-execution-playbook.md +29 -4
  65. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-phase-checklists.md +1 -1
  66. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-pre-draft-grilling.md +4 -1
  67. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-quality-gate.md +1 -1
  68. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-triage.md +159 -4
  69. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/spec-traceability-rules.md +14 -2
  70. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-contract-guide.md +146 -0
  71. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-design-contract-normalization.md +4 -29
  72. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/change-request.md +38 -1
  73. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/contracts/ui-contract.sample.yaml +52 -5
  74. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/decisions.md +12 -0
  75. package/assets/init/.qfai/assistant/skill/qfai-triage/SKILL.md +34 -21
  76. package/assets/init/.qfai/assistant/skill/qfai-verify/SKILL.md +5 -4
  77. package/assets/init/.qfai/assistant/skill/qfai-verify/references/context-load.md +9 -6
  78. package/assets/init/.qfai/assistant/skill/qfai-verify/references/verify-output-contract.md +80 -15
  79. package/assets/init/.qfai/assistant/skill/web-research/SKILL.md +35 -21
  80. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/brave-search/config.toml +2 -4
  81. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/brave-search/mcp-config.json +9 -5
  82. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/.mcp.json +1 -2
  83. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/config.toml +3 -3
  84. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/mcp-config.json +11 -8
  85. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/playwright/config.toml +1 -1
  86. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/playwright/mcp-config.json +7 -3
  87. package/assets/init/.qfai/assistant/step/common-design-md/STEP.md +33 -20
  88. package/assets/init/.qfai/assistant/step/common-gate-run/STEP.md +124 -8
  89. package/assets/init/.qfai/assistant/step/common-grilling-record/STEP.md +10 -2
  90. package/assets/init/.qfai/assistant/step/common-policy-check/STEP.md +1 -0
  91. package/assets/init/.qfai/assistant/step/common-review-cycle/STEP.md +26 -9
  92. package/assets/init/.qfai/assistant/step/discussion-interview/STEP.md +15 -8
  93. package/assets/init/.qfai/assistant/step/discussion-oq/STEP.md +5 -1
  94. package/assets/init/.qfai/assistant/step/discussion-pack/STEP.md +6 -1
  95. package/assets/init/.qfai/assistant/step/discussion-research/STEP.md +2 -0
  96. package/assets/init/.qfai/assistant/step/discussion-uiux/STEP.md +6 -4
  97. package/assets/init/.qfai/assistant/step/implement-backport/STEP.md +1 -1
  98. package/assets/init/.qfai/assistant/step/implement-benchmark/STEP.md +7 -4
  99. package/assets/init/.qfai/assistant/step/implement-dep-bump/STEP.md +11 -2
  100. package/assets/init/.qfai/assistant/step/implement-diagnose/STEP.md +2 -0
  101. package/assets/init/.qfai/assistant/step/implement-oracle-parity/STEP.md +2 -2
  102. package/assets/init/.qfai/assistant/step/implement-procedure/STEP.md +69 -0
  103. package/assets/init/.qfai/assistant/step/implement-quarantine/STEP.md +24 -7
  104. package/assets/init/.qfai/assistant/step/implement-refactor/STEP.md +2 -2
  105. package/assets/init/.qfai/assistant/step/implement-regression-fix/STEP.md +7 -6
  106. package/assets/init/.qfai/assistant/step/implement-retire/STEP.md +6 -3
  107. package/assets/init/.qfai/assistant/step/implement-revert/STEP.md +4 -3
  108. package/assets/init/.qfai/assistant/step/implement-seam/STEP.md +6 -5
  109. package/assets/init/.qfai/assistant/step/implement-stress-harness/STEP.md +34 -3
  110. package/assets/init/.qfai/assistant/step/implement-sweep/STEP.md +4 -3
  111. package/assets/init/.qfai/assistant/step/implement-tdd/STEP.md +39 -5
  112. package/assets/init/.qfai/assistant/step/implement-tooling/STEP.md +22 -2
  113. package/assets/init/.qfai/assistant/step/maintain-edit/STEP.md +18 -14
  114. package/assets/init/.qfai/assistant/step/prototyping-grill/STEP.md +4 -3
  115. package/assets/init/.qfai/assistant/step/prototyping-loop/STEP.md +4 -0
  116. package/assets/init/.qfai/assistant/step/prototyping-preflight/STEP.md +9 -3
  117. package/assets/init/.qfai/assistant/step/sdd-contract/STEP.md +11 -0
  118. package/assets/init/.qfai/assistant/step/sdd-flow/STEP.md +2 -0
  119. package/assets/init/.qfai/assistant/step/sdd-story/STEP.md +10 -0
  120. package/assets/init/.qfai/assistant/step/sdd-triage/STEP.md +34 -10
  121. package/assets/init/.qfai/assistant/step/triage-answer/STEP.md +7 -6
  122. package/assets/init/.qfai/assistant/step/triage-close/STEP.md +33 -23
  123. package/assets/init/.qfai/assistant/step/triage-cluster/STEP.md +5 -3
  124. package/assets/init/.qfai/assistant/step/triage-decompose/STEP.md +2 -0
  125. package/assets/init/.qfai/assistant/step/triage-dedupe/STEP.md +4 -0
  126. package/assets/init/.qfai/assistant/step/triage-handoff/STEP.md +13 -4
  127. package/assets/init/.qfai/assistant/step/triage-investigate/STEP.md +8 -5
  128. package/assets/init/.qfai/assistant/step/triage-operate/STEP.md +50 -0
  129. package/assets/init/.qfai/assistant/step/triage-owned-cleanup/STEP.md +86 -0
  130. package/assets/init/.qfai/assistant/step/triage-request-info/STEP.md +2 -0
  131. package/assets/init/.qfai/assistant/step/triage-security-intake/STEP.md +6 -3
  132. package/assets/init/.qfai/assistant/step/verify-change-note/STEP.md +1 -0
  133. package/assets/init/.qfai/assistant/step/verify-commit/STEP.md +37 -9
  134. package/assets/init/.qfai/assistant/step/verify-external/STEP.md +5 -2
  135. package/assets/init/.qfai/assistant/step/verify-qfai-gate/STEP.md +14 -16
  136. package/assets/init/.qfai/assistant/step/verify-release-notes/STEP.md +1 -0
  137. package/assets/init/.qfai/assistant/step/verify-repeat-run/STEP.md +46 -4
  138. package/assets/init/.qfai/assistant/step/verify-repo-gate/STEP.md +133 -30
  139. package/assets/init/root/.agents/rules/ai-readable-markdown.md +2 -2
  140. package/assets/init/root/.agents/rules/api-budget.md +187 -8
  141. package/assets/init/root/.agents/rules/document-schema.md +17 -0
  142. package/assets/init/root/.agents/rules/documentation-clarity.md +163 -34
  143. package/assets/init/root/.agents/rules/graph-exploration.md +150 -0
  144. package/assets/init/root/.agents/rules/grilling.md +100 -43
  145. package/assets/init/root/.agents/rules/minimal-implementation.md +3 -3
  146. package/assets/init/root/.agents/rules/reminders.json +27 -18
  147. package/assets/init/root/.agents/rules/session-feedback.md +131 -0
  148. package/assets/init/root/.agents/rules/temporary-files.md +33 -0
  149. package/assets/init/root/.agents/rules/untrusted-content.md +2 -2
  150. package/assets/init/root/.agents/rules/user-questions.md +51 -40
  151. package/assets/init/root/.github/workflows/qfai-docs.yml +3 -3
  152. package/assets/init/root/.github/workflows/qfai-tests.yml +88 -35
  153. package/assets/init/root/.github/workflows/qfai-validate.yml +2 -2
  154. package/assets/init/root/AGENTS.md +24 -22
  155. package/assets/sandbox-templates/default-deny.yaml +9 -3
  156. package/assets/schemas/workflow/extraction.schema.json +89 -5
  157. package/assets/scripts/check-mdschema.mjs +77 -8
  158. package/assets/uix-rev/comparison-review.md +1 -1
  159. package/assets/uix-rev/contracts-review.md +1 -2
  160. package/dist/cli/index.cjs +9444 -10071
  161. package/dist/cli/index.cjs.map +1 -1
  162. package/dist/cli/index.mjs +9421 -10041
  163. package/dist/cli/index.mjs.map +1 -1
  164. package/dist/graph/cli.cjs +1568 -0
  165. package/dist/graph/cli.cjs.map +1 -0
  166. package/dist/graph/cli.d.cts +9 -0
  167. package/dist/graph/cli.d.ts +9 -0
  168. package/dist/graph/cli.mjs +1541 -0
  169. package/dist/graph/cli.mjs.map +1 -0
  170. package/dist/graph/hook.cjs +817 -0
  171. package/dist/graph/hook.cjs.map +1 -0
  172. package/dist/graph/hook.d.cts +9 -0
  173. package/dist/graph/hook.d.ts +9 -0
  174. package/dist/graph/hook.mjs +792 -0
  175. package/dist/graph/hook.mjs.map +1 -0
  176. package/dist/index.cjs +7634 -8517
  177. package/dist/index.cjs.map +1 -1
  178. package/dist/index.d.cts +146 -658
  179. package/dist/index.d.ts +146 -658
  180. package/dist/index.mjs +7462 -8318
  181. package/dist/index.mjs.map +1 -1
  182. package/dist/process-C21nTvkT.d.cts +12 -0
  183. package/dist/process-C21nTvkT.d.ts +12 -0
  184. package/package.json +4 -6
  185. package/assets/init/root/CLAUDE.md +0 -46
package/dist/index.d.cts CHANGED
@@ -1,3 +1,5 @@
1
+ import { G as GraphProcess } from './process-C21nTvkT.cjs';
2
+
1
3
  type IssueSeverity = "info" | "warning" | "error";
2
4
  type IssueCategory = "canonical" | "change";
3
5
  type IssueLocation = {
@@ -93,6 +95,16 @@ type ValidationTimings = {
93
95
  uiuxBudgetMs: number;
94
96
  htmlMockMs: number;
95
97
  htmlMockBudgetMs: number;
98
+ /**
99
+ * What each validator of the UI/UX group took, slowest first. The validators run side by side
100
+ * on one event loop, so each time includes waiting for the others: the parts overlap, do not
101
+ * add up to `uiuxMs`, and can credit a fast validator with another's time. They say where to
102
+ * look, not how the time divides.
103
+ */
104
+ uiuxParts: readonly {
105
+ name: string;
106
+ ms: number;
107
+ }[];
96
108
  };
97
109
  type ValidationResult = {
98
110
  toolVersion: string;
@@ -129,42 +141,14 @@ type ValidationResult = {
129
141
  timings?: ValidationTimings;
130
142
  };
131
143
 
132
- type RenderEvidenceConfig = {
133
- enabled?: boolean;
134
- viewports?: string[];
135
- out?: string;
136
- baseUrl?: string;
137
- failOpen?: boolean;
138
- };
139
-
140
144
  type FailOn = "never" | "warning" | "error";
141
145
  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
146
  type QfaiPaths = {
158
147
  contractsDir: string;
159
148
  specsDir: string;
160
149
  discussionDir: string;
161
150
  outDir: string;
162
151
  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
152
  srcDir: string;
169
153
  testsDir: string;
170
154
  /**
@@ -178,6 +162,10 @@ type QfaiPaths = {
178
162
  */
179
163
  migrationsDir?: string;
180
164
  };
165
+ type QfaiForbiddenIdentifier = {
166
+ sha256: string;
167
+ byteLength: number;
168
+ };
181
169
  type QfaiValidationConfig = {
182
170
  failOn: FailOn;
183
171
  /**
@@ -186,6 +174,8 @@ type QfaiValidationConfig = {
186
174
  * (QFAI-STORY-016). Unset means no term is checked.
187
175
  */
188
176
  staleTerms?: string[];
177
+ /** Hash-only identifiers checked against tracked names and current file bytes. */
178
+ forbiddenIdentifiers?: QfaiForbiddenIdentifier[];
189
179
  testStrategy: {
190
180
  /**
191
181
  * When true (default), `qfai validate` reports the silent-placeholder
@@ -195,42 +185,10 @@ type QfaiValidationConfig = {
195
185
  * Set to false to opt out while migrating an existing project.
196
186
  */
197
187
  forbidTestTodoStubs: boolean;
198
- /**
199
- * @deprecated A dead knob: no validation reads it, so setting it to true
200
- * raises no diagnostic. It is already removed from the shipped
201
- * `qfai.config.yaml` and will be removed from the type in the next major
202
- * release. As with paths.promptsDir, the default and the parsing stay as
203
- * they were until then (this is a public type, so existing TypeScript
204
- * consumers can keep referring to it as a `boolean`).
205
- */
206
- requireLayerTags: boolean;
207
- /**
208
- * @deprecated Treated the same as `testStrategy.requireLayerTags`.
209
- */
210
- requireSizeTags: boolean;
211
188
  };
212
189
  traceability: {
213
190
  testFileGlobs: string[];
214
191
  testFileExcludeGlobs: string[];
215
- /**
216
- * @deprecated Retired. No check reads it, so setting it changes nothing,
217
- * and loading it reports QFAI-CFG-001 as an error. It stays as an optional
218
- * field for the compatibility period only, so existing config object
219
- * literals keep type-checking.
220
- */
221
- brMustHaveSc?: boolean;
222
- /**
223
- * @deprecated Retired. The finding for a missing SC test reference has a
224
- * fixed severity, so this value is not read. It stays as an optional field
225
- * for the compatibility period only.
226
- */
227
- scNoTestSeverity?: TraceabilitySeverity;
228
- /**
229
- * @deprecated Retired. The orphan-contract finding no longer exists, so
230
- * this value is not read. It stays as an optional field for the
231
- * compatibility period only.
232
- */
233
- orphanContractsPolicy?: RetiredOrphanContractsPolicy;
234
192
  };
235
193
  };
236
194
  type QfaiOutputConfig = {
@@ -272,19 +230,15 @@ type QfaiUiuxConfig = {
272
230
  * key declares nothing.
273
231
  */
274
232
  surfacePaths?: string[];
275
- renderEvidence?: RenderEvidenceConfig;
276
233
  audit?: QfaiUiuxAuditConfig;
277
234
  };
278
235
  type QfaiPrototypingExecutionConfig = {
279
236
  targetUrl?: string | null;
280
237
  /**
281
- * Browser tool handed to the AI evaluator sub-agent.
282
- *
283
- * Only `"playwright"` is accepted. `"playwright-cli"` is retired:
284
- * `normalizePrototypingExecution` rejects the value, and the doctor probe
285
- * reports `D-DEPRECATED-PROBE` at `error`.
238
+ * Browser tool handed to the AI evaluator sub-agent. Only `"playwright"` is
239
+ * accepted.
286
240
  */
287
- browserTool: "playwright" | "playwright-cli";
241
+ browserTool: "playwright";
288
242
  };
289
243
  type QfaiPrototypingConfig = {
290
244
  execution?: QfaiPrototypingExecutionConfig;
@@ -404,449 +358,20 @@ declare function resolvePath(root: string, config: QfaiConfig, key: ConfigPathKe
404
358
  declare function readRejectedPrimaryUiContract(loaded: ConfigLoadResult): string | undefined;
405
359
 
406
360
  type AtddTestKind = "e2e" | "api" | "integration";
407
- type AtddUnknownRefKind = "us" | "tc" | "conApi" | "conDb";
408
- type AtddUnknownRef = {
409
- file: string;
410
- token: string;
411
- kind: AtddUnknownRefKind;
412
- };
413
- type AtddForbiddenRef = {
414
- file: string;
415
- ids: string[];
416
- };
417
- type AtddSpecRefs = Map<string, Map<string, Set<string>>>;
418
- type AtddTraceabilityScan = {
419
- globs: string[];
420
- /**
421
- * Files collected, which is also the number an acceptance layer owns.
422
- *
423
- * The collector drops a file in no acceptance layer before it is counted,
424
- * so this is the count the coverage rules are computed from rather than a
425
- * match total a reader has to discount.
426
- */
427
- matchedFileCount: number;
428
- truncated: boolean;
429
- limit: number;
430
- /**
431
- * The selecting patterns whose files could not all be read, each with the
432
- * reason. The others were still scanned, so a reference missing from the
433
- * tests only these select is not evidence those tests hold none.
434
- */
435
- unreadable: string[];
436
- /**
437
- * Directories the scan could not read and read past, in POSIX form: relative
438
- * to the repository, or absolute where a scanned root lies outside it. The
439
- * rest of each pattern reaching one was still read, but no test inside the
440
- * directory is counted.
441
- */
442
- unreadableDirectories: string[];
443
- };
444
- /**
445
- * Obligation refs bucketed by ID kind.
446
- *
447
- * Shared by the two partitions the coverage result reports: `missing` (owed and
448
- * referenced nowhere) and `coveredByCarrierOnly` (owed, referenced, but from no
449
- * carrier that declares a runnable test).
450
- */
451
- type AtddObligationRefs = {
452
- us: string[];
453
- tc: string[];
454
- conApi: string[];
455
- conDb: string[];
456
- };
457
- type AtddTraceabilityMissing = AtddObligationRefs;
458
- type AtddCodeTraceabilityResult = {
459
- specsRoot: string;
460
- /**
461
- * The configured tests directory, resolved.
462
- *
463
- * The bound on the per-spec owner scan. Test paths are absolute, so a
464
- * checkout that itself lives under `/srv/integration/spec-0002/repo` has an
465
- * ancestor pair that reads exactly like the canonical layout, and a flat
466
- * `tests/integration/a.test.ts` inside it was attributed to `0002`.
467
- */
468
- testsRoot: string;
469
- contractsApiRoot: string;
470
- /**
471
- * Spec number -> the directory `collectSpecEntries` enumerated for it.
472
- *
473
- * Two questions, one map. **Existence** is not the same as `specUsIds.has(n)`:
474
- * a spec created moments ago has a directory and empty catalogues, and scope
475
- * logic that asks the id maps treats it as nonexistent — so a typo naming it
476
- * is handled as repo-wide when that spec's own gate would in fact own it.
477
- * **Its path** is the enumerated one, never `spec-${n}` rebuilt from the
478
- * number: on a case-sensitive filesystem a `SPEC-0001/` directory is valid and
479
- * kept verbatim by `listSpecDirs`, and a synthesised lower-case path would
480
- * send the CLI report and the GitHub annotation at a file that is not there.
481
- */
482
- declaredSpecDirs: Map<string, string>;
483
- /**
484
- * Scanned test file, normalised as the findings name it -> the spec whose
485
- * `<layer>/spec-NNNN/` directory holds it, at `paths.testsDir` or at a
486
- * package's own test root. A file in no such directory has no entry.
487
- */
488
- testFileSpecOwners: Map<string, string>;
489
- /**
490
- * Every declared `US-*`, active and deferred alike — the same
491
- * declared-not-owed distinction the contract sets draw. A deferred story is
492
- * still a known id, so an E2E test written ahead of its slice must not become
493
- * a `QFAI-ATDD-101` unknown reference.
494
- */
495
- specUsIds: Map<string, Set<string>>;
496
- /**
497
- * `US-*` refs excluded from the `QFAI-ATDD-111` obligation because their
498
- * story declares `- x-qfai-status: planned`, formatted as `SPEC-NNNN:US-…`.
499
- * Reported at `info` (`QFAI-ATDD-118`) so the deferral stays visible rather
500
- * than silently shrinking the gate — the same treatment `QFAI-ATDD-114` and
501
- * `-116` give the two contract kinds.
502
- *
503
- * Restricted to specs that owe `QFAI-ATDD-111` at all: under surface typing a
504
- * non-UI-bearing spec's stories are already outside the E2E gate, so nothing
505
- * there is deferred by the marker.
506
- */
507
- deferredUsIds: string[];
508
- specTcIds: Map<string, Set<string>>;
509
- /**
510
- * Every declared `API-NNNN`, active and deferred alike. This is the public
511
- * meaning the field has always had — adding `x-qfai-status: planned` defers
512
- * the test obligation, it does not un-declare the contract, so an external
513
- * consumer using this set for "is this ID declared?" must keep seeing it.
514
- */
515
- apiContractIds: Set<string>;
516
- /** The subset that carries the `QFAI-ATDD-113` obligation. */
517
- activeApiContractIds: Set<string>;
518
- /**
519
- * `API-NNNN` IDs excluded from the `QFAI-ATDD-113` obligation because their
520
- * contract declares `x-qfai-status: planned`. Reported as `info` so the
521
- * deferral stays visible instead of silently shrinking the gate.
522
- */
523
- deferredApiContractIds: Set<string>;
524
- contractsDbRoot: string;
525
- /** Every declared `DB-NNNN`, active and deferred alike. */
526
- dbContractIds: Set<string>;
527
- /** The subset that carries the `QFAI-ATDD-115` obligation. */
528
- activeDbContractIds: Set<string>;
529
- /** `DB-NNNN` IDs deferred by `-- x-qfai-status: planned`; reported at `info`. */
530
- deferredDbContractIds: Set<string>;
531
- refs: {
532
- us: AtddSpecRefs;
533
- tc: AtddSpecRefs;
534
- api: Map<string, Set<string>>;
535
- /**
536
- * `DB-NNNN` references found in integration tests. L3 Integration is the
537
- * layer whose declared scope is real-infrastructure integration including
538
- * the database, so it is the one that can actually exercise a DB contract.
539
- */
540
- db: Map<string, Set<string>>;
541
- };
542
- unknown: AtddUnknownRef[];
543
- forbidden: {
544
- tcInApi: AtddForbiddenRef[];
545
- tcInE2e: AtddForbiddenRef[];
546
- /** TC annotated under integration although its declared Level routes elsewhere. */
547
- tcInIntegration: AtddForbiddenRef[];
548
- };
549
- missing: AtddTraceabilityMissing;
550
- /**
551
- * Owed obligations whose every carrier declares no test a runner collects.
552
- *
553
- * Satisfaction is a text match, and the scan deliberately reads past test
554
- * code so a Gherkin feature or a markdown ledger can carry annotations. The
555
- * consequence is that a bullet list of IDs discharges `QFAI-ATDD-111` /
556
- * `-112` / `-113` / `-115` exactly as an executable acceptance test does, and
557
- * `missing: []` cannot tell the two apart. Reported at `info`
558
- * (`QFAI-ATDD-119`) and persisted into the summary artifact for the same
559
- * reason `unitComponentTcIds` is: a coverage scan must never leave "covered
560
- * by a test" and "covered by an ID written down" indistinguishable.
561
- *
562
- * Membership is decided per carrier by {@link hasRunnableTestStructure}, not
563
- * by extension: markdown never qualifies, a `.feature` with a `Scenario:`
564
- * does, and a `.test.ts` holding only an annotation comment does not — so
565
- * renaming the ledger cannot clear the obligation. Skip state is out of
566
- * scope, so this asserts a test is *declared*, never that it is enabled.
567
- *
568
- * Empty whenever `scan.truncated` is set or `scan.unreadable` or
569
- * `scan.unreadableDirectories` is not empty: an executable carrier may sit
570
- * past the file limit or in what was not read, so the claim is unproven and
571
- * suppressed rather than guessed.
572
- */
573
- coveredByCarrierOnly: AtddObligationRefs;
574
- /**
575
- * Formatted missing TC ref -> the test kind its declared `Level` routes to.
576
- * Lets the CLI phrase `QFAI-ATDD-112` (message, `suggested_action`, report)
577
- * per declared layer instead of naming `tests/integration/**` for every TC.
578
- */
579
- missingTcHomes: Map<string, AtddTestKind>;
580
- /**
581
- * `TC-*` refs excluded from the `QFAI-ATDD-112` obligation because their
582
- * declared `Level` is Unit or Component. Reported at `info`
583
- * (`QFAI-ATDD-117`) so the exclusion is visible rather than silent — the
584
- * shape a coverage scan must never take, since "nothing owed" and "nothing
585
- * scanned" are otherwise indistinguishable. No validate rule demands a test
586
- * for them.
587
- */
588
- unitComponentTcIds: string[];
589
- /**
590
- * `TC-*` refs declaring a `Level` of L4/API or L5/E2E.
591
- *
592
- * `catalog/test-layers.md` states that a `TC-*` row's `Level` stays within
593
- * L1-L3: L4's goal is a `API-NNNN` and L5's is a `US-*`, so an oracle that
594
- * derives to either means the obligation is filed under the wrong ID type —
595
- * not that the test case is an L4/L5 test.
596
- *
597
- * The routing table sends such a row to `tests/api/**` or `tests/e2e/**`
598
- * rather than rejecting it, so that the row is reported once by the rule
599
- * naming the real cause (`QFAI-ATDD-128`) instead of twice as "uncovered in
600
- * integration" and "forbidden in api".
601
- *
602
- * Every declared row, not only the ones missing an annotation: the defect is
603
- * the row's own `Level`, and covering it changes nothing about that.
604
- */
605
- misfiledLevelTcIds: string[];
606
- /**
607
- * Carrier files whose suite is bound through a variable, so nothing static —
608
- * including this scan — can say whether their tests run.
609
- *
610
- * Reported rather than counted, for the same reason `unitComponentTcIds` is:
611
- * the coverage gate's evidence here is a string in a file, and "nothing owed"
612
- * must not look like "nothing checked".
613
- */
614
- computedSuiteCarriers: string[];
615
- /**
616
- * `TC-*` refs a status marker in their own block suspends, each with the
617
- * status and — for `external` — where the obligation is really verified.
618
- *
619
- * Reported at `info` for the same reason `QFAI-ATDD-118` reports a deferred
620
- * story: an exit nobody can see is one nobody reviews.
621
- */
622
- deferredTcIds: DeferredTc[];
623
- /**
624
- * `TC-*` refs that claim `external` and name no verifier.
625
- *
626
- * They keep their obligation. The marker is not a way to say "not here": it
627
- * is a way to say where the obligation is met instead, and without that it
628
- * says nothing a reader can act on.
629
- */
630
- unsupportedTcStatusIds: string[];
631
- /** Test files outside the scanned roots; surfaced instead of dropped. */
632
- skippedTestFiles: string[];
633
- scan: AtddTraceabilityScan;
634
- };
635
361
  /** Test directory each `AtddTestKind` owns, for user-facing messages. */
636
362
  declare const ATDD_TEST_KIND_DIRS: Record<AtddTestKind, string>;
637
363
  /**
638
364
  * The same map rendered against the project's configured `paths.testsDir`.
639
365
  *
640
366
  * The scan already follows `testsDir`, so a project that renamed it to e.g.
641
- * `spec-tests` would otherwise be told by `QFAI-ATDD-112` to annotate
642
- * `tests/api/**` — a directory the scan never reads, leaving the finding
643
- * unclearable. A root-level layout (`testsDir: "."`) is the same trap in the
644
- * other direction: the scan reads `api/**` at the repository root, so the
367
+ * `acceptance` would otherwise be told to annotate `tests/api/**` — a directory
368
+ * the scan never reads. A root-level layout (`testsDir: "."`) is the same trap in
369
+ * the other direction: the scan reads `api/**` at the repository root, so the
645
370
  * `tests/` segment must be dropped rather than kept.
646
371
  * {@link ATDD_TEST_KIND_DIRS} stays as the default-shaped fallback for callers
647
372
  * with no config in hand.
648
373
  */
649
374
  declare function atddTestKindDirs(testsDirRelative: string): Record<AtddTestKind, string>;
650
- declare function evaluateAtddCodeTraceability(root: string, config: QfaiConfig): Promise<AtddCodeTraceabilityResult>;
651
- /**
652
- * Reads the declared `Level` of every TC in `06_Test-Cases.md` so the TC
653
- * obligation can be routed by declared layer instead of being hard-pinned to
654
- * `tests/integration/**`.
655
- *
656
- * Both shipped shapes are read, matching `parseTestCases` in
657
- * `core/atdd/scaffold.ts`: the heading form (`## TC-NNNN` plus `- Level: L4`
658
- * meta lines) and the table form. Heading form wins on duplicates; within
659
- * either form the first declaration wins. Every `TC-ID` table is scanned, not
660
- * just the first — a spec may split its catalogue across several tables, and
661
- * only scanning the first silently dropped the later tables' layers.
662
- */
663
- declare function collectTcLevels(rawTcText: string): Map<string, string>;
664
- declare function collectDeclaredTcIds(rawTcText: string): Set<string>;
665
- /**
666
- * Every heading-form TC id, whether or not the block declares a `Level`.
667
- *
668
- * The level collector yields a pair only when a `- Level:` line follows the
669
- * heading, so it cannot answer "does this spec declare this TC?" — a
670
- * level-less TC is still declared. {@link collectDeclaredTcIds} reads this
671
- * for that reason.
672
- */
673
- declare function collectHeadingTcIdsFrom(rawTcText: string): string[];
674
- /**
675
- * Where a declared `Level` routes its ATDD annotation obligation, or `null`
676
- * when it owes none at all (Unit / Component).
677
- *
678
- * **The single answer to "what does this `Level` mean for ATDD".** One
679
- * question answered in several places is several chances for the routing rule
680
- * and the exclusion rule to disagree about the same cell.
681
- *
682
- * `undefined` means the spec declares no `Level` for the TC (no column, no
683
- * row): that is not "no obligation", it is the default home.
684
- */
685
- declare function resolveAtddHomeKind(level: string | undefined): AtddTestKind | null;
686
- /**
687
- * Splits the declared `US-*` ids into those that still owe an E2E reference and
688
- * those a `- x-qfai-status: planned` marker defers.
689
- *
690
- * The deferred half is returned as formatted `SPEC-NNNN:US-…` refs — the shape
691
- * `narrowToScope` filters and the CLI prints — so a `--spec` run reports its own
692
- * deferrals only, exactly as it does for `QFAI-ATDD-117`.
693
- *
694
- * Only a spec inside `usObligationScope` can defer anything. A non-UI-bearing
695
- * spec's stories are already outside `QFAI-ATDD-111` under surface typing, so
696
- * reporting them at `QFAI-ATDD-118` would claim an obligation was suspended
697
- * that never existed — and its remediation would tell the implementer to add an
698
- * E2E annotation there, the annotation-only E2E that the surface-scope rule
699
- * exists to prevent.
700
- */
701
- /** A suspended test case, carried with what suspended it. */
702
- type DeferredTc = {
703
- /** `SPEC-NNNN:TC-NNNN`, the form every ref list here uses. */
704
- ref: string;
705
- status: TcVerificationStatus;
706
- /** Where the obligation is verified. Present whenever `status` is `external`. */
707
- verifiedBy?: string;
708
- };
709
- declare const PLANNED_CONTRACT_KEY = "x-qfai-status";
710
- /**
711
- * True when the contract declares itself not yet implemented.
712
- *
713
- * `/qfai-sdd` authors contracts in Phase 0 (Contracts-first) but slices them in
714
- * Phase 2, so between the second contract and the last slice every declared
715
- * `API-NNNN` would otherwise be a `QFAI-ATDD-113` error. The marker makes the
716
- * deferral explicit and reviewable in the contract itself.
717
- *
718
- * The marker is only honoured at the **document root**. A text scan that
719
- * accepted any indentation would let an `x-qfai-status: planned` on a single
720
- * OpenAPI operation defer the whole file, silently dropping the API-test
721
- * obligation for every other `API-NNNN` it declares. The document is therefore
722
- * parsed (YAML is a superset of JSON, so both contract formats go through the
723
- * same path) and only a top-level key counts. An unparseable document falls
724
- * back to a column-0 text match, which cannot see a nested key either.
725
- */
726
- declare function isPlannedApiContract(text: string): boolean;
727
- /**
728
- * What a test case says about where it is verified.
729
- *
730
- * `planned` is the `US-*` deferral, one layer down: the test is not written
731
- * yet, the obligation is suspended, and the marker keeps it visible rather than
732
- * silent.
733
- *
734
- * `external` is the state the story marker has no counterpart for. Some
735
- * acceptance criteria are true of the deployment rather than of the code — a
736
- * TLS floor, an HTTPS redirect terminated by the platform — and no layer the
737
- * annotation gate routes to can observe them. Before this, every exit was
738
- * closed: annotating anyway makes the gate green over a test that checks
739
- * something else, a waiver may not cover an error, and retiring the row walks
740
- * up the coverage rules until the requirement itself is deleted.
741
- */
742
- type TcVerificationStatus = "planned" | "external";
743
- /** A test case's declared status, with the pointer `external` requires. */
744
- type TcStatusDeclaration = {
745
- status: TcVerificationStatus;
746
- /** Where the obligation is verified. Required for `external`. */
747
- verifiedBy?: string;
748
- };
749
- /** The key an `external` test case names its real verifier with. */
750
- declare const TC_VERIFIED_BY_KEY = "x-qfai-verified-by";
751
- /**
752
- * Status markers declared in `06_Test-Cases.md`, by test case id.
753
- *
754
- * **Read from a `##`-or-deeper `TC-NNNN` block only, never from the table.**
755
- * That is the design, not a limitation. The field evidence behind this marker
756
- * is that two test cases looked identical from the spec's side — same level,
757
- * same note calling them deployment-bound — and only one of them actually was;
758
- * a marker cheap enough to write in a table cell would have been applied to
759
- * both, and the one that was observable in-process would have lost its only
760
- * real test.
761
- *
762
- * So deferring a test case costs a block of its own. The author has to lift the
763
- * row out of the table, write the status, and — for `external` — name where the
764
- * obligation is really checked. That is the price of the exit, and it is meant
765
- * to be paid deliberately.
766
- *
767
- * Fenced samples and HTML comments are masked first, on the same terms as
768
- * {@link collectTcLevels}: a block in a format example must not defer a real
769
- * test case.
770
- */
771
- declare function collectTcStatuses(rawTcText: string): Map<string, TcStatusDeclaration>;
772
- /**
773
- * Whether a declaration actually suspends the annotation obligation.
774
- *
775
- * `external` without a pointer does not. The pointer is the whole cost of the
776
- * marker: without it the line says only "not here", which is the blanket
777
- * silencer this exit exists to avoid being. Such a test case keeps its
778
- * obligation and is reported, so the marker cannot be written and forgotten.
779
- */
780
- declare function suspendsObligation(declaration: TcStatusDeclaration): boolean;
781
- /**
782
- * The `US-*` ids a spec pack defers from the `QFAI-ATDD-111` obligation.
783
- *
784
- * A story whose acceptance cannot be observed at E2E in this slice had no
785
- * in-band way to say so: `API-NNNN` and `DB-NNNN` both defer with
786
- * `x-qfai-status: planned`, while a `US-*` could only be left uncovered (a hard
787
- * `QFAI-ATDD-111` error), covered by a test asserting nothing, or erased by
788
- * declaring the whole spec non-user-facing. This is the per-story counterpart.
789
- *
790
- * Fenced samples and HTML comments are masked first, on the same terms as
791
- * {@link collectTcLevels}: an illustrative block showing the marker must not
792
- * silently drop a real story's obligation.
793
- *
794
- * A block opens at either shape {@link collectDeclaredUsIds} declares an id from — a
795
- * `##`-or-deeper heading ({@link US_HEADING_RE}) or a catalog list entry
796
- * ({@link US_LIST_ITEM_RE}) — and closes at the next opener or any heading, so
797
- * every declared story can carry the marker and none inherits a neighbour's.
798
- *
799
- * A catalog entry closes on its Markdown item boundary as well: its block ends
800
- * at the first non-blank line that does not reach the entry's *content* column —
801
- * the column its marker plus the whitespace after it ends at, which is where
802
- * Markdown puts the item's own children. Without that, prose or a blank line
803
- * after `- US-0001: …` left the entry open to the next heading, and a
804
- * document-level `- x-qfai-status: planned` written well outside it deferred
805
- * US-0001 — the very leak the document-root rule closes on the heading side.
806
- *
807
- * Comparing against the *marker* column instead would leave a narrower version
808
- * of the same leak: under a column-0 `- US-0001: …` a one-space-indented
809
- * ` - x-qfai-status: planned` clears the marker column while falling short of
810
- * the two columns a child needs, so Markdown reads it as a separate list item
811
- * and the entry must already be closed by then. The marker has to sit *inside*
812
- * the entry, indented to its content.
813
- */
814
- declare function collectPlannedUsIds(rawUsText: string): Set<string>;
815
- /**
816
- * The `US-*` ids a spec pack declares, read from the entries that declare them.
817
- *
818
- * An entry is a `##`-or-deeper heading ({@link US_HEADING_RE}) or a catalog
819
- * list item ({@link US_LIST_ITEM_RE}) — the two shapes
820
- * {@link collectPlannedUsIds} already opens a block at. A mention anywhere else
821
- * is prose about a story, not a declaration of one.
822
- *
823
- * What this replaces took the union of those entries and every loose
824
- * `US-NNNN` in the file, so any sentence naming an id declared it. A note
825
- * reserving a deleted story's id — the id is retired, do not reuse it — made
826
- * the story live again, and `QFAI-ATDD-111` then demanded an E2E reference
827
- * for something with no entry, no acceptance criteria and no behaviour. The
828
- * three ways out all cost something: an annotation with nothing behind it, the
829
- * note hidden in an HTML comment where the people it warns cannot read it, or
830
- * the id spelled so the scan misses it.
831
- *
832
- * This is the rule {@link collectDeclaredTcIds} already applies one document
833
- * over, for the same reason: an id read from somewhere other than its
834
- * declaration carries an obligation nobody wrote.
835
- *
836
- * Fenced samples and HTML comments are masked first, so an illustrative block
837
- * showing the entry shape declares nothing.
838
- */
839
- declare function collectDeclaredUsIds(rawUsText: string): Set<string>;
840
- /**
841
- * True when a DB contract declares itself not yet implemented.
842
- *
843
- * The counterpart of `isPlannedApiContract`, and deliberately a different rule:
844
- * the API marker counts at the document root only, while this one counts on any
845
- * line of the file. Exported so the shipped estimator procedure, which has to
846
- * describe both forms, can be pinned against the behaviour rather than restate
847
- * it and drift.
848
- */
849
- declare function isPlannedDbContract(text: string): boolean;
850
375
  /**
851
376
  * The extensions a set of globs names, and whether any of them was too large to
852
377
  * write out.
@@ -905,11 +430,6 @@ type TestLayerRoots = {
905
430
  /** Shared directory crosswalk for acceptance checks and migration. */
906
431
  declare function createTestLayerRoots(root: string, config: QfaiConfig): TestLayerRoots;
907
432
  declare function resolveTestKind(filePath: string, roots: TestLayerRoots): AtddTestKind | null;
908
- /**
909
- * The owning spec of a test file, read as {@link layerSpecNumber} reads it, for
910
- * callers outside the scan. `null` for a file in no acceptance layer.
911
- */
912
- declare function atddTestOwnerProbe(root: string, config: QfaiConfig): (absolutePath: string) => string | null;
913
433
  /**
914
434
  * Whether a directory carries a package manifest, memoised per scan.
915
435
  *
@@ -1017,22 +537,6 @@ declare function extractAcSpecNumber(acId: string): string | null;
1017
537
  declare function extractCaseSpecNumber(caseId: string): string | null;
1018
538
  declare function extractScSpecNumber(scId: string): string | null;
1019
539
 
1020
- declare const CANONICAL_PROTOTYPING_SURFACES: readonly ["web", "mobile", "desktop", "cli", "mixed"];
1021
- type CanonicalPrototypingSurface = (typeof CANONICAL_PROTOTYPING_SURFACES)[number];
1022
-
1023
- /**
1024
- * Prototyping surface helpers.
1025
- *
1026
- * Kept here: surface-type helpers and a `playwright-cli` constant used by
1027
- * the capture wiring.
1028
- */
1029
-
1030
- declare const PROTOTYPING_SUPPORTED_SURFACES: readonly ["web", "mobile", "desktop", "mixed"];
1031
- type PrototypingSurface = CanonicalPrototypingSurface;
1032
- type PrototypingBrowserTool = "playwright-cli";
1033
- declare function isValidPrototypingSurface(value: unknown): value is PrototypingSurface;
1034
- declare function isSupportedPrototypingSurface(surface: string): surface is PrototypingSurface;
1035
-
1036
540
  /**
1037
541
  * The input source preflight selected: the discussion pack, or an imported
1038
542
  * specification the caller names with `importPath`. An explicit user
@@ -1113,6 +617,17 @@ type ValidationOptions = {
1113
617
  flowIds?: readonly string[];
1114
618
  };
1115
619
  declare function validateProject(root: string, configResult?: ConfigLoadResult, options?: ValidationOptions): Promise<ValidationResult>;
620
+ /**
621
+ * The parts of the UI/UX group, slowest first. The html-mock part leaves out the one-off load of
622
+ * its parser, as the group's own time does.
623
+ */
624
+ declare function orderUiuxParts(parts: readonly {
625
+ name: string;
626
+ ms: number;
627
+ }[], htmlMockLoadMs: number): {
628
+ name: string;
629
+ ms: number;
630
+ }[];
1116
631
  /**
1117
632
  * Count findings by severity, skipping suppressed ones. `counts` is the SSOT
1118
633
  * every gate reads, so anything that rebuilds a `ValidationResult` from an
@@ -1224,6 +739,44 @@ declare function classifyAgainstDeclaration(declaringDir: string, packageDir: st
1224
739
  * statement but still a statement.
1225
740
  */
1226
741
  declare function findDeclaringDir(root: string): Promise<string | null>;
742
+ /**
743
+ * The range the nearest `package.json` declaring qfai gives it, or `null` when
744
+ * no manifest at or above `root` declares it with a text range.
745
+ *
746
+ * Read from the manifest {@link findDeclaringDir} stops at, so the range and the
747
+ * directory it is compared against are one declaration.
748
+ */
749
+ declare function readDeclaredQfaiRange(root: string): Promise<string | null>;
750
+ /**
751
+ * The lowest version a dependency range admits, as `x.y.z`, or `null` when the
752
+ * range does not state one this reads.
753
+ *
754
+ * Only the forms a project writes for a pinned dependency are read: an exact
755
+ * version, and a caret, tilde or `>=` range, each optionally followed by more
756
+ * comparators (`>=2.1.1 <3`). A tag (`latest`), a wildcard (`2.x`), an
757
+ * alternative (`||`), a protocol (`workspace:`, `file:`, `github:`) and a
758
+ * partial version (`^2.1`) answer `null`, so nothing is compared against them.
759
+ */
760
+ declare function lowestVersionOfRange(range: string): string | null;
761
+ /** Whether `version` is strictly older than `other`; `false` where either is unreadable. */
762
+ declare function isOlderVersion(version: string, other: string): boolean;
763
+ /** An installed qfai that is older than the version the project declares. */
764
+ type InstalledOlderThanDeclared = {
765
+ readonly installed: string;
766
+ readonly declaredRange: string;
767
+ readonly declaredLowest: string;
768
+ };
769
+ /**
770
+ * Whether the running qfai is older than the lowest version this project's
771
+ * dependency declaration admits.
772
+ *
773
+ * The declaration is the only record of "the qfai this tree was written for"
774
+ * that a project keeps: the files `qfai init` writes carry no version. An
775
+ * installed copy below that floor is the state a lockfile that moved after the
776
+ * last install leaves, and every rule whose input is a generated file can then
777
+ * disagree with the tree for a reason that is not in the tree.
778
+ */
779
+ declare function compareInstalledToDeclared(installed: string, declaredRange: string | null): InstalledOlderThanDeclared | null;
1227
780
  /**
1228
781
  * Whether `packageDir` is an installed copy resolved from outside `root`.
1229
782
  *
@@ -1287,6 +840,9 @@ declare function validateContracts(root: string, config: QfaiConfig): Promise<Is
1287
840
  declare function validateUiContractParse(root: string, config: QfaiConfig): Promise<Issue[]>;
1288
841
  declare function lintSql(text: string, file: string): Issue[];
1289
842
 
843
+ declare const CANONICAL_PROTOTYPING_SURFACES: readonly ["web", "mobile", "desktop", "cli", "mixed"];
844
+ type CanonicalPrototypingSurface = (typeof CANONICAL_PROTOTYPING_SURFACES)[number];
845
+
1290
846
  type SurfaceType = CanonicalPrototypingSurface | "non-ui";
1291
847
  declare const DISCUSSION_UI_BEARING_SURFACES: Set<SurfaceType>;
1292
848
  declare const DISCUSSION_NON_UI_SURFACES: Set<SurfaceType>;
@@ -1295,137 +851,6 @@ declare function isDiscussionUiBearingSurfaceType(surface: SurfaceType): boolean
1295
851
  declare function isNonUiDiscussionSurface(surface: SurfaceType): boolean;
1296
852
  declare function requiresVisualBrowserEvidence(surface: SurfaceType): boolean;
1297
853
 
1298
- type BrowserQaPhase = "smoke" | "interaction" | "visual" | "accessibility";
1299
- declare const BROWSER_QA_PHASES: readonly BrowserQaPhase[];
1300
- type BrowserQaPhaseStatus = "passed" | "executed" | "failed" | "skipped";
1301
- type BrowserQaFindingSeverity = "info" | "warn" | "warning" | "error";
1302
- interface BrowserQaFinding {
1303
- phase: BrowserQaPhase;
1304
- severity: BrowserQaFindingSeverity;
1305
- summary: string;
1306
- detail: string;
1307
- screen_id?: string;
1308
- selector?: string;
1309
- evidence_refs: string[];
1310
- repair_suggestions: string[];
1311
- category?: BrowserQaPhase;
1312
- message?: string;
1313
- route?: string;
1314
- repair_hint?: string;
1315
- }
1316
- type BrowserQaScreenContractRef = {
1317
- screen_id: string;
1318
- route?: string;
1319
- primary_tasks?: string[];
1320
- };
1321
- type BrowserQaPhaseResult = {
1322
- phase: BrowserQaPhase;
1323
- status: BrowserQaPhaseStatus;
1324
- findings: BrowserQaFinding[];
1325
- repair_suggestions: string[];
1326
- evidence_refs: string[];
1327
- checks_performed: string[];
1328
- skippedReason?: string;
1329
- screen_id?: string;
1330
- route?: string;
1331
- };
1332
- type BrowserQaInput = {
1333
- htmlContent?: string;
1334
- targetUrl?: string;
1335
- routes?: string[];
1336
- surface: SurfaceType;
1337
- required?: boolean;
1338
- executionSource?: "html" | "url" | "none";
1339
- screenContracts?: BrowserQaScreenContractRef[];
1340
- };
1341
- type BrowserQaRunResult = {
1342
- phases: BrowserQaPhaseResult[];
1343
- provider: string;
1344
- timestamp: string;
1345
- };
1346
- type BrowserQaSummary = {
1347
- providerId: string;
1348
- phases: Record<BrowserQaPhase, {
1349
- status: BrowserQaPhaseStatus;
1350
- findingsCount: number;
1351
- checksCount: number;
1352
- passed?: number;
1353
- failed?: number;
1354
- }>;
1355
- totalFindings: number;
1356
- totalRepairs: number;
1357
- skippedReasons: string[];
1358
- };
1359
- interface BrowserQaProvider {
1360
- providerId: string;
1361
- canRun(surface: SurfaceType): boolean;
1362
- runSmoke(input: BrowserQaInput): Promise<BrowserQaPhaseResult>;
1363
- runInteraction(input: BrowserQaInput): Promise<BrowserQaPhaseResult>;
1364
- runVisual(input: BrowserQaInput): Promise<BrowserQaPhaseResult>;
1365
- runAccessibility(input: BrowserQaInput): Promise<BrowserQaPhaseResult>;
1366
- }
1367
-
1368
- declare function runBrowserQaOrchestrated(input: BrowserQaInput, provider?: BrowserQaProvider): Promise<BrowserQaRunResult>;
1369
- declare function summarizeBrowserQaResult(result: BrowserQaRunResult): BrowserQaSummary;
1370
-
1371
- /**
1372
- * Render evidence types
1373
- *
1374
- * Canonical types for the render evidence capture pipeline.
1375
- */
1376
- type RenderCaptureTarget = {
1377
- targetId: string;
1378
- route?: string;
1379
- descriptor?: string;
1380
- viewport: string;
1381
- width?: number;
1382
- height?: number;
1383
- };
1384
- type RenderCaptureAdapter = {
1385
- captureScreenshot(target: RenderCaptureTarget, outputDir: string): Promise<string>;
1386
- captureHtml?(target: RenderCaptureTarget, outputDir: string): Promise<string>;
1387
- };
1388
- type RenderEvidenceEntryResult = {
1389
- capture_id: string;
1390
- target: string;
1391
- status: "captured" | "skipped" | "failed";
1392
- screenshot_path?: string;
1393
- html_path?: string;
1394
- viewport: string;
1395
- reason?: string;
1396
- };
1397
- type RenderRunnerResult = {
1398
- entries: RenderEvidenceEntryResult[];
1399
- filesWritten: string[];
1400
- surface?: string;
1401
- mode?: string;
1402
- generatedAt?: string;
1403
- coverageSummary?: {
1404
- total: number;
1405
- captured: number;
1406
- failed: number;
1407
- skipped: number;
1408
- };
1409
- };
1410
-
1411
- /**
1412
- * Render runner
1413
- *
1414
- * Executes render capture for a list of targets, producing truthful
1415
- * evidence entries. No placeholder paths are emitted.
1416
- */
1417
-
1418
- /**
1419
- * Run render capture for all targets.
1420
- *
1421
- * - adapter present + capture succeeds → `captured` with real file paths
1422
- * - adapter present + capture throws → `failed` with reason
1423
- * - adapter absent → `skipped` with reason
1424
- */
1425
- declare function runRenderCapture(targets: RenderCaptureTarget[], outputDir: string, adapter?: RenderCaptureAdapter, options?: {
1426
- required?: boolean;
1427
- }): Promise<RenderRunnerResult>;
1428
-
1429
854
  declare const ARCHETYPES: readonly ["minimal", "bold", "corporate", "playful", "organic", "tech", "elegant", "casual"];
1430
855
  type Archetype = (typeof ARCHETYPES)[number];
1431
856
  declare const COLOR_KEYS: readonly ["primary", "secondary", "accent", "surface", "surface_muted", "text", "text_muted", "danger", "warning", "success", "border", "overlay"];
@@ -1535,12 +960,8 @@ declare const DESIGN_MD_SAMPLE_MARKER = "QFAI-SAMPLE-DESIGN-MD";
1535
960
  * an unauthored brand as the project's brand contract, and to surface the
1536
961
  * same condition from `qfai validate` (QFAI-DCON-034).
1537
962
  *
1538
- * Two detectors, because the marker only covers samples seeded by releases
1539
- * that carry it:
1540
- * 1. `DESIGN_MD_SAMPLE_MARKER` — current samples, survives any edit that
1541
- * leaves the comment in place.
1542
- * 2. Legacy content fingerprint — samples seeded before the marker
1543
- * existed, which `qfai init` will never overwrite.
963
+ * The test is `DESIGN_MD_SAMPLE_MARKER`, which survives any edit that leaves
964
+ * the comment in place.
1544
965
  *
1545
966
  * `text` is `unknown` rather than `string` because this is re-exported from
1546
967
  * `core/index.ts` and so is reachable from untyped JavaScript, where a caller
@@ -1551,7 +972,74 @@ declare const DESIGN_MD_SAMPLE_MARKER = "QFAI-SAMPLE-DESIGN-MD";
1551
972
  */
1552
973
  declare function isUnreplacedDesignMdSample(text: unknown): boolean;
1553
974
 
975
+ interface GraphPreparation {
976
+ runtime: "installed" | "not-ready";
977
+ tools?: {
978
+ codegraph: "installed" | "not-ready";
979
+ graphify: "installed" | "not-ready";
980
+ };
981
+ code: "code-ready" | "code-not-ready";
982
+ documents: "documents-not-ready";
983
+ failedStep?: string;
984
+ recovery?: string;
985
+ previousInstallation?: "retained" | "none";
986
+ }
987
+
988
+ type GraphHookHost = "claude" | "codex" | "copilot" | "vscode";
989
+
990
+ interface GraphHostFacts {
991
+ host: GraphHookHost;
992
+ version?: string;
993
+ configuration: "configured" | "not-configured" | "failed";
994
+ denialEvent?: string;
995
+ failureProtocol?: "supported" | "unsupported" | "unverified";
996
+ trust: "required" | "granted" | "not-required" | "unknown";
997
+ }
998
+ interface GraphHostProtection {
999
+ host: GraphHookHost;
1000
+ version?: string;
1001
+ state: "configured" | "trust-required" | "runtime-verified" | "unsupported" | "failed";
1002
+ events: readonly string[];
1003
+ recovery: string;
1004
+ configuration?: GraphHostFacts["configuration"];
1005
+ trust?: GraphHostFacts["trust"];
1006
+ }
1007
+
1008
+ interface GraphSettingsComposition {
1009
+ files: string[];
1010
+ conflicts: string[];
1011
+ }
1012
+
1013
+ type GraphIntegrationPreparation = GraphPreparation & GraphSettingsComposition & {
1014
+ hosts: GraphHostProtection[];
1015
+ };
1016
+ declare function prepareGraphIntegration(root: string, run?: GraphProcess): Promise<GraphIntegrationPreparation>;
1017
+
1018
+ declare function renderGraphPreparation(preparation: GraphPreparation, hosts?: readonly GraphHostProtection[]): string;
1019
+
1020
+ interface GraphReuseEnvironment {
1021
+ platform: string;
1022
+ architecture: string;
1023
+ machine: string;
1024
+ }
1025
+
1026
+ interface DocumentPreparation {
1027
+ status: "prepared" | "not-ready" | "failed";
1028
+ source?: "current" | "restored" | "generated";
1029
+ reuse?: "published" | "unavailable";
1030
+ recovery?: string;
1031
+ }
1032
+ interface ExternalProcessingRequest {
1033
+ readonly destination: string;
1034
+ readonly scope: readonly string[];
1035
+ readonly backend: string;
1036
+ readonly model: string;
1037
+ readonly worktree: string;
1038
+ }
1039
+ type ApproveExternalProcessing = (request: ExternalProcessingRequest) => Promise<boolean>;
1040
+ declare function prepareDocumentAnalysis(root: string, run?: GraphProcess, approveExternal?: ApproveExternalProcessing, environment?: GraphReuseEnvironment): Promise<DocumentPreparation>;
1041
+
1554
1042
  /** Run one ordered spec-to-story migration step from the current project root. */
1555
1043
  declare function runMigrationStep(step: number, argv: string[]): Promise<number>;
1556
1044
 
1557
- 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, SKIPPED_WORKFLOWS_MESSAGE, 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, readSkippedWorkflows, readTestFileExtensions, readWorkflowMode, requiresVisualBrowserEvidence, resolveAtddHomeKind, resolvePath, resolveTestKind, resolveToolPackageDir, resolveToolVersion, resolvesThroughOwnNodeModules, routingEntryName, runBrowserQaOrchestrated, runMigrationStep, runRenderCapture, runSddPreflight, summarizeBrowserQaResult, suspendsObligation, validateContracts, validateDesignMd, validateProject, validateUiContractParse };
1045
+ export { AC_GHERKIN_SECTION, ARCHETYPES, ATDD_TEST_KIND_DIRS, type ApproveExternalProcessing, 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, type DocumentPreparation, type ExternalProcessingRequest, FONT_KEYS, type FailOn, type FenceMaskOptions, type FontKey, ID_PREFIXES, type IdFormatPrefix, type IdPrefix, type InstalledOlderThanDeclared, 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, compareInstalledToDeclared, 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, isOlderVersion, isUnreplacedDesignMdSample, lintSql, loadConfig, locateToolAgainstProject, lowestVersionOfRange, maskFencedCodeBlocks, orderUiuxParts, packageRootProbe, parseDesignMd, prepareDocumentAnalysis, prepareGraphIntegration, reachedThroughLinkedNodeModules, readDeclaredQfaiRange, readRejectedPrimaryUiContract, readSkippedWorkflows, readTestFileExtensions, readWorkflowMode, renderGraphPreparation, requiresVisualBrowserEvidence, resolvePath, resolveTestKind, resolveToolPackageDir, resolveToolVersion, resolvesThroughOwnNodeModules, routingEntryName, runMigrationStep, runSddPreflight, validateContracts, validateDesignMd, validateProject, validateUiContractParse };