@haposoft/cafekit 0.16.6 → 0.18.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 (194) hide show
  1. package/README.md +14 -22
  2. package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
  3. package/bin/__tests__/codex-hooks.test.js +0 -36
  4. package/bin/__tests__/codex-native.test.js +46 -88
  5. package/bin/__tests__/develop-contract.test.js +26 -57
  6. package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
  7. package/bin/__tests__/grok-envelope.test.js +0 -56
  8. package/bin/__tests__/hook-payload.test.js +4 -6
  9. package/bin/__tests__/legacy-authority-decoupled.test.js +159 -0
  10. package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
  11. package/bin/__tests__/omp-bridge.test.js +20 -17
  12. package/bin/__tests__/omp-hooks.test.js +0 -19
  13. package/bin/__tests__/optional-skill-inventory.test.js +112 -4
  14. package/bin/__tests__/orca-session.test.js +0 -2
  15. package/bin/__tests__/package-inventory.test.js +100 -541
  16. package/bin/__tests__/runtime-dir.test.js +0 -3
  17. package/bin/__tests__/runtime-schema.test.js +3 -2
  18. package/bin/__tests__/skill-routing-source.test.js +27 -15
  19. package/bin/__tests__/spec-narrowing.test.js +0 -27
  20. package/bin/__tests__/specs-v2-execution-closeout.test.js +4 -69
  21. package/bin/__tests__/ui-ux-skill.test.js +101 -0
  22. package/bin/__tests__/usage-hook-retired.test.js +114 -0
  23. package/bin/__tests__/validator-grounding.test.js +9 -28
  24. package/bin/phases/codex-runtime.js +24 -0
  25. package/bin/phases/omp-runtime.js +22 -0
  26. package/package.json +1 -1
  27. package/src/claude/CLAUDE.md +3 -10
  28. package/src/claude/agents/brainstormer.md +7 -3
  29. package/src/claude/agents/code-auditor.md +35 -50
  30. package/src/claude/agents/debugger.md +5 -79
  31. package/src/claude/agents/deployer.md +3 -5
  32. package/src/claude/agents/docs-keeper.md +2 -7
  33. package/src/claude/agents/implementer.md +6 -9
  34. package/src/claude/agents/inspector.md +2 -3
  35. package/src/claude/agents/project-manager.md +7 -15
  36. package/src/claude/agents/researcher.md +9 -3
  37. package/src/claude/agents/spec-maker.md +11 -11
  38. package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
  39. package/src/claude/hooks/__tests__/spec-gate.test.js +112 -3
  40. package/src/claude/hooks/__tests__/spec-state-touched.test.js +134 -0
  41. package/src/claude/hooks/__tests__/state.test.js +68 -0
  42. package/src/claude/hooks/__tests__/statusline.test.js +140 -11
  43. package/src/claude/hooks/agent.cjs +2 -1
  44. package/src/claude/hooks/lib/context.cjs +0 -89
  45. package/src/claude/hooks/lib/hook-payload.cjs +2 -2
  46. package/src/claude/hooks/rules.cjs +2 -1
  47. package/src/claude/hooks/session.cjs +0 -1
  48. package/src/claude/hooks/spec-gate.cjs +18 -28
  49. package/src/claude/hooks/spec-state.cjs +95 -1
  50. package/src/claude/hooks/state.cjs +21 -2
  51. package/src/claude/migration-manifest.json +30 -15
  52. package/src/claude/rules/ai-dev-rules.md +1 -1
  53. package/src/claude/rules/hook-protocols.md +8 -15
  54. package/src/claude/rules/manage-docs.md +9 -23
  55. package/src/claude/rules/orchestrator.md +3 -4
  56. package/src/claude/rules/review-audit-self-decision.md +3 -3
  57. package/src/claude/rules/skill-domain-routing.md +1 -3
  58. package/src/claude/rules/skill-workflow-routing.md +4 -5
  59. package/src/claude/rules/state-sync.md +3 -12
  60. package/src/claude/rules/workflow.md +10 -18
  61. package/src/claude/runtime.schema.json +5 -3
  62. package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
  63. package/src/claude/scripts/provenance.cjs +23 -5
  64. package/src/claude/scripts/spec-receipt.cjs +52 -6
  65. package/src/claude/scripts/spec-resolver.cjs +1 -2
  66. package/src/claude/scripts/validate-spec-output.cjs +5 -32
  67. package/src/claude/scripts/workflow-policy.cjs +22 -10
  68. package/src/claude/settings/settings.json +1 -32
  69. package/src/claude/skills/{question → ask}/SKILL.md +9 -75
  70. package/src/claude/skills/brainstorm/SKILL.md +43 -131
  71. package/src/claude/skills/code-review/SKILL.md +48 -17
  72. package/src/claude/skills/code-review/references/spec-compliance-review.md +2 -3
  73. package/src/claude/skills/code-review/references/verification-gate.md +22 -27
  74. package/src/claude/skills/debug/SKILL.md +29 -149
  75. package/src/claude/skills/develop/SKILL.md +27 -36
  76. package/src/claude/skills/develop/references/parallel-waves.md +0 -8
  77. package/src/claude/skills/develop/references/quality-gate.md +15 -13
  78. package/src/claude/skills/docs/SKILL.md +2 -2
  79. package/src/claude/skills/docs/references/update-workflow.md +1 -1
  80. package/src/claude/skills/fix/SKILL.md +153 -0
  81. package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
  82. package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
  83. package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
  84. package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
  85. package/src/claude/skills/git/SKILL.md +48 -9
  86. package/src/claude/skills/git/references/commit-protocols.md +35 -16
  87. package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
  88. package/src/claude/skills/orca/SKILL.md +9 -8
  89. package/src/claude/skills/research/SKILL.md +2 -0
  90. package/src/claude/skills/scout/SKILL.md +61 -0
  91. package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
  92. package/src/claude/skills/specs/SKILL.md +22 -29
  93. package/src/claude/skills/specs/references/review.md +8 -6
  94. package/src/claude/skills/specs/references/templates.md +38 -44
  95. package/src/claude/skills/sync/SKILL.md +8 -15
  96. package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
  97. package/src/claude/skills/sync/references/sync-protocols.md +4 -13
  98. package/src/claude/skills/test/SKILL.md +22 -3
  99. package/src/claude/skills/test/references/execution-strategy.md +83 -0
  100. package/src/claude/skills/test/references/failure-triage.md +1 -1
  101. package/src/claude/skills/ui-ux/LICENSE +21 -0
  102. package/src/claude/skills/ui-ux/SKILL.md +497 -0
  103. package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
  104. package/src/claude/skills/ui-ux/references/budgets.md +113 -0
  105. package/src/claude/skills/ui-ux/references/checklist.md +271 -0
  106. package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
  107. package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
  108. package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
  109. package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
  110. package/src/claude/skills/ui-ux/references/components/button.md +142 -0
  111. package/src/claude/skills/ui-ux/references/components/card.md +113 -0
  112. package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
  113. package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
  114. package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
  115. package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
  116. package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
  117. package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
  118. package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
  119. package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
  120. package/src/claude/skills/ui-ux/references/components/input.md +161 -0
  121. package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
  122. package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
  123. package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
  124. package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
  125. package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
  126. package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
  127. package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
  128. package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
  129. package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
  130. package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
  131. package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
  132. package/src/claude/skills/ui-ux/references/design-process.md +650 -0
  133. package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
  134. package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
  135. package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
  136. package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
  137. package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
  138. package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
  139. package/src/claude/skills/ui-ux/references/principles.md +408 -0
  140. package/src/claude/skills/ui-ux/references/refactor.md +245 -0
  141. package/src/claude/skills/ui-ux/references/responsive.md +195 -0
  142. package/src/claude/skills/ui-ux/references/review.md +528 -0
  143. package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
  144. package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
  145. package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
  146. package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
  147. package/src/claude/skills/ui-ux/references/styles.md +311 -0
  148. package/src/claude/skills/ui-ux/references/system.md +219 -0
  149. package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
  150. package/src/claude/skills/ui-ux/references/tokens.css +362 -0
  151. package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
  152. package/src/claude/skills/ui-ux/scripts/package.json +9 -0
  153. package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
  154. package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
  155. package/src/claude/status.cjs +149 -144
  156. package/src/codex/AGENTS.md +8 -13
  157. package/src/codex/hooks/agent.cjs +2 -1
  158. package/src/codex/hooks/lib/spec-receipt.cjs +5 -0
  159. package/src/codex/hooks/lib/spec-utils.cjs +0 -7
  160. package/src/codex/hooks/rules.cjs +2 -1
  161. package/src/codex/hooks/session.cjs +0 -2
  162. package/src/codex/hooks/spec-gate.cjs +8 -18
  163. package/src/codex/hooks.json +0 -20
  164. package/src/codex/rules/state-sync.md +3 -12
  165. package/src/common/AGENTS.md +2 -2
  166. package/src/omp/extensions/cafekit-bridge.mjs +6 -6
  167. package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
  168. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
  169. package/src/claude/hooks/completion-authority-check.cjs +0 -56
  170. package/src/claude/hooks/completion-authority-state.cjs +0 -527
  171. package/src/claude/hooks/completion-authority.cjs +0 -163
  172. package/src/claude/hooks/semantic-review-authority.cjs +0 -154
  173. package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
  174. package/src/claude/hooks/usage.cjs +0 -197
  175. package/src/claude/skills/delegate/SKILL.md +0 -124
  176. package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
  177. package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
  178. package/src/claude/skills/hotfix/SKILL.md +0 -289
  179. package/src/claude/skills/inspect/SKILL.md +0 -142
  180. package/src/claude/skills/loop/SKILL.md +0 -114
  181. package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
  182. package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
  183. package/src/claude/skills/route/SKILL.md +0 -94
  184. package/src/claude/skills/route/references/agent-timing.md +0 -46
  185. package/src/claude/skills/route/references/chaining-patterns.md +0 -53
  186. package/src/claude/skills/route/references/task-taxonomy.md +0 -48
  187. package/src/codex/hooks/completion-authority-check.cjs +0 -67
  188. package/src/codex/hooks/completion-authority-state.cjs +0 -527
  189. package/src/codex/hooks/completion-authority.cjs +0 -109
  190. package/src/codex/hooks/semantic-review-authority.cjs +0 -130
  191. package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
  192. /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
  193. /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
  194. /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
