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/README.md CHANGED
@@ -79,11 +79,21 @@ Then open your AI coding agent in the repository and describe the change in your
79
79
 
80
80
  > Let each customer register up to five notification addresses, with no duplicates.
81
81
 
82
+ If you only have an idea, say so: the run starts with a discussion that structures scope and open questions.
83
+
82
84
  The agent announces the goal, the stages it will run and the files it may change, then runs them.
83
85
  It asks you only what it cannot decide for you, such as whether to create a new story.
86
+ Answer its questions, or say `stop` to end the run.
84
87
  The stages fill the seeded story tree and follow the project's Standard commands in
85
88
  `<paths.contractsDir>/tech.md` (by default `.qfai/spec/03_contract/tech.md`) for its quality gates.
86
89
 
90
+ When the run ends, check the result:
91
+
92
+ ```bash
93
+ npx qfai validate
94
+ npx qfai report
95
+ ```
96
+
87
97
  To drive the stages yourself instead:
88
98
  Run `/qfai-discussion` and `/qfai-sdd` to fill the seeded story tree.
89
99
  See [Invoking a stage directly](#invoking-a-stage-directly-expert-path).
@@ -313,27 +323,22 @@ flowchart LR
313
323
  (`assistant/rule/`, `skill/`, `agent/` and `prompt/`), plus `qfai.config.yaml`.
314
324
  - Options:
315
325
 
316
- | Flag | Effect |
317
- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
318
- | `--dir <path>` | Output directory (default: the current directory). Wins over `--root` when both are given. |
319
- | `--root <path>` | Every other command reads this as the target directory; `init` reads it as the output directory too, but only when `--dir` is omitted. |
320
- | `--force` | Overwrite the shipped skills, steps, agents and rules, the rule masters under `.agents/rules/`, their host wrappers, and generated Copilot instructions, whether or not you edited them. Project content, `qfai.config.yaml`, `DESIGN.md`, `AGENTS.md`, `CLAUDE.md` and routing overrides are preserved. The managed `.gitignore` block and `core.symlinks` setting are repaired on every non-dry-run. |
321
- | `--dry-run` | Report what would change and write nothing. Use it to rehearse `--upgrade-assistant-tree`. |
322
- | `--upgrade-assistant-tree` | Copy recognized legacy assistant files into the singular tree without deleting a source or overwriting a destination. Migrate old spec packs with `/qfai-migration-v1-to-v2`. Unrecognized assistant files stay in place. |
323
- | `--yes` | Reserved for a future interactive mode; no behavioural difference today. |
324
- | `--verbose` | Expand the run report's `skipped` list to the full path listing. Off by default, so a no-op re-run prints the skip count and a pointer to this flag instead of every shipped asset path. It does not gate the written or removed listings: those are printed whenever they have entries, with or without this flag. |
325
- | `--help`, `-h` | Print the CLI usage banner and exit without writing anything. Accepted by every command, `init` included, and handled before the command runs. |
326
- | `--version`, `-V` | Print the installed QFAI version to stdout and exit 0. Accepted by every command, `init` included, and handled before the command runs, so it works outside a project too. |
327
-
328
- - `QFAI-DEPRECATED-001` means the legacy assistant layout remains past its
329
- supported window. Run `npx qfai init --upgrade-assistant-tree` to copy
330
- recognized files into the current tree without deleting their sources.
326
+ | Flag | Effect |
327
+ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
328
+ | `--dir <path>` | Output directory (default: the current directory). Wins over `--root` when both are given. |
329
+ | `--root <path>` | Every other command reads this as the target directory; `init` reads it as the output directory too, but only when `--dir` is omitted. |
330
+ | `--force` | Overwrite the shipped skills, steps, agents and rules, the rule masters under `.agents/rules/`, their host wrappers, and generated Copilot instructions, whether or not you edited them. Project content, `qfai.config.yaml`, `DESIGN.md`, `AGENTS.md` and routing overrides are preserved. The managed `.gitignore` block and `core.symlinks` setting are repaired on every non-dry-run. |
331
+ | `--dry-run` | Report what would change and write nothing. |
332
+ | `--yes` | Reserved for a future interactive mode; no behavioural difference today. |
333
+ | `--verbose` | Expand the run report's `skipped` list to the full path listing. Off by default, so a no-op re-run prints the skip count and a pointer to this flag instead of every shipped asset path. It does not gate the written or removed listings: those are printed whenever they have entries, with or without this flag. |
334
+ | `--help`, `-h` | Print the CLI usage banner and exit without writing anything. Accepted by every command, `init` included, and handled before the command runs. |
335
+ | `--version`, `-V` | Print the installed QFAI version to stdout and exit 0. Accepted by every command, `init` included, and handled before the command runs, so it works outside a project too. |
331
336
 
332
337
  - `npx qfai validate`
333
338
  - Validates the story tree, contracts and test obligations, writes `.qfai/report/validate.json`,
334
339
  and appends run logs to `.qfai/report/run-*/`; use `--fail-on error` (or `--fail-on warning`) to turn it into a CI gate,
335
340
  and `--format github` to emit GitHub-friendly annotations.
336
- Use `--flow BF-0001` to scope a run to one business flow. `--spec` is retired.
341
+ Use `--flow BF-0001` to scope a run to one business flow.
337
342
  Use `--profile discussion|sdd|prototyping|atdd|tdd|verify` for local skill-owned checks; CI should use default/full validation (or `verify` / `tdd` for the dedicated CI gates).
338
343
  - `npx qfai report`
339
344
  - Produces a human-readable report (`report.md` by default) or an internal JSON export (`report.json`) from `validate.json`; use `--base-url` to link file paths in Markdown to your repository viewer.
@@ -426,15 +431,29 @@ Notes.
426
431
  issue order are not stable; match on `issues[].code`.
427
432
  - `report.json`, `doctor.json`, and `run-*` JSON logs are internal exports and are not a stable external contract; prefer `report.md` for integrations that must survive tool upgrades.
428
433
 
429
- ## Minimal tutorial
434
+ ## Dependency security
430
435
 
431
- 1. `npx qfai init`
432
- 2. Open your AI coding agent in the repository and describe the change in your own words.
433
- If you only have an idea, say so: the run starts with a discussion that structures scope and open questions.
434
- 3. Answer the questions the work puts to you, or say `stop` to end it.
435
- 4. Run `npx qfai validate` then `npx qfai report`.
436
+ The glob-matching dependency `braces` has an unresolved
437
+ [stack-exhaustion denial-of-service advisory](https://github.com/advisories/GHSA-vfj7-8cjw-p6xm).
438
+ QFAI accepts this residual risk only for reviewed project patterns and paths,
439
+ trusted configuration and trusted SDK callers. This is risk acceptance, not a
440
+ clean audit or proof that QFAI is unaffected. Reproduction and exploitation in
441
+ QFAI remain unverified.
436
442
 
437
- To choose each stage yourself, see [Invoking a stage directly](#invoking-a-stage-directly-expert-path).
443
+ Review include, ignore and negative patterns, constructed paths and SDK inputs.
444
+ `validateProject` accepts a caller-supplied `ConfigLoadResult` without invoking
445
+ the configuration loader; the caller must review those values too. Catching
446
+ errors and limiting results after glob expansion do not defend against stack
447
+ exhaustion during expansion.
448
+
449
+ Untrusted pull requests, externally supplied configuration and service request
450
+ inputs are outside this acceptance. Review those inputs and restrict access;
451
+ do not expose glob processing to them until your mitigation and security review
452
+ cover them.
453
+
454
+ Reassess the risk when a compatible patch becomes available or untrusted input
455
+ is needed. A forced downgrade to `qfai@0.4.0` is not a compatible fix for the
456
+ current major API; do not apply it through `npm audit fix --force`.
438
457
 
439
458
  ## FAQ
440
459
 
@@ -607,6 +626,17 @@ Waiver policy.
607
626
  - Expired waivers are reported as warnings (`QFAI-WAIVER-003`) and must be renewed or removed with evidence.
608
627
  - Suppressed findings remain visible in reports as `suppressed=true`; waivers do not erase findings.
609
628
 
629
+ When upgrading a project with `.qfai/waivers.yml`, back up that file. Check the
630
+ current public finding codes and the release notes for confirmed removals or
631
+ renames. Update a `rule:` value only when the documented rename preserves the
632
+ finding's meaning and scope. Remove confirmed retired or inapplicable waivers,
633
+ keeping the backup and recording each removal's reason for the operator. Do not
634
+ rename whole code prefixes or automatically delete an unknown rule.
635
+
636
+ Rerun full validation and require zero errors. An unknown waiver rule produces
637
+ `QFAI-WAIVER-004` at `warning` and is not applied in that run; investigate it
638
+ against the current diagnostics and release notes.
639
+
610
640
  Typical customizations.
611
641
 
612
642
  - Add a `doctor` step before validate if you want to fail fast on path/glob/config issues.
@@ -929,7 +959,6 @@ commit that bumps the package, and to keep the two from being merged separately.
929
959
  │ │ └── open-questions.md
930
960
  │ └── waivers.yml
931
961
  ├── AGENTS.md
932
- ├── CLAUDE.md
933
962
  └── qfai.config.yaml
934
963
  ```
935
964
 
@@ -966,17 +995,6 @@ provide those defaults to the agent.
966
995
  The `.claude` / `.github` agent wrappers are symlinks and follow the canonical document
967
996
  automatically; the Codex profiles are generated files, so rerun `npx qfai init --force`
968
997
  to refresh them (and any other wrapper asset that has drifted).
969
- `--force` deletes as well as overwrites: it removes the command and prompt wrappers
970
- under `.claude/commands/` and `.github/prompts/`, and the skill wrappers under
971
- `.claude/skills/`, `.agents/skills/`, `.codex/skills/` and `.github/skills/` for skills that are
972
- not in the current release, whether those wrappers are directories or symlinks.
973
- Files it did not write — a project's own slash command, prompt file or skill,
974
- including one published from a project-authored `.qfai/assistant/skill/` entry — are left in place,
975
- whatever they are named. Ownership is read from the file, not the name: a wrapper is removed only when
976
- it carries the delegation line to the canonical document of the same name, or is a symlink into
977
- `.qfai/assistant/skill/`. One consequence of that: a symlink has no content to prove who wrote it, so
978
- if you publish a canonical skill of your own under a name QFAI itself once shipped, `--force` removes
979
- that link. Your `.qfai/assistant/skill/` entry is untouched; re-create the link to publish it again.
980
998
 
981
999
  ### Supported hosts
982
1000
 
@@ -986,7 +1004,9 @@ No host is declared supported in this release.
986
1004
  ### Cross-AI rules and the writing reminder
987
1005
 
988
1006
  `npx qfai init` seeds `.agents/rules/**`, the rule set every AI coding agent in
989
- the project shares, and points `AGENTS.md` and `CLAUDE.md` at it. One of those
1007
+ the project shares, and points `AGENTS.md` at it, with a line for each rule that says when
1008
+ to read it. Claude Code reads `AGENTS.md` when the project has no `CLAUDE.md` (Claude Code
1009
+ 2.1.277 or later). One of those
990
1010
  rules, `documentation-clarity.md`, sets the writing standard for pull requests,
991
1011
  issues, code comments and Markdown: no project-local identifiers, no account of
992
1012
  how the work went, cut to what a reader outside the team can follow.
@@ -216,6 +216,15 @@ routing:
216
216
  rerun_policy: changed-scope-dependents
217
217
  review_profile: default
218
218
 
219
+ - step: implement-procedure
220
+ phases:
221
+ - id: documentation
222
+ mandatory_agents: [doc-steward]
223
+ conditional_agents: []
224
+ parallel_groups: []
225
+ rerun_policy: changed-scope-dependents
226
+ review_profile: default
227
+
219
228
  # Writes the bodies of empty acceptance tests, keeping each annotation.
220
229
  - step: implement-acceptance
221
230
  phases:
@@ -4,6 +4,24 @@
4
4
  routing:
5
5
  # The triage steps change no tracked file, so each has one author and no
6
6
  # build or test phase.
7
+ - step: triage-operate
8
+ phases:
9
+ - id: operate
10
+ mandatory_agents: [devops-ci-engineer]
11
+ conditional_agents: []
12
+ parallel_groups: []
13
+ rerun_policy: changed-scope-dependents
14
+ review_profile: default
15
+
16
+ - step: triage-owned-cleanup
17
+ phases:
18
+ - id: cleanup
19
+ mandatory_agents: [devops-ci-engineer]
20
+ conditional_agents: []
21
+ parallel_groups: []
22
+ rerun_policy: changed-scope-dependents
23
+ review_profile: default
24
+
7
25
  - step: triage-close
8
26
  phases:
9
27
  - id: record
@@ -0,0 +1,8 @@
1
+ route: answer-in-session
2
+ family: close
3
+ stages:
4
+ - id: answer
5
+ kind: triage
6
+ steps: [triage-answer]
7
+ decisionPoints: []
8
+ branchPoints: []
@@ -29,6 +29,9 @@ stages:
29
29
  after: [note]
30
30
  decisionPoints: []
31
31
  branchPoints:
32
+ - step: implement-quarantine
33
+ outcomes:
34
+ - { outcome: failing-consistently, routes: decision-table }
32
35
  - step: implement-diagnose
33
36
  outcomes:
34
37
  - { outcome: product-race, routes: [fix-intermittent] }
@@ -11,6 +11,7 @@ stages:
11
11
  - { step: sdd-flow, passThrough: true }
12
12
  - { step: sdd-story, passThrough: true }
13
13
  - { step: sdd-contract, passThrough: true }
14
+ - { step: common-design-md, passThrough: true }
14
15
  - { step: sdd-cycle, passThrough: true }
15
16
  after: [diagnose]
16
17
  review: spec
@@ -0,0 +1,11 @@
1
+ route: run-operation
2
+ family: upkeep
3
+ stages:
4
+ - id: operate
5
+ kind: triage
6
+ steps: [triage-operate]
7
+ decisionPoints: []
8
+ branchPoints:
9
+ - step: triage-operate
10
+ outcomes:
11
+ - { outcome: needs-change, routes: decision-table }
@@ -0,0 +1,12 @@
1
+ route: run-owned-cleanup
2
+ family: upkeep
3
+ stages:
4
+ - id: cleanup
5
+ kind: triage
6
+ steps: [triage-owned-cleanup]
7
+ - id: close
8
+ kind: triage
9
+ steps: [triage-close]
10
+ after: [cleanup]
11
+ decisionPoints: []
12
+ branchPoints: []
@@ -0,0 +1,18 @@
1
+ route: write-procedure
2
+ family: change
3
+ stages:
4
+ - id: documentation
5
+ kind: documentation
6
+ steps: [implement-procedure]
7
+ - id: note
8
+ kind: verify
9
+ steps:
10
+ - { step: verify-change-note, passThrough: true }
11
+ after: [documentation]
12
+ review: code
13
+ - id: verify
14
+ kind: verify
15
+ steps: [verify-qfai-gate, verify-repo-gate, verify-commit]
16
+ after: [note]
17
+ decisionPoints: []
18
+ branchPoints: []
@@ -0,0 +1,174 @@
1
+ import process from "node:process";
2
+ import path from "node:path";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ async function main() {
7
+ const sdk = await import(pathToFileURL(process.argv[2]).href);
8
+ const { default: CodeGraph, setLogger, silentLogger } = sdk.default;
9
+ setLogger(silentLogger);
10
+ const mode = process.argv[4];
11
+ const corpus = process.argv[3];
12
+ const graph = mode
13
+ ? await CodeGraph.open(process.argv[3], { readOnly: true })
14
+ : await CodeGraph.init(process.argv[3], { index: false });
15
+ try {
16
+ if (mode === "inventory") {
17
+ process.stdout.write(
18
+ JSON.stringify({ files: graph.getFiles().map((file) => file.path) }) + "\n",
19
+ );
20
+ } else if (mode === "coverage") {
21
+ process.stdout.write(JSON.stringify(await coverage(graph, corpus)) + "\n");
22
+ } else if (mode === undefined) {
23
+ const result = await graph.indexAll();
24
+ const build = graph.getIndexBuildInfo();
25
+ await writeFile(
26
+ path.join(corpus, ".codegraph", "qfai-native-evidence.json"),
27
+ JSON.stringify({
28
+ engineVersion: build.version,
29
+ extractionVersion: build.extractionVersion,
30
+ lastIndexedAt: graph.getLastIndexedAt(),
31
+ result: {
32
+ success: result.success,
33
+ filesDiscovered: result.filesDiscovered ?? null,
34
+ filesIndexed: result.filesIndexed,
35
+ filesSkipped: result.filesSkipped,
36
+ filesErrored: result.filesErrored,
37
+ filesSkippedUnsupported: result.filesSkippedUnsupported ?? 0,
38
+ topUnsupportedExtensions: result.topUnsupportedExtensions ?? [],
39
+ errors: result.errors.map((error) => ({
40
+ code: error.code ?? "unclassified",
41
+ severity: error.severity,
42
+ filePath: safeRelativeInput(corpus, error.filePath),
43
+ })),
44
+ },
45
+ }) + "\n",
46
+ );
47
+ process.stdout.write("CodeGraph native indexing completed.\n");
48
+ } else {
49
+ throw new Error("Unsupported native operation");
50
+ }
51
+ } finally {
52
+ graph.close();
53
+ }
54
+ }
55
+
56
+ function safeRelativeInput(corpus, file) {
57
+ if (typeof file !== "string" || !file) return "Unidentified native input";
58
+ const relative = (path.isAbsolute(file) ? path.relative(corpus, file) : file).replaceAll(
59
+ "\\",
60
+ "/",
61
+ );
62
+ if (
63
+ path.posix.isAbsolute(relative) ||
64
+ path.win32.isAbsolute(relative) ||
65
+ relative.split("/").some((part) => !part || part === "." || part === "..") ||
66
+ /(?:^|\/)(?:\.env(?:\.|$)|\.npmrc$|\.pypirc$|\.netrc$|credentials?(?:\.|$)|secrets?(?:\.|$))|\.(?:pem|key|p12|pfx)$/i.test(
67
+ relative,
68
+ )
69
+ )
70
+ return "Unidentified native input";
71
+ return relative;
72
+ }
73
+
74
+ async function coverage(graph, corpus) {
75
+ const unknown = { complete: false, unsupported: [], unreadable: [], unparsed: [] };
76
+ let evidence;
77
+ try {
78
+ evidence = JSON.parse(
79
+ await readFile(path.join(corpus, ".codegraph", "qfai-native-evidence.json"), "utf8"),
80
+ );
81
+ } catch {
82
+ return unknown;
83
+ }
84
+ const build = graph.getIndexBuildInfo();
85
+ const result = evidence?.result;
86
+ const counts = [
87
+ "filesDiscovered",
88
+ "filesIndexed",
89
+ "filesSkipped",
90
+ "filesErrored",
91
+ "filesSkippedUnsupported",
92
+ ];
93
+ if (
94
+ !record(evidence) ||
95
+ Object.keys(evidence).length !== 4 ||
96
+ evidence.engineVersion !== build.version ||
97
+ evidence.extractionVersion !== build.extractionVersion ||
98
+ !Number.isSafeInteger(evidence.lastIndexedAt) ||
99
+ evidence.lastIndexedAt !== graph.getLastIndexedAt() ||
100
+ !record(result) ||
101
+ Object.keys(result).length !== 8 ||
102
+ typeof result.success !== "boolean" ||
103
+ counts.some((key) => !Number.isSafeInteger(result[key]) || result[key] < 0) ||
104
+ !Array.isArray(result.errors) ||
105
+ !Array.isArray(result.topUnsupportedExtensions)
106
+ )
107
+ return unknown;
108
+ const unsupported = [];
109
+ const unreadable = [];
110
+ const unparsed = [];
111
+ let documentedInputs = 0;
112
+ for (const group of result.topUnsupportedExtensions) {
113
+ if (
114
+ !record(group) ||
115
+ typeof group.ext !== "string" ||
116
+ !Number.isSafeInteger(group.count) ||
117
+ group.count < 0
118
+ )
119
+ return unknown;
120
+ if (
121
+ /^\.(?:md|mdx|txt|rst|adoc|pdf|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|mp[34]|wav)$/i.test(
122
+ group.ext,
123
+ )
124
+ ) {
125
+ documentedInputs += group.count;
126
+ } else {
127
+ unsupported.push(`${group.ext} (${group.count} inputs)`);
128
+ }
129
+ }
130
+ const namedUnsupported = result.topUnsupportedExtensions.reduce(
131
+ (count, group) => count + group.count,
132
+ 0,
133
+ );
134
+ if (namedUnsupported < result.filesSkippedUnsupported)
135
+ unsupported.push("Other unsupported native inputs");
136
+ if (
137
+ namedUnsupported > result.filesSkippedUnsupported ||
138
+ documentedInputs > result.filesSkippedUnsupported
139
+ )
140
+ return unknown;
141
+ for (const error of [
142
+ ...result.errors,
143
+ ...graph.getFiles().flatMap((file) => file.errors ?? []),
144
+ ]) {
145
+ if (!record(error) || !["error", "warning"].includes(error.severity)) return unknown;
146
+ const file = safeRelativeInput(corpus, error.filePath);
147
+ if (error.code === "read_error") unreadable.push(file);
148
+ else unparsed.push(file);
149
+ }
150
+ if (result.filesErrored > 0 && unreadable.length === 0 && unparsed.length === 0)
151
+ unparsed.push("Unidentified native extraction errors");
152
+ const complete =
153
+ result.success &&
154
+ result.filesIndexed + result.filesSkipped + result.filesErrored >= result.filesDiscovered &&
155
+ result.filesErrored === 0 &&
156
+ unsupported.length === 0 &&
157
+ unreadable.length === 0 &&
158
+ unparsed.length === 0;
159
+ return {
160
+ complete,
161
+ unsupported,
162
+ unreadable: [...new Set(unreadable)],
163
+ unparsed: [...new Set(unparsed)],
164
+ };
165
+ }
166
+
167
+ function record(value) {
168
+ return typeof value === "object" && value !== null && !Array.isArray(value);
169
+ }
170
+
171
+ main().catch(() => {
172
+ process.stderr.write("CodeGraph native indexing failed.\n");
173
+ process.exitCode = 1;
174
+ });
@@ -0,0 +1,72 @@
1
+ """Inspect the managed corpus with Graphify's native freshness operation."""
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from graphify.detect import detect_incremental
8
+
9
+
10
+ def input_inventory(output, graph):
11
+ manifest = json.loads((output / "manifest.json").read_text(encoding="utf-8"))
12
+ if not isinstance(manifest, dict):
13
+ raise ValueError("Invalid native manifest")
14
+ files = set(manifest.keys())
15
+ groups = [graph["nodes"]]
16
+ for name in ("edges", "links", "hyperedges"):
17
+ if name in graph:
18
+ groups.append(graph[name])
19
+ nested = graph.get("graph", {})
20
+ if not isinstance(nested, dict):
21
+ raise ValueError("Invalid native graph attributes")
22
+ if "hyperedges" in nested:
23
+ groups.append(nested["hyperedges"])
24
+ for group in groups:
25
+ if not isinstance(group, list):
26
+ raise ValueError("Invalid native graph references")
27
+ for item in group:
28
+ if not isinstance(item, dict):
29
+ raise ValueError("Invalid native graph reference")
30
+ for field in ("source_file", "definition_file", "path"):
31
+ value = item.get(field)
32
+ if value is None or value == "":
33
+ continue
34
+ if not isinstance(value, str):
35
+ raise ValueError("Invalid native input path")
36
+ files.add(value)
37
+ return sorted(files)
38
+
39
+
40
+ def main():
41
+ root = Path(sys.argv[1])
42
+ kind = sys.argv[2]
43
+ output = root / "graphify-out"
44
+ graph = json.loads((output / "graph.json").read_text(encoding="utf-8"))
45
+ if not isinstance(graph, dict) or not isinstance(graph.get("nodes"), list):
46
+ raise ValueError("Invalid native graph")
47
+ if kind == "inventory":
48
+ print(json.dumps({"files": input_inventory(output, graph)}))
49
+ return
50
+ if kind not in ("semantic", "ast"):
51
+ raise ValueError("Unsupported native inspection")
52
+ state = detect_incremental(
53
+ root,
54
+ manifest_path=str(output / "manifest.json"),
55
+ kind=kind,
56
+ cache_root=output,
57
+ )
58
+ print(json.dumps({
59
+ "current": state["new_total"] == 0
60
+ and not state["deleted_files"]
61
+ and not state["excluded_files"],
62
+ "unsupported": state.get("unclassified", []),
63
+ "unreadable": state.get("unreadable", []),
64
+ }))
65
+
66
+
67
+ if __name__ == "__main__":
68
+ try:
69
+ main()
70
+ except Exception:
71
+ sys.stderr.write("Graphify freshness inspection failed.\n")
72
+ sys.exit(1)
@@ -30,7 +30,7 @@
30
30
  "command": "node",
31
31
  "args": [
32
32
  "-e",
33
- "try{let p;try{p=JSON.parse(require('fs').readFileSync(0,'utf8')).prompt}catch{}if(!(typeof p==='string'&&/^[ \\t]*(<(task-notification|wake)[\\s>]|\\[SYSTEM NOTIFICATION)/m.test(p))){const m=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(m!==undefined)console.log(JSON.stringify(m))}}catch{}",
33
+ "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}const p=i.prompt;if(!(typeof p==='string'&&/^[ \\t]*(<(task-notification|wake|ci-monitor-event)[\\s>]|\\[SYSTEM NOTIFICATION)/m.test(p))){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}",
34
34
  "${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
35
35
  "structured-question"
36
36
  ]
@@ -46,7 +46,7 @@
46
46
  "command": "node",
47
47
  "args": [
48
48
  "-e",
49
- "try{let p;try{p=JSON.parse(require('fs').readFileSync(0,'utf8')).prompt}catch{}if(!(typeof p==='string'&&/^[ \\t]*(<(task-notification|wake)[\\s>]|\\[SYSTEM NOTIFICATION)/m.test(p))){const m=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(m!==undefined)console.log(JSON.stringify(m))}}catch{}",
49
+ "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}const p=i.prompt;if(!(typeof p==='string'&&/^[ \\t]*(<(task-notification|wake|ci-monitor-event)[\\s>]|\\[SYSTEM NOTIFICATION)/m.test(p))){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}",
50
50
  "${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
51
51
  "free-text-entry"
52
52
  ]
@@ -96,7 +96,7 @@
96
96
  "command": "node",
97
97
  "args": [
98
98
  "-e",
99
- "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}if((n-1)%20===0){const m=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(m!==undefined)console.log(JSON.stringify(m))}}catch{}",
99
+ "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}catch{}",
100
100
  "${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
101
101
  "grilling-design-artifact"
102
102
  ]
@@ -112,7 +112,7 @@
112
112
  "command": "node",
113
113
  "args": [
114
114
  "-e",
115
- "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}if((n-1)%20===0){const m=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(m!==undefined)console.log(JSON.stringify(m))}}catch{}",
115
+ "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}catch{}",
116
116
  "${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
117
117
  "grilling-delegation"
118
118
  ]
@@ -128,7 +128,7 @@
128
128
  "command": "node",
129
129
  "args": [
130
130
  "-e",
131
- "try{const m=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(m!==undefined)console.log(JSON.stringify(m))}catch{}",
131
+ "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}catch{}",
132
132
  "${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
133
133
  "grilling-plan"
134
134
  ]
@@ -144,7 +144,7 @@
144
144
  "command": "node",
145
145
  "args": [
146
146
  "-e",
147
- "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}const c=i.tool_input&&i.tool_input.command;if(typeof c==='string'&&/(^|[^\\w.-])gh([^\\w.-]|$)|api\\.github\\.com/.test(c)){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}if((n-1)%20===0){const m=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(m!==undefined)console.log(JSON.stringify(m))}}}catch{}",
147
+ "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}const c=i.tool_input&&i.tool_input.command;if(typeof c==='string'&&/(^|[^\\w.-])gh([^\\w.-]|$)|api\\.github\\.com/.test(c)){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}",
148
148
  "${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
149
149
  "api-budget"
150
150
  ]
@@ -163,7 +163,7 @@
163
163
  "command": "node",
164
164
  "args": [
165
165
  "-e",
166
- "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}if((n-1)%20===0){const m=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(m!==undefined)console.log(JSON.stringify(m))}}catch{}",
166
+ "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}catch{}",
167
167
  "${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
168
168
  "documentation-clarity-after-write"
169
169
  ]
@@ -175,7 +175,7 @@
175
175
  "command": "node",
176
176
  "args": [
177
177
  "-e",
178
- "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}if((n-1)%20===0){const m=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(m!==undefined)console.log(JSON.stringify(m))}}catch{}",
178
+ "try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}catch{}",
179
179
  "${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
180
180
  "documentation-clarity-after-edit"
181
181
  ]
@@ -191,7 +191,7 @@
191
191
  "command": "node",
192
192
  "args": [
193
193
  "-e",
194
- "try{const f=require('fs'),p=require('path'),R=p.dirname(p.dirname(p.dirname(process.argv[1])));let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}const u=i.tool_input&&i.tool_input.file_path;let src=true;if(typeof u==='string'&&(x=>{const s=x.split(p.sep),b=s.pop();return p.isAbsolute(x)||s[0]==='..'||s.some(y=>['tmp','test','tests','__tests__','node_modules','.qfai','.agents'].includes(y))||b[0]==='.'||/\\.(test|spec)\\./.test(b)||['.md','.mdx','.markdown','.txt','.rst','.yml','.yaml','.json','.jsonc','.json5','.toml','.ini','.csv','.tsv','.lock','.svg','.png','.jpg','.jpeg','.gif'].includes(p.extname(b).toLowerCase())})(p.relative(R,p.resolve(R,u))))src=false;if(src){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}if((n-1)%20===0){const m=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(m!==undefined)console.log(JSON.stringify(m))}}}catch{}",
194
+ "try{const f=require('fs'),p=require('path'),R=p.dirname(p.dirname(p.dirname(process.argv[1])));let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}const u=i.tool_input&&i.tool_input.file_path;let src=true;if(typeof u==='string'&&(x=>{const s=x.split(p.sep),b=s.pop();return p.isAbsolute(x)||s[0]==='..'||s.some(y=>['tmp','test','tests','__tests__','node_modules','.qfai','.agents'].includes(y))||b[0]==='.'||/\\.(test|spec)\\./.test(b)||['.md','.mdx','.markdown','.txt','.rst','.yml','.yaml','.json','.jsonc','.json5','.toml','.ini','.csv','.tsv','.lock','.svg','.png','.jpg','.jpeg','.gif'].includes(p.extname(b).toLowerCase())})(p.relative(R,p.resolve(R,u))))src=false;if(src){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}",
195
195
  "${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
196
196
  "minimal-implementation"
197
197
  ]