@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.
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/agents/architect.md +92 -0
- package/agents/builder.md +122 -0
- package/agents/code-reviewer.md +107 -0
- package/agents/concept-designer.md +207 -0
- package/agents/craft-reviewer.md +132 -0
- package/agents/critic.md +130 -0
- package/agents/doc-writer.md +85 -0
- package/agents/dreamer.md +129 -0
- package/agents/e2e-runner.md +89 -0
- package/agents/gotcha-hunter.md +127 -0
- package/agents/prototype-builder.md +193 -0
- package/agents/prototype-codifier.md +204 -0
- package/agents/prototype-reviewer.md +163 -0
- package/agents/security-reviewer.md +108 -0
- package/agents/spec-reviewer.md +94 -0
- package/agents/tracer.md +98 -0
- package/agents/wireframer.md +109 -0
- package/commands/abort.md +25 -0
- package/commands/bugfix.md +151 -0
- package/commands/evolve.md +118 -0
- package/commands/feature.md +236 -0
- package/commands/forge.md +100 -0
- package/commands/greenfield.md +185 -0
- package/commands/hotfix.md +98 -0
- package/commands/refactor.md +147 -0
- package/commands/resume.md +25 -0
- package/commands/setup.md +201 -0
- package/commands/status.md +27 -0
- package/commands/task-force.md +110 -0
- package/commands/validate.md +12 -0
- package/dist/__tests__/active-manifest.test.js +272 -0
- package/dist/__tests__/copy.test.js +96 -0
- package/dist/__tests__/gate-check.test.js +384 -0
- package/dist/__tests__/wiki.test.js +472 -0
- package/dist/__tests__/work-manifest.test.js +304 -0
- package/dist/active-manifest.js +229 -0
- package/dist/cli.js +158 -0
- package/dist/copy.js +124 -0
- package/dist/gate-check.js +326 -0
- package/dist/hooks.js +60 -0
- package/dist/init.js +140 -0
- package/dist/manifest.js +90 -0
- package/dist/merge.js +77 -0
- package/dist/paths.js +36 -0
- package/dist/uninstall.js +216 -0
- package/dist/update.js +158 -0
- package/dist/verify-manifest.js +65 -0
- package/dist/verify.js +98 -0
- package/dist/wiki-ui.js +310 -0
- package/dist/wiki.js +364 -0
- package/dist/work-manifest.js +798 -0
- package/hooks/config/gate-requirements.json +79 -0
- package/hooks/hooks.json +143 -0
- package/hooks/scripts/analyze-telemetry.sh +114 -0
- package/hooks/scripts/gate-enforcer.sh +164 -0
- package/hooks/scripts/pre-compact.sh +90 -0
- package/hooks/scripts/session-start.sh +81 -0
- package/hooks/scripts/telemetry.sh +41 -0
- package/hooks/scripts/wiki-lint.sh +87 -0
- package/hooks/templates/AGENTS.md.template +48 -0
- package/hooks/templates/CLAUDE.md.template +45 -0
- package/package.json +55 -0
- package/protocols/README.md +40 -0
- package/protocols/codex.md +151 -0
- package/protocols/graphify.md +156 -0
- package/references/common/agent-coordination.md +65 -0
- package/references/common/coding-standards.md +54 -0
- package/references/common/feature-tracking.md +21 -0
- package/references/common/io-protocol.md +36 -0
- package/references/common/phases.md +57 -0
- package/references/common/quality-gates.md +130 -0
- package/references/common/skill-authoring.md +154 -0
- package/references/common/skill-compliance.md +30 -0
- package/references/python/standards.md +44 -0
- package/references/react/standards.md +61 -0
- package/references/typescript/standards.md +42 -0
- package/rules/common/forge-system.md +59 -0
- package/rules/common/git-workflow.md +40 -0
- package/rules/common/guardrails.md +37 -0
- package/rules/common/quality-gates.md +18 -0
- package/rules/common/security.md +50 -0
- package/rules/common/skill-selection.md +78 -0
- package/rules/common/testing.md +58 -0
- package/rules/common/verification.md +39 -0
- package/skills/build-pr-workflow/SKILL.md +301 -0
- package/skills/build-pr-workflow/references/pr-template.md +62 -0
- package/skills/build-pr-workflow/references/subagent-merge.md +47 -0
- package/skills/build-pr-workflow/references/worktree-setup.md +125 -0
- package/skills/build-prototype/SKILL.md +264 -0
- package/skills/build-scaffold/SKILL.md +340 -0
- package/skills/build-tdd/SKILL.md +89 -0
- package/skills/build-wireframe/SKILL.md +110 -0
- package/skills/build-wireframe/assets/baseline-template.html +486 -0
- package/skills/build-wireframe/references/demo-walkthroughs.md +170 -0
- package/skills/build-wireframe/references/gotchas.md +188 -0
- package/skills/build-wireframe/references/legend-lines.md +141 -0
- package/skills/concept-slides/SKILL.md +192 -0
- package/skills/deliver-db-migration/SKILL.md +466 -0
- package/skills/deliver-deploy/SKILL.md +407 -0
- package/skills/deliver-onboarding/SKILL.md +198 -0
- package/skills/deliver-onboarding/references/document-templates.md +393 -0
- package/skills/deliver-onboarding/templates/getting-started.md +122 -0
- package/skills/discover-codebase-analysis/SKILL.md +448 -0
- package/skills/discover-requirements/SKILL.md +418 -0
- package/skills/discover-requirements/templates/prd.md +99 -0
- package/skills/discover-requirements/templates/technical-spec.md +123 -0
- package/skills/discover-requirements/templates/user-stories.md +76 -0
- package/skills/harden/SKILL.md +214 -0
- package/skills/iterate-prototype/SKILL.md +241 -0
- package/skills/plan-architecture/SKILL.md +457 -0
- package/skills/plan-architecture/templates/adr-template.md +52 -0
- package/skills/plan-architecture/templates/api-contract.md +99 -0
- package/skills/plan-architecture/templates/db-schema.md +81 -0
- package/skills/plan-architecture/templates/system-design.md +111 -0
- package/skills/plan-brainstorm/SKILL.md +433 -0
- package/skills/plan-design-system/SKILL.md +279 -0
- package/skills/plan-task-decompose/SKILL.md +454 -0
- package/skills/quality-code-review/SKILL.md +286 -0
- package/skills/quality-security-audit/SKILL.md +292 -0
- package/skills/quality-security-audit/references/audit-report-template.md +89 -0
- package/skills/quality-security-audit/references/owasp-checks.md +178 -0
- package/skills/quality-test-execution/SKILL.md +435 -0
- package/skills/quality-test-plan/SKILL.md +297 -0
- package/skills/quality-test-plan/references/test-type-guide.md +263 -0
- package/skills/quality-test-plan/templates/e2e-test-plan.md +72 -0
- package/skills/quality-test-plan/templates/integration-test-plan.md +74 -0
- package/skills/quality-test-plan/templates/load-test-plan.md +111 -0
- package/skills/quality-test-plan/templates/smoke-test-plan.md +68 -0
- package/skills/quality-test-plan/templates/unit-test-plan.md +56 -0
- package/skills/quality-uiux/SKILL.md +481 -0
- package/skills/support-debug/SKILL.md +464 -0
- package/skills/support-dream/SKILL.md +213 -0
- package/skills/support-gotcha/SKILL.md +249 -0
- package/skills/support-runtime-reachability/SKILL.md +190 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-01-passes-app-use/src/app.ts +7 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-01-passes-app-use/src/handlers/cases.ts +7 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-02-orphan-no-app-use/src/app.ts +8 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-02-orphan-no-app-use/src/handlers/cases.ts +7 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/App.tsx +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/components/RingingBanner.tsx +7 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/hooks/useTwilio.ts +6 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-04-jsx-component-rendered/src/App.tsx +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-04-jsx-component-rendered/src/components/MyComp.tsx +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-05-jsx-component-not-rendered/src/App.tsx +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-05-jsx-component-not-rendered/src/components/Orphan.tsx +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-06-class-instantiated/src/lib/Service.ts +6 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-06-class-instantiated/src/main.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-07-class-not-instantiated/src/lib/Lonely.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-07-class-not-instantiated/src/main.ts +2 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-08-default-export-imported-and-called/src/handler.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-08-default-export-imported-and-called/src/main.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-09-default-export-orphan/src/handler.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-09-default-export-orphan/src/main.ts +2 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-10-aliased-named-export/src/lib.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-10-aliased-named-export/src/main.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/lib/index.ts +1 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/lib/internal.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/main.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-12-test-only-caller/src/util.test.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-12-test-only-caller/src/util.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-13-gated-pending-annotation/src/future.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-14-untraceable-annotation/src/decorated.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-15-untraceable-empty/src/lazy.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-16-python-module/src/lib.py +15 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-16-python-module/src/main.py +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-17-router-use/src/parent.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-17-router-use/src/routes/cases.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-18-shadowed-name-fp/src/lib/foo.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-18-shadowed-name-fp/src/other.ts +8 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/handlers/cases.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/handlers/users.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/main.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-20-aliased-import-usage/src/handlers/cases.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-20-aliased-import-usage/src/main.ts +4 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-21-mixed-default-and-named/src/lib.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-21-mixed-default-and-named/src/main.ts +5 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-22-dynamic-import-then-caller/src/lib.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-22-dynamic-import-then-caller/src/main.ts +8 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-23-dynamic-import-with-space/src/lib.ts +3 -0
- package/skills/support-runtime-reachability/scripts/__fixtures__/case-23-dynamic-import-with-space/src/main.ts +7 -0
- package/skills/support-runtime-reachability/scripts/check.mjs +638 -0
- package/skills/support-runtime-reachability/scripts/check.test.mjs +244 -0
- package/skills/support-skill-validator/SKILL.md +194 -0
- package/skills/support-skill-validator/references/false-positives.md +59 -0
- package/skills/support-skill-validator/references/validation-checks.md +280 -0
- package/skills/support-system-guide/SKILL.md +311 -0
- package/skills/support-task-force/SKILL.md +265 -0
- package/skills/support-task-force/references/dispatch-pattern.md +178 -0
- package/skills/support-task-force/references/synthesis-template.md +126 -0
- package/skills/support-wiki-bootstrap/SKILL.md +37 -0
- package/skills/support-wiki-lint/SKILL.md +196 -0
- package/skills/support-wiki-lint/scripts/lint.mjs +488 -0
- package/skills/support-wiki-lint/scripts/lint.test.mjs +196 -0
- package/templates/README.md +23 -0
- package/templates/aiwiki/CLAUDE.md.template +78 -0
- package/templates/aiwiki/schemas/architecture.md +118 -0
- package/templates/aiwiki/schemas/convention.md +112 -0
- package/templates/aiwiki/schemas/decision.md +144 -0
- package/templates/aiwiki/schemas/gotcha.md +118 -0
- package/templates/aiwiki/schemas/oracle.md +105 -0
- package/templates/aiwiki/schemas/session.md +125 -0
- package/templates/manifests/bugfix.yaml +41 -0
- package/templates/manifests/feature.yaml +69 -0
- package/templates/manifests/greenfield.yaml +61 -0
- package/templates/manifests/hotfix.yaml +45 -0
- package/templates/manifests/refactor.yaml +44 -0
- package/templates/manifests/v5/SCHEMA.md +327 -0
- package/templates/manifests/v5/feature.yaml +77 -0
- package/templates/manifests/v6/SCHEMA.md +199 -0
- package/templates/wiki-html/dream-detail.html +378 -0
- 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
|
+
}
|
package/hooks/hooks.json
ADDED
|
@@ -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
|