qfai 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/README.md +327 -55
  2. package/assets/defaults/agent-routing/qfai-triage.yml +9 -0
  3. package/assets/defaults/workflows/change-tooling.yml +0 -4
  4. package/assets/defaults/workflows/run-owned-cleanup.yml +12 -0
  5. package/assets/init/.claude/settings.json +27 -9
  6. package/assets/init/.codex/hooks.json +20 -8
  7. package/assets/init/.qfai/assistant/agent/frontend-engineer.md +5 -0
  8. package/assets/init/.qfai/assistant/agent/implementation-reviewer.md +14 -1
  9. package/assets/init/.qfai/assistant/agent/product-experience-architect.md +4 -0
  10. package/assets/init/.qfai/assistant/agent/product-surface-reviewer.md +7 -0
  11. package/assets/init/.qfai/assistant/agent/qa-gatekeeper.md +29 -0
  12. package/assets/init/.qfai/assistant/agent/requirements-analyst.md +8 -0
  13. package/assets/init/.qfai/assistant/rule/communication.md +18 -8
  14. package/assets/init/.qfai/assistant/rule/constitution.md +5 -4
  15. package/assets/init/.qfai/assistant/rule/drift-protocol.md +72 -5
  16. package/assets/init/.qfai/assistant/rule/references/asset-authoring.md +72 -0
  17. package/assets/init/.qfai/assistant/rule/references/context-summary.md +30 -0
  18. package/assets/init/.qfai/assistant/rule/references/gate-failure-autorepair.md +42 -0
  19. package/assets/init/.qfai/assistant/rule/references/griller-recommendations.md +23 -0
  20. package/assets/init/.qfai/assistant/rule/references/host-backstops.md +60 -0
  21. package/assets/init/.qfai/assistant/rule/references/placeholder-scan.md +25 -0
  22. package/assets/init/.qfai/assistant/rule/references/reviewer-findings.md +41 -0
  23. package/assets/init/.qfai/assistant/rule/references/reviewer-remit.md +35 -0
  24. package/assets/init/.qfai/assistant/rule/references/worker-edit-boundary.md +48 -0
  25. package/assets/init/.qfai/assistant/rule/shared-skill-delegation-baseline.md +39 -162
  26. package/assets/init/.qfai/assistant/rule/shared-skill-operating-baseline.md +14 -145
  27. package/assets/init/.qfai/assistant/rule/stage-cost.md +1 -1
  28. package/assets/init/.qfai/assistant/rule/test-layers.md +42 -0
  29. package/assets/init/.qfai/assistant/rule/ui-definition-protocol.md +6 -1
  30. package/assets/init/.qfai/assistant/skill/qfai-configure/SKILL.md +11 -45
  31. package/assets/init/.qfai/assistant/skill/qfai-configure/references/principles.md +25 -0
  32. package/assets/init/.qfai/assistant/skill/qfai-discussion/SKILL.md +1 -1
  33. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/discussion-completion-matrix.md +12 -9
  34. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui-bearing-playbook.md +2 -4
  35. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux/platform_baselines.md +11 -0
  36. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux/review_audit_playbook.md +11 -0
  37. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux_best_practices.md +13 -42
  38. package/assets/init/.qfai/assistant/skill/qfai-discussion/templates/uiux/00_index.md +0 -16
  39. package/assets/init/.qfai/assistant/skill/qfai-grill/SKILL.md +1 -1
  40. package/assets/init/.qfai/assistant/skill/qfai-grilling/SKILL.md +16 -88
  41. package/assets/init/.qfai/assistant/skill/qfai-grilling/references/delegated-session.md +58 -0
  42. package/assets/init/.qfai/assistant/skill/qfai-grilling/references/session-record.md +42 -0
  43. package/assets/init/.qfai/assistant/skill/qfai-implement/SKILL.md +17 -4
  44. package/assets/init/.qfai/assistant/skill/qfai-implement/references/credential-reuse.md +9 -0
  45. package/assets/init/.qfai/assistant/skill/qfai-implement/references/cross-spec-ownership.md +41 -0
  46. package/assets/init/.qfai/assistant/skill/qfai-implement/references/oracle-strength.md +6 -1
  47. package/assets/init/.qfai/assistant/skill/qfai-implement/references/parallelization-policy.md +9 -1
  48. package/assets/init/.qfai/assistant/skill/qfai-implement/references/red-admissibility.md +47 -1
  49. package/assets/init/.qfai/assistant/skill/qfai-implement/references/ui-affecting.md +4 -3
  50. package/assets/init/.qfai/assistant/skill/qfai-maintain/SKILL.md +4 -4
  51. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/SKILL.md +16 -10
  52. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/references/migration-guide.md +10 -260
  53. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/references/migration-placement.md +273 -0
  54. package/assets/init/.qfai/assistant/skill/qfai-prototyping/SKILL.md +5 -4
  55. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/design-md-spec.md +14 -5
  56. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/generator-prompt.md +10 -0
  57. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/handoff.md +1 -1
  58. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/iteration-loop.md +3 -4
  59. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/review-payload-schema.md +12 -5
  60. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/reviewer-prompt.md +33 -4
  61. package/assets/init/.qfai/assistant/skill/qfai-run/SKILL.md +34 -36
  62. package/assets/init/.qfai/assistant/skill/qfai-run/references/evidence-delivery.md +322 -0
  63. package/assets/init/.qfai/assistant/skill/qfai-run/references/extraction.md +67 -24
  64. package/assets/init/.qfai/assistant/skill/qfai-run/references/operator-screens.md +246 -5
  65. package/assets/init/.qfai/assistant/skill/qfai-run/references/plan.md +29 -5
  66. package/assets/init/.qfai/assistant/skill/qfai-run/references/stage-points.md +43 -0
  67. package/assets/init/.qfai/assistant/skill/qfai-sdd/SKILL.md +5 -4
  68. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/concrete-abstract-cycle.md +10 -1
  69. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/contract-artifact-rules.md +22 -9
  70. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-anti-patterns.md +41 -6
  71. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-md-authoring.md +11 -5
  72. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-md-brand-catalog.md +17 -2
  73. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/requirements-decomposition.md +10 -2
  74. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-execution-playbook.md +34 -9
  75. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-phase-checklists.md +4 -4
  76. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-quality-gate.md +2 -2
  77. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-triage.md +177 -7
  78. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/spec-traceability-rules.md +29 -5
  79. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-contract-guide.md +130 -1
  80. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-design-contract-normalization.md +4 -30
  81. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/change-request.md +39 -2
  82. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/contracts/db-contract.sample.sql +2 -2
  83. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/contracts/ui-contract.sample.yaml +52 -5
  84. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/03_contract/cli/cli-NNNN-title.md +2 -2
  85. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/03_contract/tech.md +1 -1
  86. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/decisions.md +7 -0
  87. package/assets/init/.qfai/assistant/skill/qfai-triage/SKILL.md +30 -20
  88. package/assets/init/.qfai/assistant/skill/qfai-verify/SKILL.md +6 -5
  89. package/assets/init/.qfai/assistant/skill/qfai-verify/references/context-load.md +9 -6
  90. package/assets/init/.qfai/assistant/skill/qfai-verify/references/verify-output-contract.md +92 -13
  91. package/assets/init/.qfai/assistant/skill/web-research/SKILL.md +37 -31
  92. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/brave-search/config.toml +2 -4
  93. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/brave-search/mcp-config.json +9 -5
  94. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/.mcp.json +1 -2
  95. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/config.toml +3 -3
  96. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/mcp-config.json +11 -8
  97. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/playwright/config.toml +1 -1
  98. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/playwright/mcp-config.json +7 -3
  99. package/assets/init/.qfai/assistant/step/common-design-md/STEP.md +17 -7
  100. package/assets/init/.qfai/assistant/step/common-gate-run/STEP.md +126 -13
  101. package/assets/init/.qfai/assistant/step/common-review-cycle/STEP.md +3 -2
  102. package/assets/init/.qfai/assistant/step/discussion-pack/STEP.md +1 -0
  103. package/assets/init/.qfai/assistant/step/discussion-research/STEP.md +2 -0
  104. package/assets/init/.qfai/assistant/step/discussion-uiux/STEP.md +12 -4
  105. package/assets/init/.qfai/assistant/step/implement-backport/STEP.md +1 -1
  106. package/assets/init/.qfai/assistant/step/implement-benchmark/STEP.md +7 -4
  107. package/assets/init/.qfai/assistant/step/implement-dep-bump/STEP.md +11 -1
  108. package/assets/init/.qfai/assistant/step/implement-diagnose/STEP.md +2 -0
  109. package/assets/init/.qfai/assistant/step/implement-oracle-parity/STEP.md +2 -2
  110. package/assets/init/.qfai/assistant/step/implement-quarantine/STEP.md +3 -3
  111. package/assets/init/.qfai/assistant/step/implement-refactor/STEP.md +2 -2
  112. package/assets/init/.qfai/assistant/step/implement-regression-fix/STEP.md +4 -3
  113. package/assets/init/.qfai/assistant/step/implement-retire/STEP.md +6 -3
  114. package/assets/init/.qfai/assistant/step/implement-revert/STEP.md +4 -3
  115. package/assets/init/.qfai/assistant/step/implement-scaffold/STEP.md +3 -3
  116. package/assets/init/.qfai/assistant/step/implement-seam/STEP.md +9 -6
  117. package/assets/init/.qfai/assistant/step/implement-stress-harness/STEP.md +1 -1
  118. package/assets/init/.qfai/assistant/step/implement-sweep/STEP.md +4 -3
  119. package/assets/init/.qfai/assistant/step/implement-tdd/STEP.md +42 -6
  120. package/assets/init/.qfai/assistant/step/implement-test-fix/STEP.md +2 -0
  121. package/assets/init/.qfai/assistant/step/implement-tooling/STEP.md +22 -2
  122. package/assets/init/.qfai/assistant/step/maintain-edit/STEP.md +18 -14
  123. package/assets/init/.qfai/assistant/step/prototyping-grill/STEP.md +4 -3
  124. package/assets/init/.qfai/assistant/step/prototyping-loop/STEP.md +4 -0
  125. package/assets/init/.qfai/assistant/step/prototyping-preflight/STEP.md +1 -3
  126. package/assets/init/.qfai/assistant/step/sdd-contract/STEP.md +11 -0
  127. package/assets/init/.qfai/assistant/step/sdd-flow/STEP.md +4 -0
  128. package/assets/init/.qfai/assistant/step/sdd-gate/STEP.md +4 -0
  129. package/assets/init/.qfai/assistant/step/sdd-story/STEP.md +10 -0
  130. package/assets/init/.qfai/assistant/step/sdd-triage/STEP.md +37 -11
  131. package/assets/init/.qfai/assistant/step/triage-answer/STEP.md +5 -4
  132. package/assets/init/.qfai/assistant/step/triage-close/STEP.md +33 -23
  133. package/assets/init/.qfai/assistant/step/triage-cluster/STEP.md +5 -3
  134. package/assets/init/.qfai/assistant/step/triage-decompose/STEP.md +2 -0
  135. package/assets/init/.qfai/assistant/step/triage-dedupe/STEP.md +4 -0
  136. package/assets/init/.qfai/assistant/step/triage-handoff/STEP.md +13 -4
  137. package/assets/init/.qfai/assistant/step/triage-investigate/STEP.md +8 -5
  138. package/assets/init/.qfai/assistant/step/triage-owned-cleanup/STEP.md +86 -0
  139. package/assets/init/.qfai/assistant/step/triage-request-info/STEP.md +2 -0
  140. package/assets/init/.qfai/assistant/step/triage-security-intake/STEP.md +6 -3
  141. package/assets/init/.qfai/assistant/step/verify-change-note/STEP.md +9 -5
  142. package/assets/init/.qfai/assistant/step/verify-commit/STEP.md +39 -6
  143. package/assets/init/.qfai/assistant/step/verify-external/STEP.md +5 -2
  144. package/assets/init/.qfai/assistant/step/verify-qfai-gate/STEP.md +10 -2
  145. package/assets/init/.qfai/assistant/step/verify-release-notes/STEP.md +1 -0
  146. package/assets/init/.qfai/assistant/step/verify-repeat-run/STEP.md +7 -5
  147. package/assets/init/.qfai/assistant/step/verify-repo-gate/STEP.md +136 -28
  148. package/assets/init/.qfai/waivers.yml +1 -1
  149. package/assets/init/root/.agents/rules/ai-readable-markdown.md +98 -0
  150. package/assets/init/root/.agents/rules/api-budget.md +191 -13
  151. package/assets/init/root/.agents/rules/documentation-clarity.md +192 -35
  152. package/assets/init/root/.agents/rules/grilling.md +4 -4
  153. package/assets/init/root/.agents/rules/interface-clarity.md +5 -5
  154. package/assets/init/root/.agents/rules/minimal-implementation.md +11 -3
  155. package/assets/init/root/.agents/rules/reminders.json +27 -14
  156. package/assets/init/root/.agents/rules/session-feedback.md +136 -0
  157. package/assets/init/root/.agents/rules/temporary-files.md +47 -5
  158. package/assets/init/root/.agents/rules/untrusted-content.md +3 -3
  159. package/assets/init/root/.agents/rules/user-questions.md +74 -36
  160. package/assets/init/root/.gitattributes +9 -0
  161. package/assets/init/root/.github/workflows/qfai-docs.yml +44 -12
  162. package/assets/init/root/.github/workflows/qfai-tests.yml +58 -15
  163. package/assets/init/root/.github/workflows/qfai-validate.yml +49 -42
  164. package/assets/init/root/AGENTS.md +23 -21
  165. package/assets/mdschema/story/03_contract/tech.mdschema.yml +7 -3
  166. package/assets/sandbox-templates/default-deny.yaml +9 -5
  167. package/assets/schemas/workflow/extraction.schema.json +85 -5
  168. package/assets/schemas/workflow/plan.schema.json +9 -2
  169. package/assets/scripts/check-mdschema.mjs +172 -5
  170. package/assets/uix-rev/comparison-review.md +1 -1
  171. package/assets/uix-rev/contracts-review.md +1 -2
  172. package/dist/cli/index.cjs +6499 -7688
  173. package/dist/cli/index.cjs.map +1 -1
  174. package/dist/cli/index.mjs +6449 -7641
  175. package/dist/cli/index.mjs.map +1 -1
  176. package/dist/index.cjs +5548 -7823
  177. package/dist/index.cjs.map +1 -1
  178. package/dist/index.d.cts +40 -666
  179. package/dist/index.d.ts +40 -666
  180. package/dist/index.mjs +5397 -7648
  181. package/dist/index.mjs.map +1 -1
  182. package/docs/MIGRATION-2.0.0.md +1 -1
  183. package/package.json +5 -7
  184. package/assets/init/root/CLAUDE.md +0 -45