@@ -13,8 +13,6 @@ const os = require('os');
13
13
  const path = require('path');
14
14
  const { execSync } = require('child_process');
15
15
 
16
- // Usage cache file path (written by usage.cjs hook)
17
- const USAGE_CACHE_FILE = path.join(os.tmpdir(), 'ck-usage-limits-cache.json');
18
16
  const WARN_THRESHOLD = 70;
19
17
  const CRITICAL_THRESHOLD = 90;
20
18
  const {
@@ -214,51 +212,6 @@ function buildSessionSection(staticEnv = {}) {
214
212
  ];
215
213
  }
216
214
 
217
- /**
218
- * Read usage limits from cache file (written by usage.cjs)
219
- * @returns {Object|null} Usage data or null if unavailable
220
- */
221
- function readUsageCache() {
222
- try {
223
- if (fs.existsSync(USAGE_CACHE_FILE)) {
224
- const cache = JSON.parse(fs.readFileSync(USAGE_CACHE_FILE, 'utf-8'));
225
- // Cache is valid for 5 minutes for injection purposes
226
- if (Date.now() - cache.timestamp < 300000 && cache.data) {
227
- return cache.data;
228
- }
229
- }
230
- } catch { }
231
- return null;
232
- }
233
-
234
- /**
235
- * Format time until reset
236
- * @param {string} resetAt - ISO timestamp
237
- * @returns {string|null} Formatted time or null
238
- */
239
- function formatTimeUntilReset(resetAt) {
240
- if (!resetAt) return null;
241
- const resetTime = new Date(resetAt);
242
- const remaining = Math.floor(resetTime.getTime() / 1000) - Math.floor(Date.now() / 1000);
243
- if (remaining <= 0 || remaining > 18000) return null; // Only show if < 5 hours
244
- const hours = Math.floor(remaining / 3600);
245
- const mins = Math.floor((remaining % 3600) / 60);
246
- return `${hours}h ${mins}m`;
247
- }
248
-
249
- /**
250
- * Format percentage with warning level
251
- * @param {number} value - Percentage value
252
- * @param {string} label - Label prefix
253
- * @returns {string} Formatted string with warning if applicable
254
- */
255
- function formatUsagePercent(value, label) {
256
- const pct = Math.round(value);
257
- if (pct >= CRITICAL_THRESHOLD) return `${label}: ${pct}% [CRITICAL]`;
258
- if (pct >= WARN_THRESHOLD) return `${label}: ${pct}% [WARNING]`;
259
- return `${label}: ${pct}%`;
260
- }
261
-
262
215
  /**
263
216
  * Build context window section from statusline cache
264
217
  * @param {string} sessionId - Session ID
@@ -297,43 +250,6 @@ function buildContextSection(sessionId) {
297
250
  }
298
251
  }
299
252
 
300
- /**
301
- * Build usage section from cache
302
- * @returns {string[]} Lines for usage section
303
- */
304
- function buildUsageSection() {
305
- const usage = readUsageCache();
306
- if (!usage) return [];
307
-
308
- const lines = [];
309
- const parts = [];
310
-
311
- // 5-hour limit
312
- if (usage.five_hour) {
313
- const util = usage.five_hour.utilization;
314
- if (typeof util === 'number') {
315
- parts.push(formatUsagePercent(util, '5h'));
316
- }
317
- const timeLeft = formatTimeUntilReset(usage.five_hour.resets_at);
318
- if (timeLeft) {
319
- parts.push(`resets in ${timeLeft}`);
320
- }
321
- }
322
-
323
- // 7-day limit
324
- if (usage.seven_day?.utilization != null) {
325
- parts.push(formatUsagePercent(usage.seven_day.utilization, '7d'));
326
- }
327
-
328
- if (parts.length > 0) {
329
- lines.push(`## Usage Limits`);
330
- lines.push(`- ${parts.join(' | ')}`);
331
- lines.push(``);
332
- }
333
-
334
- return lines;
335
- }
336
-
337
253
  /**
338
254
  * Build rules section
339
255
  * @param {Object} params
@@ -489,13 +405,11 @@ function buildReminder(params) {
489
405
  // Respect hooks config — skip sections when their corresponding hook is disabled
490
406
  const hooksConfig = hooks || {};
491
407
  const contextEnabled = hooksConfig['context-tracking'] !== false;
492
- const usageEnabled = hooksConfig['usage'] !== false;
493
408
 
494
409
  return [
495
410
  ...buildLanguageSection({ thinkingLanguage, responseLanguage }),
496
411
  ...buildSessionSection(staticEnv),
497
412
  ...(contextEnabled ? buildContextSection(sessionId) : []),
498
- ...(usageEnabled ? buildUsageSection() : []),
499
413
  ...buildRulesSection({ devRulesPath, catalogScript, skillsVenv, plansPath, docsPath }),
500
414
  ...buildModularizationSection(),
501
415
  ...buildPathsSection({ reportsPath, plansPath, docsPath, docsMaxLoc }),
@@ -564,7 +478,6 @@ function buildReminderContext({ sessionId, config, staticEnv, configDirName = '.
564
478
  // Respect hooks config for sections object too
565
479
  const hooksConfig = cfg.hooks || {};
566
480
  const contextEnabled = hooksConfig['context-tracking'] !== false;
567
- const usageEnabled = hooksConfig['usage'] !== false;
568
481
 
569
482
  return {
570
483
  content: lines.join('\n'),
@@ -573,7 +486,6 @@ function buildReminderContext({ sessionId, config, staticEnv, configDirName = '.
573
486
  language: buildLanguageSection({ thinkingLanguage: params.thinkingLanguage, responseLanguage: params.responseLanguage }),
574
487
  session: buildSessionSection(staticEnv),
575
488
  context: contextEnabled ? buildContextSection(sessionId) : [],
576
- usage: usageEnabled ? buildUsageSection() : [],
577
489
  rules: buildRulesSection({ devRulesPath, catalogScript, skillsVenv, plansPath: params.plansPath, docsPath: params.docsPath }),
578
490
  modularization: buildModularizationSection(),
579
491
  paths: buildPathsSection({ reportsPath: params.reportsPath, plansPath: params.plansPath, docsPath: params.docsPath, docsMaxLoc: params.docsMaxLoc }),
@@ -596,7 +508,6 @@ module.exports = {
596
508
  buildLanguageSection,
597
509
  buildSessionSection,
598
510
  buildContextSection,
599
- buildUsageSection,
600
511
  buildRulesSection,
601
512
  buildModularizationSection,
602
513
  buildPathsSection,
@@ -54,8 +54,8 @@ const EVENT_NAMES = Object.freeze({
54
54
  * Grok's registry and matcher aliases come first, then omp's lowercase registry. Where
55
55
  * both hosts use a spelling (`write`, `grep`) they agree on the destination. `write` is
56
56
  * grok's file-creating tool and maps to `Write`, while `search_replace` edits an existing
57
- * file and maps to `Edit`; conflating them would make the scaffold guard reject ordinary
58
- * edits. `delete_file` maps to `Write` so a delete is gated like any other file write.
57
+ * file and maps to `Edit`, which keeps grok's file creation distinct from an edit.
58
+ * `delete_file` maps to `Write` so a delete is gated like any other file write.
59
59
  */
