@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.
@@ -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: PRD saved at {path}. Score: {N}/10.
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: PRP saved at {path}. Score: {N}/10. Ready for handoff.
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: passed (attempts: N)
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: passed (attempts: N)
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
- Reply: Dev complete. {N} files changed, {M} commits. Next /sse:test.
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: Plan saved at {path}. Score: {N}/10.
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: PR opened: {url}.
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: passed (attempts: N)
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
- gates: code-style ok, conventions ok
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
- passed: N, failed: M
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: Tests {passed/failed}. {summary}.
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 PM and SSE plugin pipelines.
3
- #
4
- # Shows the current stage of the active feature. Stages, in order:
5
- # prd (product-manager plugin)
6
- # prp (product-manager plugin)
7
- # plan (staff-software-engineer plugin)
8
- # dev (staff-software-engineer plugin)
9
- # test (staff-software-engineer plugin)
10
- # pr (staff-software-engineer plugin)
11
- #
12
- # State per stage is "pending" (no file), "drafting" (file exists, no approval
13
- # marker), or "approved" (approval marker present). The bar shows the previous
14
- # approved stage plus the current stage, with the next command to run.
15
- #
16
- # A feature is "active" when any of its artifact files was modified in the last
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 · start /product-manager:run or /sse:run"
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 · start /product-manager:run or /sse:run"
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](https://img.shields.io/badge/version-3.0.0-blue.svg)](VERSION)
8
+ [![Version](https://img.shields.io/badge/version-3.1.1-blue.svg)](VERSION)
9
9
  [![Claude Code](https://img.shields.io/badge/Claude%20Code-plugin-8b5cf6.svg)](https://claude.ai/code)
10
10
  [![Plugins](https://img.shields.io/badge/plugins-2-success.svg)](#layout)
11
11
  [![Pipeline](https://img.shields.io/badge/stages-6-informational.svg)](#usage)
12
12
  [![License](https://img.shields.io/badge/license-MIT-lightgrey.svg)](LICENSE)
13
13
 
14
+ <br/>
15
+
16
+ ![harness-kit demo](demo/preview.gif)
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/space-metrics-ai/harness-kit ~/.harness-kit
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
- Pipeline order: `prd prp plan → dev → test → pr`. Each stage gets an approval marker. The status bar tracks the current one.
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
- │ │ └── status-line.sh pipeline status indicator
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 the 6-stage pipeline. Examples:
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 · start /product-manager:run or /sse:run
169
- multi-currency · prd drafting · next /product-manager:prd
170
- multi-currency · prd approved · prp pending · next /product-manager:prp
171
- multi-currency · prp approved · plan pending · next /sse:plan
172
- multi-currency · plan approved · dev pending · next /sse:dev
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
- State derives from artifact files plus the `<!-- approved: -->` marker. A feature is "active" when any of its artifacts was modified in the last hour. With no recent activity, the bar shows the idle prompt.
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.0.0
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.0.0",
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/space-metrics-ai/harness-kit.git"
9
+ "url": "git+https://github.com/Pierry/harness-kit.git"
10
10
  },
11
- "homepage": "https://github.com/space-metrics-ai/harness-kit",
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/space-metrics-ai/harness-kit ~/.harness-kit"
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 hook
69
+ # Copy harness-kit hooks (status-line + pipeline tracking)
70
70
  mkdir -p "$TARGET/.claude/hooks"
71
- cp "$SOURCE_ROOT/.claude/hooks/status-line.sh" "$TARGET/.claude/hooks/status-line.sh"
72
- chmod +x "$TARGET/.claude/hooks/status-line.sh"
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
- # Settings.json
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 the installer.
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" "$@"