@sienklogic/plan-build-run 2.0.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 (221) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/CLAUDE.md +149 -0
  3. package/LICENSE +21 -0
  4. package/README.md +247 -0
  5. package/dashboard/bin/cli.js +25 -0
  6. package/dashboard/package.json +34 -0
  7. package/dashboard/public/.gitkeep +0 -0
  8. package/dashboard/public/css/layout.css +406 -0
  9. package/dashboard/public/css/status-colors.css +98 -0
  10. package/dashboard/public/js/htmx-title.js +5 -0
  11. package/dashboard/public/js/sidebar-toggle.js +20 -0
  12. package/dashboard/src/app.js +78 -0
  13. package/dashboard/src/middleware/errorHandler.js +52 -0
  14. package/dashboard/src/middleware/notFoundHandler.js +9 -0
  15. package/dashboard/src/repositories/planning.repository.js +128 -0
  16. package/dashboard/src/routes/events.routes.js +40 -0
  17. package/dashboard/src/routes/index.routes.js +31 -0
  18. package/dashboard/src/routes/pages.routes.js +195 -0
  19. package/dashboard/src/server.js +42 -0
  20. package/dashboard/src/services/dashboard.service.js +222 -0
  21. package/dashboard/src/services/phase.service.js +167 -0
  22. package/dashboard/src/services/project.service.js +57 -0
  23. package/dashboard/src/services/roadmap.service.js +171 -0
  24. package/dashboard/src/services/sse.service.js +58 -0
  25. package/dashboard/src/services/todo.service.js +254 -0
  26. package/dashboard/src/services/watcher.service.js +48 -0
  27. package/dashboard/src/views/coming-soon.ejs +11 -0
  28. package/dashboard/src/views/error.ejs +13 -0
  29. package/dashboard/src/views/index.ejs +5 -0
  30. package/dashboard/src/views/layout.ejs +1 -0
  31. package/dashboard/src/views/partials/dashboard-content.ejs +77 -0
  32. package/dashboard/src/views/partials/footer.ejs +3 -0
  33. package/dashboard/src/views/partials/head.ejs +21 -0
  34. package/dashboard/src/views/partials/header.ejs +12 -0
  35. package/dashboard/src/views/partials/layout-bottom.ejs +15 -0
  36. package/dashboard/src/views/partials/layout-top.ejs +8 -0
  37. package/dashboard/src/views/partials/phase-content.ejs +181 -0
  38. package/dashboard/src/views/partials/phases-content.ejs +117 -0
  39. package/dashboard/src/views/partials/roadmap-content.ejs +142 -0
  40. package/dashboard/src/views/partials/sidebar.ejs +38 -0
  41. package/dashboard/src/views/partials/todo-create-content.ejs +53 -0
  42. package/dashboard/src/views/partials/todo-detail-content.ejs +38 -0
  43. package/dashboard/src/views/partials/todos-content.ejs +53 -0
  44. package/dashboard/src/views/phase-detail.ejs +5 -0
  45. package/dashboard/src/views/phases.ejs +5 -0
  46. package/dashboard/src/views/roadmap.ejs +5 -0
  47. package/dashboard/src/views/todo-create.ejs +5 -0
  48. package/dashboard/src/views/todo-detail.ejs +5 -0
  49. package/dashboard/src/views/todos.ejs +5 -0
  50. package/package.json +57 -0
  51. package/plugins/pbr/.claude-plugin/plugin.json +13 -0
  52. package/plugins/pbr/UI-CONSISTENCY-GAPS.md +61 -0
  53. package/plugins/pbr/agents/codebase-mapper.md +271 -0
  54. package/plugins/pbr/agents/debugger.md +281 -0
  55. package/plugins/pbr/agents/executor.md +407 -0
  56. package/plugins/pbr/agents/general.md +164 -0
  57. package/plugins/pbr/agents/integration-checker.md +141 -0
  58. package/plugins/pbr/agents/plan-checker.md +280 -0
  59. package/plugins/pbr/agents/planner.md +358 -0
  60. package/plugins/pbr/agents/researcher.md +363 -0
  61. package/plugins/pbr/agents/synthesizer.md +230 -0
  62. package/plugins/pbr/agents/verifier.md +454 -0
  63. package/plugins/pbr/commands/begin.md +5 -0
  64. package/plugins/pbr/commands/build.md +5 -0
  65. package/plugins/pbr/commands/config.md +5 -0
  66. package/plugins/pbr/commands/continue.md +5 -0
  67. package/plugins/pbr/commands/debug.md +5 -0
  68. package/plugins/pbr/commands/discuss.md +5 -0
  69. package/plugins/pbr/commands/explore.md +5 -0
  70. package/plugins/pbr/commands/health.md +5 -0
  71. package/plugins/pbr/commands/help.md +5 -0
  72. package/plugins/pbr/commands/import.md +5 -0
  73. package/plugins/pbr/commands/milestone.md +5 -0
  74. package/plugins/pbr/commands/note.md +5 -0
  75. package/plugins/pbr/commands/pause.md +5 -0
  76. package/plugins/pbr/commands/plan.md +5 -0
  77. package/plugins/pbr/commands/quick.md +5 -0
  78. package/plugins/pbr/commands/resume.md +5 -0
  79. package/plugins/pbr/commands/review.md +5 -0
  80. package/plugins/pbr/commands/scan.md +5 -0
  81. package/plugins/pbr/commands/setup.md +5 -0
  82. package/plugins/pbr/commands/status.md +5 -0
  83. package/plugins/pbr/commands/todo.md +5 -0
  84. package/plugins/pbr/contexts/dev.md +27 -0
  85. package/plugins/pbr/contexts/research.md +28 -0
  86. package/plugins/pbr/contexts/review.md +36 -0
  87. package/plugins/pbr/hooks/hooks.json +183 -0
  88. package/plugins/pbr/references/agent-anti-patterns.md +24 -0
  89. package/plugins/pbr/references/agent-interactions.md +134 -0
  90. package/plugins/pbr/references/agent-teams.md +54 -0
  91. package/plugins/pbr/references/checkpoints.md +157 -0
  92. package/plugins/pbr/references/common-bug-patterns.md +13 -0
  93. package/plugins/pbr/references/continuation-format.md +212 -0
  94. package/plugins/pbr/references/deviation-rules.md +112 -0
  95. package/plugins/pbr/references/git-integration.md +226 -0
  96. package/plugins/pbr/references/integration-patterns.md +117 -0
  97. package/plugins/pbr/references/model-profiles.md +99 -0
  98. package/plugins/pbr/references/model-selection.md +31 -0
  99. package/plugins/pbr/references/pbr-rules.md +193 -0
  100. package/plugins/pbr/references/plan-authoring.md +181 -0
  101. package/plugins/pbr/references/plan-format.md +283 -0
  102. package/plugins/pbr/references/planning-config.md +213 -0
  103. package/plugins/pbr/references/questioning.md +214 -0
  104. package/plugins/pbr/references/reading-verification.md +127 -0
  105. package/plugins/pbr/references/stub-patterns.md +160 -0
  106. package/plugins/pbr/references/subagent-coordination.md +119 -0
  107. package/plugins/pbr/references/ui-formatting.md +399 -0
  108. package/plugins/pbr/references/verification-patterns.md +198 -0
  109. package/plugins/pbr/references/wave-execution.md +95 -0
  110. package/plugins/pbr/scripts/auto-continue.js +80 -0
  111. package/plugins/pbr/scripts/check-dangerous-commands.js +136 -0
  112. package/plugins/pbr/scripts/check-doc-sprawl.js +102 -0
  113. package/plugins/pbr/scripts/check-phase-boundary.js +196 -0
  114. package/plugins/pbr/scripts/check-plan-format.js +270 -0
  115. package/plugins/pbr/scripts/check-roadmap-sync.js +252 -0
  116. package/plugins/pbr/scripts/check-skill-workflow.js +262 -0
  117. package/plugins/pbr/scripts/check-state-sync.js +476 -0
  118. package/plugins/pbr/scripts/check-subagent-output.js +144 -0
  119. package/plugins/pbr/scripts/config-schema.json +251 -0
  120. package/plugins/pbr/scripts/context-budget-check.js +287 -0
  121. package/plugins/pbr/scripts/event-handler.js +151 -0
  122. package/plugins/pbr/scripts/event-logger.js +92 -0
  123. package/plugins/pbr/scripts/hook-logger.js +76 -0
  124. package/plugins/pbr/scripts/hooks-schema.json +79 -0
  125. package/plugins/pbr/scripts/log-subagent.js +152 -0
  126. package/plugins/pbr/scripts/log-tool-failure.js +88 -0
  127. package/plugins/pbr/scripts/pbr-tools.js +1301 -0
  128. package/plugins/pbr/scripts/post-write-dispatch.js +66 -0
  129. package/plugins/pbr/scripts/post-write-quality.js +207 -0
  130. package/plugins/pbr/scripts/pre-bash-dispatch.js +56 -0
  131. package/plugins/pbr/scripts/pre-write-dispatch.js +62 -0
  132. package/plugins/pbr/scripts/progress-tracker.js +228 -0
  133. package/plugins/pbr/scripts/session-cleanup.js +254 -0
  134. package/plugins/pbr/scripts/status-line.js +285 -0
  135. package/plugins/pbr/scripts/suggest-compact.js +119 -0
  136. package/plugins/pbr/scripts/task-completed.js +45 -0
  137. package/plugins/pbr/scripts/track-context-budget.js +119 -0
  138. package/plugins/pbr/scripts/validate-commit.js +200 -0
  139. package/plugins/pbr/scripts/validate-plugin-structure.js +172 -0
  140. package/plugins/pbr/skills/begin/SKILL.md +545 -0
  141. package/plugins/pbr/skills/begin/templates/PROJECT.md.tmpl +33 -0
  142. package/plugins/pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -0
  143. package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +49 -0
  144. package/plugins/pbr/skills/begin/templates/config.json.tmpl +63 -0
  145. package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +19 -0
  146. package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +30 -0
  147. package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +16 -0
  148. package/plugins/pbr/skills/build/SKILL.md +962 -0
  149. package/plugins/pbr/skills/config/SKILL.md +241 -0
  150. package/plugins/pbr/skills/continue/SKILL.md +127 -0
  151. package/plugins/pbr/skills/debug/SKILL.md +489 -0
  152. package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +16 -0
  153. package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +27 -0
  154. package/plugins/pbr/skills/discuss/SKILL.md +338 -0
  155. package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +61 -0
  156. package/plugins/pbr/skills/discuss/templates/decision-categories.md +9 -0
  157. package/plugins/pbr/skills/explore/SKILL.md +362 -0
  158. package/plugins/pbr/skills/health/SKILL.md +186 -0
  159. package/plugins/pbr/skills/health/templates/check-pattern.md.tmpl +30 -0
  160. package/plugins/pbr/skills/health/templates/output-format.md.tmpl +63 -0
  161. package/plugins/pbr/skills/help/SKILL.md +140 -0
  162. package/plugins/pbr/skills/import/SKILL.md +490 -0
  163. package/plugins/pbr/skills/milestone/SKILL.md +673 -0
  164. package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +48 -0
  165. package/plugins/pbr/skills/milestone/templates/stats-file.md.tmpl +30 -0
  166. package/plugins/pbr/skills/note/SKILL.md +212 -0
  167. package/plugins/pbr/skills/pause/SKILL.md +235 -0
  168. package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +71 -0
  169. package/plugins/pbr/skills/plan/SKILL.md +628 -0
  170. package/plugins/pbr/skills/plan/decimal-phase-calc.md +98 -0
  171. package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +21 -0
  172. package/plugins/pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -0
  173. package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +38 -0
  174. package/plugins/pbr/skills/plan/templates/researcher-prompt.md.tmpl +19 -0
  175. package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +23 -0
  176. package/plugins/pbr/skills/quick/SKILL.md +335 -0
  177. package/plugins/pbr/skills/resume/SKILL.md +388 -0
  178. package/plugins/pbr/skills/review/SKILL.md +652 -0
  179. package/plugins/pbr/skills/review/templates/debugger-prompt.md.tmpl +60 -0
  180. package/plugins/pbr/skills/review/templates/gap-planner-prompt.md.tmpl +40 -0
  181. package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +115 -0
  182. package/plugins/pbr/skills/scan/SKILL.md +269 -0
  183. package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +201 -0
  184. package/plugins/pbr/skills/setup/SKILL.md +227 -0
  185. package/plugins/pbr/skills/shared/commit-planning-docs.md +35 -0
  186. package/plugins/pbr/skills/shared/config-loading.md +102 -0
  187. package/plugins/pbr/skills/shared/context-budget.md +40 -0
  188. package/plugins/pbr/skills/shared/context-loader-task.md +86 -0
  189. package/plugins/pbr/skills/shared/digest-select.md +79 -0
  190. package/plugins/pbr/skills/shared/domain-probes.md +125 -0
  191. package/plugins/pbr/skills/shared/error-reporting.md +79 -0
  192. package/plugins/pbr/skills/shared/gate-prompts.md +388 -0
  193. package/plugins/pbr/skills/shared/phase-argument-parsing.md +45 -0
  194. package/plugins/pbr/skills/shared/progress-display.md +53 -0
  195. package/plugins/pbr/skills/shared/revision-loop.md +81 -0
  196. package/plugins/pbr/skills/shared/state-loading.md +62 -0
  197. package/plugins/pbr/skills/shared/state-update.md +161 -0
  198. package/plugins/pbr/skills/shared/universal-anti-patterns.md +33 -0
  199. package/plugins/pbr/skills/status/SKILL.md +353 -0
  200. package/plugins/pbr/skills/todo/SKILL.md +181 -0
  201. package/plugins/pbr/templates/CONTEXT.md.tmpl +52 -0
  202. package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +151 -0
  203. package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +97 -0
  204. package/plugins/pbr/templates/ROADMAP.md.tmpl +40 -0
  205. package/plugins/pbr/templates/SUMMARY.md.tmpl +81 -0
  206. package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +116 -0
  207. package/plugins/pbr/templates/codebase/ARCHITECTURE.md.tmpl +98 -0
  208. package/plugins/pbr/templates/codebase/CONCERNS.md.tmpl +93 -0
  209. package/plugins/pbr/templates/codebase/CONVENTIONS.md.tmpl +104 -0
  210. package/plugins/pbr/templates/codebase/INTEGRATIONS.md.tmpl +78 -0
  211. package/plugins/pbr/templates/codebase/STACK.md.tmpl +78 -0
  212. package/plugins/pbr/templates/codebase/STRUCTURE.md.tmpl +80 -0
  213. package/plugins/pbr/templates/codebase/TESTING.md.tmpl +107 -0
  214. package/plugins/pbr/templates/continue-here.md.tmpl +73 -0
  215. package/plugins/pbr/templates/prompt-partials/phase-project-context.md.tmpl +37 -0
  216. package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +124 -0
  217. package/plugins/pbr/templates/research/STACK.md.tmpl +71 -0
  218. package/plugins/pbr/templates/research/SUMMARY.md.tmpl +112 -0
  219. package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +81 -0
  220. package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +99 -0
  221. package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +36 -0