60
60
  const TOOL_ALIASES = Object.freeze({
61
61
  // grok — every spelling that reaches a Bash matcher
@@ -70,6 +70,7 @@ try {
70
70
  const baseDir = cwd;
71
71
  const plansPath = path.join(baseDir, runtime.paths?.plans || 'plans');
72
72
  const docsPath = path.join(baseDir, runtime.paths?.docs || 'docs');
73
+ const specsPath = path.join(baseDir, runtime.paths?.specs || 'specs');
73
74
  const maxLoc = runtime.docs?.maxLoc || 800;
74
75
 
75
76
  const lines = [];
@@ -84,7 +85,7 @@ try {
84
85
 
85
86
  lines.push(
86
87
  '## Rules',
87
- `- Markdown files: Plans → "${plansPath}/" | Docs → "${docsPath}/"`,
88
+ `- Markdown files: Specs → "${specsPath}/" | Plans → "${plansPath}/" | Docs → "${docsPath}/"`,
88
89
  '- **DO NOT** create markdown files outside of those directories unless explicitly asked.',
89
90
  `- docs.maxLoc: ${maxLoc} lines max per doc file`
90
91
  );
@@ -202,7 +202,6 @@ try {
202
202
  const envFile = process.env.CLAUDE_ENV_FILE;
203
203
  const cwd = projectRoot();
204
204
  const runtime = readRuntime(cwd);
205
- try { require('./completion-authority-state.cjs').clearState(cwd, payload.session_id); } catch { /* old installs remain fail-open */ }
206
205
 
207
206
  // Check CafeKit update in parallel with project detection (async, fail-open).
208
207
  const updateCheckPromise = checkCafeKitUpdate(cwd);
@@ -90,17 +90,17 @@ try {
90
90
  let POLICY;
91
91
  let RESOLVER;
92
92
  let RECEIPT;
93
- let FINAL_STATE;
94
93
  try {
95
94
  POLICY = require(policyPath);
95
+ // One gate run checks every done receipt against the same checkout; capture it once.
96
+ require(path.join(__dirname, '..', 'scripts', 'provenance.cjs')).enableSnapshotMemo();
96
97
  RESOLVER = require(path.join(__dirname, '..', 'scripts', 'spec-resolver.cjs'));
97
98
  RECEIPT = require(path.join(__dirname, '..', 'scripts', 'spec-receipt.cjs'));
98
- FINAL_STATE = require('./completion-authority-check.cjs');
99
99
  if (typeof POLICY.validateCanonicalReceipt !== 'function'
100
100
  || typeof POLICY.completionDecisionForSpec !== 'function'
101
101
  || typeof RECEIPT.checkTaskReceipt !== 'function'
102
102
  || typeof RECEIPT.checkWorkflowReceiptSet !== 'function'
103
- || typeof FINAL_STATE.evaluateCloseout !== 'function') {
103
+ || typeof RESOLVER.resolveWorkflowCandidate !== 'function') {
104
104
  throw new Error('shared workflow policy lacks completion authority functions');
105
105
  }
106
106
  } catch (error) {
@@ -127,9 +127,7 @@ try {
127
127
  || (typeof RESOLVER.readActiveFeatureTarget === 'function'
128
128
  ? RESOLVER.readActiveFeatureTarget({ projectRoot: baseDir, runtime })
129
129
  : null);
130
- let resolved = typeof RESOLVER.resolveWorkflowCandidate === 'function'
131
- ? RESOLVER.resolveWorkflowCandidate({ projectRoot: baseDir, runtime, target, includeCompleted: true })
132
- : FINAL_STATE.resolveCandidate({ resolver: RESOLVER, projectRoot: baseDir, runtime, payload });
130
+ let resolved = RESOLVER.resolveWorkflowCandidate({ projectRoot: baseDir, runtime, target, includeCompleted: true });
133
131
  if (typeof RESOLVER.refineWorkflowGateResolution === 'function') {
134
132
  resolved = RESOLVER.refineWorkflowGateResolution(resolved);
135
133
  }
@@ -150,7 +148,8 @@ try {
150
148
  const lines = [`⚠️ Completion gate: ${checked.failures.length} done task(s) lack a verification receipt.`];
151
149
  for (const failure of checked.failures) {
152
150
  lines.push(`- \`${failure.featureName}/${failure.taskPath}\`: failed check(s) ${failure.failures.join(', ')}`);
153
- lines.push(` Fix: write a runtime-bound \`## Receipt\` with the planned command in \`specs/${failure.featureName}/${failure.taskPath}\`.`);
151
+ const hint = typeof RECEIPT.receiptFixHint === 'function' ? RECEIPT.receiptFixHint(failure.failures, failure.body) : null;
152
+ lines.push(` Fix in \`specs/${failure.featureName}/${failure.taskPath}\`: ${hint || 'write a runtime-bound `## Receipt` with the planned command'}.`);
154
153
  }
155
154
  emitBlock(lines.slice(0, 8).join('\n'));
156
155
  process.exit(0);
@@ -185,19 +184,10 @@ try {
185
184
  const lifecyclePhase = activeSpec.current_phase || activeSpec.phase;
186
185
  const explicitCloseout = ['done', 'completed', 'complete'].includes(activeSpec.status)
187
186
  || ['closeout', 'completion', 'completed', 'complete'].includes(lifecyclePhase);
188
- if (!processWorkflow && activeSpec.schema_version === '2.1') {
189
- const finalState = FINAL_STATE.evaluateCloseout({
190
- resolver: RESOLVER,
191
- policy: POLICY,
192
- projectRoot: baseDir,
193
- runtime,
194
- payload: { ...payload, session_id: sessionIdentity(payload) },
195
- });
196
- if (!finalState.ok) {
197
- emitBlock(`Completion gate: ${finalState.reason}`);
198
- process.exit(0);
199
- }
200
- if (finalState.active) process.exit(0);
187
+ // Legacy 2.1 closeout is retired; a 2.1 packet mid-execution keeps the ordinary receipt checks.
188
+ if (!processWorkflow && activeSpec.schema_version === '2.1' && explicitCloseout) {
189
+ emitBlock('Completion gate: Legacy spec.json closeout is no longer supported; move the packet to plan.md with flat task files');
190
+ process.exit(0);
201
191
  }
202
192
  const runtimeContext = POLICY.deriveRuntimeContext({
203
193
  projectRoot: baseDir,
@@ -250,12 +240,12 @@ try {
250
240
  );
251
241
  const featureDir = path.join(specsPath, featureName);
252
242
  const failures = allDoneTasks
253
- .map((taskPath) => ({
254
- taskPath,
255
- fails: processWorkflow
256
- ? RECEIPT.checkWorkflowTaskReceipt(featureDir, taskPath, runtimeContext, POLICY).failures
257
- : RECEIPT.checkTaskReceipt(featureDir, taskPath, taskRegistry[taskPath], runtimeContext, POLICY).failures,
258
- }))
243
+ .map((taskPath) => {
244
+ const proof = processWorkflow
245
+ ? RECEIPT.checkWorkflowTaskReceipt(featureDir, taskPath, runtimeContext, POLICY)
246
+ : RECEIPT.checkTaskReceipt(featureDir, taskPath, taskRegistry[taskPath], runtimeContext, POLICY);
247
+ return { taskPath, fails: proof.failures, body: proof.body };
248
+ })
259
249
  .filter((f) => f.fails.length > 0);
260
250
 
261
251
  const featureCloseoutRequired = !processWorkflow && explicitCloseout && allDoneTasks.length > 0;
@@ -297,12 +287,12 @@ try {
297
287
  ? `⚠️ Completion gate: ${failures.length} done task(s) lack a verification receipt.`
298
288
  : '⚠️ Completion gate: workflow completion proof is incomplete.',
299
289
  ];
300
- for (const { taskPath, fails } of failures) {
290
+ for (const { taskPath, fails, body } of failures) {
301
291
  lines.push(`- \`${taskPath}\`: failed check(s) ${fails.join(', ')}`);
302
292
  lines.push(taskPath === 'feature-receipt.md'
303
293
  ? ` Fix: run final integration proof, then write \`specs/${featureName}/feature-receipt.md\`.`
304
294
  : processWorkflow
305
- ? ` Fix: write a runtime-bound \`## Receipt\` with command output in \`specs/${featureName}/${path.posix.basename(taskPath)}\`.`
295
+ ? ` Fix in \`specs/${featureName}/${path.posix.basename(taskPath)}\`: ${(typeof RECEIPT.receiptFixHint === 'function' && RECEIPT.receiptFixHint(fails, body)) || 'write a runtime-bound `## Receipt` with command output'}.`
306
296
  : ` Fix: write canonical proof to \`specs/${featureName}/receipts/${path.posix.basename(taskPath)}\`; legacy \`## Evidence\` remains read-compatible.`);
307
297
  }
308
298
  if (completionBlocked) {
@@ -44,6 +44,40 @@ function canonicalPath(value) {
44
44
  try { return fs.realpathSync(value); } catch { return null; }
45
45
  }
46
46
 
47
+ /** Real path of `value`, resolving its deepest existing ancestor (the file may not exist yet). */
48
+ function canonicalTarget(value) {
49
+ if (typeof value !== 'string' || !value) return null;
50
+ let current = path.resolve(value);
51
+ const rest = [];
52
+ while (!fs.existsSync(current)) {
53
+ const parent = path.dirname(current);
54
+ if (parent === current) return null;
55
+ rest.unshift(path.basename(current));
56
+ current = parent;
57
+ }
58
+ const real = canonicalPath(current);
59
+ return real ? path.join(real, ...rest) : null;
60
+ }
61
+
62
+ function escapeRegExp(text) {
63
+ return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
64
+ }
65
+
66
+ /**
67
+ * Packets the prompt names: `specs/<name>`, a `/cf:<command> <name>` token, or the bare
68
+ * name when it carries `-`, `_` or a digit. A plain word such as `docs` must use `specs/`,
69
+ * so an ordinary sentence does not touch a packet.
70
+ */
71
+ function packetsNamedIn(text, names) {
72
+ if (typeof text !== 'string' || !text) return [];
73
+ return names.filter((name) => {
74
+ const n = escapeRegExp(name);
75
+ if (new RegExp(`(^|[^\\w-])specs/${n}(?![\\w-])`).test(text)) return true;
76
+ if (new RegExp(`/cf:[\\w-]+\\s+(?:specs/)?${n}(?![\\w-])`).test(text)) return true;
77
+ return /[-_0-9]/.test(name) && new RegExp(`(^|[^\\w/-])${n}(?![\\w-])`).test(text);
78
+ });
79
+ }
80
+
47
81
  try {
48
82
  // ── Main ──────────────────────────────────────────────────────────────────
49
83
 
@@ -51,7 +85,8 @@ try {
51
85
  if (!stdin) process.exit(0);
52
86
 
53
87
  const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
54
- const payload = normalizeHookPayload(JSON.parse(stdin));
88
+ const rawPayload = JSON.parse(stdin);
89
+ const payload = normalizeHookPayload(rawPayload);
55
90
  const cwd = payload.cwd || process.cwd();
56
91
 
57
92
  let POLICY;
@@ -84,6 +119,64 @@ try {
84
119
  const baseDir = installedRoot || process.env.PROJECT_ROOT || cwd;
85
120
  const explicitFeature = payload.featureName || payload.feature || payload.explicitFeature || null;
86
121
  const explicitPath = payload.specPath || payload.spec_path || payload.featurePath || null;
122
+
123
+ // ── Per-session touch set (Claude only) ───────────────────────────────────
124
+ // Several sessions can share one checkout. On Claude the tollgate speaks only about a
125
+ // packet this session touched. omp and Codex run their own copies, and grok sends
126
+ // `sessionId` before normalization, so all three keep the old behaviour.
127
+ const claudeSession = rawPayload && typeof rawPayload.session_id === 'string' && rawPayload.session_id
128
+ ? rawPayload.session_id
129
+ : null;
130
+ const touchFilter = runtimeDirName() === '.claude' && claudeSession !== null;
131
+ const touchFile = touchFilter
132
+ ? path.join(
133
+ require('./lib/hook-state-dir.cjs').hookStateDir(),
134
+ `spec-touched-${require('crypto').createHash('sha256').update(claudeSession).digest('hex')}.json`,
135
+ )
136
+ : null;
137
+ const readTouched = () => {
138
+ try {
139
+ const list = JSON.parse(fs.readFileSync(touchFile, 'utf8'));
140
+ return new Set(Array.isArray(list) ? list : []);
141
+ } catch { return new Set(); }
142
+ };
143
+ const addTouched = (names) => {
144
+ const touched = readTouched();
145
+ const before = touched.size;
146
+ for (const name of names) touched.add(name);
147
+ if (touched.size === before) return;
148
+ try {
149
+ fs.mkdirSync(path.dirname(touchFile), { recursive: true });
150
+ fs.writeFileSync(touchFile, JSON.stringify([...touched].sort()));
151
+ } catch { /* an unwritable state dir only loses the touch */ }
152
+ };
153
+ let specsRoot = null;
154
+ try { specsRoot = RESOLVER.specsDirectory(baseDir, runtime); } catch { /* resolver reports it below */ }
155
+
156
+ if (payload.hook_event_name === 'PostToolUse') {
157
+ // Record only; no resolve, no git, no tollgate cache.
158
+ if (touchFilter && specsRoot && /^(Edit|Write|MultiEdit)$/.test(payload.tool_name || '')) {
159
+ const realRoot = canonicalPath(specsRoot);
160
+ const realTarget = canonicalTarget(payload.tool_input && payload.tool_input.file_path);
161
+ if (realRoot && realTarget) {
162
+ const rel = path.relative(realRoot, realTarget);
163
+ const parts = rel.split(path.sep);
164
+ if (rel && parts[0] !== '..' && !path.isAbsolute(rel) && parts.length > 1) addTouched([parts[0]]);
165
+ }
166
+ }
167
+ process.exit(0);
168
+ }
169
+
170
+ if (touchFilter) {
171
+ let packets = [];
172
+ try {
173
+ packets = fs.readdirSync(specsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
174
+ } catch { /* no specs root */ }
175
+ const named = packetsNamedIn(payload.prompt, packets);
176
+ if (typeof explicitFeature === 'string' && explicitFeature) named.push(explicitFeature);
177
+ if (named.length) addTouched(named);
178
+ }
179
+
87
180
  const resolveWorkflow = typeof RESOLVER.resolveWorkflowCandidate === 'function'
88
181
  ? RESOLVER.resolveWorkflowCandidate
89
182
  : RESOLVER.resolveActiveSpec;
@@ -118,6 +211,7 @@ try {
118
211
  const activeSpec = resolved.spec || {};
119
212
  const processWorkflow = resolved.layoutKind === 'process-v3';
120
213
  const featureName = resolved.featureName;
214
+ if (touchFilter && !readTouched().has(featureName)) process.exit(0);
121
215
  const specsPath = resolved.specsDir;
122
216
  const runtimeContext = POLICY.deriveRuntimeContext({
123
217
  projectRoot: baseDir,
@@ -148,6 +148,21 @@ try {
148
148
  return data;
149
149
  }
150
150
 
151
+ /**
152
+ * True when latest.md holds something beyond the filler this hook writes itself:
153
+ * headings, HTML comments, the three placeholder bullets and timestamp-only agent results.
154
+ */
155
+ function hasPriorContent(text) {
156
+ const filler = [
157
+ /^#{1,6}\s/,
158
+ /^<!--.*-->$/,
159
+ /^- \((No completed tasks recorded|All tasks completed|No file changes detected)\)$/,
160
+ /^- Completed at \d{2}:\d{2}:\d{2}$/,
161
+ ];
162
+ return text.split('\n').map((line) => line.trim())
163
+ .some((line) => line && !filler.some((pattern) => pattern.test(line)));
164
+ }
165
+
151
166
  function buildStateContent(data) {
152
167
  const done = data.todos.filter((todo) => todo.status === 'completed' || todo.status === 'done');
153
168
  const pending = data.todos.filter((todo) => !['completed', 'done'].includes(todo.status));
@@ -226,14 +241,18 @@ try {
226
241
  if (!stdin) process.exit(0);
227
242
 
228
243
  const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
229
- const data = normalizeHookPayload(JSON.parse(stdin));
244
+ const rawPayload = JSON.parse(stdin);
245
+ const data = normalizeHookPayload(rawPayload);
230
246
  const event = data.hook_event_name || '';
231
247
  const cwd = data.cwd || process.cwd();
232
248
  const dir = stateDir(cwd);
233
249
 
234
250
  if (event === 'SessionStart') {
235
251
  const previous = loadLatest(cwd);
236
- if (previous) {
252
+ // Claude only: grok (camelCase sessionId), omp and Codex keep printing the block.
253
+ const claudeSession = runtimeDirName() === '.claude'
254
+ && typeof rawPayload.session_id === 'string' && rawPayload.session_id.length > 0;
255
+ if (previous && (!claudeSession || hasPriorContent(previous))) {
237
256
  console.log('\n=== Prior Execution Context ===');
238
257
  console.log(previous.trim());
239
258
  console.log('=== End of Prior Context ===\n');
@@ -7,23 +7,21 @@
7
7
  "skills": {
8
8
  "required": [
9
9
  "agent-browser",
10
+ "ask",
10
11
  "brainstorm",
11
12
  "chrome-devtools",
12
13
  "code-review",
13
14
  "debug",
14
- "delegate",
15
15
  "develop",
16
+ "fix",
16
17
  "git",
17
- "hotfix",
18
- "inspect",
19
- "loop",
20
18
  "orca",
21
- "question",
22
19
  "research",
23
- "route",
20
+ "scout",
24
21
  "specs",
25
22
  "sync",
26
23
  "test",
24
+ "ui-ux",
27
25
  "ui-ux-pro-max",
28
26
  "web-testing"
29
27
  ],
@@ -86,7 +84,13 @@
86
84
  "frontend-design",
87
85
  "mobile-development",
88
86
  "devops",
89
- "react-best-practices"
87
+ "react-best-practices",
88
+ "inspect",
89
+ "hotfix",
90
+ "question",
91
+ "loop",
92
+ "route",
93
+ "delegate"
90
94
  ],
91
95
  "agents": {
92
96
  "claude": ["agents/god-developer.md"],
@@ -112,10 +116,27 @@
112
116
  "skills/specs/rules/ears-format.md",
113
117
  "skills/specs/rules/phase-decision-matrix.md",
114
118
  "skills/specs/rules/task-scoring-rubric.md",
115
- "skills/specs/rules/tasks-generation.md"
119
+ "skills/specs/rules/tasks-generation.md",
120
+ "hooks/usage.cjs",
121
+ "hooks/completion-authority.cjs",
122
+ "hooks/completion-authority-check.cjs",
123
+ "hooks/completion-authority-state.cjs",
124
+ "hooks/semantic-review-authority.cjs",
125
+ "hooks/task-scaffold-guard.cjs"
116
126
  ],
117
127
  "settingsHookCommandSubstrings": [
118
- "hooks/skill-router.cjs"
128
+ "hooks/skill-router.cjs",
129
+ ".claude/hooks/usage.cjs",
130
+ ".claude/hooks/docs-sync.cjs",
131
+ ".claude/hooks/completion-authority.cjs",
132
+ ".codex/hooks/completion-authority.cjs",
133
+ ".codex\\hooks\\completion-authority.cjs",
134
+ ".claude/hooks/semantic-review-authority.cjs",
135
+ ".codex/hooks/semantic-review-authority.cjs",
136
+ ".codex\\hooks\\semantic-review-authority.cjs",
137
+ ".claude/hooks/task-scaffold-guard.cjs",
138
+ ".codex/hooks/task-scaffold-guard.cjs",
139
+ ".codex\\hooks\\task-scaffold-guard.cjs"
119
140
  ]
120
141
  },
121
142
  "runtime": {
@@ -128,18 +149,12 @@
128
149
  "hooks/precompact.cjs",
129
150
  "hooks/docs-sync.cjs",
130
151
  "hooks/agent.cjs",
131
- "hooks/usage.cjs",
132
152
  "hooks/privacy-block.cjs",
133
153
  "hooks/secret-output-guardrail.cjs",
134
154
  "hooks/inspect-block.cjs",
135
- "hooks/task-scaffold-guard.cjs",
136
155
  "hooks/rules.cjs",
137
156
  "hooks/spec-state.cjs",
138
157
  "hooks/spec-gate.cjs",
139
- "hooks/completion-authority.cjs",
140
- "hooks/completion-authority-check.cjs",
141
- "hooks/completion-authority-state.cjs",
142
- "hooks/semantic-review-authority.cjs",
143
158
  "hooks/state.cjs",
144
159
  "hooks/lib/color.cjs",
145
160
  "hooks/lib/parser.cjs",
@@ -42,7 +42,7 @@ Keep implementation simple, scoped, and verifiable.
42
42
 
43
43
  ## Git Hygiene
44
44
 
45
- - Lint before commit.
45
+ - Lint before commit when the project has a linter.
46
46
  - Run required tests before push.
47
47
  - Keep commits focused.
48
48
  - Use conventional commits.
@@ -4,28 +4,21 @@ Hooks are instruction boundaries. Do not bypass or work around them.
4
4
 
5
5
  ## Privacy Block Hook
6
6
 
7
- When a tool call is blocked by the privacy hook, the output contains a JSON marker between:
7
+ When a tool call touches a sensitive file, or the hook cannot classify it safely,
8
+ the privacy hook returns a permission decision of `ask` with the reason, and the
9
+ host asks the user to approve or deny that one call. Do not ask again yourself.
10
+ On a host whose hook can only deny, the call is blocked; ask the user instead of
11
+ retrying it another way.
8
12
 
9
- ```text
10
- @@PRIVACY_PROMPT_START@@
11
- ...
12
- @@PRIVACY_PROMPT_END@@
13
- ```
14
-
15
- Required flow:
16
-
17
- 1. Parse the JSON payload from the marker.
18
- 2. Ask the user for approval using the available user-question tool and the prompt/options from the payload.
19
- 3. If approved, retry only the blocked action.
20
- 4. If denied, continue without reading that file or performing that blocked action.
13
+ - If approved, the call runs; do not widen it to other files.
14
+ - If denied, continue without reading that file or performing that blocked action.
21
15
 
22
16
  Never use another command, path, encoding, or side channel to access a privacy-blocked file without explicit approval.
23
17
 
24
18
  ## State And Spec Hooks
25
19
 
26
20
  - If a hook reports state drift, run the appropriate sync/audit flow before continuing.
27
- - If a hook rejects task completion, keep the task `in_progress` or `blocked` until proof exists.
28
- - Do not mark a task `done` unless the matching task file contains a valid verification receipt.
21
+ - If a hook rejects task completion, keep the task `in_progress` or `blocked` until proof exists; the receipt contract lives in `state-sync.md`.
29
22
 
30
23
  ## Hook Failure Handling
31
24
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Living Documents
4
4
 
5
- The project maintains these core documents in `./docs`:
5
+ When the project keeps these core documents in `./docs`, keep them current (a project may have only some of them):
6
6
 
7
7
  | Document | Purpose |
8
8
  |----------|---------|
@@ -14,8 +14,6 @@ The project maintains these core documents in `./docs`:
14
14
  ### Freshness Rule
15
15
 
16
16
  - Before updating any doc, check its last modified date
17
- - If a doc hasn't been updated in >2 weeks while development is active, flag it for review
18
- - The `docs-keeper` agent should proactively scan for stale docs during weekly reviews
19
17
 
20
18
  ## When to Update
21
19
 
@@ -26,7 +24,6 @@ The `docs-keeper` agent is responsible for keeping these documents current. Trig
26
24
  - A significant feature ships or a critical bug is resolved
27
25
  - Security patches are applied or dependencies change
28
26
  - Project scope or timeline shifts
29
- - Weekly progress reviews are due
30
27
 
31
28
  ### Update Discipline
32
29
 
@@ -43,7 +40,7 @@ Use [Keep a Changelog](https://keepachangelog.com/) convention:
43
40
  ## [version] - YYYY-MM-DD
44
41
 
45
42
  ### Added
46
- - Feature description (#PR-number)
43
+ - Feature description (#PR-number when there is one)
47
44
 
48
45
  ### Fixed
49
46
  - Bug fix description (#issue-number)
@@ -74,38 +71,27 @@ specs/
74
71
 
75
72
  Task files are direct children beside `plan.md`; do not place them in a task
76
73
  subdirectory.
77
- The plan records the C1 scope decision, explicit exclusions, acceptance
74
+ The plan records the GATE-SCOPE scope decision, explicit exclusions, acceptance
78
75
  criteria, and task mapping. Each task owns one usable outcome, bounded paths,
79
76
  dependencies, acceptance, and a runnable Verification Plan.
80
77
 
81
78
  ### Gates and execution handoff
82
79
 
83
- - C1: the user chooses EXPAND, KEEP, or CUT before the plan is written.
84
- - C2: the user accepts, rejects, or revises deduplicated adversarial findings.
80
+ - GATE-SCOPE: the user chooses EXPAND, KEEP, or CUT before the plan is written.
81
+ - GATE-REVIEW: the user accepts, rejects, or revises deduplicated adversarial findings.
85
82
  - Specs stops after planning; implementation begins only through a new explicit
86
83
  Develop invocation.
87
- - C3: after execution, current receipts and limitations are shown and the user
84
+ - GATE-DONE: after execution, current receipts and limitations are shown and the user
88
85
  decides whether the feature is complete.
89
86
 
90
87
  ### Task state and proof
91
88
 
92
- Each task contains exactly one `Status:` field. The final inline `## Receipt`
93
- is canonical proof and must contain the exact command, `Exit: 0`,
94
- `Verification: PASS`, runtime-derived Base and Head values, and non-empty
95
- fenced current output before the task becomes done.
89
+ Task status, the inline `## Receipt` and its proof contract are defined in
90
+ `state-sync.md`.
96
91
 
97
92
  Sync only observed state with surgical edits. Re-read the changed task after
98
93
  every update; never infer missing proof, approval, or readiness. When a done
99
94
  task changes user-facing behavior, architecture, API contracts, operations, or
100
95
  project status, classify docs impact and update only affected existing docs.
101
96
 
102
- Comply with the overarching rules in `./rules/ai-dev-rules.md`.
103
-
104
- ## Legacy specification layout
105
-
106
- Existing features containing `spec.json`, nested `tasks/task-R*.md`, or other
107
- legacy kernel artifacts keep their installed adapters and original layout.
108
- Preserve `task_registry`, `semantic_model`, `planning_depth`, lane,
109
- `execution_tier`, machine authority, typed topology, separate receipts, and
110
- feature closeout files. Do not migrate them as part of unrelated documentation
111
- work.
97
+ Comply with the overarching rules in `ai-dev-rules.md`.