autonomous-sdlc-harness 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +0 -0
  3. package/dist/commands/docs.js +2 -1
  4. package/dist/commands/docs.js.map +1 -1
  5. package/dist/commands/doctor.js +27 -6
  6. package/dist/commands/doctor.js.map +1 -1
  7. package/dist/commands/init.js +102 -20
  8. package/dist/commands/init.js.map +1 -1
  9. package/dist/config/check.js +11 -4
  10. package/dist/config/check.js.map +1 -1
  11. package/dist/config/model.js +25 -0
  12. package/dist/config/model.js.map +1 -1
  13. package/dist/core/git.js +29 -0
  14. package/dist/core/git.js.map +1 -1
  15. package/dist/core/paths.js +22 -2
  16. package/dist/core/paths.js.map +1 -1
  17. package/dist/core/prompt.js +6 -2
  18. package/dist/core/prompt.js.map +1 -1
  19. package/dist/core/writer.js +2 -0
  20. package/dist/core/writer.js.map +1 -1
  21. package/dist/doctor/checks.js +343 -28
  22. package/dist/doctor/checks.js.map +1 -1
  23. package/dist/generators/githubWorkflows.js +66 -0
  24. package/dist/generators/githubWorkflows.js.map +1 -0
  25. package/dist/generators/notifications.js +105 -19
  26. package/dist/generators/notifications.js.map +1 -1
  27. package/dist/generators/outerLoopScripts.js +17 -3
  28. package/dist/generators/outerLoopScripts.js.map +1 -1
  29. package/dist/generators/permissionProfile.js +101 -12
  30. package/dist/generators/permissionProfile.js.map +1 -1
  31. package/dist/generators/repoRoot.js +18 -2
  32. package/dist/generators/repoRoot.js.map +1 -1
  33. package/dist/generators/stateDir.js +9 -3
  34. package/dist/generators/stateDir.js.map +1 -1
  35. package/dist/machine/paths.js +4 -1
  36. package/dist/machine/paths.js.map +1 -1
  37. package/dist/remote/githubActions.js +86 -0
  38. package/dist/remote/githubActions.js.map +1 -0
  39. package/dist/retrieval/queryLog.js +6 -4
  40. package/dist/retrieval/queryLog.js.map +1 -1
  41. package/dist/retrieval/runtime.js +12 -22
  42. package/dist/retrieval/runtime.js.map +1 -1
  43. package/dist/retrieval/search.js +21 -10
  44. package/dist/retrieval/search.js.map +1 -1
  45. package/dist/retrieval/server.js +1 -1
  46. package/dist/retrieval/setup.js +2 -1
  47. package/dist/retrieval/setup.js.map +1 -1
  48. package/package.json +1 -1
  49. package/templates/README.md +3 -2
  50. package/templates/claude/context/conventions.md +1 -1
  51. package/templates/claude/context/layer.md +1 -1
  52. package/templates/claude/push-notify.env.example +7 -2
  53. package/templates/claude/settings.autonomous.json +1 -1
  54. package/templates/github/workflows/harness-resume.yml +124 -0
  55. package/templates/github/workflows/harness-run.yml +446 -0
  56. package/templates/repo/gitignore +5 -0
  57. package/templates/scripts/README.md +1 -1
  58. package/templates/scripts/autonomous-watcher.sh +1141 -143
  59. package/templates/scripts/flow-walker.sh +629 -0
  60. package/templates/scripts/flows/task_plan_writing.graph.json +192 -0
  61. package/templates/scripts/lib/flow-walker-gates.sh +165 -0
  62. package/templates/scripts/lib/harness-run-lib.sh +433 -18
  63. package/templates/scripts/remote-run.sh +1785 -0
  64. package/templates/scripts/restart-watcher.sh +21 -1
  65. package/templates/scripts/run-test-suite.sh +182 -0
  66. package/templates/scripts/scratch-run.sh +2 -1
  67. package/templates/state-dir/README-root.md +1 -1
  68. package/templates/state-dir/autonomous_logs/README.md +1 -1
  69. package/templates/state-dir/improvement_observations/README.md +2 -0
  70. package/templates/state-dir/scratch/README.md +1 -1
  71. package/templates/state-dir/test_fix_plan_reviews/README.md +9 -0
  72. package/templates/state-dir/test_fix_plans/README.md +9 -0
  73. package/templates/state-dir/test_fix_point_reviews/README.md +9 -0
  74. package/templates/state-dir/test_run_logs/README.md +11 -0