@@ -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>]/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)[\\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{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)[\\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 m=JSON.parse(require('fs').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 m=JSON.parse(require('fs').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 c=JSON.parse(require('fs').readFileSync(0,'utf8')).tool_input.command;if(typeof c==='string'&&/(^|[^\\w.-])gh([^\\w.-]|$)|api\\.github\\.com/.test(c)){const m=JSON.parse(require('fs').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 m=JSON.parse(require('fs').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 m=JSON.parse(require('fs').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,13 +191,31 @@
191
191
  "command": "node",
192
192
  "args": [
193
193
  "-e",
194
- "try{const m=JSON.parse(require('fs').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
  ]
198
198
  }
199
199
  ]
200
200
  }
201
+ ],
202
+ "Stop": [
203
+ {
204
+ "matcher": "*",
205
+ "hooks": [
206
+ {
207
+ "type": "command",
208
+ "statusMessage": "QFAI session feedback reminder",
209
+ "command": "node",
210
+ "args": [
211
+ "-e",
212
+ "try{const f=require('fs');let i;try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{process.exit(0)}const l=typeof i.last_assistant_message==='string'?i.last_assistant_message.trim():'';if(i.stop_hook_active===true||(l.length>0&&('?'+String.fromCharCode(65311)).includes(l.slice(-1))))process.exit(0);const m=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(m!==undefined)console.log(JSON.stringify(m))}catch{}",
213
+ "${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
214
+ "session-feedback"
215
+ ]
216
+ }
217
+ ]
218
+ }
201
219
  ]
202
220
  }
203
221
  }
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "type": "command",
8
8
  "statusMessage": "QFAI structured-question reminder",
9
- "command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){const m=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];m===undefined||console.log(JSON.stringify(m))}}catch{}\" structured-question",
9
+ "command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}\" structured-question",
10
10
  "timeout": 10
11
11
  }
12
12
  ]
