@pieerry/harness-kit 3.0.0 → 3.1.1
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/.claude/commands/pipeline/continue.md +15 -0
- package/.claude/commands/pipeline/reset.md +11 -0
- package/.claude/commands/product-manager/prd.md +12 -1
- package/.claude/commands/product-manager/prp.md +12 -1
- package/.claude/commands/product-manager/run.md +12 -6
- package/.claude/commands/sse/dev.md +13 -1
- package/.claude/commands/sse/plan.md +12 -1
- package/.claude/commands/sse/pr.md +12 -1
- package/.claude/commands/sse/run.md +18 -7
- package/.claude/commands/sse/test.md +15 -1
- package/.claude/hooks/pipeline-postedit.sh +14 -0
- package/.claude/hooks/pipeline-postwrite.sh +14 -0
- package/.claude/hooks/pipeline-prompt.sh +31 -0
- package/.claude/hooks/pipeline-session-start.sh +17 -0
- package/.claude/hooks/status-line.sh +17 -19
- package/.claude/scripts/pipeline.py +352 -0
- package/.claude/scripts/stage-card.md +74 -0
- package/.claude/settings.local.json +17 -1
- package/README.md +55 -12
- package/VERSION +1 -1
- package/bin/hk.js +10 -1
- package/package.json +3 -3
- package/setup/install.sh +40 -8
- package/setup/update.sh +18 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Resume the active pipeline at the next pending stage.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Resume the active pipeline.
|
|
6
|
+
|
|
7
|
+
1. Read state: `python3 .claude/scripts/pipeline.py read`. If no `feature_id` or `current`, tell the user the pipeline is idle and suggest `/product-manager:run` or `/sse:run`.
|
|
8
|
+
2. Read the next command: `python3 .claude/scripts/pipeline.py next`. This prints the slash command to invoke.
|
|
9
|
+
3. Show the current status line: `python3 .claude/scripts/pipeline.py render`.
|
|
10
|
+
4. Invoke the next-stage command. Use the recorded `feature_id` so artifacts land under the same name.
|
|
11
|
+
5. After the stage completes (file written, approval marker applied), the post-write/post-edit hooks update state automatically. Run `/pipeline:continue` again to chain into the next stage, or stop here.
|
|
12
|
+
|
|
13
|
+
If state has `pipeline` but no `feature_id` yet (intent recorded, first stage not started), invoke the first stage in `pipeline`.
|
|
14
|
+
|
|
15
|
+
If the user wants to abandon the run instead, suggest `/pipeline:reset`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Clear the pipeline state file. Abandons the active feature run.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Reset the pipeline.
|
|
6
|
+
|
|
7
|
+
1. Confirm with the user that they want to abandon the active pipeline. Show `python3 .claude/scripts/pipeline.py render` so they see what they are dropping.
|
|
8
|
+
2. On confirmation: `python3 .claude/scripts/pipeline.py clear`.
|
|
9
|
+
3. Output files under `.claude/plugins/*/outputs/` are not deleted. Only the in-memory state is cleared.
|
|
10
|
+
|
|
11
|
+
The status bar will return to idle after reset.
|
|
@@ -4,6 +4,8 @@ description: Generate a Product Requirements Document for an team squad. Busines
|
|
|
4
4
|
|
|
5
5
|
Generate a PRD. Follow .claude/plugins/product-manager/guides/pipeline.md for retry, approval, and publish.
|
|
6
6
|
|
|
7
|
+
Print a header card before drafting and a footer card after gates run. Format: .claude/scripts/stage-card.md.
|
|
8
|
+
|
|
7
9
|
Ask once if missing: squad, problem in 1-2 sentences, customers, hypothesis, bet link, stage.
|
|
8
10
|
|
|
9
11
|
Compute feature_id = {YYYY-MM-DD}-{squad}-{slug}. Before generating, write the phase start marker:
|
|
@@ -28,4 +30,13 @@ Sensors: .claude/plugins/product-manager/sensors/prd-structure.md, .claude/plugi
|
|
|
28
30
|
|
|
29
31
|
Evals: .claude/plugins/product-manager/evals/prd-quality.md, .claude/plugins/product-manager/evals/prd-readiness.md.
|
|
30
32
|
|
|
31
|
-
After save reply
|
|
33
|
+
After save, reply with this exact shape (name the actual sensors/evals/guides that ran, do not abbreviate):
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
PRD saved at {path}.
|
|
37
|
+
sensors: prd-structure ok, prd-acceptance-criteria ok
|
|
38
|
+
eval: prd-quality {N}/10, prd-readiness {N}/10
|
|
39
|
+
guides: product-guidelines.md, prd-guidelines.md, writing-style.md, templates/prd.md
|
|
40
|
+
refs: business-info.md, squads/{squad}/context.md
|
|
41
|
+
next: /product-manager:prp
|
|
42
|
+
```
|
|
@@ -4,6 +4,8 @@ description: Generate a Product Requirements Prompt for engineering handoff. Nee
|
|
|
4
4
|
|
|
5
5
|
Generate a PRP. Follow .claude/plugins/product-manager/guides/pipeline.md for retry, approval, and publish.
|
|
6
6
|
|
|
7
|
+
Print a header card before drafting and a footer card after gates run. Format: .claude/scripts/stage-card.md.
|
|
8
|
+
|
|
7
9
|
Source PRD: if user passes a path, use it. Else pick the most recent in .claude/plugins/product-manager/outputs/prd/. None found, abort. Tell user to run /product-manager:prd first. .claude/plugins/product-manager/hooks/pre-prp-check.sh blocks if the PRD lacks the approved marker.
|
|
8
10
|
|
|
9
11
|
Compute feature_id from the source PRD filename (basename without .md). Save the PRP to .claude/plugins/product-manager/outputs/prp/{feature_id}.md so it matches.
|
|
@@ -32,4 +34,13 @@ Sensors: .claude/plugins/product-manager/sensors/prp-structure.md, .claude/plugi
|
|
|
32
34
|
|
|
33
35
|
Evals: .claude/plugins/product-manager/evals/prp-quality.md, .claude/plugins/product-manager/evals/prp-context-readiness.md.
|
|
34
36
|
|
|
35
|
-
After save reply
|
|
37
|
+
After save, reply with this exact shape (name the actual sensors/evals/guides that ran):
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
PRP saved at {path}.
|
|
41
|
+
sensors: prp-structure ok, prp-context-quality ok, prp-links ok
|
|
42
|
+
eval: prp-quality {N}/10, prp-context-readiness {N}/10
|
|
43
|
+
guides: prp-guidelines.md, writing-style.md, templates/prp.md
|
|
44
|
+
refs: prd/{feature_id}.md, {target repo paths probed}
|
|
45
|
+
next: /sse:plan (ready for handoff)
|
|
46
|
+
```
|
|
@@ -11,21 +11,27 @@ Run end to end.
|
|
|
11
11
|
|
|
12
12
|
Follow .claude/plugins/product-manager/guides/pipeline.md. Read .claude/agents/product-manager.md for inputs and rules.
|
|
13
13
|
|
|
14
|
-
Return format
|
|
14
|
+
Return format. Name every sensor, eval, and guide that ran. Generic summaries are not acceptable — list specifics so the user sees what was checked and what was loaded.
|
|
15
15
|
|
|
16
16
|
```
|
|
17
17
|
Pipeline complete.
|
|
18
18
|
|
|
19
19
|
PRD: .claude/plugins/product-manager/outputs/prd/{path}
|
|
20
|
-
sensors:
|
|
21
|
-
eval: {score}/10
|
|
20
|
+
sensors: prd-structure ok, prd-acceptance-criteria ok ({sub-checks})
|
|
21
|
+
eval: prd-quality {score}/10, prd-readiness {score}/10 (attempts: N)
|
|
22
|
+
guides: product-guidelines.md, prd-guidelines.md, writing-style.md, templates/prd.md
|
|
23
|
+
refs: business-info.md, squads/{squad}/context.md
|
|
22
24
|
|
|
23
25
|
PRP: .claude/plugins/product-manager/outputs/prp/{path}
|
|
24
|
-
sensors:
|
|
25
|
-
eval: {score}/10
|
|
26
|
+
sensors: prp-structure ok, prp-context-quality ok, prp-links ok
|
|
27
|
+
eval: prp-quality {score}/10, prp-context-readiness {score}/10 (attempts: N)
|
|
28
|
+
guides: prp-guidelines.md, writing-style.md, templates/prp.md
|
|
29
|
+
refs: prd/{feature_id}.md, target repo paths probed
|
|
26
30
|
|
|
27
|
-
Confluence: {published | skipped, reason}
|
|
31
|
+
Confluence: {published to {space-key}: {url} | skipped, reason}
|
|
28
32
|
|
|
29
33
|
Blockers:
|
|
30
34
|
- {file:line, issue, fix}
|
|
31
35
|
```
|
|
36
|
+
|
|
37
|
+
If a phase has no sensors/eval/guides/refs, omit that line rather than printing an empty one.
|
|
@@ -4,6 +4,8 @@ description: Implement the approved plan in code. Writes commits, runs gates, re
|
|
|
4
4
|
|
|
5
5
|
Implement the plan. Follow .claude/plugins/staff-software-engineer/guides/pipeline.md.
|
|
6
6
|
|
|
7
|
+
Print a header card before coding and a footer card after gates run. Format: .claude/scripts/stage-card.md.
|
|
8
|
+
|
|
7
9
|
Source plan: latest in .claude/plugins/staff-software-engineer/outputs/plan/ with approved marker. If none, abort and ask user to run /sse:plan first.
|
|
8
10
|
|
|
9
11
|
Before coding, write the phase start marker:
|
|
@@ -44,4 +46,14 @@ Append approval marker when all gates pass:
|
|
|
44
46
|
<!-- approved: {YYYY-MM-DD} -->
|
|
45
47
|
```
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
After approval, reply with this exact shape (name the actual sensors/guides that ran):
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Dev complete. branch {branch}.
|
|
53
|
+
files changed: {N}
|
|
54
|
+
commits: {M} ({short-sha}, {short-sha}, ...)
|
|
55
|
+
sensors: code-conventions ok, test-coverage ok
|
|
56
|
+
guides: coding-style.md, commit-style.md, skills/{area}/SKILL.md
|
|
57
|
+
refs: plan/{feature_id}.md, conventions/{area}.md
|
|
58
|
+
next: /sse:test
|
|
59
|
+
```
|
|
@@ -4,6 +4,8 @@ description: Generate an implementation plan from an approved PRP. Sensors and e
|
|
|
4
4
|
|
|
5
5
|
Generate a technical plan. Follow .claude/plugins/staff-software-engineer/guides/pipeline.md for retry, approval, and publish.
|
|
6
6
|
|
|
7
|
+
Print a header card before drafting and a footer card after gates run. Format: .claude/scripts/stage-card.md.
|
|
8
|
+
|
|
7
9
|
Source PRP: if user passes a path, use it. Else pick the most recent in .claude/plugins/product-manager/outputs/prp/. None found, abort. Tell user to run /product-manager:prp first.
|
|
8
10
|
|
|
9
11
|
Compute feature_id from the source PRP filename (basename without .md). Save the plan to .claude/plugins/staff-software-engineer/outputs/plan/{feature_id}.md so it matches.
|
|
@@ -30,4 +32,13 @@ Sensors: .claude/plugins/staff-software-engineer/sensors/plan-structure.md.
|
|
|
30
32
|
|
|
31
33
|
Evals: .claude/plugins/staff-software-engineer/evals/plan-quality.md.
|
|
32
34
|
|
|
33
|
-
After save reply
|
|
35
|
+
After save, reply with this exact shape (name the actual sensors/evals/guides that ran):
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Plan saved at {path}.
|
|
39
|
+
sensors: plan-structure ok ({sub-checks: problem, files, gates, scope})
|
|
40
|
+
eval: plan-quality {N}/10
|
|
41
|
+
guides: pipeline.md, coding-style.md, skills/{area}/SKILL.md
|
|
42
|
+
refs: prp/{feature_id}.md, conventions/{area}.md
|
|
43
|
+
next: /sse:dev
|
|
44
|
+
```
|
|
@@ -4,6 +4,8 @@ description: Open a Pull Request on GitHub following team conventions. Draft by
|
|
|
4
4
|
|
|
5
5
|
Open a Pull Request.
|
|
6
6
|
|
|
7
|
+
Print a header card before opening and a footer card after gh returns. Format: .claude/scripts/stage-card.md.
|
|
8
|
+
|
|
7
9
|
Prerequisites:
|
|
8
10
|
- All previous gates passed (plan approved, dev approved, test approved).
|
|
9
11
|
- Branch pushed to origin with current changes.
|
|
@@ -40,4 +42,13 @@ Append approval marker:
|
|
|
40
42
|
<!-- approved: {YYYY-MM-DD} ready-for-handoff: true -->
|
|
41
43
|
```
|
|
42
44
|
|
|
43
|
-
Reply
|
|
45
|
+
Reply with this exact shape:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
PR opened: {url}
|
|
49
|
+
title: {title}
|
|
50
|
+
draft: {yes|no}
|
|
51
|
+
guides: pr-template.md, commit-style.md
|
|
52
|
+
refs: plan/{feature_id}.md, dev/{feature_id}.md
|
|
53
|
+
next: request review (if draft, mark ready when checks pass)
|
|
54
|
+
```
|
|
@@ -12,28 +12,39 @@ Run end to end.
|
|
|
12
12
|
|
|
13
13
|
Follow .claude/plugins/staff-software-engineer/guides/pipeline.md for retry, approval markers, token accounting, and publish behavior.
|
|
14
14
|
|
|
15
|
-
Return format
|
|
15
|
+
Return format. Name every sensor, eval, and guide that ran. Generic summaries are not acceptable — list specifics so the user sees what was checked and what was loaded.
|
|
16
16
|
|
|
17
17
|
```
|
|
18
18
|
Engineering pipeline complete.
|
|
19
19
|
|
|
20
20
|
Plan: .claude/plugins/staff-software-engineer/outputs/plan/{path}
|
|
21
|
-
sensors:
|
|
22
|
-
eval: {score}/10
|
|
21
|
+
sensors: {sensor-name} ok ({sub-check, sub-check, ...}), {sensor-name} ok
|
|
22
|
+
eval: {eval-name} {score}/10 (attempts: N)
|
|
23
|
+
guides: {guide-1.md}, {guide-2.md}, skills/{area}/SKILL.md
|
|
24
|
+
refs: prp/{feature_id}.md, conventions/{area}.md
|
|
23
25
|
|
|
24
26
|
Dev: branch {branch}
|
|
25
27
|
files changed: N
|
|
26
|
-
commits: N
|
|
27
|
-
|
|
28
|
+
commits: N ({short-sha}, {short-sha}, ...)
|
|
29
|
+
sensors: code-conventions ok, test-coverage ok
|
|
30
|
+
guides: coding-style.md, commit-style.md, skills/{area}/SKILL.md
|
|
31
|
+
refs: plan/{feature_id}.md, conventions/{area}.md
|
|
28
32
|
|
|
29
33
|
Test: .claude/plugins/staff-software-engineer/outputs/test/{path}
|
|
30
|
-
|
|
34
|
+
command: {detected-test-command}
|
|
35
|
+
passed: N, failed: M
|
|
36
|
+
duration: {seconds}s
|
|
31
37
|
|
|
32
38
|
PR: {url}
|
|
39
|
+
title: {title}
|
|
33
40
|
draft: yes|no
|
|
41
|
+
guides: pr-template.md, commit-style.md
|
|
42
|
+
refs: plan/{feature_id}.md, dev/{feature_id}.md
|
|
34
43
|
|
|
35
|
-
Confluence: {published | skipped, reason}
|
|
44
|
+
Confluence: {published to {space-key}: {url} | skipped, reason}
|
|
36
45
|
|
|
37
46
|
Blockers:
|
|
38
47
|
- {file:line, issue, fix}
|
|
39
48
|
```
|
|
49
|
+
|
|
50
|
+
If a phase has no sensors/eval/guides/refs, omit that line for that phase rather than printing an empty one.
|
|
@@ -4,6 +4,8 @@ description: Run the project test suite. Reports results to .claude/plugins/staf
|
|
|
4
4
|
|
|
5
5
|
Run the test suite for the current repo.
|
|
6
6
|
|
|
7
|
+
Print a header card before running and a footer card after the suite finishes. Format: .claude/scripts/stage-card.md.
|
|
8
|
+
|
|
7
9
|
Detect the project test command (in order):
|
|
8
10
|
1. Check README.md or CONTRIBUTING.md for explicit test instructions.
|
|
9
11
|
2. Maven (pom.xml present): `./mvnw test` or `mvn test`
|
|
@@ -35,4 +37,16 @@ Append approval marker when exit code is 0:
|
|
|
35
37
|
|
|
36
38
|
If tests fail, return a blocker with the failing test names and a snippet of the failure output. Do not retry automatically; let the user decide.
|
|
37
39
|
|
|
38
|
-
Reply
|
|
40
|
+
Reply with this exact shape:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Tests {passed|failed}.
|
|
44
|
+
command: {detected-test-command}
|
|
45
|
+
passed: {N}
|
|
46
|
+
failed: {M}
|
|
47
|
+
duration: {seconds}s
|
|
48
|
+
output: {path/to/test/output.md}
|
|
49
|
+
next: /sse:pr (if passed) | fix failing tests (if failed)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
If failed, append a `failures:` block listing each failing test name with a one-line snippet from the failure output.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# PostToolUse hook for Edit. Re-detects state of tracked artifact (Edit may
|
|
3
|
+
# have added the approval marker).
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
FILE_PATH="${CLAUDE_TOOL_FILE_PATH:-}"
|
|
8
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
9
|
+
|
|
10
|
+
PIPELINE_PY=".claude/scripts/pipeline.py"
|
|
11
|
+
[ -x "$PIPELINE_PY" ] || exit 0
|
|
12
|
+
|
|
13
|
+
python3 "$PIPELINE_PY" detect-from-file "$FILE_PATH" >/dev/null 2>&1 || true
|
|
14
|
+
exit 0
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# PostToolUse hook for Write. If the written file is a tracked pipeline
|
|
3
|
+
# artifact, update state via pipeline.py detect-from-file.
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
FILE_PATH="${CLAUDE_TOOL_FILE_PATH:-}"
|
|
8
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
9
|
+
|
|
10
|
+
PIPELINE_PY=".claude/scripts/pipeline.py"
|
|
11
|
+
[ -x "$PIPELINE_PY" ] || exit 0
|
|
12
|
+
|
|
13
|
+
python3 "$PIPELINE_PY" detect-from-file "$FILE_PATH" >/dev/null 2>&1 || true
|
|
14
|
+
exit 0
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# UserPromptSubmit hook. Detects pipeline slash commands in the user's input
|
|
3
|
+
# and records intent in the pipeline state file.
|
|
4
|
+
#
|
|
5
|
+
# Reads JSON from stdin (Claude Code hook payload). Looks at the prompt text
|
|
6
|
+
# and matches known commands. Does not block submission.
|
|
7
|
+
|
|
8
|
+
set -e
|
|
9
|
+
|
|
10
|
+
PROMPT="$(python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("prompt","") or d.get("user_prompt",""))' 2>/dev/null || echo "")"
|
|
11
|
+
|
|
12
|
+
[ -z "$PROMPT" ] && exit 0
|
|
13
|
+
|
|
14
|
+
PIPELINE_PY=".claude/scripts/pipeline.py"
|
|
15
|
+
[ -x "$PIPELINE_PY" ] || exit 0
|
|
16
|
+
|
|
17
|
+
# Match leading slash commands. Order matters: more specific first.
|
|
18
|
+
case "$PROMPT" in
|
|
19
|
+
/product-manager:run*) python3 "$PIPELINE_PY" intent pm-run >/dev/null 2>&1 ;;
|
|
20
|
+
/product-manager:prd*) python3 "$PIPELINE_PY" intent pm-prd >/dev/null 2>&1 ;;
|
|
21
|
+
/product-manager:prp*) python3 "$PIPELINE_PY" intent pm-prp >/dev/null 2>&1 ;;
|
|
22
|
+
/sse:run*) python3 "$PIPELINE_PY" intent sse-run >/dev/null 2>&1 ;;
|
|
23
|
+
/sse:plan*) python3 "$PIPELINE_PY" intent sse-plan >/dev/null 2>&1 ;;
|
|
24
|
+
/sse:dev*) python3 "$PIPELINE_PY" intent sse-dev >/dev/null 2>&1 ;;
|
|
25
|
+
/sse:test*) python3 "$PIPELINE_PY" intent sse-test >/dev/null 2>&1 ;;
|
|
26
|
+
/sse:pr*) python3 "$PIPELINE_PY" intent sse-pr >/dev/null 2>&1 ;;
|
|
27
|
+
/pipeline:continue*) python3 "$PIPELINE_PY" intent pipeline-continue >/dev/null 2>&1 ;;
|
|
28
|
+
/pipeline:reset*) python3 "$PIPELINE_PY" clear >/dev/null 2>&1 ;;
|
|
29
|
+
esac
|
|
30
|
+
|
|
31
|
+
exit 0
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# SessionStart hook. If a pipeline state exists with an incomplete pipeline,
|
|
3
|
+
# print a resume hint that the user can act on.
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
PIPELINE_PY=".claude/scripts/pipeline.py"
|
|
8
|
+
STATE_FILE=".claude/.pipeline-state.json"
|
|
9
|
+
[ -x "$PIPELINE_PY" ] || exit 0
|
|
10
|
+
[ -f "$STATE_FILE" ] || exit 0
|
|
11
|
+
|
|
12
|
+
CURRENT="$(python3 "$PIPELINE_PY" next 2>/dev/null || true)"
|
|
13
|
+
[ -z "$CURRENT" ] && exit 0
|
|
14
|
+
|
|
15
|
+
LINE="$(python3 "$PIPELINE_PY" render 2>/dev/null || true)"
|
|
16
|
+
printf 'pipeline resume available: %s\nrun /pipeline:continue or /pipeline:reset to abandon\n' "$LINE"
|
|
17
|
+
exit 0
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# Status line for the
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
#
|
|
17
|
-
# hour. With no active feature, the bar shows an idle prompt.
|
|
2
|
+
# Status line for the harness-kit pipeline. Reads .claude/.pipeline-state.json
|
|
3
|
+
# (managed by .claude/scripts/pipeline.py) for live state. Falls back to a
|
|
4
|
+
# file-scan if state is absent.
|
|
5
|
+
|
|
6
|
+
PIPELINE_PY=".claude/scripts/pipeline.py"
|
|
7
|
+
|
|
8
|
+
if [ -x "$PIPELINE_PY" ] && [ -f ".claude/.pipeline-state.json" ]; then
|
|
9
|
+
OUT="$(python3 "$PIPELINE_PY" render 2>/dev/null)"
|
|
10
|
+
if [ -n "$OUT" ]; then
|
|
11
|
+
printf '%s' "$OUT"
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# --- Fallback: original file-scan logic ---
|
|
18
17
|
|
|
19
18
|
PM_DIR=".claude/plugins/product-manager"
|
|
20
19
|
SSE_DIR=".claude/plugins/staff-software-engineer"
|
|
@@ -31,7 +30,7 @@ render() {
|
|
|
31
30
|
done
|
|
32
31
|
|
|
33
32
|
if [ -z "$LATEST" ]; then
|
|
34
|
-
printf "idle ·
|
|
33
|
+
printf "idle · /product-manager:run · /sse:run · /pipeline:continue"
|
|
35
34
|
return 0
|
|
36
35
|
fi
|
|
37
36
|
|
|
@@ -39,12 +38,11 @@ render() {
|
|
|
39
38
|
MTIME=$(stat -f %m "$LATEST" 2>/dev/null || stat -c %Y "$LATEST" 2>/dev/null || echo 0)
|
|
40
39
|
AGE=$((NOW - MTIME))
|
|
41
40
|
if [ "$AGE" -ge 3600 ]; then
|
|
42
|
-
printf "idle ·
|
|
41
|
+
printf "idle · /product-manager:run · /sse:run · /pipeline:continue"
|
|
43
42
|
return 0
|
|
44
43
|
fi
|
|
45
44
|
|
|
46
45
|
FID=$(basename "$LATEST" .md)
|
|
47
|
-
# Strip leading YYYY-MM-DD-. Optional team prefix accepted (any kebab-case slug).
|
|
48
46
|
SLUG=$(echo "$FID" | sed -E 's/^[0-9]{4}-[0-9]{2}-[0-9]{2}-//')
|
|
49
47
|
|
|
50
48
|
check_state() {
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pipeline state manager for harness-kit.
|
|
3
|
+
|
|
4
|
+
Tracks the active feature, its pipeline shape (which stages will run), and
|
|
5
|
+
each stage's state (pending|drafting|approved). Stored at
|
|
6
|
+
.claude/.pipeline-state.json relative to repo root.
|
|
7
|
+
|
|
8
|
+
Subcommands:
|
|
9
|
+
init <feature_id> <stage,stage,...> create state, set pipeline shape
|
|
10
|
+
intent <kind> record last slash invocation intent
|
|
11
|
+
(sse-run, pm-run, full-run, sse-plan,
|
|
12
|
+
sse-dev, sse-test, sse-pr, pm-prd,
|
|
13
|
+
pm-prp, pipeline-continue)
|
|
14
|
+
set-feature <feature_id> attach feature_id (if not yet known)
|
|
15
|
+
set-stage <stage> <state> pending|drafting|approved
|
|
16
|
+
detect-from-file <abs_path> infer stage+state from output file
|
|
17
|
+
path; auto-init pipeline if needed
|
|
18
|
+
read print state JSON
|
|
19
|
+
render print one-line status bar
|
|
20
|
+
next print next pending stage command
|
|
21
|
+
clear remove state file
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
from datetime import datetime, timezone
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
STATE_FILE = Path(".claude/.pipeline-state.json")
|
|
31
|
+
|
|
32
|
+
PM_STAGES = ("prd", "prp")
|
|
33
|
+
SSE_STAGES = ("plan", "dev", "test", "pr")
|
|
34
|
+
ALL_STAGES = PM_STAGES + SSE_STAGES
|
|
35
|
+
|
|
36
|
+
INTENT_TO_PIPELINE = {
|
|
37
|
+
"pm-run": list(PM_STAGES),
|
|
38
|
+
"sse-run": list(SSE_STAGES),
|
|
39
|
+
"full-run": list(ALL_STAGES),
|
|
40
|
+
"pm-prd": ["prd"],
|
|
41
|
+
"pm-prp": ["prp"],
|
|
42
|
+
"sse-plan": ["plan"],
|
|
43
|
+
"sse-dev": ["dev"],
|
|
44
|
+
"sse-test": ["test"],
|
|
45
|
+
"sse-pr": ["pr"],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
STAGE_TO_COMMAND = {
|
|
49
|
+
"prd": "/product-manager:prd",
|
|
50
|
+
"prp": "/product-manager:prp",
|
|
51
|
+
"plan": "/sse:plan",
|
|
52
|
+
"dev": "/sse:dev",
|
|
53
|
+
"test": "/sse:test",
|
|
54
|
+
"pr": "/sse:pr",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
STAGE_TO_OUTPUT_DIR = {
|
|
58
|
+
"prd": ".claude/plugins/product-manager/outputs/prd",
|
|
59
|
+
"prp": ".claude/plugins/product-manager/outputs/prp",
|
|
60
|
+
"plan": ".claude/plugins/staff-software-engineer/outputs/plan",
|
|
61
|
+
"dev": ".claude/plugins/staff-software-engineer/outputs/dev",
|
|
62
|
+
"test": ".claude/plugins/staff-software-engineer/outputs/test",
|
|
63
|
+
"pr": ".claude/plugins/staff-software-engineer/outputs/pr",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def now_iso():
|
|
68
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def load_state():
|
|
72
|
+
if not STATE_FILE.exists():
|
|
73
|
+
return None
|
|
74
|
+
try:
|
|
75
|
+
return json.loads(STATE_FILE.read_text())
|
|
76
|
+
except (json.JSONDecodeError, OSError):
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def save_state(state):
|
|
81
|
+
state["updated_at"] = now_iso()
|
|
82
|
+
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
STATE_FILE.write_text(json.dumps(state, indent=2) + "\n")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def init_state(feature_id, pipeline):
|
|
87
|
+
state = {
|
|
88
|
+
"feature_id": feature_id,
|
|
89
|
+
"pipeline": pipeline,
|
|
90
|
+
"stages": {s: "pending" for s in pipeline},
|
|
91
|
+
"current": pipeline[0] if pipeline else None,
|
|
92
|
+
"intent": None,
|
|
93
|
+
"started_at": now_iso(),
|
|
94
|
+
"updated_at": now_iso(),
|
|
95
|
+
}
|
|
96
|
+
save_state(state)
|
|
97
|
+
return state
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def merge_pipeline(state, new_stages):
|
|
101
|
+
"""Extend pipeline with stages not already present, preserving order."""
|
|
102
|
+
pipeline = state.get("pipeline", [])
|
|
103
|
+
stages = state.get("stages", {})
|
|
104
|
+
changed = False
|
|
105
|
+
for s in new_stages:
|
|
106
|
+
if s not in pipeline:
|
|
107
|
+
pipeline.append(s)
|
|
108
|
+
stages[s] = "pending"
|
|
109
|
+
changed = True
|
|
110
|
+
if changed:
|
|
111
|
+
# Reorder by canonical sequence
|
|
112
|
+
pipeline.sort(key=lambda s: ALL_STAGES.index(s))
|
|
113
|
+
state["pipeline"] = pipeline
|
|
114
|
+
state["stages"] = stages
|
|
115
|
+
return state
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def recompute_current(state):
|
|
119
|
+
pipeline = state.get("pipeline", [])
|
|
120
|
+
stages = state.get("stages", {})
|
|
121
|
+
for s in pipeline:
|
|
122
|
+
if stages.get(s) != "approved":
|
|
123
|
+
state["current"] = s
|
|
124
|
+
return state
|
|
125
|
+
state["current"] = None
|
|
126
|
+
return state
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def slug(feature_id):
|
|
130
|
+
if not feature_id:
|
|
131
|
+
return ""
|
|
132
|
+
parts = feature_id.split("-")
|
|
133
|
+
if len(parts) > 3 and parts[0].isdigit() and len(parts[0]) == 4:
|
|
134
|
+
return "-".join(parts[3:])
|
|
135
|
+
return feature_id
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def render_line():
|
|
139
|
+
state = load_state()
|
|
140
|
+
if not state or not state.get("pipeline"):
|
|
141
|
+
return "idle · /product-manager:run · /sse:run · /pipeline:continue"
|
|
142
|
+
|
|
143
|
+
fid = state.get("feature_id")
|
|
144
|
+
name = slug(fid) if fid else f"starting {state.get('intent') or '?'}"
|
|
145
|
+
|
|
146
|
+
current = state.get("current")
|
|
147
|
+
pipeline = state["pipeline"]
|
|
148
|
+
stages = state.get("stages", {})
|
|
149
|
+
|
|
150
|
+
if current is None:
|
|
151
|
+
return f"{name} · complete ({'/'.join(pipeline)})"
|
|
152
|
+
|
|
153
|
+
# Find prev approved
|
|
154
|
+
prev = None
|
|
155
|
+
for s in pipeline:
|
|
156
|
+
if s == current:
|
|
157
|
+
break
|
|
158
|
+
if stages.get(s) == "approved":
|
|
159
|
+
prev = s
|
|
160
|
+
|
|
161
|
+
cur_state = stages.get(current, "pending")
|
|
162
|
+
next_cmd = STAGE_TO_COMMAND.get(current, "?")
|
|
163
|
+
|
|
164
|
+
shape = "+".join(pipeline)
|
|
165
|
+
if prev:
|
|
166
|
+
return f"{name} [{shape}] · {prev} approved · {current} {cur_state} · next {next_cmd}"
|
|
167
|
+
return f"{name} [{shape}] · {current} {cur_state} · next {next_cmd}"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def cmd_init(args):
|
|
171
|
+
if len(args) < 2:
|
|
172
|
+
print("usage: pipeline.py init <feature_id> <stage,stage,...>", file=sys.stderr)
|
|
173
|
+
return 1
|
|
174
|
+
fid = args[0]
|
|
175
|
+
pipeline = [s for s in args[1].split(",") if s in ALL_STAGES]
|
|
176
|
+
if not pipeline:
|
|
177
|
+
print("no valid stages", file=sys.stderr)
|
|
178
|
+
return 1
|
|
179
|
+
init_state(fid, pipeline)
|
|
180
|
+
return 0
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def cmd_intent(args):
|
|
184
|
+
if not args:
|
|
185
|
+
print("usage: pipeline.py intent <kind>", file=sys.stderr)
|
|
186
|
+
return 1
|
|
187
|
+
kind = args[0]
|
|
188
|
+
|
|
189
|
+
if kind == "pipeline-continue":
|
|
190
|
+
# Don't mutate pipeline; just record intent
|
|
191
|
+
state = load_state() or {}
|
|
192
|
+
state["intent"] = kind
|
|
193
|
+
if state.get("pipeline"):
|
|
194
|
+
save_state(state)
|
|
195
|
+
return 0
|
|
196
|
+
|
|
197
|
+
new_stages = INTENT_TO_PIPELINE.get(kind)
|
|
198
|
+
if not new_stages:
|
|
199
|
+
print(f"unknown intent: {kind}", file=sys.stderr)
|
|
200
|
+
return 1
|
|
201
|
+
|
|
202
|
+
state = load_state()
|
|
203
|
+
if not state:
|
|
204
|
+
state = {
|
|
205
|
+
"feature_id": None,
|
|
206
|
+
"pipeline": list(new_stages),
|
|
207
|
+
"stages": {s: "pending" for s in new_stages},
|
|
208
|
+
"current": new_stages[0],
|
|
209
|
+
"intent": kind,
|
|
210
|
+
"started_at": now_iso(),
|
|
211
|
+
"updated_at": now_iso(),
|
|
212
|
+
}
|
|
213
|
+
save_state(state)
|
|
214
|
+
return 0
|
|
215
|
+
|
|
216
|
+
# Existing state: extend pipeline with new stages
|
|
217
|
+
state["intent"] = kind
|
|
218
|
+
merge_pipeline(state, new_stages)
|
|
219
|
+
recompute_current(state)
|
|
220
|
+
save_state(state)
|
|
221
|
+
return 0
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def cmd_set_feature(args):
|
|
225
|
+
if not args:
|
|
226
|
+
print("usage: pipeline.py set-feature <feature_id>", file=sys.stderr)
|
|
227
|
+
return 1
|
|
228
|
+
state = load_state()
|
|
229
|
+
if not state:
|
|
230
|
+
print("no state file", file=sys.stderr)
|
|
231
|
+
return 1
|
|
232
|
+
state["feature_id"] = args[0]
|
|
233
|
+
save_state(state)
|
|
234
|
+
return 0
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def cmd_set_stage(args):
|
|
238
|
+
if len(args) < 2:
|
|
239
|
+
print("usage: pipeline.py set-stage <stage> <pending|drafting|approved>", file=sys.stderr)
|
|
240
|
+
return 1
|
|
241
|
+
stage, st = args[0], args[1]
|
|
242
|
+
if stage not in ALL_STAGES:
|
|
243
|
+
print(f"unknown stage: {stage}", file=sys.stderr)
|
|
244
|
+
return 1
|
|
245
|
+
if st not in ("pending", "drafting", "approved"):
|
|
246
|
+
print(f"unknown state: {st}", file=sys.stderr)
|
|
247
|
+
return 1
|
|
248
|
+
state = load_state()
|
|
249
|
+
if not state:
|
|
250
|
+
# Lazy init with this single stage
|
|
251
|
+
state = {
|
|
252
|
+
"feature_id": None,
|
|
253
|
+
"pipeline": [stage],
|
|
254
|
+
"stages": {stage: st},
|
|
255
|
+
"current": stage,
|
|
256
|
+
"intent": None,
|
|
257
|
+
"started_at": now_iso(),
|
|
258
|
+
"updated_at": now_iso(),
|
|
259
|
+
}
|
|
260
|
+
else:
|
|
261
|
+
if stage not in state["pipeline"]:
|
|
262
|
+
merge_pipeline(state, [stage])
|
|
263
|
+
state["stages"][stage] = st
|
|
264
|
+
recompute_current(state)
|
|
265
|
+
save_state(state)
|
|
266
|
+
return 0
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def cmd_detect_from_file(args):
|
|
270
|
+
if not args:
|
|
271
|
+
print("usage: pipeline.py detect-from-file <abs_path>", file=sys.stderr)
|
|
272
|
+
return 1
|
|
273
|
+
path = args[0]
|
|
274
|
+
stage = None
|
|
275
|
+
for s, d in STAGE_TO_OUTPUT_DIR.items():
|
|
276
|
+
if d in path and path.endswith(".md"):
|
|
277
|
+
stage = s
|
|
278
|
+
break
|
|
279
|
+
if not stage:
|
|
280
|
+
return 0 # not a tracked path, no-op
|
|
281
|
+
|
|
282
|
+
fid = os.path.basename(path)[:-3] # strip .md
|
|
283
|
+
|
|
284
|
+
# Determine state by file contents
|
|
285
|
+
file_state = "drafting"
|
|
286
|
+
try:
|
|
287
|
+
with open(path) as f:
|
|
288
|
+
if "<!-- approved:" in f.read():
|
|
289
|
+
file_state = "approved"
|
|
290
|
+
except OSError:
|
|
291
|
+
pass
|
|
292
|
+
|
|
293
|
+
state = load_state()
|
|
294
|
+
if not state:
|
|
295
|
+
state = init_state(fid, [stage])
|
|
296
|
+
else:
|
|
297
|
+
if not state.get("feature_id"):
|
|
298
|
+
state["feature_id"] = fid
|
|
299
|
+
merge_pipeline(state, [stage])
|
|
300
|
+
state["stages"][stage] = file_state
|
|
301
|
+
recompute_current(state)
|
|
302
|
+
save_state(state)
|
|
303
|
+
return 0
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def cmd_read(_args):
|
|
307
|
+
state = load_state()
|
|
308
|
+
print(json.dumps(state or {}, indent=2))
|
|
309
|
+
return 0
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def cmd_render(_args):
|
|
313
|
+
print(render_line())
|
|
314
|
+
return 0
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def cmd_next(_args):
|
|
318
|
+
state = load_state()
|
|
319
|
+
if not state or not state.get("current"):
|
|
320
|
+
return 1
|
|
321
|
+
print(STAGE_TO_COMMAND.get(state["current"], ""))
|
|
322
|
+
return 0
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def cmd_clear(_args):
|
|
326
|
+
if STATE_FILE.exists():
|
|
327
|
+
STATE_FILE.unlink()
|
|
328
|
+
return 0
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
COMMANDS = {
|
|
332
|
+
"init": cmd_init,
|
|
333
|
+
"intent": cmd_intent,
|
|
334
|
+
"set-feature": cmd_set_feature,
|
|
335
|
+
"set-stage": cmd_set_stage,
|
|
336
|
+
"detect-from-file": cmd_detect_from_file,
|
|
337
|
+
"read": cmd_read,
|
|
338
|
+
"render": cmd_render,
|
|
339
|
+
"next": cmd_next,
|
|
340
|
+
"clear": cmd_clear,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def main():
|
|
345
|
+
if len(sys.argv) < 2 or sys.argv[1] not in COMMANDS:
|
|
346
|
+
print(__doc__, file=sys.stderr)
|
|
347
|
+
return 2
|
|
348
|
+
return COMMANDS[sys.argv[1]](sys.argv[2:])
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
if __name__ == "__main__":
|
|
352
|
+
sys.exit(main())
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Stage card output convention
|
|
2
|
+
|
|
3
|
+
Every pipeline stage command (PRD, PRP, plan, dev, test, pr) prints two blocks
|
|
4
|
+
in its chat response. The card mirrors what the Remotion demo shows in its
|
|
5
|
+
"active artifacts" panel — guides, refs, sensors, eval, next step — but renders
|
|
6
|
+
inline in the Claude session so it works without any UI.
|
|
7
|
+
|
|
8
|
+
The card is part of the LLM's output, not a hook. Hooks update the status bar;
|
|
9
|
+
this card explains what the stage is about to do (or just did) in human-readable
|
|
10
|
+
form.
|
|
11
|
+
|
|
12
|
+
## Header card
|
|
13
|
+
|
|
14
|
+
Printed before drafting begins. Lists what will be loaded and what comes next.
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
━━━ {command} · {feature_id}{area_suffix} ━━━
|
|
18
|
+
guides: {guide-1.md}, {guide-2.md}, ...
|
|
19
|
+
refs: {ref-1.md}, {ref-2.md}, ...
|
|
20
|
+
sensors: {sensor-name}, {sensor-name}, ...
|
|
21
|
+
eval: {eval-name}
|
|
22
|
+
next: {/next:command}
|
|
23
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`{area_suffix}` is ` · {area}` for SSE stages (backend, web, mobile, devops),
|
|
27
|
+
empty otherwise.
|
|
28
|
+
|
|
29
|
+
## Footer card
|
|
30
|
+
|
|
31
|
+
Printed after the artifact is saved and gates ran. Shows pass/fail and score.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
━━━ done · {command} ━━━
|
|
35
|
+
artifact: {relative/output/path.md}
|
|
36
|
+
sensors: {pass | failed: name1, name2}
|
|
37
|
+
eval: {N}/10
|
|
38
|
+
next: {/next:command | (pipeline complete)}
|
|
39
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
━━━ /product-manager:prd · 2026-05-13-billing-multi-currency ━━━
|
|
46
|
+
guides: prd-guidelines.md, writing-style.md
|
|
47
|
+
refs: business-info.md, squads/billing/context.md
|
|
48
|
+
sensors: prd-structure, prd-acceptance-criteria
|
|
49
|
+
eval: prd-quality
|
|
50
|
+
next: /product-manager:prp
|
|
51
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
━━━ done · /product-manager:prd ━━━
|
|
56
|
+
artifact: outputs/prd/2026-05-13-billing-multi-currency.md
|
|
57
|
+
sensors: pass
|
|
58
|
+
eval: 8.6/10
|
|
59
|
+
next: /product-manager:prp
|
|
60
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
━━━ /sse:plan · 2026-05-13-billing-multi-currency · backend ━━━
|
|
65
|
+
guides: pipeline.md, coding-style.md, skills/backend/SKILL.md
|
|
66
|
+
refs: prp/2026-05-13-billing-multi-currency.md, conventions/backend.md
|
|
67
|
+
sensors: plan-structure
|
|
68
|
+
eval: plan-quality
|
|
69
|
+
next: /sse:dev
|
|
70
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Keep the card terse. List filenames only (no full paths). Skip a line that has
|
|
74
|
+
no entries.
|
|
@@ -55,7 +55,23 @@
|
|
|
55
55
|
"Bash(git -C /Users/pierryborges/Development/eng-delivery-playbook status -sb)",
|
|
56
56
|
"Bash(git -C /Users/pierryborges/Development/eng-delivery-playbook push origin main)",
|
|
57
57
|
"Bash(git remote *)",
|
|
58
|
-
"Bash(git fetch *)"
|
|
58
|
+
"Bash(git fetch *)",
|
|
59
|
+
"Bash(npx remotion *)",
|
|
60
|
+
"Bash(ffmpeg -version)",
|
|
61
|
+
"Bash(ffmpeg -y -i out/demo.mp4 -vf \"fps=12,scale=960:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=bayer:bayer_scale=4\" -loop 0 preview.gif)",
|
|
62
|
+
"Bash(ffmpeg -y -i out/demo.mp4 -vf \"fps=10,scale=720:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=96[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5\" -loop 0 preview.gif)",
|
|
63
|
+
"Bash(mkdir -p /tmp/hk-test)",
|
|
64
|
+
"Read(//tmp/**)",
|
|
65
|
+
"Bash(cd /)",
|
|
66
|
+
"Bash(rm -rf /tmp/hk-test)",
|
|
67
|
+
"Bash(npx tsc *)",
|
|
68
|
+
"Bash(git -C /Users/pierryborges/Development/harness-kit log --oneline demo/preview.gif)",
|
|
69
|
+
"Bash(git -C /Users/pierryborges/Development/harness-kit log -1 --stat demo/preview.gif)",
|
|
70
|
+
"Bash(git -C /Users/pierryborges/Development/harness-kit check-ignore demo/out/demo.mp4)",
|
|
71
|
+
"Bash(git -C /Users/pierryborges/Development/harness-kit diff --stat)",
|
|
72
|
+
"Bash(rm -rf /tmp/hk-fresh /tmp/hk-existing)",
|
|
73
|
+
"Bash(npm whoami *)",
|
|
74
|
+
"Bash(npm publish *)"
|
|
59
75
|
]
|
|
60
76
|
}
|
|
61
77
|
}
|
package/README.md
CHANGED
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
Claude Code harness for product + engineering delivery.
|
|
6
6
|
From idea to merged PR, one pipeline.
|
|
7
7
|
|
|
8
|
-
[](VERSION)
|
|
9
9
|
[](https://claude.ai/code)
|
|
10
10
|
[](#layout)
|
|
11
11
|
[](#usage)
|
|
12
12
|
[](LICENSE)
|
|
13
13
|
|
|
14
|
+
<br/>
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
<sub>100s walkthrough · install → 6 commands → PR → resume. Each command scene shows the active **guide · ref · sensor · eval**. Dedicated scenes for the dynamic status bar and `/pipeline:continue` resume flow.</sub>
|
|
19
|
+
|
|
14
20
|
</div>
|
|
15
21
|
|
|
16
22
|
---
|
|
@@ -25,6 +31,7 @@ From idea to merged PR, one pipeline.
|
|
|
25
31
|
- [Samples](#samples)
|
|
26
32
|
- [Layout](#layout)
|
|
27
33
|
- [Project conventions](#project-conventions)
|
|
34
|
+
- [Anatomy of a stage](#anatomy-of-a-stage)
|
|
28
35
|
- [Status bar](#status-bar)
|
|
29
36
|
- [Tooling](#tooling)
|
|
30
37
|
|
|
@@ -54,7 +61,7 @@ CLI subcommands:
|
|
|
54
61
|
No-npm path (if you don't want a Node dep):
|
|
55
62
|
|
|
56
63
|
```bash
|
|
57
|
-
git clone https://github.com/
|
|
64
|
+
git clone https://github.com/Pierry/harness-kit ~/.harness-kit
|
|
58
65
|
bash ~/.harness-kit/setup/install.sh
|
|
59
66
|
```
|
|
60
67
|
|
|
@@ -78,8 +85,18 @@ Pulls latest source and reinstalls. Idempotent. Version is read from the package
|
|
|
78
85
|
| `/sse:test` | Run the project test suite |
|
|
79
86
|
| `/sse:pr` | Open the draft PR |
|
|
80
87
|
| `/sse:run` | Full SSE pipeline (plan, dev, test, pr) |
|
|
88
|
+
| `/pipeline:continue` | Resume the active pipeline at its next pending stage |
|
|
89
|
+
| `/pipeline:reset` | Abandon the active pipeline run (clears state, keeps artifacts) |
|
|
90
|
+
|
|
91
|
+
Pipeline order: `prd → prp → plan → dev → test → pr`. Each stage gets an approval marker.
|
|
81
92
|
|
|
82
|
-
|
|
93
|
+
You can enter the pipeline at any stage. Three common shapes:
|
|
94
|
+
|
|
95
|
+
- `prd → prp → plan → dev → test → pr` (full PM + SSE)
|
|
96
|
+
- `prd → prp` (PM only, hand off to a separate engineering process)
|
|
97
|
+
- `plan → dev → test → pr` (SSE only, when discovery already happened elsewhere)
|
|
98
|
+
|
|
99
|
+
Status bar tracks the shape you started with. Close the session and reopen later — `/pipeline:continue` picks up at the next pending stage. `/pipeline:reset` clears the run if you decide to abandon it.
|
|
83
100
|
|
|
84
101
|
### Workflow
|
|
85
102
|
|
|
@@ -128,7 +145,14 @@ Reference artifacts ship inside the plugins:
|
|
|
128
145
|
│ ├── commands/ slash commands per plugin namespace
|
|
129
146
|
│ ├── agents/ Task-tool-invokable orchestrators
|
|
130
147
|
│ ├── hooks/
|
|
131
|
-
│ │
|
|
148
|
+
│ │ ├── status-line.sh pipeline status indicator
|
|
149
|
+
│ │ ├── pipeline-prompt.sh slash-command intent tracking
|
|
150
|
+
│ │ ├── pipeline-postwrite.sh stage-state from artifact writes
|
|
151
|
+
│ │ ├── pipeline-postedit.sh stage-state from approval marker
|
|
152
|
+
│ │ └── pipeline-session-start.sh resume hint on startup
|
|
153
|
+
│ ├── scripts/
|
|
154
|
+
│ │ └── pipeline.py state manager (state file CRUD)
|
|
155
|
+
│ ├── .pipeline-state.json active feature + per-stage state
|
|
132
156
|
│ └── settings.json hooks wiring + permissions
|
|
133
157
|
├── context-library/ reusable org/squad context
|
|
134
158
|
├── setup/
|
|
@@ -160,20 +184,39 @@ The installer scaffolds `.claude/conventions/README.md` to remind you of the con
|
|
|
160
184
|
|
|
161
185
|
---
|
|
162
186
|
|
|
187
|
+
## Anatomy of a stage
|
|
188
|
+
|
|
189
|
+
Every stage in the pipeline runs the same loop. Same four ingredients, every time:
|
|
190
|
+
|
|
191
|
+
| Ingredient | What it is | Example |
|
|
192
|
+
|-----------|------------|---------|
|
|
193
|
+
| **guide** | How to write the artifact. Style + structure rules the LLM follows. | `prd-guidelines.md`, `coding-style.md` |
|
|
194
|
+
| **ref** | Context pulled in before drafting. Org/squad data + prior artifacts. | `business-info.md`, `outputs/prp/...md`, `.claude/conventions/backend.md` |
|
|
195
|
+
| **sensor** | Must-pass structural check. Blocks the stage from being approved. | `prd-structure`, `prp-links`, `code-conventions`, `test-coverage` |
|
|
196
|
+
| **eval** | Scored quality rubric. Returns a 0–10 score with diff suggestions. | `prd-quality`, `prp-context-readiness`, `plan-quality` |
|
|
197
|
+
|
|
198
|
+
Sensors are pass/fail (deterministic, fast). Evals are scored (LLM-judged, retried until ≥ threshold or max attempts). Approval markers (`<!-- approved: -->`) gate the next stage.
|
|
199
|
+
|
|
200
|
+
The 85s demo above shows every command running with these artifacts loading live on the right panel.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
163
204
|
## Status bar
|
|
164
205
|
|
|
165
|
-
The status line follows the active feature through
|
|
206
|
+
The status line follows the active feature through whatever pipeline shape you started. It is dynamic: a `UserPromptSubmit` hook records intent the moment you type a slash command, and `PostToolUse` hooks update state as artifact files land on disk.
|
|
166
207
|
|
|
167
208
|
```
|
|
168
|
-
idle ·
|
|
169
|
-
|
|
170
|
-
multi-currency
|
|
171
|
-
multi-currency ·
|
|
172
|
-
multi-currency ·
|
|
173
|
-
multi-currency · complete
|
|
209
|
+
idle · /product-manager:run · /sse:run · /pipeline:continue
|
|
210
|
+
starting sse-run [plan+dev+test+pr] · plan pending · next /sse:plan
|
|
211
|
+
multi-currency [plan+dev+test+pr] · plan drafting · next /sse:plan
|
|
212
|
+
multi-currency [plan+dev+test+pr] · plan approved · dev pending · next /sse:dev
|
|
213
|
+
multi-currency [prd+prp+plan+dev+test+pr] · prp approved · plan drafting · next /sse:plan
|
|
214
|
+
multi-currency · complete (prd+prp+plan+dev+test+pr)
|
|
174
215
|
```
|
|
175
216
|
|
|
176
|
-
|
|
217
|
+
The bracketed list is the pipeline shape — the stages this run will execute. The shape is inferred from the slash command you invoked and extended when you chain commands (e.g. running `/sse:run` after `/product-manager:run` appends `plan+dev+test+pr` to the existing `prd+prp`).
|
|
218
|
+
|
|
219
|
+
State lives at `.claude/.pipeline-state.json`. Close the session and reopen — the SessionStart hook prints a one-line resume hint, and `/pipeline:continue` invokes the next pending stage. `/pipeline:reset` clears the file. Output artifacts under `.claude/plugins/*/outputs/` are never deleted by reset.
|
|
177
220
|
|
|
178
221
|
---
|
|
179
222
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.1.1
|
package/bin/hk.js
CHANGED
|
@@ -62,9 +62,17 @@ function cmdUninstall(target) {
|
|
|
62
62
|
'.claude/plugins/staff-software-engineer',
|
|
63
63
|
'.claude/commands/product-manager',
|
|
64
64
|
'.claude/commands/sse',
|
|
65
|
+
'.claude/commands/pipeline',
|
|
65
66
|
'.claude/agents/product-manager.md',
|
|
66
67
|
'.claude/agents/staff-software-engineer.md',
|
|
67
68
|
'.claude/hooks/status-line.sh',
|
|
69
|
+
'.claude/hooks/pipeline-prompt.sh',
|
|
70
|
+
'.claude/hooks/pipeline-postwrite.sh',
|
|
71
|
+
'.claude/hooks/pipeline-postedit.sh',
|
|
72
|
+
'.claude/hooks/pipeline-session-start.sh',
|
|
73
|
+
'.claude/scripts/pipeline.py',
|
|
74
|
+
'.claude/scripts/stage-card.md',
|
|
75
|
+
'.claude/.pipeline-state.json',
|
|
68
76
|
'.claude/settings.json',
|
|
69
77
|
'.claude/.hk-version',
|
|
70
78
|
];
|
|
@@ -113,7 +121,8 @@ usage:
|
|
|
113
121
|
|
|
114
122
|
after install, restart Claude Code and use:
|
|
115
123
|
/product-manager:prd | :prp | :run
|
|
116
|
-
/sse:plan | :dev | :test | :pr | :run
|
|
124
|
+
/sse:plan | :dev | :test | :pr | :run
|
|
125
|
+
/pipeline:continue | :reset`);
|
|
117
126
|
}
|
|
118
127
|
|
|
119
128
|
function main() {
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pieerry/harness-kit",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Claude Code harness for product + engineering delivery. From idea to merged PR, one pipeline.",
|
|
5
5
|
"author": "Space Metrics AI",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/
|
|
9
|
+
"url": "git+https://github.com/Pierry/harness-kit.git"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://github.com/
|
|
11
|
+
"homepage": "https://github.com/Pierry/harness-kit",
|
|
12
12
|
"keywords": [
|
|
13
13
|
"claude-code",
|
|
14
14
|
"claude",
|
package/setup/install.sh
CHANGED
|
@@ -16,7 +16,7 @@ VERSION="$(cat "$SOURCE_ROOT/VERSION" 2>/dev/null || echo "0.0.0")"
|
|
|
16
16
|
|
|
17
17
|
if [ ! -d "$SOURCE_ROOT/.claude/plugins/product-manager" ]; then
|
|
18
18
|
echo "missing plugins at $SOURCE_ROOT/.claude/plugins/"
|
|
19
|
-
echo "clone the repo first, then re-run: git clone https://github.com/
|
|
19
|
+
echo "clone the repo first, then re-run: git clone https://github.com/Pierry/harness-kit ~/.harness-kit"
|
|
20
20
|
exit 1
|
|
21
21
|
fi
|
|
22
22
|
|
|
@@ -51,7 +51,7 @@ done
|
|
|
51
51
|
|
|
52
52
|
# Copy slash commands and Task-invokable agents
|
|
53
53
|
mkdir -p "$TARGET/.claude/commands" "$TARGET/.claude/agents"
|
|
54
|
-
for ns in product-manager sse; do
|
|
54
|
+
for ns in product-manager sse pipeline; do
|
|
55
55
|
rm -rf "$TARGET/.claude/commands/$ns"
|
|
56
56
|
cp -R "$SOURCE_ROOT/.claude/commands/$ns" "$TARGET/.claude/commands/$ns"
|
|
57
57
|
done
|
|
@@ -66,12 +66,27 @@ for plugin in product-manager staff-software-engineer; do
|
|
|
66
66
|
"$TARGET/.claude/plugins/$plugin/.claude-plugin"
|
|
67
67
|
done
|
|
68
68
|
|
|
69
|
-
# Copy status-line
|
|
69
|
+
# Copy harness-kit hooks (status-line + pipeline tracking)
|
|
70
70
|
mkdir -p "$TARGET/.claude/hooks"
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
for h in status-line.sh pipeline-prompt.sh pipeline-postwrite.sh pipeline-postedit.sh pipeline-session-start.sh; do
|
|
72
|
+
cp "$SOURCE_ROOT/.claude/hooks/$h" "$TARGET/.claude/hooks/$h"
|
|
73
|
+
chmod +x "$TARGET/.claude/hooks/$h"
|
|
74
|
+
done
|
|
73
75
|
|
|
74
|
-
#
|
|
76
|
+
# Copy harness-kit scripts (pipeline state manager + stage-card convention)
|
|
77
|
+
mkdir -p "$TARGET/.claude/scripts"
|
|
78
|
+
cp "$SOURCE_ROOT/.claude/scripts/pipeline.py" "$TARGET/.claude/scripts/pipeline.py"
|
|
79
|
+
cp "$SOURCE_ROOT/.claude/scripts/stage-card.md" "$TARGET/.claude/scripts/stage-card.md"
|
|
80
|
+
chmod +x "$TARGET/.claude/scripts/pipeline.py"
|
|
81
|
+
|
|
82
|
+
# Settings.json (back up existing if content differs)
|
|
83
|
+
if [ -f "$TARGET/.claude/settings.json" ]; then
|
|
84
|
+
STAMP="$(date +%Y%m%d-%H%M%S)"
|
|
85
|
+
BACKUP="$TARGET/.claude/settings.json.bak.$STAMP"
|
|
86
|
+
cp "$TARGET/.claude/settings.json" "$BACKUP"
|
|
87
|
+
echo "backed up existing settings.json -> .claude/settings.json.bak.$STAMP"
|
|
88
|
+
echo " if you customized permissions or hooks, merge them back from the backup."
|
|
89
|
+
fi
|
|
75
90
|
cat > "$TARGET/.claude/settings.json" <<'EOF'
|
|
76
91
|
{
|
|
77
92
|
"permissions": {
|
|
@@ -93,6 +108,20 @@ cat > "$TARGET/.claude/settings.json" <<'EOF'
|
|
|
93
108
|
]
|
|
94
109
|
},
|
|
95
110
|
"hooks": {
|
|
111
|
+
"SessionStart": [
|
|
112
|
+
{
|
|
113
|
+
"hooks": [
|
|
114
|
+
{ "type": "command", "command": "[ -x .claude/hooks/pipeline-session-start.sh ] && bash .claude/hooks/pipeline-session-start.sh; exit 0" }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"UserPromptSubmit": [
|
|
119
|
+
{
|
|
120
|
+
"hooks": [
|
|
121
|
+
{ "type": "command", "command": "[ -x .claude/hooks/pipeline-prompt.sh ] && bash .claude/hooks/pipeline-prompt.sh; exit 0" }
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
],
|
|
96
125
|
"PreToolUse": [
|
|
97
126
|
{
|
|
98
127
|
"matcher": "Write",
|
|
@@ -107,7 +136,8 @@ cat > "$TARGET/.claude/settings.json" <<'EOF'
|
|
|
107
136
|
"hooks": [
|
|
108
137
|
{ "type": "command", "command": "[ -x .claude/plugins/product-manager/hooks/post-write-prd.sh ] && bash .claude/plugins/product-manager/hooks/post-write-prd.sh; exit 0" },
|
|
109
138
|
{ "type": "command", "command": "[ -x .claude/plugins/product-manager/hooks/post-write-prp.sh ] && bash .claude/plugins/product-manager/hooks/post-write-prp.sh; exit 0" },
|
|
110
|
-
{ "type": "command", "command": "[ -x .claude/plugins/staff-software-engineer/hooks/post-write-plan.sh ] && bash .claude/plugins/staff-software-engineer/hooks/post-write-plan.sh; exit 0" }
|
|
139
|
+
{ "type": "command", "command": "[ -x .claude/plugins/staff-software-engineer/hooks/post-write-plan.sh ] && bash .claude/plugins/staff-software-engineer/hooks/post-write-plan.sh; exit 0" },
|
|
140
|
+
{ "type": "command", "command": "[ -x .claude/hooks/pipeline-postwrite.sh ] && bash .claude/hooks/pipeline-postwrite.sh; exit 0" }
|
|
111
141
|
]
|
|
112
142
|
},
|
|
113
143
|
{
|
|
@@ -115,7 +145,8 @@ cat > "$TARGET/.claude/settings.json" <<'EOF'
|
|
|
115
145
|
"hooks": [
|
|
116
146
|
{ "type": "command", "command": "[ -x .claude/plugins/product-manager/hooks/post-eval-prd.sh ] && bash .claude/plugins/product-manager/hooks/post-eval-prd.sh; exit 0" },
|
|
117
147
|
{ "type": "command", "command": "[ -x .claude/plugins/product-manager/hooks/post-eval-prp.sh ] && bash .claude/plugins/product-manager/hooks/post-eval-prp.sh; exit 0" },
|
|
118
|
-
{ "type": "command", "command": "[ -x .claude/plugins/staff-software-engineer/hooks/post-eval-plan.sh ] && bash .claude/plugins/staff-software-engineer/hooks/post-eval-plan.sh; exit 0" }
|
|
148
|
+
{ "type": "command", "command": "[ -x .claude/plugins/staff-software-engineer/hooks/post-eval-plan.sh ] && bash .claude/plugins/staff-software-engineer/hooks/post-eval-plan.sh; exit 0" },
|
|
149
|
+
{ "type": "command", "command": "[ -x .claude/hooks/pipeline-postedit.sh ] && bash .claude/hooks/pipeline-postedit.sh; exit 0" }
|
|
119
150
|
]
|
|
120
151
|
}
|
|
121
152
|
]
|
|
@@ -152,3 +183,4 @@ echo "$VERSION" > "$TARGET/.claude/.hk-version"
|
|
|
152
183
|
echo "done. restart Claude Code to load."
|
|
153
184
|
echo " /product-manager:prd | :prp | :run"
|
|
154
185
|
echo " /sse:plan | :dev | :test | :pr | :run"
|
|
186
|
+
echo " /pipeline:continue | :reset"
|
package/setup/update.sh
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# harness-kit updater. Pulls latest source, then re-runs
|
|
2
|
+
# harness-kit updater. Pulls latest source (git installs only), then re-runs
|
|
3
|
+
# the installer. For npm installs, you must upgrade the package first:
|
|
4
|
+
# npm i -g @pieerry/harness-kit@latest
|
|
3
5
|
#
|
|
4
6
|
# Usage:
|
|
5
7
|
# bash /path/to/harness-kit/setup/update.sh [target-dir]
|
|
@@ -8,10 +10,25 @@ set -e
|
|
|
8
10
|
|
|
9
11
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
12
|
SOURCE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
|
+
SOURCE_VERSION="$(cat "$SOURCE_ROOT/VERSION" 2>/dev/null || echo "0.0.0")"
|
|
11
14
|
|
|
12
15
|
if [ -d "$SOURCE_ROOT/.git" ]; then
|
|
16
|
+
echo "git install detected at $SOURCE_ROOT"
|
|
13
17
|
echo "fetching latest source..."
|
|
14
18
|
git -C "$SOURCE_ROOT" pull --ff-only
|
|
19
|
+
NEW_VERSION="$(cat "$SOURCE_ROOT/VERSION" 2>/dev/null || echo "0.0.0")"
|
|
20
|
+
if [ "$SOURCE_VERSION" != "$NEW_VERSION" ]; then
|
|
21
|
+
echo "source: v$SOURCE_VERSION → v$NEW_VERSION"
|
|
22
|
+
fi
|
|
23
|
+
else
|
|
24
|
+
echo "npm install detected at $SOURCE_ROOT (no .git)"
|
|
25
|
+
echo "source version bundled in this package: v$SOURCE_VERSION"
|
|
26
|
+
echo ""
|
|
27
|
+
echo "to fetch a newer version, upgrade the npm package first:"
|
|
28
|
+
echo " npm i -g @pieerry/harness-kit@latest"
|
|
29
|
+
echo "then rerun: hk update"
|
|
30
|
+
echo ""
|
|
31
|
+
echo "continuing with reinstall of v$SOURCE_VERSION..."
|
|
15
32
|
fi
|
|
16
33
|
|
|
17
34
|
exec bash "$SCRIPT_DIR/install.sh" "$@"
|