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.
- package/README.md +2 -2
- package/dist/cli.js +0 -0
- package/dist/commands/docs.js +2 -1
- package/dist/commands/docs.js.map +1 -1
- package/dist/commands/doctor.js +27 -6
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +102 -20
- package/dist/commands/init.js.map +1 -1
- package/dist/config/check.js +11 -4
- package/dist/config/check.js.map +1 -1
- package/dist/config/model.js +25 -0
- package/dist/config/model.js.map +1 -1
- package/dist/core/git.js +29 -0
- package/dist/core/git.js.map +1 -1
- package/dist/core/paths.js +22 -2
- package/dist/core/paths.js.map +1 -1
- package/dist/core/prompt.js +6 -2
- package/dist/core/prompt.js.map +1 -1
- package/dist/core/writer.js +2 -0
- package/dist/core/writer.js.map +1 -1
- package/dist/doctor/checks.js +343 -28
- package/dist/doctor/checks.js.map +1 -1
- package/dist/generators/githubWorkflows.js +66 -0
- package/dist/generators/githubWorkflows.js.map +1 -0
- package/dist/generators/notifications.js +105 -19
- package/dist/generators/notifications.js.map +1 -1
- package/dist/generators/outerLoopScripts.js +17 -3
- package/dist/generators/outerLoopScripts.js.map +1 -1
- package/dist/generators/permissionProfile.js +101 -12
- package/dist/generators/permissionProfile.js.map +1 -1
- package/dist/generators/repoRoot.js +18 -2
- package/dist/generators/repoRoot.js.map +1 -1
- package/dist/generators/stateDir.js +9 -3
- package/dist/generators/stateDir.js.map +1 -1
- package/dist/machine/paths.js +4 -1
- package/dist/machine/paths.js.map +1 -1
- package/dist/remote/githubActions.js +86 -0
- package/dist/remote/githubActions.js.map +1 -0
- package/dist/retrieval/queryLog.js +6 -4
- package/dist/retrieval/queryLog.js.map +1 -1
- package/dist/retrieval/runtime.js +12 -22
- package/dist/retrieval/runtime.js.map +1 -1
- package/dist/retrieval/search.js +21 -10
- package/dist/retrieval/search.js.map +1 -1
- package/dist/retrieval/server.js +1 -1
- package/dist/retrieval/setup.js +2 -1
- package/dist/retrieval/setup.js.map +1 -1
- package/package.json +1 -1
- package/templates/README.md +3 -2
- package/templates/claude/context/conventions.md +1 -1
- package/templates/claude/context/layer.md +1 -1
- package/templates/claude/push-notify.env.example +7 -2
- package/templates/claude/settings.autonomous.json +1 -1
- package/templates/github/workflows/harness-resume.yml +124 -0
- package/templates/github/workflows/harness-run.yml +446 -0
- package/templates/repo/gitignore +5 -0
- package/templates/scripts/README.md +1 -1
- package/templates/scripts/autonomous-watcher.sh +1141 -143
- package/templates/scripts/flow-walker.sh +629 -0
- package/templates/scripts/flows/task_plan_writing.graph.json +192 -0
- package/templates/scripts/lib/flow-walker-gates.sh +165 -0
- package/templates/scripts/lib/harness-run-lib.sh +433 -18
- package/templates/scripts/remote-run.sh +1785 -0
- package/templates/scripts/restart-watcher.sh +21 -1
- package/templates/scripts/run-test-suite.sh +182 -0
- package/templates/scripts/scratch-run.sh +2 -1
- package/templates/state-dir/README-root.md +1 -1
- package/templates/state-dir/autonomous_logs/README.md +1 -1
- package/templates/state-dir/improvement_observations/README.md +2 -0
- package/templates/state-dir/scratch/README.md +1 -1
- package/templates/state-dir/test_fix_plan_reviews/README.md +9 -0
- package/templates/state-dir/test_fix_plans/README.md +9 -0
- package/templates/state-dir/test_fix_point_reviews/README.md +9 -0
- 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
|
+
}
|