@@ -16,7 +16,7 @@
16
16
  {
17
17
  "type": "command",
18
18
  "statusMessage": "QFAI free-text entry reminder",
19
- "command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){const m=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];m===undefined||console.log(JSON.stringify(m))}}catch{}\" free-text-entry",
19
+ "command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}\" free-text-entry",
20
20
  "timeout": 10
21
21
  }
22
22
  ]
@@ -50,7 +50,7 @@
50
50
  {
51
51
  "type": "command",
52
52
  "statusMessage": "QFAI grilling reminder: design artifact",
53
- "command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){const m=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];m===undefined||console.log(JSON.stringify(m))}}catch{}\" grilling-design-artifact",
53
+ "command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}\" grilling-design-artifact",
54
54
  "timeout": 10
55
55
  }
56
56
  ]
@@ -61,7 +61,7 @@
61
61
  {
62
62
  "type": "command",
63
63
  "statusMessage": "QFAI grilling reminder: delegation",
64
- "command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){const m=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];m===undefined||console.log(JSON.stringify(m))}}catch{}\" grilling-delegation",
64
+ "command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}\" grilling-delegation",
65
65
  "timeout": 10
66
66
  }
67
67
  ]
@@ -72,7 +72,7 @@
72
72
  {
73
73
  "type": "command",
74
74
  "statusMessage": "QFAI api-budget reminder",
75
- "command": "node -e \"try{const i=JSON.parse(require('fs').readFileSync(0,'utf8')).tool_input.command;if(typeof i==='string'&&/[^\\w.-]gh[^\\w.-]|api\\.github\\.com/.test(' '+i+' ')){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){const m=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];m===undefined||console.log(JSON.stringify(m))}}}catch{}\" api-budget",
75
+ "command": "node -e \"try{const j=JSON.parse(require('fs').readFileSync(0,'utf8')),i=j.tool_input.command;if(typeof i==='string'&&/[^\\w.-]gh[^\\w.-]|api\\.github\\.com/.test(' '+i+' ')){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}}catch{}\" api-budget",
76
76
  "timeout": 10
77
77
  }
78
78
  ]
