Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 27 additions & 32 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,34 @@

Portable, installable workflow enforcement system for Claude Code. One setup script installs the entire spec→hook→test→PR pipeline with full audit trail. Includes enforceable workflows — ordered step pipelines backed by hooks.

## Session State
## Status: Complete

- On branch: main (all merged)
- Pushed to grobomo/spec-hook (public)
- E2E proof test (28 tests) passes locally — all 9 gates covered
- SHTD deployed to CCC workers 1-4 (Docker containers)
- All path resolution simplified via hooks/lib symlink
- Code review complete — no remaining duplication
All tasks done. 12-page evidence PDF with real worker screenshots.

## Completed

- [x] T001 Create lib/audit.js — unified JSONL audit log
- [x] T002 Create lib/task_claims.py — multi-tab negotiation with OS locking
- [x] T003 Create hooksall PreToolUse/PostToolUse/Stop modules
- [x] T004 Create install.sh — cross-platform setup (Windows/Mac/Linux)
- [x] T005 Create rules, CLAUDE.md, status CLI, secret-scan CI, .gitignore
- [x] T006 Workflow engine (lib/workflow.js) — YAML parser, state manager, step validator
- [x] T007 Workflow gate hook (shtd_workflow-gate.js) — enforce step order
- [x] T008 Workflow CLI (shtd-workflow.sh) — start/status/complete/reset
- [x] T009 First workflow: test-claude-install with step scripts
- [x] T010 Update installer and CLAUDE.md for workflow engine
- [x] T011 Run test-claude-install workflow on EC2validate full pipeline
- [x] T012 Merge feature branches to main, push to grobomo/spec-hook
- [x] T013 README.md with install instructions (merged to main)
- [x] T014 E2E proof test — 28 tests proving real-world hook behavior (all 9 gates)
- [x] T015 Code review: DRY up getAudit() helper — extracted to lib/get-audit.js
- [x] T016 Code review: DRY up allowed-path patterns — extracted to lib/allowed-paths.js
- [x] T017 YAML parser hardening — 12 edge case tests, all passed, added id filter
- [x] T018 Add e2e-merge-gate and remote-tracking-gate to e2e proof test
- [x] T019 (skipped — AMI not needed, deploy script handles fresh installs)
- [x] T020 Deploy SHTD to CCC workers 1-4 via deploy-to-workers.sh
- [x] T021 Final code review: simplify path resolution across all hooks

## Status: Complete

All tasks done. Project is published at grobomo/spec-hook and deployed to production workers.
- [x] T001-T021 (all core tasks — see git log)
- [x] T022 Initial evidence report (PDF with tables, user rejected — needs real screenshots)
- [x] Global MCP configmcp-manager added to ~/.claude.json with `claude mcp add -s user`
- [x] T023 Evidence report with REAL screenshots + critical bug fix
- **Critical bug found and fixed**: All 10 hook modules used `{ blocked: true }` return format,
but hook-runner expects `{ decision: 'block' }`. Hooks were silently not blocking in production.
Fixed all modules to use correct `decision: 'block'` format. Updated tests to match.
- Deployed fixed hooks to Worker 1 via scripts/deploy-to-worker.sh
- Captured 5 live evidence scenarios from EC2 Worker 1 (Docker container):
1. install.sh --check — all 16 components verified OK
2. branch-gate BLOCKS Write on masterreturns JSON decision:block
3. spec-gate BLOCKS Write without specs/ — returns JSON decision:block
4. All gates PASS with proper setup (feature branch + specs + tracking)
5. remote-tracking-gate BLOCKS untracked branch
- Desktop screenshots with taskbar clock (evidence-terminal.png, e2e-local-tests.png)
- 28/28 local e2e tests pass with new decision format
- 12-page PDF: reports/shtd_flow_evidence_20260403_205805.pdf

## Scripts Created