@@ -0,0 +1,192 @@
1
+ {
2
+ "formatVersion": 1,
3
+ "flow": "task_plan_writing",
4
+ "description": "The routing of ${CLAUDE_PLUGIN_ROOT}/instructions/task_plan_writing_instructions_core.md as data: the task-plan loop, the UI-test-plan loop and convergence, with the values the walker resolves (counters, path templates, heartbeats, findings folders, skip gates, cap summaries). The dispatch blocks each agent receives are not here; they stay in that core. Copied verbatim into the adopter's scripts directory, never rendered.",
5
+ "start": "plan_writer",
6
+ "entries": ["plan_writer", "business_parity_review", "ui_writer", "ui_review", "convergence"],
7
+ "counters": {
8
+ "iteration": { "cap": 5 }
9
+ },
10
+ "paths": {
11
+ "story_index": "{state_dir}/story_plans/{branch}_story_plan.md",
12
+ "ui_test_index": "{state_dir}/ui_test_plans/{branch}_ui_test_plan.md"
13
+ },
14
+ "reports": ["business_parity_review", "architecture_review", "plan_review", "ui_test"],
15
+ "nodes": {
16
+ "plan_writer": {
17
+ "kind": "dispatch",
18
+ "agent": "task-plan-writer",
19
+ "heartbeat": "[plan-write · iter {iteration}] → task-plan-writer",
20
+ "prompts": ["initial", "revision"],
21
+ "outcomes": {
22
+ "returned": { "to": "business_parity_review" },
23
+ "questions": { "binding": "<ask>", "resume": "plan_writer" },
24
+ "error": { "binding": "<escalate>", "reason": "error" },
25
+ "blocker": { "binding": "<escalate>", "reason": "blocker" }
26
+ }
27
+ },
28
+ "business_parity_review": {
29
+ "kind": "dispatch",
30
+ "agent": "business-parity-reviewer",
31
+ "heartbeat": "[plan-write · parity · iter {iteration}] → business-parity-reviewer",
32
+ "findingsFolder": "{state_dir}/business_parity_reviews/{branch}/",
33
+ "skip": [
34
+ {
35
+ "construct": "passedByExclusion",
36
+ "runModeId": "parity",
37
+ "to": "architecture_review",
38
+ "report": { "business_parity_review": "passed-by-exclusion" }
39
+ },
40
+ {
41
+ "construct": "skipped",
42
+ "phase": "parity",
43
+ "to": "architecture_review",
44
+ "report": { "business_parity_review": "skipped" }
45
+ }
46
+ ],
47
+ "outcomes": {
48
+ "PASS": {
49
+ "to": "architecture_review",
50
+ "report": { "business_parity_review": "passed" }
51
+ },
52
+ "FAIL": {
53
+ "to": "plan_writer",
54
+ "increment": "iteration",
55
+ "prompt": "revision",
56
+ "onCap": {
57
+ "binding": "<escalate>",
58
+ "reason": "cap",
59
+ "summary": "the plan loop reached its {cap}-revision cap; the business-parity gate was open when it fired",
60
+ "roundCounts": ["business_parity_review", "architecture_review", "plan_review"],
61
+ "evidence": "story_index"
62
+ }
63
+ },
64
+ "error": { "binding": "<escalate>", "reason": "error" },
65
+ "blocker": { "binding": "<escalate>", "reason": "blocker" }
66
+ }
67
+ },
68
+ "architecture_review": {
69
+ "kind": "dispatch",
70
+ "agent": "architecture-reviewer",
71
+ "heartbeat": "[plan-write · arch · iter {iteration}] → architecture-reviewer",
72
+ "findingsFolder": "{state_dir}/architecture_reviews/{branch}/",
73
+ "skip": [
74
+ {
75
+ "construct": "passedByExclusion",
76
+ "runModeId": "architecture",
77
+ "to": "plan_review",
78
+ "report": { "architecture_review": "passed-by-exclusion" }
79
+ }
80
+ ],
81
+ "outcomes": {
82
+ "PASS": {
83
+ "to": "plan_review",
84
+ "report": { "architecture_review": "passed" }
85
+ },
86
+ "FAIL": {
87
+ "to": "plan_writer",
88
+ "increment": "iteration",
89
+ "prompt": "revision",
90
+ "onCap": {
91
+ "binding": "<escalate>",
92
+ "reason": "cap",
93
+ "summary": "the plan loop reached its {cap}-revision cap; the architecture gate was open when it fired",
94
+ "roundCounts": ["business_parity_review", "architecture_review", "plan_review"],
95
+ "evidence": "story_index"
96
+ }
97
+ },
98
+ "error": { "binding": "<escalate>", "reason": "error" },
99
+ "blocker": { "binding": "<escalate>", "reason": "blocker" }
100
+ }
101
+ },
102
+ "plan_review": {
103
+ "kind": "dispatch",
104
+ "agent": "task-plan-reviewer",
105
+ "heartbeat": "[plan-write · iter {iteration}] → task-plan-reviewer",
106
+ "findingsFolder": "{state_dir}/task_plan_reviews/{branch}/",
107
+ "outcomes": {
108
+ "PASS": {
109
+ "to": "ui_writer",
110
+ "reset": "iteration",
111
+ "ledger": "P1",
112
+ "report": { "plan_review": "passed" }
113
+ },
114
+ "FAIL": {
115
+ "to": "plan_writer",
116
+ "increment": "iteration",
117
+ "prompt": "revision",
118
+ "onCap": {
119
+ "binding": "<escalate>",
120
+ "reason": "cap",
121
+ "summary": "the plan loop reached its {cap}-revision cap; the plan-review gate was open when it fired",
122
+ "roundCounts": ["business_parity_review", "architecture_review", "plan_review"],
123
+ "evidence": "story_index"
124
+ }
125
+ },
126
+ "error": { "binding": "<escalate>", "reason": "error" },
127
+ "blocker": { "binding": "<escalate>", "reason": "blocker" }
128
+ }
129
+ },
130
+ "ui_writer": {
131
+ "kind": "dispatch",
132
+ "agent": "ui-tests-plan-writer",
133
+ "heartbeat": "[ui-test-write · iter {iteration}] → ui-tests-plan-writer",
134
+ "prompts": ["initial", "revision"],
135
+ "skip": [
136
+ {
137
+ "construct": "skipped",
138
+ "phase": "qa",
139
+ "to": "convergence",
140
+ "report": { "ui_test": "qa-phase-off" }
141
+ },
142
+ {
143
+ "construct": "passedByExclusion",
144
+ "runModeId": "qa",
145
+ "to": "convergence",
146
+ "report": { "ui_test": "run-mode-skipped" }
147
+ }
148
+ ],
149
+ "outcomes": {
150
+ "returned": { "to": "ui_review" },
151
+ "no_ui": {
152
+ "to": "convergence",
153
+ "ledger": "P2",
154
+ "report": { "ui_test": "no_ui" }
155
+ },
156
+ "questions": { "binding": "<ask>", "resume": "ui_writer" },
157
+ "error": { "binding": "<escalate>", "reason": "error" },
158
+ "blocker": { "binding": "<escalate>", "reason": "blocker" }
159
+ }
160
+ },
161
+ "ui_review": {
162
+ "kind": "dispatch",
163
+ "agent": "ui-tests-plan-reviewer",
164
+ "heartbeat": "[ui-test-write · iter {iteration}] → ui-tests-plan-reviewer",
165
+ "findingsFolder": "{state_dir}/ui_test_plan_reviews/{branch}/",
166
+ "outcomes": {
167
+ "PASS": {
168
+ "to": "convergence",
169
+ "ledger": "P2",
170
+ "report": { "ui_test": "passed" }
171
+ },
172
+ "FAIL": {
173
+ "to": "ui_writer",
174
+ "increment": "iteration",
175
+ "prompt": "revision",
176
+ "onCap": {
177
+ "binding": "<escalate>",
178
+ "reason": "cap",
179
+ "summary": "{cap} UI-test-plan-review iterations did not converge",
180
+ "evidence": "ui_test_index"
181
+ }
182
+ },
183
+ "error": { "binding": "<escalate>", "reason": "error" },
184
+ "blocker": { "binding": "<escalate>", "reason": "blocker" }
185
+ }
186
+ },
187
+ "convergence": {
188
+ "kind": "terminal",
189
+ "binding": "<terminal_handoff>"
190
+ }
191
+ }
192
+ }
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env bash
2
+ # flow-walker-gates.sh — the mechanical answers the flow walker needs at a
3
+ # gate, each defined once: the next free `review_<n>` index in a findings
4
+ # folder, that folder's round count, the run mode's skipped ids, whether a
5
+ # `phases.*` flag is on, and the escalation evidence a story index or UI-test
6
+ # index carries.
7
+ #
8
+ # IT ANSWERS AND NEVER DECIDES. Which node comes next, whether a counter hit
9
+ # its cap and what the walker prints are the walker's. Nothing here writes
10
+ # anything — not even the walker's state file — and the findings-folder
11
+ # functions list names only and open no file.
12
+ #
13
+ # SOURCED, NEVER EXECUTED, AND IT SOURCES NOTHING. It sets no shell option, so
14
+ # the sourcing script's shell is left exactly as found. `fw_phase_on` calls
15
+ # `hr_phase_enabled`, so the caller sources `lib/harness-run-lib.sh` first.
16
+ #
17
+ # BASH 3.2 AND JQ 1.5 ARE THE FLOORS, as in `lib/harness-run-lib.sh`: no
18
+ # associative array, no array-reading builtin, no `${x,,}` / `${x^^}`, no `local -n`.
19
+ # Nothing here calls `jq` directly.
20
+ #
21
+ # NAMING AND STATUSES. Every function is prefixed `fw_` and touches no variable
22
+ # outside a `local`. Statuses follow `lib/harness-run-lib.sh`: 0 yes / answered,
23
+ # 1 no / not set, 2 unresolvable — and a caller that treats 2 as 1 acts on a
24
+ # record it could not read.
25
+ #
26
+ # REPRO — reproduce any answer by hand, against a throwaway fixture:
27
+ #
28
+ # root=$(mktemp -d); git -C "$root" init -q
29
+ # printf '{"defaultBranch":"main","stateDir":"sdlc-harness/","phases":{"qa":true}}' > "$root/harness.config.json"
30
+ # . <repo>/<scripts_dir>/lib/harness-run-lib.sh
31
+ # . <repo>/<scripts_dir>/lib/flow-walker-gates.sh
32
+ #
33
+ # review index mkdir "$root/f"; : > "$root/f/review_0.md"; : > "$root/f/review_3.md"
34
+ # fw_next_review_index "$root/f" -> 4 (a gap is never back-filled)
35
+ # fw_round_count "$root/f" -> 2
36
+ # fw_next_review_index "$root/nope"; fw_round_count "$root/nope" -> 0, 0
37
+ # run mode fw_run_mode_skipped "$root" sdlc-harness/ feat_x none -> none
38
+ # fw_run_mode_skipped "$root" sdlc-harness/ feat_x ''; echo $? -> 1, prints nothing
39
+ # mkdir -p "$root/sdlc-harness/flow_progress"
40
+ # printf '## Run mode\n- skipped: parity, qa\n' > "$root/sdlc-harness/flow_progress/feat_x_progress.md"
41
+ # fw_run_mode_skipped "$root" sdlc-harness/ feat_x none -> parity, qa (the ledger wins)
42
+ # fw_id_in_list qa 'parity, qa'; echo $? -> 0
43
+ # fw_id_in_list qa none; echo $? -> 1
44
+ # phase flag fw_phase_on "$root" qa; echo $? -> 0
45
+ # fw_phase_on "$root" docs; echo $? -> 1
46
+ # fw_phase_on "$root" bogus; echo $? -> 2
47
+ # index evidence printf '## Rejected findings\n- **3** (rejected round 1; rebutted round 2 — call stands) — r\n- **5** (rejected round 2) — r\n## Scope register\n' > "$root/i.md"
48
+ # fw_index_evidence "$root/i.md"
49
+ # -> evidence: <root>/i.md ## Rejected findings
50
+ # evidence.open: - **5** (rejected round 2) — r
51
+ # evidence: <root>/i.md ## Scope register
52
+ # fw_index_evidence "$root/absent.md" -> prints nothing
53
+
54
+ # Print one past the highest <n> among entries named exactly `review_<n>.md`,
55
+ # or 0 when the folder is absent or holds none. `ls`, not a glob, so a caller
56
+ # running with pathname expansion off gets the same answer.
57
+ fw_next_review_index() {
58
+ local folder="${1-}" name n max=-1
59
+ [ -d "$folder" ] || { printf '0\n'; return 0; }
60
+ ls -1 -- "$folder" 2>/dev/null | {
61
+ while IFS= read -r name; do
62
+ case "$name" in
63
+ review_*.md) ;;
64
+ *) continue ;;
65
+ esac
66
+ n="${name#review_}"
67
+ n="${n%.md}"
68
+ case "$n" in
69
+ ''|*[!0-9]*) continue ;;
70
+ esac
71
+ n=$((10#$n))
72
+ [ "$n" -gt "$max" ] && max="$n"
73
+ done
74
+ printf '%s\n' "$((max + 1))"
75
+ }
76
+ return 0
77
+ }
78
+
79
+ # Print how many entries start with `review_` — the core's
80
+ # `ls <findings_folder> | grep -c '^review_'`, not the highest index.
81
+ fw_round_count() {
82
+ local folder="${1-}" name count=0
83
+ [ -d "$folder" ] || { printf '0\n'; return 0; }
84
+ ls -1 -- "$folder" 2>/dev/null | {
85
+ while IFS= read -r name; do
86
+ case "$name" in
87
+ review_*) count=$((count + 1)) ;;
88
+ esac
89
+ done
90
+ printf '%s\n' "$count"
91
+ }
92
+ return 0
93
+ }
94
+
95
+ # Print the run mode's skipped ids (comma-separated, or `none`). A ledger at
96
+ # `<state_dir>/flow_progress/<branch>_progress.md` is the record and its
97
+ # `## Run mode` block's `- skipped: ` line wins over <flag_value>
98
+ # (`run_mode_instructions.md` → `## The durable record`, **Tie-break**).
99
+ # Returns 2 when the ledger exists but carries no such line; returns 1 when
100
+ # there is no ledger and <flag_value> is empty — there is no default.
101
+ fw_run_mode_skipped() {
102
+ local root="${1-}" state_dir="${2-}" branch="${3-}" flag="${4-}" ledger value
103
+ state_dir="${state_dir%/}"
104
+ ledger="$root/$state_dir/flow_progress/${branch}_progress.md"
105
+ if [ -f "$ledger" ]; then
106
+ value=$(awk '
107
+ /^## / { in_rm = ($0 ~ /^## Run mode[ \t\r]*$/); next }
108
+ in_rm && /^- skipped: / { v = substr($0, 12); sub(/[ \t\r]+$/, "", v); print v; exit }
109
+ ' "$ledger" 2>/dev/null)
110
+ [ -n "$value" ] || return 2
111
+ printf '%s\n' "$value"
112
+ return 0
113
+ fi
114
+ [ -n "$flag" ] || return 1
115
+ printf '%s\n' "$flag"
116
+ return 0
117
+ }
118
+
119
+ # Exit 0 when <id> is a member of <csv>, 1 otherwise. `none` is the empty set;
120
+ # whitespace around each member is ignored.
121
+ fw_id_in_list() {
122
+ local id="${1-}" rest="${2-}" item
123
+ [ -n "$id" ] || return 1
124
+ while [ -n "$rest" ]; do
125
+ item="${rest%%,*}"
126
+ case "$rest" in
127
+ *,*) rest="${rest#*,}" ;;
128
+ *) rest='' ;;
129
+ esac
130
+ item="${item#"${item%%[![:space:]]*}"}"
131
+ item="${item%"${item##*[![:space:]]}"}"
132
+ [ "$item" = none ] && continue
133
+ [ "$item" = "$id" ] && return 0
134
+ done
135
+ return 1
136
+ }
137
+
138
+ # Pass `hr_phase_enabled`'s 0 / 1 / 2 through unchanged.
139
+ fw_phase_on() {
140
+ hr_phase_enabled "${1-}" "${2-}"
141
+ }
142
+
143
+ # Print the escalation evidence an index carries, or nothing when the file is
144
+ # absent. `call stands` marks a rebutted entry (`task-plan-writer.md` →
145
+ # `## Revision mode`); every other `- ` entry under `## Rejected findings` is
146
+ # open. Which `## Scope register` rows are in dispute is judgement and stays
147
+ # with the orchestrator — only the heading's presence is reported.
148
+ fw_index_evidence() {
149
+ local index="${1-}"
150
+ [ -f "$index" ] || return 0
151
+ FW_INDEX="$index" awk '
152
+ /^## / {
153
+ h = $0; sub(/[ \t\r]+$/, "", h); in_rj = 0
154
+ if (h == "## Rejected findings") {
155
+ if (!rj) print "evidence: " ENVIRON["FW_INDEX"] " ## Rejected findings"
156
+ rj = 1; in_rj = 1
157
+ } else if (h == "## Scope register") {
158
+ sr = 1
159
+ }
160
+ next
161
+ }
162
+ in_rj && /^- / { if (index($0, "call stands") == 0) print "evidence.open: " $0 }
163
+ END { if (sr) print "evidence: " ENVIRON["FW_INDEX"] " ## Scope register" }
164
+ ' "$index"
165
+ }