@@ -85,13 +85,13 @@
85
85
  {
86
86
  "type": "command",
87
87
  "statusMessage": "QFAI documentation-clarity reminder",
88
- "command": "node -e \"try{const i=JSON.parse(require('fs').readFileSync(0,'utf8')).tool_input.command;if(typeof i==='string'&&/^\\*\\*\\* Add File: .*\\.md\\r?\\n/m.test(i)){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){const m=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];m===undefined||console.log(JSON.stringify(m))}}}catch{}\" documentation-clarity-after-write",
88
+ "command": "node -e \"try{const j=JSON.parse(require('fs').readFileSync(0,'utf8')),i=j.tool_input.command;if(typeof i==='string'&&/^\\*\\*\\* Add File: .*\\.md\\r?\\n/m.test(i)){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}}catch{}\" documentation-clarity-after-write",
89
89
  "timeout": 10
90
90
  },
91
91
  {
92
92
  "type": "command",
93
93
  "statusMessage": "QFAI documentation-clarity reminder",
94
- "command": "node -e \"try{const i=JSON.parse(require('fs').readFileSync(0,'utf8')).tool_input.command;if(typeof i==='string'&&/^\\*\\*\\* Update File: .*\\.md\\r?\\n/m.test(i)){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){const m=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];m===undefined||console.log(JSON.stringify(m))}}}catch{}\" documentation-clarity-after-edit",
94
+ "command": "node -e \"try{const j=JSON.parse(require('fs').readFileSync(0,'utf8')),i=j.tool_input.command;if(typeof i==='string'&&/^\\*\\*\\* Update File: .*\\.md\\r?\\n/m.test(i)){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}}catch{}\" documentation-clarity-after-edit",
95
95
  "timeout": 10
96
96
  }
97
97
  ]