- `scripts/capture-evidence.sh` — Run real hook modules on worker containers, capture output
- `scripts/deploy-to-worker.sh` — Deploy SHTD to worker Docker containers
- `scripts/check-worker-install.sh` — Verify SHTD installation on workers
- `scripts/take-screenshot.sh` — Desktop/command/remote screenshot tool (Python PIL)
- `scripts/generate-evidence-report.py` — Generate 12-page PDF with pm-report skill
2 changes: 1 addition & 1 deletion hooks/PreToolUse/shtd_branch-gate.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = function(input) {

if (branch === 'main' || branch === 'master') {
return {
blocked: true,
decision: 'block',
reason: `[shtd] On ${branch} branch. Create a feature branch first: git checkout -b <NNN>-<feature-name>`
};
}
Expand Down
2 changes: 1 addition & 1 deletion hooks/PreToolUse/shtd_e2e-merge-gate.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ module.exports = function(input) {
if (!markers.some(m => fs.existsSync(m))) {
getAudit().logEvent('merge_blocked', { reason: 'no_e2e', branch });
return {
blocked: true,
decision: 'block',
reason: `[shtd] Feature branch "${branch}" has no E2E test results. Run integration tests and create .test-results/${branch}.passed before merging to main.`
};
}
Expand Down
2 changes: 1 addition & 1 deletion hooks/PreToolUse/shtd_pr-per-task-gate.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = function(input) {
const title = titleMatch[1];
if (!/T\d+/i.test(title)) {
return {
blocked: true,
decision: 'block',
reason: '[shtd] PR title must include task ID (e.g. "T001: Add config parser"). One PR per task.'
};
}
Expand Down
2 changes: 1 addition & 1 deletion hooks/PreToolUse/shtd_remote-tracking-gate.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ module.exports = function(input) {
});
} catch(e) {
return {
blocked: true,
decision: 'block',
reason: `[shtd] Branch "${branch}" doesn't track a remote. Run: git push -u origin ${branch}`
};
}
Expand Down
2 changes: 1 addition & 1 deletion hooks/PreToolUse/shtd_secret-scan-gate.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = function(input) {

if (!fs.existsSync(scanFile)) {
return {
blocked: true,
decision: 'block',
reason: '[shtd] No .github/workflows/secret-scan.yml. Add a secret scan CI workflow before pushing.'
};
}
Expand Down
4 changes: 2 additions & 2 deletions hooks/PreToolUse/shtd_spec-gate.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ module.exports = function(input) {
const specsDir = path.join(projectDir, 'specs');
if (!fs.existsSync(specsDir)) {
getAudit().logEvent('code_blocked', { reason: 'no_specs_dir', file: path.basename(filePath) });
return { blocked: true, reason: '[shtd] No specs/ directory. Create a spec first: specs/<NNN>-<feature>/spec.md' };
return { decision: 'block', reason: '[shtd] No specs/ directory. Create a spec first: specs/<NNN>-<feature>/spec.md' };
}

try {
const specs = fs.readdirSync(specsDir).filter(f =>
fs.statSync(path.join(specsDir, f)).isDirectory());
if (specs.length === 0) {
return { blocked: true, reason: '[shtd] specs/ is empty. Define at least one spec before writing code.' };
return { decision: 'block', reason: '[shtd] specs/ is empty. Define at least one spec before writing code.' };
}
} catch(e) {}

Expand Down
2 changes: 1 addition & 1 deletion hooks/PreToolUse/shtd_task-claim.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ module.exports = function(input) {
} else if (parsed.reason === 'all_claimed') {
const summary = Object.entries(parsed.claimed || {}).map(([t, s]) => `${t}→${s}`).join(', ');
return {
blocked: true,
decision: 'block',
reason: `[shtd] All tasks claimed by other sessions (${summary}). Work on code review, docs, or wait for a task to free up.`
};
}
Expand Down
2 changes: 1 addition & 1 deletion hooks/PreToolUse/shtd_test-first-gate.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ module.exports = function(input) {
reason: 'no_test_for_task', task: taskId, file: path.basename(filePath)
});
return {
blocked: true,
decision: 'block',
reason: `[shtd] Test-first: no test found for ${taskId}. Write a test in scripts/test/ or test/ before implementation code.`
};
}
Expand Down
2 changes: 1 addition & 1 deletion hooks/PreToolUse/shtd_workflow-gate.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module.exports = function(input) {
if (!check.allowed) {
const reasons = (check.reasons || []).join('; ');
return {
blocked: true,
decision: 'block',
reason: `[shtd] Workflow "${state.workflow}" step "${current}" blocked: ${reasons}`
};
}
Expand Down
Binary file added reports/screenshots/desktop-timestamp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added reports/screenshots/e2e-local-tests.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
114 changes: 114 additions & 0 deletions reports/screenshots/evidence-capture-output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
=============================================
SHTD Evidence Capture — Worker 1 (18.219.224.145)
2026-04-03 20:48:00
=============================================

╔══════════════════════════════════════════╗
║ Evidence 1: SHTD Installation Verified ║
╚══════════════════════════════════════════╝
Worker: ip-172-31-21-27 | 2026-04-04 01:48:06 UTC
Docker:
NAMES STATUS IMAGE
claude-portable Up 19 hours claude-portable:latest

=== install.sh --check ===

=== Verifying SHTD Flow installation ===
[OK] lib/audit.js
[OK] lib/task_claims.py
[OK] lib/workflow.js
[OK] lib/get-audit.js
[OK] lib/allowed-paths.js
[OK] PreToolUse/shtd_spec-gate.js
[OK] PreToolUse/shtd_test-first-gate.js
[OK] PreToolUse/shtd_branch-gate.js
[OK] PreToolUse/shtd_pr-per-task-gate.js
[OK] PreToolUse/shtd_e2e-merge-gate.js
[OK] PreToolUse/shtd_remote-tracking-gate.js
[OK] PreToolUse/shtd_secret-scan-gate.js
[OK] PreToolUse/shtd_task-claim.js
[OK] PreToolUse/shtd_workflow-gate.js
[OK] PostToolUse/shtd_audit-logger.js
[OK] Stop/shtd_task-release.js
[OK] rules/shtd-audit-log.md

--- Setting up demo project with git remote ---
warning: You appear to have cloned an empty repository.

╔══════════════════════════════════════════╗
║ Evidence 2: branch-gate BLOCKS on main ║
╚══════════════════════════════════════════╝
Project: /tmp/demo-proj
Branch: master
specs/: EXISTS | Remote: /tmp/demo-proj.git

>>> Claude tries to Write src/app.js on master <<<

HOOK OUTPUT:
{
"decision": "block",
"reason": "[shtd] On master branch. Create a feature branch first: git checkout -b <NNN>-<feature-name>"
}

>>> BLOCKED by branch-gate: cannot edit code on master

╔══════════════════════════════════════════╗
║ Evidence 3: spec-gate BLOCKS (no specs) ║
╚══════════════════════════════════════════╝
warning: You appear to have cloned an empty repository.
Project: /tmp/demo-bare
Branch: 001-add-feature
specs/: MISSING
Tracking: origin/001-add-feature

>>> Claude tries to Write src/app.js (no specs/) <<<

HOOK OUTPUT:
{
"decision": "block",
"reason": "[shtd] No specs/ directory. Create a spec first: specs/<NNN>-<feature>/spec.md"
}

>>> BLOCKED by spec-gate: must create specs/ before writing code

╔═══════════════════════════════════════════════╗
║ Evidence 4: All gates PASS (proper setup) ║
╚═══════════════════════════════════════════════╝
Project: /tmp/demo-proj
Branch: 001-add-feature
specs/: EXISTS
Tracking: origin/001-add-feature

>>> Claude tries to Write src/app.js (all conditions met) <<<

HOOK OUTPUT: <empty — all gates passed>

>>> ALLOWED: feature branch + specs/ + remote tracking = all gates pass

╔══════════════════════════════════════════════════════╗
║ Evidence 5: remote-tracking-gate BLOCKS untracked ║
╚══════════════════════════════════════════════════════╝
Project: /tmp/demo-proj
Branch: 002-untracked-branch
specs/: EXISTS
Tracking: NONE

>>> Claude tries to Write on untracked feature branch <<<

HOOK OUTPUT:
{
"decision": "block",
"reason": "[shtd] Branch \"002-untracked-branch\" doesn't track a remote. Run: git push -u origin 002-untracked-branch"
}

>>> BLOCKED by remote-tracking-gate: must push -u before editing

╔══════════════════════════════════════════╗
║ Evidence 6: E2E Test Suite (local) ║
╚══════════════════════════════════════════╝
Test script not found locally

=============================================
Evidence capture complete
2026-04-03 20:48:12
=============================================
Binary file added reports/screenshots/evidence-terminal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added reports/shtd_flow_evidence_20260403_205805.pdf
Binary file not shown.
Loading
Loading