@@ -0,0 +1,251 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Plan-Build-Run Config",
4
+ "description": "Configuration schema for .planning/config.json",
5
+ "type": "object",
6
+ "properties": {
7
+ "version": { "type": "integer", "enum": [1, 2] },
8
+ "context_strategy": { "type": "string", "enum": ["aggressive", "conservative", "balanced"] },
9
+ "mode": { "type": "string", "enum": ["interactive", "autonomous"] },
10
+ "depth": { "type": "string", "enum": ["quick", "standard", "comprehensive"], "description": "Workflow depth: quick = budget mode (fewer spawns, skip optional stages), standard = balanced mode (conditional spawns), comprehensive = thorough mode (all spawns, current default)" },
11
+ "features": {
12
+ "type": "object",
13
+ "properties": {
14
+ "structured_planning": { "type": "boolean" },
15
+ "goal_verification": { "type": "boolean" },
16
+ "integration_verification": { "type": "boolean" },
17
+ "context_isolation": { "type": "boolean" },
18
+ "atomic_commits": { "type": "boolean" },
19
+ "session_persistence": { "type": "boolean" },
20
+ "research_phase": { "type": "boolean" },
21
+ "plan_checking": { "type": "boolean" },
22
+ "tdd_mode": { "type": "boolean" },
23
+ "status_line": { "type": "boolean" },
24
+ "auto_continue": { "type": "boolean" },
25
+ "auto_advance": { "type": "boolean" },
26
+ "team_discussions": { "type": "boolean" },
27
+ "inline_verify": { "type": "boolean" }
28
+ },
29
+ "additionalProperties": false
30
+ },
31
+ "models": {
32
+ "type": "object",
33
+ "properties": {
34
+ "researcher": { "type": "string" },
35
+ "planner": { "type": "string" },
36
+ "executor": { "type": "string" },
37
+ "verifier": { "type": "string" },
38
+ "integration_checker": { "type": "string" },
39
+ "debugger": { "type": "string" },
40
+ "mapper": { "type": "string" },
41
+ "synthesizer": { "type": "string" },
42
+ "complexity_map": {
43
+ "type": "object",
44
+ "properties": {
45
+ "simple": { "type": "string", "default": "haiku" },
46
+ "medium": { "type": "string", "default": "sonnet" },
47
+ "complex": { "type": "string", "default": "inherit" }
48
+ },
49
+ "additionalProperties": false
50
+ }
51
+ },
52
+ "additionalProperties": false
53
+ },
54
+ "parallelization": {
55
+ "type": "object",
56
+ "properties": {
57
+ "enabled": { "type": "boolean" },
58
+ "plan_level": { "type": "boolean" },
59
+ "task_level": { "type": "boolean" },
60
+ "max_concurrent_agents": { "type": "integer", "minimum": 1, "maximum": 10 },
61
+ "min_plans_for_parallel": { "type": "integer", "minimum": 1 },
62
+ "use_teams": { "type": "boolean" }
63
+ },
64
+ "additionalProperties": false
65
+ },
66
+ "teams": {
67
+ "type": "object",
68
+ "properties": {
69
+ "planning_roles": {
70
+ "type": "array",
71
+ "items": { "type": "string" },
72
+ "default": ["architect", "security-reviewer", "test-designer"]
73
+ },
74
+ "review_roles": {
75
+ "type": "array",
76
+ "items": { "type": "string" },
77
+ "default": ["functional-reviewer", "security-auditor", "performance-analyst"]
78
+ },
79
+ "synthesis_model": {
80
+ "type": "string",
81
+ "default": "sonnet",
82
+ "description": "Model used for the synthesizer agent that combines team outputs"
83
+ },
84
+ "coordination": {
85
+ "type": "string",
86
+ "enum": ["file-based", "sequential"],
87
+ "default": "file-based",
88
+ "description": "How team members coordinate: file-based (parallel writes to separate files) or sequential"
89
+ }
90
+ },
91
+ "additionalProperties": false
92
+ },
93
+ "planning": {
94
+ "type": "object",
95
+ "properties": {
96
+ "commit_docs": { "type": "boolean" },
97
+ "max_tasks_per_plan": { "type": "integer", "minimum": 1, "maximum": 10 },
98
+ "search_gitignored": { "type": "boolean" }
99
+ },
100
+ "additionalProperties": false
101
+ },
102
+ "git": {
103
+ "type": "object",
104
+ "properties": {
105
+ "branching": { "type": "string", "enum": ["none", "phase", "milestone", "disabled"] },
106
+ "commit_format": { "type": "string" },
107
+ "phase_branch_template": { "type": "string" },
108
+ "milestone_branch_template": { "type": "string" },
109
+ "mode": { "type": "string", "enum": ["enabled", "disabled"] }
110
+ },
111
+ "additionalProperties": false
112
+ },
113
+ "gates": {
114
+ "type": "object",
115
+ "properties": {
116
+ "confirm_project": { "type": "boolean" },
117
+ "confirm_roadmap": { "type": "boolean" },
118
+ "confirm_plan": { "type": "boolean" },
119
+ "confirm_execute": { "type": "boolean" },
120
+ "confirm_transition": { "type": "boolean" },
121
+ "issues_review": { "type": "boolean" }
122
+ },
123
+ "additionalProperties": false
124
+ },
125
+ "safety": {
126
+ "type": "object",
127
+ "properties": {
128
+ "always_confirm_destructive": { "type": "boolean" },
129
+ "always_confirm_external_services": { "type": "boolean" },
130
+ "enforce_phase_boundaries": { "type": "boolean" }
131
+ },
132
+ "additionalProperties": false
133
+ },
134
+ "hooks": {
135
+ "type": "object",
136
+ "properties": {
137
+ "autoFormat": { "type": "boolean" },
138
+ "typeCheck": { "type": "boolean" },
139
+ "detectConsoleLogs": { "type": "boolean" },
140
+ "blockDocSprawl": { "type": "boolean" },
141
+ "compactThreshold": { "type": "integer", "minimum": 10, "maximum": 200 }
142
+ },
143
+ "additionalProperties": false
144
+ },
145
+ "depth_profiles": {
146
+ "type": "object",
147
+ "description": "Depth profile overrides. Each profile maps depth value to effective feature/gate settings.",
148
+ "properties": {
149
+ "quick": {
150
+ "type": "object",
151
+ "properties": {
152
+ "features.research_phase": { "type": "boolean" },
153
+ "features.plan_checking": { "type": "boolean" },
154
+ "features.goal_verification": { "type": "boolean" },
155
+ "features.inline_verify": { "type": "boolean" },
156
+ "scan.mapper_count": { "type": "integer", "minimum": 1, "maximum": 4 },
157
+ "scan.mapper_areas": { "type": "array", "items": { "type": "string" } },
158
+ "debug.max_hypothesis_rounds": { "type": "integer", "minimum": 1, "maximum": 20 }
159
+ },
160
+ "additionalProperties": false
161
+ },
162
+ "standard": {
163
+ "type": "object",
164
+ "properties": {
165
+ "features.research_phase": { "type": "boolean" },
166
+ "features.plan_checking": { "type": "boolean" },
167
+ "features.goal_verification": { "type": "boolean" },
168
+ "features.inline_verify": { "type": "boolean" },
169
+ "scan.mapper_count": { "type": "integer", "minimum": 1, "maximum": 4 },
170
+ "scan.mapper_areas": { "type": "array", "items": { "type": "string" } },
171
+ "debug.max_hypothesis_rounds": { "type": "integer", "minimum": 1, "maximum": 20 }
172
+ },
173
+ "additionalProperties": false
174
+ },
175
+ "comprehensive": {
176
+ "type": "object",
177
+ "properties": {
178
+ "features.research_phase": { "type": "boolean" },
179
+ "features.plan_checking": { "type": "boolean" },
180
+ "features.goal_verification": { "type": "boolean" },
181
+ "features.inline_verify": { "type": "boolean" },
182
+ "scan.mapper_count": { "type": "integer", "minimum": 1, "maximum": 4 },
183
+ "scan.mapper_areas": { "type": "array", "items": { "type": "string" } },
184
+ "debug.max_hypothesis_rounds": { "type": "integer", "minimum": 1, "maximum": 20 }
185
+ },
186
+ "additionalProperties": false
187
+ }
188
+ },
189
+ "additionalProperties": false
190
+ },
191
+ "debug": {
192
+ "type": "object",
193
+ "properties": {
194
+ "max_hypothesis_rounds": { "type": "integer", "minimum": 1, "maximum": 20 }
195
+ },
196
+ "additionalProperties": false
197
+ },
198
+ "spinner_tips": {
199
+ "type": "object",
200
+ "description": "Custom spinner tips shown during agent execution (Claude Code 2.1.45+)",
201
+ "properties": {
202
+ "tips": {
203
+ "type": "array",
204
+ "items": { "type": "string" },
205
+ "description": "Array of custom tip strings to display in the spinner"
206
+ },
207
+ "exclude_defaults": {
208
+ "type": "boolean",
209
+ "description": "When true, only show custom tips (no default Claude Code tips)"
210
+ }
211
+ },
212
+ "additionalProperties": false
213
+ },
214
+ "status_line": {
215
+ "type": "object",
216
+ "properties": {
217
+ "sections": {
218
+ "type": "array",
219
+ "items": { "type": "string", "enum": ["phase", "plan", "status", "context"] }
220
+ },
221
+ "brand_text": { "type": "string" },
222
+ "max_status_length": { "type": "integer", "minimum": 10, "maximum": 200 },
223
+ "context_bar": {
224
+ "type": "object",
225
+ "properties": {
226
+ "width": { "type": "integer", "minimum": 1, "maximum": 50 },
227
+ "thresholds": {
228
+ "type": "object",
229
+ "properties": {
230
+ "green": { "type": "integer", "minimum": 0, "maximum": 100 },
231
+ "yellow": { "type": "integer", "minimum": 0, "maximum": 100 }
232
+ },
233
+ "additionalProperties": false
234
+ },
235
+ "chars": {
236
+ "type": "object",
237
+ "properties": {
238
+ "filled": { "type": "string" },
239
+ "empty": { "type": "string" }
240
+ },
241
+ "additionalProperties": false
242
+ }
243
+ },
244
+ "additionalProperties": false
245
+ }
246
+ },
247
+ "additionalProperties": false
248
+ }
249
+ },
250
+ "additionalProperties": false
251
+ }
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PreCompact hook: Preserves current state to STATE.md before
5
+ * lossy context compaction.
6
+ *
7
+ * Updates STATE.md with:
8
+ * - Timestamp of last compaction
9
+ * - ROADMAP progress summary (phase list, status)
10
+ * - Current plan context (objective from latest PLAN.md)
11
+ * - Config highlights (depth, mode, models, gates)
12
+ * - Active operation context
13
+ *
14
+ * Also outputs additionalContext for post-compaction recovery.
15
+ *
16
+ * Exit codes:
17
+ * 0 = always (informational hook, never blocks)
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const { logHook } = require('./hook-logger');
23
+ const { logEvent } = require('./event-logger');
24
+ const { atomicWrite, configLoad, tailLines } = require('./pbr-tools');
25
+
26
+ function main() {
27
+ const cwd = process.cwd();
28
+ const planningDir = path.join(cwd, '.planning');
29
+ const stateFile = path.join(planningDir, 'STATE.md');
30
+
31
+ // Not a Plan-Build-Run project or no STATE.md
32
+ if (!fs.existsSync(stateFile)) {
33
+ process.exit(0);
34
+ }
35
+
36
+ try {
37
+ let content = fs.readFileSync(stateFile, 'utf8');
38
+ const timestamp = new Date().toISOString();
39
+
40
+ // Gather context from multiple sources
41
+ const activeOp = readActiveOperation(planningDir);
42
+ const roadmapSummary = readRoadmapSummary(planningDir);
43
+ const currentPlan = readCurrentPlan(planningDir, content);
44
+ const configHighlights = readConfigHighlights(planningDir);
45
+ const recentErrors = readRecentErrors(planningDir, 3);
46
+ const recentAgents = readRecentAgents(planningDir, 5);
47
+
48
+ // Build continuity section
49
+ const continuityParts = [
50
+ `Last session: ${timestamp}`,
51
+ 'Compaction occurred: context was auto-compacted at this point'
52
+ ];
53
+
54
+ if (activeOp) {
55
+ continuityParts.push(`Active operation at compaction: ${activeOp}`);
56
+ }
57
+ if (roadmapSummary) {
58
+ continuityParts.push(`Roadmap progress:\n${roadmapSummary}`);
59
+ }
60
+ if (currentPlan) {
61
+ continuityParts.push(`Current plan: ${currentPlan}`);
62
+ }
63
+ if (configHighlights) {
64
+ continuityParts.push(`Config: ${configHighlights}`);
65
+ }
66
+ if (recentErrors.length > 0) {
67
+ continuityParts.push(`Recent errors:\n${recentErrors.map(e => ' - ' + e).join('\n')}`);
68
+ }
69
+ if (recentAgents.length > 0) {
70
+ continuityParts.push(`Recent agents: ${recentAgents.join(', ')}`);
71
+ }
72
+
73
+ continuityParts.push('Note: Some conversation context may have been lost. Check STATE.md and SUMMARY.md files for ground truth.');
74
+
75
+ // Update or add Session Continuity section
76
+ const continuityHeader = '## Session Continuity';
77
+ const continuityContent = continuityParts.join('\n');
78
+
79
+ if (content.includes(continuityHeader)) {
80
+ // Replace existing section
81
+ content = content.replace(
82
+ /## Session Continuity[\s\S]*?(?=\n## |\n---|\s*$)/,
83
+ `${continuityHeader}\n${continuityContent}\n`
84
+ );
85
+ } else {
86
+ // Append section
87
+ content = content.trimEnd() + `\n\n${continuityHeader}\n${continuityContent}\n`;
88
+ }
89
+
90
+ atomicWrite(stateFile, content);
91
+
92
+ // Output additionalContext for post-compaction recovery
93
+ const recoveryContext = buildRecoveryContext(activeOp, roadmapSummary, currentPlan, configHighlights, recentErrors, recentAgents);
94
+ if (recoveryContext) {
95
+ const output = {
96
+ additionalContext: recoveryContext
97
+ };
98
+ process.stdout.write(JSON.stringify(output));
99
+ }
100
+
101
+ logHook('context-budget-check', 'PreCompact', 'saved', {
102
+ stateFile: 'STATE.md',
103
+ hasRoadmap: !!roadmapSummary,
104
+ hasPlan: !!currentPlan,
105
+ hasConfig: !!configHighlights
106
+ });
107
+ logEvent('workflow', 'compaction', { timestamp });
108
+ } catch (e) {
109
+ logHook('context-budget-check', 'PreCompact', 'error', { error: e.message });
110
+ }
111
+
112
+ process.exit(0);
113
+ }
114
+
115
+ function readActiveOperation(planningDir) {
116
+ const activeOpFile = path.join(planningDir, '.active-operation');
117
+ if (!fs.existsSync(activeOpFile)) return '';
118
+ try {
119
+ return fs.readFileSync(activeOpFile, 'utf8').trim();
120
+ } catch (_e) {
121
+ return '';
122
+ }
123
+ }
124
+
125
+ function readRoadmapSummary(planningDir) {
126
+ const roadmapFile = path.join(planningDir, 'ROADMAP.md');
127
+ if (!fs.existsSync(roadmapFile)) return '';
128
+
129
+ try {
130
+ const roadmap = fs.readFileSync(roadmapFile, 'utf8');
131
+
132
+ // Extract progress table
133
+ const progressMatch = roadmap.match(/## Progress[\s\S]*?\|[\s\S]*?(?=\n##|\s*$)/);
134
+ if (!progressMatch) return '';
135
+
136
+ const rows = progressMatch[0].split('\n').filter(r => r.includes('|'));
137
+ // Skip header and separator rows
138
+ const dataRows = rows.filter(r => !r.includes('---') && !r.toLowerCase().includes('phase'));
139
+
140
+ if (dataRows.length === 0) return '';
141
+
142
+ // Build compact summary
143
+ const phases = [];
144
+ for (const row of dataRows) {
145
+ const cols = row.split('|').map(c => c.trim()).filter(Boolean);
146
+ if (cols.length >= 4) {
147
+ const num = cols[0];
148
+ const name = cols[1] || '';
149
+ const status = cols[3] || '';
150
+ if (num && /^\d+/.test(num)) {
151
+ phases.push(` Phase ${num} (${name.substring(0, 30)}): ${status}`);
152
+ }
153
+ }
154
+ }
155
+
156
+ return phases.join('\n');
157
+ } catch (_e) {
158
+ return '';
159
+ }
160
+ }
161
+
162
+ function readCurrentPlan(planningDir, stateContent) {
163
+ // Prefer .active-plan signal file (definitive) over directory listing (guesswork)
164
+ const activePlanFile = path.join(planningDir, '.active-plan');
165
+ if (fs.existsSync(activePlanFile)) {
166
+ try {
167
+ const activePlan = fs.readFileSync(activePlanFile, 'utf8').trim();
168
+ if (activePlan) return activePlan;
169
+ } catch (_e) { /* fall through */ }
170
+ }
171
+
172
+ // Fallback: Extract current phase from STATE.md and find latest plan
173
+ const phaseMatch = stateContent.match(/Phase:\s*(\d+)\s+of\s+\d+/);
174
+ if (!phaseMatch) return '';
175
+
176
+ const currentPhase = phaseMatch[1].padStart(2, '0');
177
+
178
+ // Find the phase directory
179
+ const phasesDir = path.join(planningDir, 'phases');
180
+ if (!fs.existsSync(phasesDir)) return '';
181
+
182
+ try {
183
+ const dirs = fs.readdirSync(phasesDir).filter(d => d.startsWith(currentPhase));
184
+ if (dirs.length === 0) return '';
185
+
186
+ const phaseDir = path.join(phasesDir, dirs[0]);
187
+
188
+ // Find PLAN.md files
189
+ const planFiles = fs.readdirSync(phaseDir).filter(f => f.endsWith('PLAN.md'));
190
+ if (planFiles.length === 0) return 'No PLAN.md found in current phase';
191
+
192
+ // Read the last plan's objective only (frontmatter + objective tag)
193
+ const planFile = path.join(phaseDir, planFiles[planFiles.length - 1]);
194
+ const planContent = fs.readFileSync(planFile, 'utf8');
195
+
196
+ const objMatch = planContent.match(/<objective>([\s\S]*?)<\/objective>/);
197
+ const objective = objMatch ? objMatch[1].trim().substring(0, 150) : '';
198
+
199
+ return `${dirs[0]}/${planFiles[planFiles.length - 1]}${objective ? ' — ' + objective : ''}`;
200
+ } catch (_e) {
201
+ return '';
202
+ }
203
+ }
204
+
205
+ function readRecentErrors(planningDir, maxErrors) {
206
+ const count = maxErrors || 3;
207
+ try {
208
+ const eventsLog = path.join(planningDir, 'logs', 'events.jsonl');
209
+ // Only read the last 50 lines — errors are rare, so 50 tail lines
210
+ // is more than enough to find the most recent ones
211
+ const lines = tailLines(eventsLog, 50);
212
+ if (lines.length === 0) return [];
213
+
214
+ const errors = [];
215
+ // Read backwards for most recent
216
+ for (let i = lines.length - 1; i >= 0 && errors.length < count; i--) {
217
+ try {
218
+ const entry = JSON.parse(lines[i]);
219
+ if (entry.cat === 'error' || entry.event === 'tool-failure' || (entry.cat === 'workflow' && entry.status === 'block')) {
220
+ errors.push(`${entry.event || entry.cat}: ${entry.error || entry.reason || entry.message || 'unknown'}`.substring(0, 120));
221
+ }
222
+ } catch (_e) { /* skip */ }
223
+ }
224
+ return errors;
225
+ } catch (_e) {
226
+ return [];
227
+ }
228
+ }
229
+
230
+ function readRecentAgents(planningDir, maxAgents) {
231
+ const count = maxAgents || 5;
232
+ try {
233
+ const hooksLog = path.join(planningDir, 'logs', 'hooks.jsonl');
234
+ // Only read the last 30 lines — agent spawns are interspersed with
235
+ // other hook events, so 30 tail lines covers recent agents well
236
+ const lines = tailLines(hooksLog, 30);
237
+ if (lines.length === 0) return [];
238
+
239
+ const agents = [];
240
+ // Read backwards for most recent
241
+ for (let i = lines.length - 1; i >= 0 && agents.length < count; i--) {
242
+ try {
243
+ const entry = JSON.parse(lines[i]);
244
+ if (entry.event === 'SubagentStart' && entry.decision === 'spawned') {
245
+ const type = entry.agent_type || 'unknown';
246
+ const desc = entry.description ? ` (${entry.description.substring(0, 60)})` : '';
247
+ agents.push(`${type}${desc}`);
248
+ }
249
+ } catch (_e) { /* skip */ }
250
+ }
251
+ return agents.reverse(); // Chronological order
252
+ } catch (_e) {
253
+ return [];
254
+ }
255
+ }
256
+
257
+ function readConfigHighlights(planningDir) {
258
+ const config = configLoad(planningDir);
259
+ if (!config) return '';
260
+
261
+ const parts = [];
262
+ if (config.depth) parts.push(`depth=${config.depth}`);
263
+ if (config.mode) parts.push(`mode=${config.mode}`);
264
+ if (config.models && config.models.executor) parts.push(`executor=${config.models.executor}`);
265
+ if (config.gates && config.gates.verification !== undefined) parts.push(`verify=${config.gates.verification}`);
266
+ if (config.git && config.git.auto_commit !== undefined) parts.push(`auto_commit=${config.git.auto_commit}`);
267
+ return parts.join(', ');
268
+ }
269
+
270
+ function buildRecoveryContext(activeOp, roadmapSummary, currentPlan, configHighlights, recentErrors, recentAgents) {
271
+ const parts = ['[Post-Compaction Recovery] Context was auto-compacted. Key state preserved:'];
272
+
273
+ if (activeOp) parts.push(`Active operation: ${activeOp}`);
274
+ if (currentPlan) parts.push(`Current plan: ${currentPlan}`);
275
+ if (configHighlights) parts.push(`Config: ${configHighlights}`);
276
+ if (recentErrors && recentErrors.length > 0) parts.push(`Recent errors: ${recentErrors.join('; ')}`);
277
+ if (recentAgents && recentAgents.length > 0) parts.push(`Recent agents: ${recentAgents.join(', ')}`);
278
+ if (roadmapSummary) parts.push(`Progress:\n${roadmapSummary}`);
279
+
280
+ parts.push('Read .planning/STATE.md for full context.');
281
+
282
+ // Only return if we have something meaningful beyond header and footer
283
+ return parts.length > 2 ? parts.join('\n') : '';
284
+ }
285
+
286
+ module.exports = { readRoadmapSummary, readCurrentPlan, readConfigHighlights, buildRecoveryContext, readRecentErrors, readRecentAgents };
287
+ if (require.main === module) { main(); }
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SubagentStop event handler for auto-verification triggering.
5
+ *
6
+ * Detects executor agent completion and conditionally queues verification
7
+ * by writing a .auto-verify signal file. Respects depth profile and
8
+ * config.features.goal_verification setting.
9
+ *
10
+ * Non-blocking — exits 0 always.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { logHook } = require('./hook-logger');
16
+ const { logEvent } = require('./event-logger');
17
+ // configLoad not used here to avoid mtime-based cache issues across directories.
18
+ // Config is read directly in shouldAutoVerify().
19
+
20
+ function readStdin() {
21
+ try {
22
+ const input = fs.readFileSync(0, 'utf8').trim();
23
+ if (input) return JSON.parse(input);
24
+ } catch (_e) {
25
+ // empty or non-JSON stdin
26
+ }
27
+ return {};
28
+ }
29
+
30
+ /**
31
+ * Check if the stdin data represents an executor agent completion.
32
+ * @param {object} data - Parsed stdin JSON from SubagentStop event
33
+ * @returns {boolean}
34
+ */
35
+ function isExecutorAgent(data) {
36
+ const agentType = data.agent_type || data.subagent_type || null;
37
+ return agentType === 'pbr:executor';
38
+ }
39
+
40
+ /**
41
+ * Determine whether auto-verification should run based on config.
42
+ * @param {string} planningDir - Path to .planning directory
43
+ * @returns {boolean}
44
+ */
45
+ function shouldAutoVerify(planningDir) {
46
+ // Read config directly instead of using configLoad to avoid mtime-based
47
+ // cache issues when called repeatedly with different planning directories.
48
+ const configPath = path.join(planningDir, 'config.json');
49
+ let config;
50
+ try {
51
+ if (!fs.existsSync(configPath)) return false;
52
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
53
+ } catch (_e) {
54
+ return false;
55
+ }
56
+ if (config === null) return false;
57
+
58
+ // Check explicit goal_verification toggle
59
+ if (config.features && config.features.goal_verification === false) return false;
60
+
61
+ // Check depth profile
62
+ const depth = (config.depth || 'standard').toLowerCase();
63
+ if (depth === 'quick') return false;
64
+
65
+ // "standard", "comprehensive", and any other depth default to true
66
+ return true;
67
+ }
68
+
69
+ /**
70
+ * Parse current phase info from STATE.md.
71
+ * @param {string} planningDir - Path to .planning directory
72
+ * @returns {{ phase: number, total: number, status: string } | null}
73
+ */
74
+ function getPhaseFromState(planningDir) {
75
+ const statePath = path.join(planningDir, 'STATE.md');
76
+ try {
77
+ if (!fs.existsSync(statePath)) return null;
78
+ const content = fs.readFileSync(statePath, 'utf8');
79
+
80
+ const phaseMatch = content.match(/Phase:\s*(\d+)\s+of\s+(\d+)/);
81
+ if (!phaseMatch) return null;
82
+
83
+ const statusMatch = content.match(/\*{0,2}(?:Phase\s+)?Status\*{0,2}:\s*["']?(\w+)["']?/i);
84
+ const status = statusMatch ? statusMatch[1] : null;
85
+
86
+ return {
87
+ phase: parseInt(phaseMatch[1], 10),
88
+ total: parseInt(phaseMatch[2], 10),
89
+ status: status
90
+ };
91
+ } catch (_e) {
92
+ return null;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Write signal file for orchestrator to pick up and spawn verifier.
98
+ * @param {string} planningDir - Path to .planning directory
99
+ * @param {number} phaseNumber - Current phase number
100
+ */
101
+ function writeAutoVerifySignal(planningDir, phaseNumber) {
102
+ const signalPath = path.join(planningDir, '.auto-verify');
103
+ const payload = {
104
+ phase: phaseNumber,
105
+ timestamp: new Date().toISOString()
106
+ };
107
+ fs.writeFileSync(signalPath, JSON.stringify(payload, null, 2), 'utf8');
108
+ }
109
+
110
+ function main() {
111
+ const data = readStdin();
112
+
113
+ // Only handle executor agent completions
114
+ if (!isExecutorAgent(data)) {
115
+ process.exit(0);
116
+ }
117
+
118
+ const agentType = data.agent_type || data.subagent_type;
119
+
120
+ logHook('event-handler', 'SubagentStop', 'executor-complete', { agent_type: agentType });
121
+ logEvent('workflow', 'executor-complete', { agent_type: agentType });
122
+
123
+ const planningDir = path.join(process.cwd(), '.planning');
124
+ if (!fs.existsSync(planningDir)) {
125
+ process.exit(0);
126
+ }
127
+
128
+ if (!shouldAutoVerify(planningDir)) {
129
+ logHook('event-handler', 'SubagentStop', 'skip-verify', { reason: 'config/depth' });
130
+ process.exit(0);
131
+ }
132
+
133
+ const stateInfo = getPhaseFromState(planningDir);
134
+ if (!stateInfo || stateInfo.status !== 'building') {
135
+ logHook('event-handler', 'SubagentStop', 'skip-verify', {
136
+ reason: stateInfo ? `status=${stateInfo.status}` : 'no-state'
137
+ });
138
+ process.exit(0);
139
+ }
140
+
141
+ writeAutoVerifySignal(planningDir, stateInfo.phase);
142
+
143
+ const output = {
144
+ message: `Executor complete. Auto-verification queued for Phase ${stateInfo.phase}.`
145
+ };
146
+ process.stdout.write(JSON.stringify(output));
147
+ process.exit(0);
148
+ }
149
+
150
+ module.exports = { isExecutorAgent, shouldAutoVerify, getPhaseFromState };
151
+ if (require.main === module) { main(); }