@@ -102,7 +102,19 @@
102
102
  {
103
103
  "type": "command",
104
104
  "statusMessage": "QFAI minimal-implementation reminder",
105
- "command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){const m=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];m===undefined||console.log(JSON.stringify(m))}}catch{}\" minimal-implementation",
105
+ "command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let go=true,j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))}catch{}try{const R=p.dirname(p.dirname(p.dirname(r))),n=(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())}),a=[...j.tool_input.command.matchAll(/^\\*\\*\\* (?:(?:Add|Update|Delete) File|Move to): (.+)/gm)].map(m=>p.relative(R,p.resolve(process.cwd(),m[1].trim())));if(a.length>0&&a.every(n))go=false}catch{}if(go){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}}catch{}\" minimal-implementation",
106
+ "timeout": 10
107
+ }
108
+ ]
109
+ }
110
+ ],
111
+ "Stop": [
112
+ {
113
+ "hooks": [
114
+ {
115
+ "type": "command",
116
+ "statusMessage": "QFAI session feedback reminder",
117
+ "command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let i;try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{process.exit(0)}const l=typeof i.last_assistant_message==='string'?i.last_assistant_message.trim():'';if(i.stop_hook_active===true||(l.length>0&&('?'+String.fromCharCode(65311)).includes(l.slice(-1))))process.exit(0);const m=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];m===undefined||console.log(JSON.stringify(m))}}catch{}\" session-feedback",
106
118
  "timeout": 10
107
119
  }
108
120
  ]
@@ -45,6 +45,11 @@ specialization_tags:
45
45
  a deliberate shortcut with its ceiling and the condition that lifts it.
46
46
  - Apply `.qfai/assistant/rule/ui-procurement.md`: install a component before writing one, and for anything the prototype does not show, take the adopted system's default rather than inventing one.
47
47
  - Apply `.agents/rules/interface-clarity.md`: label a control in the user's words, never the parameter's. The label stays; the sentence explaining it goes.
48
+ - Read each screen's `supplements` and `structure` in its UI contract before adding any text.
49
+ Show the title, the group headings, the labels and the declared supplements, and no other text.
50
+ A text the screen needs that the contract lacks, or a declared one the screen should not show,
51
+ is a change to the contract and goes back to `/qfai-sdd`.
52
+ Element ids stay the same when only wording changes.
48
53
 
49
54
  ## Inputs you must read
50
55
 
@@ -30,8 +30,13 @@ specialization_tags:
30
30
 
31
31
  ## Domain Responsibilities
32
32
 
33
- - Review changed production code and tests against the affected BF, AC, EX, BR and contracts. Check correctness, input validation, security, performance, maintainability and operational failure paths.
33
+ - Review changed production code and tests against the affected BF, AC, EX, BR and contracts. Check correctness, performance, maintainability and operational failure paths, and security as set out below.
34
34
  - Apply the repository review checklist and `.agents/rules/minimal-implementation.md`. Require a concrete smaller implementation when reporting excess; preserve every safety-floor obligation.
35
+ - Check that the change adds test files sized like their neighbours, commits no
36
+ scratch checks, follows § 4 on unrequested fixes, including its exception for
37
+ a necessary fix and reporting requirement, and states any assumption it built
38
+ on (`.qfai/assistant/rule/test-layers.md#test-suite-sizing`,
39
+ `.agents/rules/minimal-implementation.md` § 4).
35
40
  - Check each new module against the `## Architecture` table of `<paths.contractsDir>/tech.md`: it belongs to one layer and imports only from the layers its row lists. A crossing is a finding.
36
41
  - In TypeScript, flag unjustified assertions, unchecked `unknown`, needless generic complexity and promises that callers neither await nor return.
37
42
  - Read the whole of every file the change touches for silent failure and type design, not only the lines the change adds or alters.
@@ -46,6 +51,14 @@ specialization_tags:
46
51
  - Type design, against § 2 of the same rule: a value crossing a trust boundary is parsed there into a form that cannot hold an invalid value.
47
52
  Flag mutable internals exposed to outside code, an invariant held only by documentation, validation missing at construction,
48
53
  enforcement that differs from one mutation to another, and outside code left to maintain an invariant the type should own.
54
+ - Security, read on the same scope as silent failure and type design above. Check the three shapes the drift protocol names:
55
+ missing validation on an input the code already treats as trusted, credential or personal-data exposure, and an injection or traversal path opened by the change.
56
+ Cover injection, cross-site scripting, server-side request forgery, hardcoded secrets, insecure direct object reference, auth bypass,
57
+ unsafe deserialization and path traversal. Judge validation at a trust boundary against `.agents/rules/minimal-implementation.md` § 2.
58
+ - Follow every input the change adds or alters to where it is used, across files the change did not touch.
59
+ A path the change opens there, such as an insecure direct object reference, a server-side request forgery or an auth bypass reached through several files, blocks.
60
+ This is the one reach beyond the touched files, and it covers only the inputs the change handles.
61
+ A security finding the review demonstrates traces to `defect:security`.
49
62
  - Check the EX test's oracle, selector and RED/GREEN/Refactor evidence.
50
63
  - Check for code written only to pass a test: no value hard-coded to the test's
51
64
  inputs and no branch written only for the test, and a wrong test or infeasible
@@ -51,6 +51,10 @@ specialization_tags:
51
51
  - Apply `.agents/rules/minimal-implementation.md`: the first rung is this stage's — whether the thing needs to exist. After a spec row is agreed, that question is a Change Request.
52
52
  - Apply `.qfai/assistant/rule/ui-procurement.md`: the theme is adopted from a published one, and a screen's composition comes from a catalogue before it is drawn.
53
53
  - Apply `.agents/rules/interface-clarity.md`: a screen that needs explaining is a screen that needs redesigning. Hint text only where a need was demonstrated and the control was improved first.
54
+ - Record the decisions that rule leads to in the screen contract: the `supplements` a screen shows,
55
+ each with the reason it is needed, and the `structure` of any heading beyond the title
56
+ (`.qfai/assistant/skill/qfai-sdd/references/ui-contract-guide.md`).
57
+ Fix the control before adding a supplement for it.
54
58
 
55
59
  ## Inputs you must read
56
60
 
@@ -61,6 +61,13 @@ specialization_tags:
61
61
  - Require more work only on what `.qfai/assistant/rule/shared-skill-delegation-baseline.md#what-a-reviewer-may-demand-more-of-must` admits, and report any other gap as advisory.
62
62
  - Apply `.qfai/assistant/rule/ui-procurement.md`: report a region that was drawn where a catalogue had it, and name the item that would have served.
63
63
  - Apply `.agents/rules/interface-clarity.md`: report copy that explains the interface as a finding against the control under it, and name the control to fix.
64
+ - Compare the rendered text with each screen's `supplements` and `structure` in its UI contract.
65
+ A text no contract key declares is excess; a declared supplement that is not shown is an unmet need.
66
+ Name the screen, the task or state, the duplicated meaning or unmet need, and the text to remove or the control or structure to repair.
67
+ Apply `.qfai/assistant/skill/qfai-sdd/references/design-anti-patterns.md#copy-that-adds-no-meaning`.
68
+ Never cut a condition, cost, destination, result, recovery step, consequence, label, accessible name, role or state,
69
+ and never move one into a tooltip or placeholder.
70
+ Word counts are observations, never a verdict.
64
71
 
65
72
  ## Inputs you must read
66
73
 
@@ -59,6 +59,11 @@ scope.
59
59
  code, control, setting or copy to remove or simplify and what replaces it.
60
60
  Do not weaken an active obligation to reduce code.
61
61
  - Treat test volume and density as review signals, not independent hard gates.
62
+ - Check that the change adds test files sized like their neighbours, commits no
63
+ scratch checks, follows § 4 on unrequested fixes, including its exception for
64
+ a necessary fix and reporting requirement, and states any assumption it built
65
+ on
66
+ (`.qfai/assistant/rule/test-layers.md#test-suite-sizing`, `.agents/rules/minimal-implementation.md` § 4).
62
67
 
63
68
  ## Coverage gate
64
69
 
@@ -79,6 +84,30 @@ required gap or weak oracle; report a gap owned by another stage with that
79
84
  owner. During SDD, assess the requirement links without demanding tests that
80
85
  implementation has not written.
81
86
 
87
+ ## Refactor survival check (advisory)
88
+
89
+ Read each test on four questions. Three are already judged elsewhere; apply
90
+ them there, not twice.
91
+
92
+ | Question | Where it is judged |
93
+ | ----------------------------------------------------------------- | ----------------------------------------------------------------------- |
94
+ | Does it cover the behaviour that matters, not just the lines run? | The coverage gate above |
95
+ | Are the recurring gaps covered: kept failures and boundary cases? | The coverage gate above |
96
+ | Would it fail on a concrete regression? | `skill/qfai-implement/references/oracle-strength.md`, proof per example |
97
+ | Would it survive a refactor that keeps the behaviour? | This check |
98
+
99
+ A test fails the last question when it asserts on what the contract does not
100
+ name: a private function, an internal call order, a mock of the code's own
101
+ collaborators, or the structure of a value rather than what it means. Such a
102
+ test breaks on a safe change and teaches authors to weaken the test instead of
103
+ fixing the code.
104
+
105
+ Read the whole of every test file the change touches, not only the tests it
106
+ adds or alters. Record each finding with the test and a concrete
107
+ behaviour-preserving change it would fail on. A finding with no named change is
108
+ not admitted. A finding on a test that existed before the change is recorded
109
+ and deferred. This check does not REVISE on its own and carries no score.
110
+
82
111
  ## Completion and runtime gate
83
112
 
84
113
  - Read the current BF's test selectors, command results and reviewer
@@ -64,6 +64,14 @@ specialization_tags:
64
64
  `/qfai-prototyping` at the validate lane, under `QFAI-AUD-001`. The shipped
65
65
  `ui-contract.sample.yaml` carries filled entries; replace them with the
66
66
  screen's own rather than emptying the list.
67
+ - For UI-bearing surfaces: every screen also declares `supplements` (`[]` when it
68
+ shows no text beyond its title, group headings and labels), and `structure` when
69
+ it shows a heading beyond its title. Check each proposed text element against the
70
+ screen's tasks and the information the user needs there, and write the ones that
71
+ stay into the contract with the reason. A text that only repeats a heading, an
72
+ action or a condition the user already has is left out. Discussion sidecars
73
+ inform these keys; they do not replace them. See
74
+ `.qfai/assistant/skill/qfai-sdd/references/ui-contract-guide.md`.
67
75
 
68
76
  ## Stop conditions
69
77
 
@@ -45,14 +45,24 @@ Applies to every text an agent produces: pull request and issue titles and
45
45
  bodies, source-code comments, and Markdown files — inside the current change
46
46
  only, never beyond its diff.
47
47
 
48
- - No issue or pull-request numbers, ticket identifiers, or names only this
49
- project understands, anywhere in source code or Markdown files. Those belong
50
- in the pull request, the commit message and the changelog.
51
- - No account of how the work went. State the current behaviour and why it is
52
- that way; the history is already in the git log and the pull request.
53
- - Cut what is self-evident, repeated, or wordy.
54
- - Ordinary vocabulary, one claim per sentence, lines short enough to scan.
55
- - Bullet lists and tables for parallel items.
48
+ - Keep out issue or pull-request numbers, ticket identifiers and names only
49
+ this project understands, anywhere in source code or Markdown files. Those
50
+ belong in the pull request, the commit message and the changelog.
51
+ Pull request and issue bodies may contain numbers and links.
52
+ - Keep out any account of how the work went. State the current behaviour and
53
+ why it is that way; the history is already in the git log and the pull
54
+ request.
55
+ - Settle the message, then open with the conclusion: the title or opening says
56
+ the result, with no preamble.
57
+ - Cut what is self-evident, repeated or wordy, and announcements, recaps and
58
+ empty intensifiers.
59
+ - Use ordinary vocabulary, one claim per sentence, and name who acts; software
60
+ does not know or decide.
61
+ - Mark what is unverified, and say what a claim rests on.
62
+ - Use bullet lists and tables for parallel items, with lines short enough to
63
+ scan.
64
+ - Open a report with the outcome, and separate what was checked from what was
65
+ not.
56
66
  - Re-read every changed line afterwards. Text that reads as a literal
57
67
  translation is a defect in any language pair — rewrite it the way someone
58
68
  writing natively in that language would.
@@ -95,7 +95,7 @@ The test obligation is determined by the ID and the test's layer:
95
95
 
96
96
  - `BF-*` requires a `QFAI:BF-NNNN` annotation in an E2E test.
97
97
  - `AC-*` requires a `QFAI:AC-NNNN-NNNN-NN` annotation in an integration or API test.
98
- - `EX-*` requires a `QFAI:EX-NNNN-NNNN-NN` annotation in a selected non-E2E test file.
98
+ - `EX-*` requires a `QFAI:EX-NNNN-NNNN-NN` annotation in a selected non-E2E test file, on the comment line directly before the test declaration it marks.
99
99
 
100
100
  `.qfai/assistant/rule/test-layers.md` defines the layer directories. Test
101
101
  selection uses `validation.traceability.testFileGlobs`. A missing BF, AC, or EX
@@ -429,7 +429,8 @@ This article survives context compaction because `.qfai/assistant/rule/constitut
429
429
 
430
430
  Scratch files an agent creates for its own convenience — working notes, one-off
431
431
  scripts, captured command output, downloaded samples, intermediate data —
432
- must be placed under the repository‑root `tmp/` directory.
432
+ go in the scratch directory the host names for the session, and under the
433
+ repository-root `tmp/` directory where the host names none.
433
434
 
434
435
  Scope: this article is about files written **into the working tree**. Two kinds
435
436
  of output are outside it:
@@ -445,7 +446,7 @@ of output are outside it:
445
446
  Rules:
446
447
 
447
448
  1. **Never** create a scratch file in the repository root, `src/`, `.qfai/spec/`, or any other production/artifact directory.
448
- 2. Use `tmp/` (repository root) as the sole staging area. Create subdirectories as needed (e.g., `tmp/notes/`, `tmp/capture/`).
449
+ 2. When the host names a scratch directory for the session, use it. Otherwise use `tmp/` (repository root) as the sole staging area, with subdirectories as needed (e.g., `tmp/notes/`, `tmp/capture/`). A file the repository's own tooling must read from the working tree goes under `tmp/` whichever the host names.
449
450
  3. `tmp/` MUST be listed in `.gitignore` so temporary files are never committed.
450
- 4. Clean up `tmp/` contents when the task that created them is complete.
451
+ 4. Clean up `tmp/` contents when the task that created them is complete. The host's own scratch directory is the host's to clean.
451
452
  5. If a scratch file is found outside `tmp/` **in the working tree**, treat it as a defect and move or delete it immediately. A test's `mkdtemp` sandbox and configured toolchain output are not scratch files — see Scope above.
@@ -11,7 +11,18 @@ open-questions.md, and every file under paths.contractsDir. Production code and
11
11
  obligation may change in a later task, but the change must preserve or deliberately reapprove that obligation.
12
12
  QFAI-owned files under .qfai/assistant/rule/ are updated through the package, then synchronized by init.
13
13
 
14
- A new change-request row in decisions.md is permitted without an earlier change request. Existing rows are append-only records: a prior row may change its Status cell, while its ID, Content, and Approach stay fixed. A changed obligation needs a new row, even when an older row discusses the same topic.
14
+ A new change-request row in decisions.md is permitted without an earlier change request.
15
+ Existing rows are append-only records: a prior row may change its Status cell,
16
+ while its ID, Content, and Approach stay fixed. The sole ID-change exception is
17
+ explicit local renumbering of a unique branch-added decision absent from the
18
+ unique common ancestor of HEAD and a required local base. That operation changes
19
+ only its ID and provably branch-added exact references. Inherited row cells and
20
+ references stay fixed. A changed obligation needs a new row, even when an older
21
+ row discusses the same topic.
22
+
23
+ The sole Approach-change exception is the published decision prefix recovery
24
+ below. It preserves the target's ID, Content, Status and complete original
25
+ Approach suffix. It excludes inherited rows and open-question rows.
15
26
 
16
27
  ## Allowed exceptions (minimal whitelist)
17
28
 
@@ -25,6 +36,51 @@ diff changes no file under `01_policy/`, `02_business-flow/` or
25
36
  paths.contractsDir appends no `Change request:` row. A row naming a story file
26
37
  the bugfix did not touch would state an upstream change that did not happen.
27
38
 
39
+ ## Published decision prefix recovery
40
+
41
+ The SDD owner may prepend only an approved Evidence/Grounds prefix to a
42
+ published malformed decision added by the branch. Approval of this protocol
43
+ does not authorize any target. First obtain a separate new in-force recovery
44
+ change request naming the target and its repository-relative file.
45
+
46
+ 1. Fix the explicit base commit and its unique common ancestor with current
47
+ HEAD. The target ID must be absent from both. The documented published
48
+ source commit must be an ancestor of current HEAD and expose the unique
49
+ original row. Inherited records and open-question rows are excluded.
50
+ 2. Read the original raw cell bytes. The row must be single-line valid UTF-8,
51
+ with exactly one ASCII padding space at each Approach delimiter and no
52
+ additional boundary whitespace. Hash the stored Approach bytes with
53
+ SHA-256, excluding only the delimiter pipes and those two padding spaces.
54
+ Do not decode escapes, trim text or normalize it. Ambiguous boundaries,
55
+ additional padding or invalid UTF-8 stop recovery.
56
+ 3. Confirm the original Residual risk and Rollback items already satisfy the
57
+ required ordered, nonempty form. The new Approach must be exactly
58
+ `- Evidence: <actual file references> - Grounds: `, including its trailing
59
+ space, followed by the complete original Approach as a byte-identical
60
+ suffix. Preserve the original wording, order, facts, authority and scope.
61
+ The prefixed row must satisfy the existing form checks.
62
+ 4. Record the target ID, published source commit, fixed base and common
63
+ ancestor, old and new Approach digests, exact prefix, actual file evidence,
64
+ approval authority, recovery reason, residual risk and rollback in the new
65
+ request. The old digest must match the published source Approach; the new
66
+ digest must match the exact prefix plus that source value. Record actual
67
+ authority; do not invent a user answer or approval.
68
+ 5. Immediately before writing, recheck the unique current target, its
69
+ byte-identical ID, Content and Status, and its recorded old Approach digest.
70
+ Unknown or unavailable provenance, inherited or duplicate targets, stale
71
+ data or a change beyond the approved prefix stops recovery before writing.
72
+ Apply only that prefix. Keep every other row byte-identical and run the existing
73
+ validation without an exemption.
74
+ 6. Rollback removes only the recorded prefix when the current new Approach
75
+ digest still matches. Otherwise preserve the current row and stop. Removal
76
+ restores the original Approach and its validation failure. Do not reset,
77
+ amend or force push.
78
+
79
+ Status changes, supersession and a diagnostic's rewrite suggestion supply no
80
+ recovery authority and clear no form error. Inherited malformed records need a
81
+ separate owning change request; this operation cannot repair them. These are
82
+ owner instructions, not an automatic recovery command or a concurrency guarantee.
83
+
28
84
  ## Drift classes
29
85
 
30
86
  Intent drift means an approved obligation should change. Record the current obligation, the proposed behavior, realistic
@@ -49,14 +105,18 @@ Both classes use the same approval and owner-rerun path.
49
105
 
50
106
  When the user cannot be asked, write no row to either table: report the proposed change as the decision still
51
107
  needed and keep the affected items stopped. A WIP Change request: row is the in-force authorization that the drift gate reads.
52
- TODO is not authorization. A DONE row authorizes only on the branch that appended it or moved it from WIP. A DONE
53
- row the base already holds records a change already applied, so a later edit to the same path needs a row of its own.
108
+ TODO is not authorization. A DONE row authorizes only on the branch that appended it or moved it from WIP. A row
109
+ the base already holds at DONE records a change already applied and authorizes nothing at any Status, so setting it back to WIP
110
+ reopens nothing and a later edit to the same path needs a row of its own.
54
111
 
55
112
  4. Rerun the owner skill against the affected artifact. The owner names the approved decision row, the input revision, and whether it is confirming existing content or changing it.
56
113
  A contract with a contract ID, such as `API-0002`, is selected by its full ID; a contract without one is selected by its repository-relative path.
57
114
  The owner updates the specification and its tests together, then validates the relevant flow.
58
115
  5. Recheck every dependent BF, AC, and EX test obligation and every affected contract reference. Rewrite tests where their former expectation is invalid. Report any uncovered obligation. No execution ledger, TC row, or status reset substitutes for this check.
59
- 6. Complete the decision row by changing Status from WIP to DONE only after the owner artifact and dependent checks are complete. Cite the DEC ID in the stage report. A second open request on the same artifact waits for the first outcome and is restated if its premise changed.
116
+ 6. Complete the decision row by changing Status from WIP to DONE only after the owner artifact and dependent checks are complete.
117
+ A row has one Status, so DONE claims every action its Approach lists. While one of them is deferred, keep the row at WIP.
118
+ Cite the DEC ID in the stage report.
119
+ A second open request on the same artifact waits for the first outcome and is restated if its premise changed.
60
120
 
61
121
  The decision table has no separate Applied at field and no standalone CR file. Approval alone does not certify a change as applied.
62
122
 
@@ -70,6 +130,13 @@ Open requests have independent affected sets. Their union is the set of work pau
70
130
 
71
131
  A defect is demonstrable from the deliverable or an existing obligation: incorrect behavior, missing input validation, data loss, or a failing required quality gate. It is blocking with the concrete artifact and evidence as provenance.
72
132
 
133
+ No repository gate scans for a security or data-integrity defect. Review is that
134
+ class's detector: `.qfai/assistant/agent/implementation-reviewer.md` checks each
135
+ change for missing validation on an input the code already treats as trusted,
136
+ credential or personal-data exposure, and an injection or traversal path opened
137
+ by the change. A finding the reviewer demonstrates traces to `defect:security`
138
+ and blocks like the other blocking classes.
139
+
73
140
  New scope adds product behavior or a quality bar the approved story tree and contracts do not require. A reviewer records it as advisory and sends it to the SDD owner. It becomes binding only after the owner records the decision and updates the relevant BF, AC, EX, BR, or contract. An advisory does not create a test assertion by itself.
74
141
 
75
142
  ### Provenance and routing
@@ -101,6 +168,6 @@ copy of a locked file before deciding its lock is wrong.
101
168
  ## Non-negotiable constraints
102
169
 
103
170
  - Downstream stages do not patch protected upstream artifacts before an in-force Change request: decision row authorizes the path. The drift profile compares the branch against baseBranch and reports QFAI-DRIFT-001 for an unapproved protected change. The SDD owner may create the request row itself without a prior row.
104
- - Existing decisions.md and open-questions.md rows retain their ID, Content, and Approach. A former row may change Status; new content is appended as a new row.
171
+ - Existing decisions.md and open-questions.md rows retain their ID, Content, and Approach, except for the explicit branch-added decision renumbering and published decision prefix recovery above. Inherited rows retain those cells without exception. A former row may change Status; new content is appended as a new row.
105
172
  - Vendored assistant rules are changed in the package and synchronized into projects by `npx qfai init --force`, which overwrites a local edit. A project rule lives in a `*.local.md` overlay beside the vendored one.
106
173
  - When approval is unavailable, keep the affected items stopped and report the decision needed. Continue unrelated work.
@@ -0,0 +1,72 @@
1
+ # Asset Authoring
2
+
3
+ Read when you write or change a shipped asset.
4
+
5
+ ## Asset Authoring Shape (Mandatory)
6
+
7
+ A `SKILL.md` states the contract and points at the file that carries the detail. It is not where the detail lives.
8
+
9
+ - **Keep in `SKILL.md`**: what the skill is for, its non-goals, its hard constraints, the phase/step order, and the gate conditions. Enough for an agent to know what it must do and when it is done.
10
+ - **Move out**: command sets, table schemas, field-by-field contracts, worked procedures, checklists and rationale. These go under the owning tree's own directory:
11
+ - `references/` — normative detail the body cites (`references/<topic>.md`)
12
+ - `templates/` — artifacts the file produces, as fillable skeletons
13
+ - `examples/` — worked instances that illustrate, and bind, nothing
14
+ - **One topic per file.** Do not replace an oversized `SKILL.md` with an oversized `references/everything.md`; that is the same problem one directory down. Split by topic and keep each file readable on its own — a reader who followed one pointer should not have to scan past three unrelated subjects to reach the one they came for.
15
+ - **Every pointer resolves.** A line that moves detail out must name the file (and anchor, when the file covers more than one topic) so the reader is never left guessing where the rule went.
16
+
17
+ A hard line ceiling backs this up: **500 lines per Markdown assistant asset file** and **800 lines per YAML assistant asset file**, for every `.qfai/assistant/**/*.{md,yml,yaml}` file, counted as `content.split(/\r?\n/).length` — blank lines included.
18
+ `npx qfai doctor` measures it and reports every file over its ceiling as `assets.lineBudget`.
19
+ The ceiling is a backstop, not the rule: a file approaching it is a signal to move a section out, not to raise the number.
20
+
21
+ The Markdown ceiling is the limit an agent can follow in one file: it follows fewer instructions as its context grows, and follows those in the middle of a long file worst. A Markdown file has a `references/` home to move detail into. A YAML asset is parsed as data and cannot be split that way (see below), which is why its ceiling is higher.
22
+
23
+ A body at its ceiling stops shedding topics and starts packing them into longer lines, and a line count cannot see that. The width ceiling below is what catches it.
24
+
25
+ **A width ceiling makes the count honest: 400 characters per line.** A count of lines bounds reading cost only while a line is a roughly constant unit of reading, and packing broke that — one line in the shipped tree ran 9,104 characters against a median of 118, and cost the budget one unit. The two are read together, because each permits what the other refuses: width alone allows a thin
26
+ file of a thousand short lines, and the count alone allows a packed one.
27
+
28
+ Two shapes are not measured, and for the same reason — the author cannot make them narrower:
29
+
30
+ | not measured | why |
31
+ | -------------- | --------------------------------------------------------- |
32
+ | a table row | markdown gives it no continuation, so it cannot wrap |
33
+ | a fenced block | its content is a command, a diagram or a sample, verbatim |
34
+
35
+ A table is found by its delimiter row, not by a leading pipe: that pipe is optional, so a table written without one is still a table, and a paragraph that opens with one is still a paragraph. Rows a blank line has cut off from their delimiter are prose, because that is how they render.
36
+
37
+ Some shipped files predate the ceiling and carry a recorded width of their own. It is the width each arrives with, so `npx qfai doctor` does not report a fresh tree for content you received rather than wrote. That record belongs to the package and shrinks there; it is not a per-project allowance, and it never loosens the ceiling on an asset you write. **Everything you author is held
38
+ at 400.**
39
+
40
+ The exemption from the line ceiling does not carry here. Its reason is about a file's length, not about how wide one line may be.
41
+
42
+ No shipped prose asset has a line-ceiling exemption. Agent definitions are
43
+ individual cards under `assistant/agent/`, so their count does not lengthen
44
+ one roster file.
45
+
46
+ ### The owning tree is the one the file sits in
47
+
48
+ The ceiling applies to **every** shipped assistant prose asset, including
49
+ rules and cards. A rule at the ceiling moves topic detail to
50
+ `rule/references/<topic>.md`; a skill uses its own `references/` directory.
51
+ The tree that owns the file owns its detail.
52
+
53
+ For a prose asset, raising the ceiling or claiming an exemption is not the remedy. An exemption claims no split is possible, and a Markdown file whose tree has a `references/` home available cannot make that claim.
54
+
55
+ ### Machine-readable assets split in their own format, or say why they cannot
56
+
57
+ The ceiling is measured over YAML assets too — package routing and review-profile defaults and the shipped contract templates. The split above is not open to them: a validator parses those files as structured data, or a skill copies one whole into a project, so an entry moved into a Markdown sibling under `references/` leaves the parsed document and stops meaning anything. Prose about the
58
+ file may move there; the file's own items may not.
59
+
60
+ So for a machine-readable asset at the ceiling the remedy is, in order:
61
+
62
+ 1. **Split it in its own format** — a sibling of the same kind that the loader or schema already reads, so every item stays parsed.
63
+ 2. **Record an exemption** where the document has to stay whole — a file generated from another asset, one the schema admits only as a single document — naming what makes the split impossible. "This file is long" is not that reason, and a prose asset may not use this step.
64
+
65
+ ## Citation Path Form (Mandatory)
66
+
67
+ A pointer only resolves if the reader knows what base to resolve it against. There is one base, and it is the project root.
68
+
69
+ - **Cite a shared rule by its full path from the project root.** Write the path from the project root, with `#anchor` appended when one applies: `.qfai/assistant/rule/drift-protocol.md`. Not the bare filename, not a relative climb. That form resolves from every directory in the tree,
70
+ and the only one that still resolves when read with the cwd at the project root — which is where an agent's cwd is.
71
+ - **The same holds when a rule cites another one**, including a sibling in its own directory. A bare name works there only by accident of where the citing file sits; it stops working the moment the text is quoted elsewhere.
72
+ - **Within a skill's own directory, keep pointers relative to that directory**: `references/<topic>.md`, `templates/<name>.md`. Those name the skill's own parts, not a shared document, and the surrounding text already establishes which skill is meant.