@jamie-tam/forge 6.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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +389 -0
  3. package/agents/architect.md +92 -0
  4. package/agents/builder.md +122 -0
  5. package/agents/code-reviewer.md +107 -0
  6. package/agents/concept-designer.md +207 -0
  7. package/agents/craft-reviewer.md +132 -0
  8. package/agents/critic.md +130 -0
  9. package/agents/doc-writer.md +85 -0
  10. package/agents/dreamer.md +129 -0
  11. package/agents/e2e-runner.md +89 -0
  12. package/agents/gotcha-hunter.md +127 -0
  13. package/agents/prototype-builder.md +193 -0
  14. package/agents/prototype-codifier.md +204 -0
  15. package/agents/prototype-reviewer.md +163 -0
  16. package/agents/security-reviewer.md +108 -0
  17. package/agents/spec-reviewer.md +94 -0
  18. package/agents/tracer.md +98 -0
  19. package/agents/wireframer.md +109 -0
  20. package/commands/abort.md +25 -0
  21. package/commands/bugfix.md +151 -0
  22. package/commands/evolve.md +118 -0
  23. package/commands/feature.md +236 -0
  24. package/commands/forge.md +100 -0
  25. package/commands/greenfield.md +185 -0
  26. package/commands/hotfix.md +98 -0
  27. package/commands/refactor.md +147 -0
  28. package/commands/resume.md +25 -0
  29. package/commands/setup.md +201 -0
  30. package/commands/status.md +27 -0
  31. package/commands/task-force.md +110 -0
  32. package/commands/validate.md +12 -0
  33. package/dist/__tests__/active-manifest.test.js +272 -0
  34. package/dist/__tests__/copy.test.js +96 -0
  35. package/dist/__tests__/gate-check.test.js +384 -0
  36. package/dist/__tests__/wiki.test.js +472 -0
  37. package/dist/__tests__/work-manifest.test.js +304 -0
  38. package/dist/active-manifest.js +229 -0
  39. package/dist/cli.js +158 -0
  40. package/dist/copy.js +124 -0
  41. package/dist/gate-check.js +326 -0
  42. package/dist/hooks.js +60 -0
  43. package/dist/init.js +140 -0
  44. package/dist/manifest.js +90 -0
  45. package/dist/merge.js +77 -0
  46. package/dist/paths.js +36 -0
  47. package/dist/uninstall.js +216 -0
  48. package/dist/update.js +158 -0
  49. package/dist/verify-manifest.js +65 -0
  50. package/dist/verify.js +98 -0
  51. package/dist/wiki-ui.js +310 -0
  52. package/dist/wiki.js +364 -0
  53. package/dist/work-manifest.js +798 -0
  54. package/hooks/config/gate-requirements.json +79 -0
  55. package/hooks/hooks.json +143 -0
  56. package/hooks/scripts/analyze-telemetry.sh +114 -0
  57. package/hooks/scripts/gate-enforcer.sh +164 -0
  58. package/hooks/scripts/pre-compact.sh +90 -0
  59. package/hooks/scripts/session-start.sh +81 -0
  60. package/hooks/scripts/telemetry.sh +41 -0
  61. package/hooks/scripts/wiki-lint.sh +87 -0
  62. package/hooks/templates/AGENTS.md.template +48 -0
  63. package/hooks/templates/CLAUDE.md.template +45 -0
  64. package/package.json +55 -0
  65. package/protocols/README.md +40 -0
  66. package/protocols/codex.md +151 -0
  67. package/protocols/graphify.md +156 -0
  68. package/references/common/agent-coordination.md +65 -0
  69. package/references/common/coding-standards.md +54 -0
  70. package/references/common/feature-tracking.md +21 -0
  71. package/references/common/io-protocol.md +36 -0
  72. package/references/common/phases.md +57 -0
  73. package/references/common/quality-gates.md +130 -0
  74. package/references/common/skill-authoring.md +154 -0
  75. package/references/common/skill-compliance.md +30 -0
  76. package/references/python/standards.md +44 -0
  77. package/references/react/standards.md +61 -0
  78. package/references/typescript/standards.md +42 -0
  79. package/rules/common/forge-system.md +59 -0
  80. package/rules/common/git-workflow.md +40 -0
  81. package/rules/common/guardrails.md +37 -0
  82. package/rules/common/quality-gates.md +18 -0
  83. package/rules/common/security.md +50 -0
  84. package/rules/common/skill-selection.md +78 -0
  85. package/rules/common/testing.md +58 -0
  86. package/rules/common/verification.md +39 -0
  87. package/skills/build-pr-workflow/SKILL.md +301 -0
  88. package/skills/build-pr-workflow/references/pr-template.md +62 -0
  89. package/skills/build-pr-workflow/references/subagent-merge.md +47 -0
  90. package/skills/build-pr-workflow/references/worktree-setup.md +125 -0
  91. package/skills/build-prototype/SKILL.md +264 -0
  92. package/skills/build-scaffold/SKILL.md +340 -0
  93. package/skills/build-tdd/SKILL.md +89 -0
  94. package/skills/build-wireframe/SKILL.md +110 -0
  95. package/skills/build-wireframe/assets/baseline-template.html +486 -0
  96. package/skills/build-wireframe/references/demo-walkthroughs.md +170 -0
  97. package/skills/build-wireframe/references/gotchas.md +188 -0
  98. package/skills/build-wireframe/references/legend-lines.md +141 -0
  99. package/skills/concept-slides/SKILL.md +192 -0
  100. package/skills/deliver-db-migration/SKILL.md +466 -0
  101. package/skills/deliver-deploy/SKILL.md +407 -0
  102. package/skills/deliver-onboarding/SKILL.md +198 -0
  103. package/skills/deliver-onboarding/references/document-templates.md +393 -0
  104. package/skills/deliver-onboarding/templates/getting-started.md +122 -0
  105. package/skills/discover-codebase-analysis/SKILL.md +448 -0
  106. package/skills/discover-requirements/SKILL.md +418 -0
  107. package/skills/discover-requirements/templates/prd.md +99 -0
  108. package/skills/discover-requirements/templates/technical-spec.md +123 -0
  109. package/skills/discover-requirements/templates/user-stories.md +76 -0
  110. package/skills/harden/SKILL.md +214 -0
  111. package/skills/iterate-prototype/SKILL.md +241 -0
  112. package/skills/plan-architecture/SKILL.md +457 -0
  113. package/skills/plan-architecture/templates/adr-template.md +52 -0
  114. package/skills/plan-architecture/templates/api-contract.md +99 -0
  115. package/skills/plan-architecture/templates/db-schema.md +81 -0
  116. package/skills/plan-architecture/templates/system-design.md +111 -0
  117. package/skills/plan-brainstorm/SKILL.md +433 -0
  118. package/skills/plan-design-system/SKILL.md +279 -0
  119. package/skills/plan-task-decompose/SKILL.md +454 -0
  120. package/skills/quality-code-review/SKILL.md +286 -0
  121. package/skills/quality-security-audit/SKILL.md +292 -0
  122. package/skills/quality-security-audit/references/audit-report-template.md +89 -0
  123. package/skills/quality-security-audit/references/owasp-checks.md +178 -0
  124. package/skills/quality-test-execution/SKILL.md +435 -0
  125. package/skills/quality-test-plan/SKILL.md +297 -0
  126. package/skills/quality-test-plan/references/test-type-guide.md +263 -0
  127. package/skills/quality-test-plan/templates/e2e-test-plan.md +72 -0
  128. package/skills/quality-test-plan/templates/integration-test-plan.md +74 -0
  129. package/skills/quality-test-plan/templates/load-test-plan.md +111 -0
  130. package/skills/quality-test-plan/templates/smoke-test-plan.md +68 -0
  131. package/skills/quality-test-plan/templates/unit-test-plan.md +56 -0
  132. package/skills/quality-uiux/SKILL.md +481 -0
  133. package/skills/support-debug/SKILL.md +464 -0
  134. package/skills/support-dream/SKILL.md +213 -0
  135. package/skills/support-gotcha/SKILL.md +249 -0
  136. package/skills/support-runtime-reachability/SKILL.md +190 -0
  137. package/skills/support-runtime-reachability/scripts/__fixtures__/case-01-passes-app-use/src/app.ts +7 -0
  138. package/skills/support-runtime-reachability/scripts/__fixtures__/case-01-passes-app-use/src/handlers/cases.ts +7 -0
  139. package/skills/support-runtime-reachability/scripts/__fixtures__/case-02-orphan-no-app-use/src/app.ts +8 -0
  140. package/skills/support-runtime-reachability/scripts/__fixtures__/case-02-orphan-no-app-use/src/handlers/cases.ts +7 -0
  141. package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/App.tsx +5 -0
  142. package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/components/RingingBanner.tsx +7 -0
  143. package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/hooks/useTwilio.ts +6 -0
  144. package/skills/support-runtime-reachability/scripts/__fixtures__/case-04-jsx-component-rendered/src/App.tsx +5 -0
  145. package/skills/support-runtime-reachability/scripts/__fixtures__/case-04-jsx-component-rendered/src/components/MyComp.tsx +3 -0
  146. package/skills/support-runtime-reachability/scripts/__fixtures__/case-05-jsx-component-not-rendered/src/App.tsx +3 -0
  147. package/skills/support-runtime-reachability/scripts/__fixtures__/case-05-jsx-component-not-rendered/src/components/Orphan.tsx +3 -0
  148. package/skills/support-runtime-reachability/scripts/__fixtures__/case-06-class-instantiated/src/lib/Service.ts +6 -0
  149. package/skills/support-runtime-reachability/scripts/__fixtures__/case-06-class-instantiated/src/main.ts +4 -0
  150. package/skills/support-runtime-reachability/scripts/__fixtures__/case-07-class-not-instantiated/src/lib/Lonely.ts +5 -0
  151. package/skills/support-runtime-reachability/scripts/__fixtures__/case-07-class-not-instantiated/src/main.ts +2 -0
  152. package/skills/support-runtime-reachability/scripts/__fixtures__/case-08-default-export-imported-and-called/src/handler.ts +3 -0
  153. package/skills/support-runtime-reachability/scripts/__fixtures__/case-08-default-export-imported-and-called/src/main.ts +3 -0
  154. package/skills/support-runtime-reachability/scripts/__fixtures__/case-09-default-export-orphan/src/handler.ts +3 -0
  155. package/skills/support-runtime-reachability/scripts/__fixtures__/case-09-default-export-orphan/src/main.ts +2 -0
  156. package/skills/support-runtime-reachability/scripts/__fixtures__/case-10-aliased-named-export/src/lib.ts +5 -0
  157. package/skills/support-runtime-reachability/scripts/__fixtures__/case-10-aliased-named-export/src/main.ts +3 -0
  158. package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/lib/index.ts +1 -0
  159. package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/lib/internal.ts +3 -0
  160. package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/main.ts +3 -0
  161. package/skills/support-runtime-reachability/scripts/__fixtures__/case-12-test-only-caller/src/util.test.ts +5 -0
  162. package/skills/support-runtime-reachability/scripts/__fixtures__/case-12-test-only-caller/src/util.ts +3 -0
  163. package/skills/support-runtime-reachability/scripts/__fixtures__/case-13-gated-pending-annotation/src/future.ts +4 -0
  164. package/skills/support-runtime-reachability/scripts/__fixtures__/case-14-untraceable-annotation/src/decorated.ts +4 -0
  165. package/skills/support-runtime-reachability/scripts/__fixtures__/case-15-untraceable-empty/src/lazy.ts +4 -0
  166. package/skills/support-runtime-reachability/scripts/__fixtures__/case-16-python-module/src/lib.py +15 -0
  167. package/skills/support-runtime-reachability/scripts/__fixtures__/case-16-python-module/src/main.py +5 -0
  168. package/skills/support-runtime-reachability/scripts/__fixtures__/case-17-router-use/src/parent.ts +5 -0
  169. package/skills/support-runtime-reachability/scripts/__fixtures__/case-17-router-use/src/routes/cases.ts +5 -0
  170. package/skills/support-runtime-reachability/scripts/__fixtures__/case-18-shadowed-name-fp/src/lib/foo.ts +3 -0
  171. package/skills/support-runtime-reachability/scripts/__fixtures__/case-18-shadowed-name-fp/src/other.ts +8 -0
  172. package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/handlers/cases.ts +4 -0
  173. package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/handlers/users.ts +4 -0
  174. package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/main.ts +5 -0
  175. package/skills/support-runtime-reachability/scripts/__fixtures__/case-20-aliased-import-usage/src/handlers/cases.ts +3 -0
  176. package/skills/support-runtime-reachability/scripts/__fixtures__/case-20-aliased-import-usage/src/main.ts +4 -0
  177. package/skills/support-runtime-reachability/scripts/__fixtures__/case-21-mixed-default-and-named/src/lib.ts +5 -0
  178. package/skills/support-runtime-reachability/scripts/__fixtures__/case-21-mixed-default-and-named/src/main.ts +5 -0
  179. package/skills/support-runtime-reachability/scripts/__fixtures__/case-22-dynamic-import-then-caller/src/lib.ts +3 -0
  180. package/skills/support-runtime-reachability/scripts/__fixtures__/case-22-dynamic-import-then-caller/src/main.ts +8 -0
  181. package/skills/support-runtime-reachability/scripts/__fixtures__/case-23-dynamic-import-with-space/src/lib.ts +3 -0
  182. package/skills/support-runtime-reachability/scripts/__fixtures__/case-23-dynamic-import-with-space/src/main.ts +7 -0
  183. package/skills/support-runtime-reachability/scripts/check.mjs +638 -0
  184. package/skills/support-runtime-reachability/scripts/check.test.mjs +244 -0
  185. package/skills/support-skill-validator/SKILL.md +194 -0
  186. package/skills/support-skill-validator/references/false-positives.md +59 -0
  187. package/skills/support-skill-validator/references/validation-checks.md +280 -0
  188. package/skills/support-system-guide/SKILL.md +311 -0
  189. package/skills/support-task-force/SKILL.md +265 -0
  190. package/skills/support-task-force/references/dispatch-pattern.md +178 -0
  191. package/skills/support-task-force/references/synthesis-template.md +126 -0
  192. package/skills/support-wiki-bootstrap/SKILL.md +37 -0
  193. package/skills/support-wiki-lint/SKILL.md +196 -0
  194. package/skills/support-wiki-lint/scripts/lint.mjs +488 -0
  195. package/skills/support-wiki-lint/scripts/lint.test.mjs +196 -0
  196. package/templates/README.md +23 -0
  197. package/templates/aiwiki/CLAUDE.md.template +78 -0
  198. package/templates/aiwiki/schemas/architecture.md +118 -0
  199. package/templates/aiwiki/schemas/convention.md +112 -0
  200. package/templates/aiwiki/schemas/decision.md +144 -0
  201. package/templates/aiwiki/schemas/gotcha.md +118 -0
  202. package/templates/aiwiki/schemas/oracle.md +105 -0
  203. package/templates/aiwiki/schemas/session.md +125 -0
  204. package/templates/manifests/bugfix.yaml +41 -0
  205. package/templates/manifests/feature.yaml +69 -0
  206. package/templates/manifests/greenfield.yaml +61 -0
  207. package/templates/manifests/hotfix.yaml +45 -0
  208. package/templates/manifests/refactor.yaml +44 -0
  209. package/templates/manifests/v5/SCHEMA.md +327 -0
  210. package/templates/manifests/v5/feature.yaml +77 -0
  211. package/templates/manifests/v6/SCHEMA.md +199 -0
  212. package/templates/wiki-html/dream-detail.html +378 -0
  213. package/templates/wiki-html/dreams-list.html +155 -0
@@ -0,0 +1,79 @@
1
+ {
2
+ "_comment": "Maps manifest gate names to required skill and agent invocations. Used by gate-enforcer.sh to verify prerequisites before allowing gate-passed: true. Agents are dispatched by skills internally — listed here so the enforcer can verify they were invoked via telemetry.",
3
+ "requirements": {
4
+ "skill": "discover-requirements",
5
+ "agent": null
6
+ },
7
+ "codebase-analysis": {
8
+ "skill": "discover-codebase-analysis",
9
+ "agent": "architect"
10
+ },
11
+ "brainstorm": {
12
+ "skill": "plan-brainstorm",
13
+ "agent": "critic"
14
+ },
15
+ "architecture": {
16
+ "skill": "plan-architecture",
17
+ "agents": ["architect", "critic"]
18
+ },
19
+ "task-decompose": {
20
+ "skill": "plan-task-decompose",
21
+ "agent": "spec-reviewer"
22
+ },
23
+ "design-system": {
24
+ "skill": "plan-design-system",
25
+ "agent": null
26
+ },
27
+ "scaffold": {
28
+ "skill": "build-scaffold",
29
+ "agent": null
30
+ },
31
+ "concept": {
32
+ "skill": "concept-slides",
33
+ "agent": "concept-designer"
34
+ },
35
+ "wireframe": {
36
+ "skill": "build-wireframe",
37
+ "agent": "wireframer"
38
+ },
39
+ "prototype": {
40
+ "skill": "build-prototype",
41
+ "agent": "prototype-builder"
42
+ },
43
+ "iterate": {
44
+ "skill": "iterate-prototype",
45
+ "agent": "prototype-reviewer"
46
+ },
47
+ "codify": {
48
+ "skill": "harden",
49
+ "agent": "prototype-codifier"
50
+ },
51
+ "runtime-reach": {
52
+ "skill": "support-runtime-reachability",
53
+ "agent": null
54
+ },
55
+ "wiki-lint": {
56
+ "skill": "support-wiki-lint",
57
+ "agent": null
58
+ },
59
+ "code-review": {
60
+ "skill": "quality-code-review",
61
+ "agent": "craft-reviewer"
62
+ },
63
+ "code-review-final": {
64
+ "skill": "quality-code-review",
65
+ "agent": "code-reviewer"
66
+ },
67
+ "test-plan": {
68
+ "skill": "quality-test-plan",
69
+ "agent": "critic"
70
+ },
71
+ "test-execution": {
72
+ "skill": "quality-test-execution",
73
+ "agent": "e2e-runner"
74
+ },
75
+ "uiux-review": {
76
+ "skill": "quality-uiux",
77
+ "agent": null
78
+ }
79
+ }
@@ -0,0 +1,143 @@
1
+ {
2
+ "description": "Guardrails for safe git operations and context preservation",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Bash",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "if": "Bash(git *--no-verify*)",
11
+ "command": "echo 'BLOCKED: --no-verify is not allowed. Fix the underlying issue instead of skipping hooks.' >&2; exit 2"
12
+ }
13
+ ]
14
+ },
15
+ {
16
+ "matcher": "Bash",
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "if": "Bash(git push*)",
21
+ "command": "echo 'Reminder: Review your changes before pushing. Have tests passed? Is the branch up to date?' >&2; exit 0"
22
+ }
23
+ ]
24
+ },
25
+ {
26
+ "matcher": "Edit",
27
+ "hooks": [
28
+ {
29
+ "type": "command",
30
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scripts/gate-enforcer.sh\"",
31
+ "timeout": 5
32
+ }
33
+ ]
34
+ },
35
+ {
36
+ "matcher": "Write",
37
+ "hooks": [
38
+ {
39
+ "type": "command",
40
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scripts/gate-enforcer.sh\"",
41
+ "timeout": 5
42
+ }
43
+ ]
44
+ },
45
+ {
46
+ "matcher": "MultiEdit",
47
+ "hooks": [
48
+ {
49
+ "type": "command",
50
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scripts/gate-enforcer.sh\"",
51
+ "timeout": 5
52
+ }
53
+ ]
54
+ }
55
+ ],
56
+ "PostToolUse": [
57
+ {
58
+ "matcher": "Bash",
59
+ "hooks": [
60
+ {
61
+ "type": "command",
62
+ "if": "Bash(git commit*)",
63
+ "command": "echo 'Reminder: Run the test suite to verify this commit does not break anything.' >&2; exit 0"
64
+ }
65
+ ]
66
+ },
67
+ {
68
+ "matcher": "Skill",
69
+ "hooks": [
70
+ {
71
+ "type": "command",
72
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scripts/telemetry.sh\"",
73
+ "timeout": 5
74
+ }
75
+ ]
76
+ },
77
+ {
78
+ "matcher": "Task",
79
+ "hooks": [
80
+ {
81
+ "type": "command",
82
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scripts/telemetry.sh\"",
83
+ "timeout": 5
84
+ }
85
+ ]
86
+ },
87
+ {
88
+ "matcher": "Edit",
89
+ "hooks": [
90
+ {
91
+ "type": "command",
92
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scripts/wiki-lint.sh\"",
93
+ "timeout": 10
94
+ }
95
+ ]
96
+ },
97
+ {
98
+ "matcher": "Write",
99
+ "hooks": [
100
+ {
101
+ "type": "command",
102
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scripts/wiki-lint.sh\"",
103
+ "timeout": 10
104
+ }
105
+ ]
106
+ },
107
+ {
108
+ "matcher": "MultiEdit",
109
+ "hooks": [
110
+ {
111
+ "type": "command",
112
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scripts/wiki-lint.sh\"",
113
+ "timeout": 10
114
+ }
115
+ ]
116
+ }
117
+ ],
118
+ "SessionStart": [
119
+ {
120
+ "matcher": "*",
121
+ "hooks": [
122
+ {
123
+ "type": "command",
124
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scripts/session-start.sh\"",
125
+ "timeout": 5
126
+ }
127
+ ]
128
+ }
129
+ ],
130
+ "PreCompact": [
131
+ {
132
+ "matcher": "*",
133
+ "hooks": [
134
+ {
135
+ "type": "command",
136
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/scripts/pre-compact.sh\"",
137
+ "timeout": 10
138
+ }
139
+ ]
140
+ }
141
+ ]
142
+ }
143
+ }
@@ -0,0 +1,114 @@
1
+ #!/bin/bash
2
+ # Analyze telemetry from a harness run.
3
+ # Usage: bash analyze-telemetry.sh [command-name]
4
+ # Example: bash analyze-telemetry.sh greenfield
5
+ #
6
+ # Reads .forge/state/telemetry.jsonl and reports:
7
+ # 1. Which skills were called (and how many times)
8
+ # 2. Which agents were dispatched
9
+ # 3. If a command name is given, compares against expected skills
10
+
11
+ set -euo pipefail
12
+
13
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
14
+ TELEMETRY_FILE="$PROJECT_ROOT/.forge/state/telemetry.jsonl"
15
+
16
+ if [ ! -f "$TELEMETRY_FILE" ]; then
17
+ echo "No telemetry file found at $TELEMETRY_FILE"
18
+ echo "Run a command first to generate telemetry."
19
+ exit 1
20
+ fi
21
+
22
+ COMMAND="${1:-}"
23
+
24
+ echo "=== TELEMETRY ANALYSIS ==="
25
+ echo ""
26
+
27
+ echo "## Skills Called"
28
+ jq -r 'select(.type == "skill") | .name' "$TELEMETRY_FILE" | sort | uniq -c | sort -rn
29
+ echo ""
30
+
31
+ echo "## Agents Dispatched"
32
+ jq -r 'select(.type == "agent") | "\(.name) — \(.description)"' "$TELEMETRY_FILE" | sort | uniq -c | sort -rn
33
+ echo ""
34
+
35
+ echo "## Timeline"
36
+ jq -r '"\(.timestamp) [\(.type)] \(.name)"' "$TELEMETRY_FILE"
37
+ echo ""
38
+
39
+ # Expected skills per command
40
+ if [ -n "$COMMAND" ]; then
41
+ echo "## Compliance Check: /$COMMAND"
42
+ echo ""
43
+
44
+ case "$COMMAND" in
45
+ greenfield)
46
+ # discover-requirements is conditional (substantive existing requirements docs only).
47
+ # plan-design-system is conditional (skipped for backend-only/CLI projects without a frontend).
48
+ # quality-uiux is conditional (frontend projects only).
49
+ EXPECTED_SKILLS="discover-requirements concept-slides build-wireframe plan-design-system build-prototype iterate-prototype harden build-tdd quality-code-review quality-test-plan quality-test-execution quality-uiux build-pr-workflow support-gotcha"
50
+ EXPECTED_AGENTS=""
51
+ ;;
52
+ feature)
53
+ # plan-design-system is conditional (backend-only features skip; frontend features run).
54
+ # quality-uiux is conditional (frontend features only); deliver-deploy/onboarding conditional (non-internal features).
55
+ EXPECTED_SKILLS="discover-codebase-analysis concept-slides build-wireframe plan-design-system build-prototype iterate-prototype harden build-pr-workflow build-tdd quality-code-review quality-test-plan quality-test-execution quality-uiux deliver-deploy deliver-onboarding support-gotcha"
56
+ EXPECTED_AGENTS=""
57
+ ;;
58
+ bugfix)
59
+ EXPECTED_SKILLS="support-debug build-tdd quality-code-review build-pr-workflow support-gotcha"
60
+ EXPECTED_AGENTS=""
61
+ ;;
62
+ hotfix)
63
+ EXPECTED_SKILLS="support-debug build-tdd quality-test-execution quality-code-review deliver-deploy support-gotcha"
64
+ EXPECTED_AGENTS=""
65
+ ;;
66
+ refactor)
67
+ EXPECTED_SKILLS="discover-codebase-analysis plan-brainstorm plan-task-decompose build-tdd quality-code-review quality-test-execution build-pr-workflow support-gotcha"
68
+ EXPECTED_AGENTS=""
69
+ ;;
70
+ *)
71
+ echo "Unknown command: $COMMAND"
72
+ exit 1
73
+ ;;
74
+ esac
75
+
76
+ ACTUAL_SKILLS=$(jq -r 'select(.type == "skill") | .name' "$TELEMETRY_FILE" | sort -u)
77
+ ACTUAL_AGENTS=$(jq -r 'select(.type == "agent") | .name' "$TELEMETRY_FILE" | sort -u)
78
+
79
+ echo "### Skills"
80
+ for skill in $EXPECTED_SKILLS; do
81
+ if echo "$ACTUAL_SKILLS" | grep -q "^${skill}$"; then
82
+ echo " ✓ $skill"
83
+ else
84
+ echo " ✗ $skill — MISSING"
85
+ fi
86
+ done
87
+
88
+ echo ""
89
+ echo "### Agents"
90
+ if [ -n "$EXPECTED_AGENTS" ]; then
91
+ for agent in $EXPECTED_AGENTS; do
92
+ if echo "$ACTUAL_AGENTS" | grep -q "^${agent}$"; then
93
+ echo " ✓ $agent"
94
+ else
95
+ echo " ✗ $agent — MISSING"
96
+ fi
97
+ done
98
+ else
99
+ echo " (none expected for /$COMMAND)"
100
+ fi
101
+
102
+ echo ""
103
+
104
+ # Unexpected calls
105
+ UNEXPECTED=""
106
+ for skill in $ACTUAL_SKILLS; do
107
+ if ! echo "$EXPECTED_SKILLS" | tr ' ' '\n' | grep -q "^${skill}$"; then
108
+ UNEXPECTED="$UNEXPECTED $skill"
109
+ fi
110
+ done
111
+ if [ -n "$UNEXPECTED" ]; then
112
+ echo "### Unexpected skills called:$UNEXPECTED"
113
+ fi
114
+ fi
@@ -0,0 +1,164 @@
1
+ #!/bin/bash
2
+ # PreToolUse hook: blocks manifest gate-passed updates when required
3
+ # skill/agent was not invoked. Reads gate-requirements.json for mapping
4
+ # and telemetry.jsonl for invocation history.
5
+ #
6
+ # Only fires on Edit/Write targeting manifest.yaml with gate-passed: true.
7
+ # Returns { continue: false } to block, { continue: true } to allow.
8
+ #
9
+ # Gate matching handles three YAML cases:
10
+ # 1. Inline: requirements: { status: complete, gate-passed: true }
11
+ # 2. Block: requirements:\n gate-passed: true (name on different line)
12
+ # 3. Narrow edit: old_string="gate-passed: false" new_string="gate-passed: true"
13
+
14
+ set -euo pipefail
15
+
16
+ INPUT=$(cat)
17
+
18
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
19
+ FILE_PATH=""
20
+ CONTENT=""
21
+
22
+ # Extract file path and content based on tool type
23
+ if [ "$TOOL_NAME" = "Edit" ]; then
24
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
25
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // ""')
26
+ elif [ "$TOOL_NAME" = "Write" ]; then
27
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
28
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // ""')
29
+ elif [ "$TOOL_NAME" = "MultiEdit" ]; then
30
+ # MultiEdit targets one file with multiple edits. file_path is at the top
31
+ # level (same as Edit); the new content lives in tool_input.edits[*].new_string.
32
+ # Concatenate all new_string fields so gate detection scans every proposed
33
+ # change in the batch — otherwise a multi-edit could slip a `gate-passed: true`
34
+ # write past the enforcement gate that single-Edit catches.
35
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
36
+ CONTENT=$(echo "$INPUT" | jq -r '[.tool_input.edits[]?.new_string] | join("\n")')
37
+ else
38
+ echo '{"continue":true}'
39
+ exit 0
40
+ fi
41
+
42
+ # Only check manifest.yaml edits
43
+ if [[ "$FILE_PATH" != *manifest.yaml ]]; then
44
+ echo '{"continue":true}'
45
+ exit 0
46
+ fi
47
+
48
+ # Inline YAML on a gate key is a bypass surface — `code-review-final: { status:
49
+ # passed, gate-passed: true }` sneaks past the block-form line-anchored regex
50
+ # below because `gate-passed:` is mid-line. Reject inline form on any gate key
51
+ # explicitly so authors must use block form (where enforcement actually works).
52
+ # A gate key followed immediately by `{` indicates flow-mapping syntax; pair
53
+ # that with `gate-passed: true` anywhere on the same line and we treat it as a
54
+ # hard block.
55
+ INLINE_BYPASS_LINE=$(echo "$CONTENT" | grep -nE "^[[:space:]]*[a-z][a-z0-9-]*:[[:space:]]*\{[^}]*gate-passed:[[:space:]]*true" || true)
56
+ if [ -n "$INLINE_BYPASS_LINE" ]; then
57
+ echo "GATE ENFORCEMENT: inline-form YAML for a gate-passed assignment is not allowed (line: ${INLINE_BYPASS_LINE}). Use block form so the enforcer can validate prerequisites:" >&2
58
+ echo " code-review-final:" >&2
59
+ echo " status: complete" >&2
60
+ echo " gate-passed: true" >&2
61
+ echo "{\"continue\":false,\"reason\":\"GATE ENFORCEMENT: inline-form YAML on a gate-passed assignment is not allowed; use block form.\"}"
62
+ exit 2
63
+ fi
64
+
65
+ # Only check edits that set gate-passed: true.
66
+ # Anchor to actual YAML-key syntax: line must start (after optional whitespace) with
67
+ # the literal key "gate-passed:" followed by optional whitespace and "true". This
68
+ # prevents false-positives on prose/comments that mention the phrase — e.g. a
69
+ # manifest comment explaining when the field becomes true.
70
+ if ! echo "$CONTENT" | grep -qE "^[[:space:]]*gate-passed:[[:space:]]*true([[:space:]]|$|,|})"; then
71
+ echo '{"continue":true}'
72
+ exit 0
73
+ fi
74
+
75
+ # Locate config and telemetry
76
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
77
+ CONFIG="$SCRIPT_DIR/../config/gate-requirements.json"
78
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
79
+ TELEMETRY="$PROJECT_ROOT/.forge/state/telemetry.jsonl"
80
+
81
+ if [ ! -f "$CONFIG" ]; then
82
+ echo '{"continue":true}' # No config = no enforcement
83
+ exit 0
84
+ fi
85
+
86
+ # Check each gate being passed against requirements
87
+ BLOCKED_REASONS=""
88
+
89
+ for GATE in $(jq -r 'keys[] | select(. != "_comment")' "$CONFIG"); do
90
+ GATE_MATCH=false
91
+
92
+ # Method 1: Gate name appears anywhere in the edit content
93
+ # Handles inline YAML and block YAML (gate name on different line from gate-passed)
94
+ if echo "$CONTENT" | grep -q "$GATE"; then
95
+ GATE_MATCH=true
96
+ # Method 2 (Edit/MultiEdit): Gate name is near the edit location in the actual file
97
+ # Handles narrow edits (e.g., changing just "gate-passed: false" to "true")
98
+ elif [ "$TOOL_NAME" = "Edit" ] && [ -f "$FILE_PATH" ]; then
99
+ OLD_STRING=$(echo "$INPUT" | jq -r '.tool_input.old_string // ""')
100
+ if [ -n "$OLD_STRING" ]; then
101
+ FIRST_LINE=$(echo "$OLD_STRING" | head -1)
102
+ if [ -n "$FIRST_LINE" ] && grep -B 10 -F "$FIRST_LINE" "$FILE_PATH" 2>/dev/null | grep -q "[[:space:]]*${GATE}[[:space:]]*:"; then
103
+ GATE_MATCH=true
104
+ fi
105
+ fi
106
+ elif [ "$TOOL_NAME" = "MultiEdit" ] && [ -f "$FILE_PATH" ]; then
107
+ # For each edit's old_string, check the first line and see whether the
108
+ # gate name appears in nearby file context. Emit one first-line per edit
109
+ # (jq splits on records, not embedded newlines) so multi-line old_strings
110
+ # still get probed by their starting line.
111
+ EDIT_COUNT=$(echo "$INPUT" | jq '.tool_input.edits | length // 0')
112
+ for ((i = 0; i < EDIT_COUNT; i++)); do
113
+ OLD_STRING=$(echo "$INPUT" | jq -r ".tool_input.edits[$i].old_string // \"\"")
114
+ [ -z "$OLD_STRING" ] && continue
115
+ FIRST_LINE=$(echo "$OLD_STRING" | head -1)
116
+ if [ -n "$FIRST_LINE" ] && grep -B 10 -F "$FIRST_LINE" "$FILE_PATH" 2>/dev/null | grep -q "[[:space:]]*${GATE}[[:space:]]*:"; then
117
+ GATE_MATCH=true
118
+ break
119
+ fi
120
+ done
121
+ fi
122
+
123
+ if [ "$GATE_MATCH" = true ]; then
124
+ REQUIRED_SKILL=$(jq -r ".\"$GATE\".skill // empty" "$CONFIG")
125
+
126
+ # Resolve required agents: agents array first, then single agent field
127
+ REQUIRED_AGENTS=$(jq -r "if .\"$GATE\".agents then .\"$GATE\".agents[] else .\"$GATE\".agent // empty end" "$CONFIG" 2>/dev/null)
128
+
129
+ # Check skill invocation in telemetry
130
+ if [ -n "$REQUIRED_SKILL" ]; then
131
+ if [ ! -f "$TELEMETRY" ] || ! grep -q "\"name\":\"$REQUIRED_SKILL\"" "$TELEMETRY"; then
132
+ BLOCKED_REASONS="${BLOCKED_REASONS}Gate '$GATE' requires skill '$REQUIRED_SKILL' (not found in telemetry). "
133
+ fi
134
+ fi
135
+
136
+ # Check agent dispatch in telemetry
137
+ for AGENT in $REQUIRED_AGENTS; do
138
+ if [ -n "$AGENT" ] && [ "$AGENT" != "null" ]; then
139
+ if [ ! -f "$TELEMETRY" ] || ! grep -q "\"name\":\"$AGENT\"" "$TELEMETRY"; then
140
+ BLOCKED_REASONS="${BLOCKED_REASONS}Gate '$GATE' requires agent '$AGENT' (not found in telemetry). "
141
+ fi
142
+ fi
143
+ done
144
+ fi
145
+ done
146
+
147
+ if [ -n "$BLOCKED_REASONS" ]; then
148
+ # The hook's JSON `reason` field is currently swallowed by Claude Code (only
149
+ # the generic "Execution stopped by hook" line is surfaced to the user).
150
+ # Echo the reason to stderr so it actually reaches the user — stderr from
151
+ # PreToolUse hooks IS surfaced in the tool-error output.
152
+ echo "GATE ENFORCEMENT: ${BLOCKED_REASONS}Load the required skill/agent before marking the gate as passed." >&2
153
+
154
+ # Escape for JSON (still emit reason in case a future harness surfaces it).
155
+ ESCAPED=$(echo "$BLOCKED_REASONS" | sed 's/"/\\"/g')
156
+ echo "{\"continue\":false,\"reason\":\"GATE ENFORCEMENT: ${ESCAPED}Load the required skill/agent before marking the gate as passed.\"}"
157
+ # PreToolUse hooks only block when exit code is 2; exit 0 would allow the
158
+ # tool call despite the JSON block decision. Keep the JSON emit above for
159
+ # user-facing context.
160
+ exit 2
161
+ fi
162
+
163
+ echo '{"continue":true}'
164
+ exit 0
@@ -0,0 +1,90 @@
1
+ #!/bin/bash
2
+ # Pre-compact hook: Save critical state before context window compression.
3
+ # This ensures the agent can recover its working context after compaction.
4
+ #
5
+ # Writes to .forge/state/notepad.md which survives context compression
6
+ # because it's re-read by the agent when it detects context loss.
7
+ #
8
+ # Scans `.forge/work/*/` (typed subdirs) for the single in-progress work item.
9
+ # Skips `escalated`/`completed` manifests (terminal states). Uses `{type}/{name}`
10
+ # as the canonical work identifier because names can collide across types.
11
+
12
+ set -euo pipefail
13
+
14
+ # Find project root (look for .forge/ or .git/)
15
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
16
+ STATE_DIR="$PROJECT_ROOT/.forge/state"
17
+
18
+ # Create state directory if needed
19
+ mkdir -p "$STATE_DIR"
20
+
21
+ # Ensure .forge/state/ is gitignored
22
+ if [ -f "$PROJECT_ROOT/.gitignore" ]; then
23
+ if ! grep -q '.forge/state' "$PROJECT_ROOT/.gitignore" 2>/dev/null; then
24
+ echo '.forge/state/' >> "$PROJECT_ROOT/.gitignore"
25
+ fi
26
+ fi
27
+
28
+ # Find active work item (status: in-progress), scanning typed subdirs
29
+ NOTEPAD="$STATE_DIR/notepad.md"
30
+ ACTIVE_WORK="" # Canonical identifier: {type}/{name}
31
+ MANIFEST_PATH=""
32
+
33
+ if [ -d "$PROJECT_ROOT/.forge/work" ]; then
34
+ for manifest in "$PROJECT_ROOT/.forge/work"/*/*/manifest.yaml; do
35
+ if [ -f "$manifest" ] && grep -q 'status: in-progress' "$manifest" 2>/dev/null; then
36
+ WORK_NAME="$(basename "$(dirname "$manifest")")"
37
+ WORK_TYPE="$(basename "$(dirname "$(dirname "$manifest")")")"
38
+ ACTIVE_WORK="$WORK_TYPE/$WORK_NAME"
39
+ MANIFEST_PATH="$manifest"
40
+ break
41
+ fi
42
+ done
43
+ fi
44
+
45
+ # Build notepad content
46
+ {
47
+ echo "# Forge Context Recovery"
48
+ echo ""
49
+ echo "**This file was auto-generated before context compaction.**"
50
+ echo "**Re-read the manifest and continue from where you left off.**"
51
+ echo ""
52
+ echo "## Active Work Item"
53
+
54
+ if [ -n "$ACTIVE_WORK" ]; then
55
+ echo "- Work: $ACTIVE_WORK"
56
+ echo "- Manifest: $MANIFEST_PATH"
57
+ echo ""
58
+
59
+ # Extract current phase info from manifest
60
+ if command -v grep &>/dev/null && [ -f "$MANIFEST_PATH" ]; then
61
+ echo "### Phase Status (from manifest)"
62
+ echo '```yaml'
63
+ grep -A 1 'status:\|gate-passed:' "$MANIFEST_PATH" 2>/dev/null | head -30
64
+ echo '```'
65
+ fi
66
+ else
67
+ echo "- No active work item found"
68
+ fi
69
+
70
+ echo ""
71
+ echo "## Recovery Instructions"
72
+ echo "1. Read the manifest file listed above"
73
+ echo "2. Find the first phase with \`status: pending\` or \`gate-passed: false\`"
74
+ echo "3. Continue from that phase"
75
+ echo "4. If build phase: check which tasks are complete vs pending"
76
+ echo ""
77
+ echo "---"
78
+ echo "*Generated at: $(date -u '+%Y-%m-%dT%H:%M:%SZ')*"
79
+ } > "$NOTEPAD"
80
+
81
+ # Log compaction event to telemetry
82
+ TELEMETRY_FILE="$STATE_DIR/telemetry.jsonl"
83
+ TIMESTAMP="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
84
+ WORK_JSON="${ACTIVE_WORK:-null}"
85
+ if [ -n "$ACTIVE_WORK" ]; then WORK_JSON="\"$ACTIVE_WORK\""; fi
86
+
87
+ echo "{\"event\":\"compact\",\"timestamp\":\"$TIMESTAMP\",\"work\":$WORK_JSON}" >> "$TELEMETRY_FILE"
88
+
89
+ echo "Pre-compact: Saved context recovery state to $NOTEPAD" >&2
90
+ exit 0
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env bash
2
+ # Session start check: surface promotion-pending gotchas, unresolved hotfix
3
+ # workarounds, and paused work items. Runs as a SessionStart hook — warns
4
+ # loudly via stderr but does not block (Claude Code treats SessionStart exit
5
+ # codes advisorily).
6
+ #
7
+ # Resolves .forge at the repo root via `git rev-parse --show-toplevel` to match
8
+ # pre-compact.sh and telemetry.sh. Scans `.forge/work/*/` (typed subdirs) and
9
+ # skips `escalated`/`completed` manifests (terminal states).
10
+
11
+ set -u
12
+
13
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
14
+ FORGE_DIR="$PROJECT_ROOT/.forge"
15
+ GLOBAL_GOTCHAS="$HOME/.claude/gotchas"
16
+ WARNINGS=""
17
+
18
+ # Hard-interrupt: promotion-pending gotchas.
19
+ # When `support-gotcha` auto-drafts a proposed rule at the 3rd occurrence,
20
+ # the gotcha's INDEX status column flips to `promotion-pending` and a
21
+ # `Proposed Rule` block is appended to the gotcha file. The next
22
+ # session-start surfaces every such entry so the user can approve / defer /
23
+ # reject before doing other work.
24
+ #
25
+ # Match the INDEX table cell explicitly — `| promotion-pending |` — so the
26
+ # scan does not false-positive on prose that happens to mention the literal
27
+ # string ("see legend: promotion-pending entries..."). The cell can carry
28
+ # trailing whitespace before the closing pipe; allow it with `[[:space:]]*`.
29
+ PENDING_FILES=""
30
+ for index_path in "$PROJECT_ROOT/aiwiki/gotchas/INDEX.md" "$GLOBAL_GOTCHAS/INDEX.md"; do
31
+ [ -f "$index_path" ] || continue
32
+ if grep -Eq '\|[[:space:]]*promotion-pending[[:space:]]*\|' "$index_path" 2>/dev/null; then
33
+ PENDING_FILES="${PENDING_FILES}${index_path}\n"
34
+ fi
35
+ done
36
+
37
+ if [ -n "$PENDING_FILES" ]; then
38
+ COUNT=$(printf "%b" "$PENDING_FILES" | grep -c '.' || echo 0)
39
+ WARNINGS="${WARNINGS}HARD-INTERRUPT: ${COUNT} gotcha index file(s) contain promotion-pending entries — auto-drafted rules awaiting review.\n"
40
+ WARNINGS="${WARNINGS} Files:\n"
41
+ WARNINGS="${WARNINGS}$(printf "%b" "$PENDING_FILES" | sed 's/^/ /')\n"
42
+ WARNINGS="${WARNINGS} Action: invoke support-gotcha to walk each promotion-pending entry (approve / defer / reject) BEFORE starting other work.\n"
43
+ fi
44
+
45
+ # Check for unresolved hotfix workarounds
46
+ if [ -d "$PROJECT_ROOT/aiwiki/gotchas" ]; then
47
+ HOTFIX_FILES=$(grep -rl "severity: hotfix-workaround" "$PROJECT_ROOT/aiwiki/gotchas/" 2>/dev/null | while read -r f; do
48
+ if grep -q "resolved: false" "$f" 2>/dev/null || ! grep -q "resolved: true" "$f" 2>/dev/null; then
49
+ echo "$f"
50
+ fi
51
+ done)
52
+
53
+ if [ -n "$HOTFIX_FILES" ]; then
54
+ COUNT=$(echo "$HOTFIX_FILES" | wc -l | tr -d ' ')
55
+ WARNINGS="${WARNINGS}WARNING: ${COUNT} unresolved hotfix workaround(s) in aiwiki/gotchas/. Run /evolve or address these before starting new work.\n"
56
+ fi
57
+ fi
58
+
59
+ # Check for paused work items across all typed subdirs
60
+ if [ -d "$FORGE_DIR/work" ]; then
61
+ PAUSED_ITEMS=""
62
+ for manifest in "$FORGE_DIR/work"/*/*/manifest.yaml; do
63
+ [ -f "$manifest" ] || continue
64
+ if grep -q 'status: paused' "$manifest" 2>/dev/null; then
65
+ # Extract {type}/{name} from path: .../.forge/work/{type}/{name}/manifest.yaml
66
+ TYPE_NAME="$(basename "$(dirname "$(dirname "$manifest")")")/$(basename "$(dirname "$manifest")")"
67
+ PAUSED_ITEMS="${PAUSED_ITEMS}${TYPE_NAME}\n"
68
+ fi
69
+ done
70
+
71
+ if [ -n "$PAUSED_ITEMS" ]; then
72
+ COUNT=$(printf "%b" "$PAUSED_ITEMS" | grep -c '.' || echo 0)
73
+ WARNINGS="${WARNINGS}WARNING: ${COUNT} paused work item(s) found. Resume with the relevant command or close them out.\n"
74
+ fi
75
+ fi
76
+
77
+ if [ -n "$WARNINGS" ]; then
78
+ printf "%b" "$WARNINGS" >&2
79
+ fi
80
+
81
+ exit 0