@manhquy/flow-skill 0.1.0 → 0.2.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 +11 -8
- package/README_VN.md +7 -4
- package/package.json +1 -1
- package/skills/flow/SKILL.md +1 -1
- package/skills/flow/harness/GAP-MATRIX-0.1.17.md +4 -3
- package/skills/flow/harness/README.md +29 -2
- package/skills/flow/harness/_db.py +175 -6
- package/skills/flow/harness/flow_harness.py +165 -9
- package/skills/flow/harness/graph_executor.py +1205 -0
- package/skills/flow/harness/graph_ids.py +35 -0
- package/skills/flow/harness/graph_predicates.py +48 -0
- package/skills/flow/harness/pins/flow-topology.sha256 +1 -0
- package/skills/flow/harness/schema/014-graph-executor.sql +67 -0
- package/skills/flow/references/auto-run.md +29 -2
- package/skills/flow/references/command-dispatch.md +1 -0
- package/skills/flow/references/concierge.md +1 -0
- package/skills/flow/references/flow-topology.json +146 -0
- package/skills/flow/runner/flow.sh +216 -5
- package/skills-manifest.json +8 -2
|
@@ -136,9 +136,17 @@ stage_name_at() { # $1 = index -> "NN-name"
|
|
|
136
136
|
current_stage_idx() { # highest CONTIGUOUS-from-00 stage index, or -1 if 00 missing
|
|
137
137
|
# Contiguous (not just highest-existing) so a manually-dropped future stage file
|
|
138
138
|
# cannot make 'next' report a false PLANNING COMPLETE while earlier stages are missing.
|
|
139
|
+
# A DEBT-SKIPPED stage satisfies contiguity without a file: cmd_skip deliberately
|
|
140
|
+
# scaffolds only the successor, so treating its absence as a gap would pin the index
|
|
141
|
+
# at the pre-skip stage forever (05 never scaffolded, planning_complete never true,
|
|
142
|
+
# '/flow card' deadlocked). Skips stay governed by cmd_skip alone - this only READS
|
|
143
|
+
# flow/.skipped, exactly like planning_complete does.
|
|
139
144
|
local i=0 s idx=-1
|
|
140
145
|
for s in $STAGES; do
|
|
141
|
-
if [ -f "$FLOW_DIR/$s.md" ]; then idx=$i
|
|
146
|
+
if [ -f "$FLOW_DIR/$s.md" ]; then idx=$i
|
|
147
|
+
elif stage_skipped "$s"; then :
|
|
148
|
+
else break
|
|
149
|
+
fi
|
|
142
150
|
i=$((i + 1))
|
|
143
151
|
done
|
|
144
152
|
echo "$idx"
|
|
@@ -187,7 +195,11 @@ _python() {
|
|
|
187
195
|
# harness_call_checked always returns the real harness exit (for callers that care).
|
|
188
196
|
_harness_run() {
|
|
189
197
|
# runs harness; prints stderr (masked lightly); echoes nothing; returns real rc
|
|
190
|
-
# usage: _harness_run <args...> sets _HARNESS_LAST_RC
|
|
198
|
+
# usage: _harness_run [--emit-stdout] <args...> sets _HARNESS_LAST_RC
|
|
199
|
+
# --emit-stdout: pass harness stdout THROUGH (value-carrying graph verbs) and
|
|
200
|
+
# suppress the warn line for the semantic rc 3 (no-next/paused, not a failure).
|
|
201
|
+
local _emit=0
|
|
202
|
+
[ "${1:-}" = "--emit-stdout" ] && { _emit=1; shift; }
|
|
191
203
|
_HARNESS_LAST_RC=0
|
|
192
204
|
[ -n "${FLOW_HARNESS_DISABLE:-}" ] && return 0
|
|
193
205
|
[ -f "$HARNESS_PY" ] || return 0
|
|
@@ -198,6 +210,8 @@ _harness_run() {
|
|
|
198
210
|
# capture rc without flipping the caller's set -e state
|
|
199
211
|
out="$(FLOW_PROJECT_ROOT="$ROOT" "$py" "$HARNESS_PY" "$@" 2>"$errf")" || rc=$?
|
|
200
212
|
_HARNESS_LAST_RC=$rc
|
|
213
|
+
[ "$_emit" = 1 ] && [ -n "$out" ] && printf '%s\n' "$out"
|
|
214
|
+
if [ "$_emit" = 1 ] && [ "$rc" -eq 3 ]; then rm -f "$errf" 2>/dev/null || true; return "$rc"; fi
|
|
201
215
|
if [ "$rc" -ne 0 ]; then
|
|
202
216
|
local errline summary
|
|
203
217
|
errline="$(tr -d '\r' <"$errf" 2>/dev/null | head -n 3 | tr '\n' ' ')"
|
|
@@ -234,6 +248,33 @@ harness_call_checked() {
|
|
|
234
248
|
_harness_run "$@"
|
|
235
249
|
}
|
|
236
250
|
|
|
251
|
+
# Wrap a command in a wall-clock bound when `timeout` exists (GNU coreutils / Git Bash);
|
|
252
|
+
# a missing timeout degrades to running unbounded, exactly as before.
|
|
253
|
+
_with_timeout() { # $1 = seconds, rest = command
|
|
254
|
+
local secs="$1"; shift
|
|
255
|
+
if command -v timeout >/dev/null 2>&1; then timeout "$secs" "$@"; else "$@"; fi
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
harness_capture_checked() {
|
|
259
|
+
# VALUE-carrying variant for graph verbs: emits harness stdout and returns the
|
|
260
|
+
# real exit code, via _harness_run --emit-stdout so the availability triad,
|
|
261
|
+
# stderr redaction, and rc plumbing live in ONE place. Fail-closed contract:
|
|
262
|
+
# 0=value, 3=no-next/paused, 4=harness unavailable (DISABLE / file / python
|
|
263
|
+
# missing - the soft rc-0 ambiguity _harness_run keeps for durable writes is
|
|
264
|
+
# exactly what value calls must NOT inherit), 1/2=failure.
|
|
265
|
+
# No consumers yet; cmd_next delegates through this when FLOW_GRAPH_EXECUTOR lands.
|
|
266
|
+
[ -n "${FLOW_HARNESS_DISABLE:-}" ] && return 4
|
|
267
|
+
[ -f "$HARNESS_PY" ] || return 4
|
|
268
|
+
local py; py="$(_python)"; [ -n "$py" ] || return 4
|
|
269
|
+
# 20s: generous for a contended BEGIN IMMEDIATE (busy_timeout is 5s) yet bounded.
|
|
270
|
+
# rc 124 = timed out -> treated as "harness unavailable" by callers, never a hang.
|
|
271
|
+
local rc=0
|
|
272
|
+
_with_timeout "${FLOW_HARNESS_TIMEOUT:-20}" env FLOW_PROJECT_ROOT="$ROOT" \
|
|
273
|
+
"$(_python)" "$HARNESS_PY" "$@" || rc=$?
|
|
274
|
+
[ "$rc" = "124" ] && { printf 'flow-harness: call timed out (%ss)\n' "${FLOW_HARNESS_TIMEOUT:-20}" >&2; return 4; }
|
|
275
|
+
return "$rc"
|
|
276
|
+
}
|
|
277
|
+
|
|
237
278
|
# When STRICT=fail, surface durable failure from card/check call sites (D5).
|
|
238
279
|
_harness_or_fail() {
|
|
239
280
|
harness_call "$@" || {
|
|
@@ -258,6 +299,65 @@ harness_emit() { # run a harness subcommand and ECHO its stdout (best-effort; no
|
|
|
258
299
|
FLOW_PROJECT_ROOT="$ROOT" "$(_python)" "$HARNESS_PY" "$@" 2>/dev/null || true
|
|
259
300
|
}
|
|
260
301
|
|
|
302
|
+
# ---------- graph-executor recording contract (dark until FLOW_GRAPH_EXECUTOR=1) ----------
|
|
303
|
+
# The auto-run loop is agent-driven prose (auto-run.md), so boundaries are recorded by
|
|
304
|
+
# instrumenting verbs the agent ALREADY calls: workspace add -> card-dispatch,
|
|
305
|
+
# check -> card-build/review evidence, card done -> card-verify-live, workspace remove ->
|
|
306
|
+
# merge proof (executor-computed) + worktree telemetry ingest. Everything here is
|
|
307
|
+
# best-effort: a recording failure must never break the mechanical engine.
|
|
308
|
+
_graph_on() { [ "${FLOW_GRAPH_EXECUTOR:-}" = "1" ] && harness_available; }
|
|
309
|
+
|
|
310
|
+
# Execution IDENTITY must live beside the DB, and the DB path is the harness's own
|
|
311
|
+
# decision (worktree translation, monorepo sub-projects, --separate-git-dir). Re-deriving
|
|
312
|
+
# it here with a shell heuristic drifted on exactly those shapes and re-split the journal,
|
|
313
|
+
# so ASK the resolver instead of guessing: `graph root` prints dirname(default_db_path()).
|
|
314
|
+
_graph_exec_id() { # [--no-create] -> echo the project's running auto_run execution id
|
|
315
|
+
# The whole selection is ONE atomic harness call: the DB row IS the pin, and SQLite's
|
|
316
|
+
# write transaction is the arbitration point. A bash pin file needed a claim dir,
|
|
317
|
+
# corpse recovery, TOCTOU-free reclaim AND a liveness check to approximate this - and
|
|
318
|
+
# each of those was a defect (stolen claims forked the journal; an existence-only
|
|
319
|
+
# check adopted abandoned executions and blackholed every later record).
|
|
320
|
+
_graph_on || return 1
|
|
321
|
+
local id
|
|
322
|
+
if [ "${1:-}" = "--no-create" ]; then
|
|
323
|
+
id="$(harness_capture_checked graph session --no-create 2>/dev/null | tr -d '\r\n')" || return 1
|
|
324
|
+
else
|
|
325
|
+
id="$(harness_capture_checked graph session 2>/dev/null | tr -d '\r\n')" || return 1
|
|
326
|
+
fi
|
|
327
|
+
[ -n "$id" ] || return 1
|
|
328
|
+
printf '%s' "$id"
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
_graph_planning_id() { # echo the planning execution id (kind=planning, ns='')
|
|
332
|
+
_graph_on || return 1
|
|
333
|
+
local id
|
|
334
|
+
id="$(harness_capture_checked graph session --kind planning 2>/dev/null | tr -d '\r\n')" || return 1
|
|
335
|
+
[ -n "$id" ] || return 1
|
|
336
|
+
printf '%s' "$id"
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
_graph_stage_record() { # $1 = stage just passed -> record the planning boundary (ns='')
|
|
340
|
+
_graph_on || return 0
|
|
341
|
+
local stage="$1" eid
|
|
342
|
+
eid="$(_graph_planning_id)" || return 0
|
|
343
|
+
local node="stage-${stage%%-*}" # 03-prd -> stage-03 (topology node id)
|
|
344
|
+
harness_capture_checked graph record --execution "$eid" --ns "" --node "$node" \
|
|
345
|
+
--gate-exit 0 --gate-cmd "gate $stage" >/dev/null 2>&1 || true
|
|
346
|
+
return 0
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
_graph_record() { # $1=card-id (REQUIRED for card-scoped nodes) $2=node ; rest passed through
|
|
350
|
+
_graph_on || return 0
|
|
351
|
+
local card="$1" node="$2"; shift 2
|
|
352
|
+
# A card-scoped node with no card would land in the ROOT (stage) namespace and derail
|
|
353
|
+
# the planning walk - workspace add/remove are legitimately card-less for spikes.
|
|
354
|
+
[ -n "$card" ] || return 0
|
|
355
|
+
local eid; eid="$(_graph_exec_id)" || return 0
|
|
356
|
+
harness_capture_checked graph record --execution "$eid" --ns "card:$card" --node "$node" "$@" \
|
|
357
|
+
>/dev/null 2>&1 || true
|
|
358
|
+
return 0
|
|
359
|
+
}
|
|
360
|
+
|
|
261
361
|
cmd_harness() {
|
|
262
362
|
# passthrough to the full durable-layer CLI (visible output + real exit code)
|
|
263
363
|
if [ ! -f "$HARNESS_PY" ]; then echo "harness not installed at $HARNESS_PY"; return 1; fi
|
|
@@ -876,6 +976,10 @@ cmd_next() {
|
|
|
876
976
|
if [ "$idx" -ge "$LAST_STAGE_IDX" ]; then
|
|
877
977
|
FLOW_LOG_STAGE_FROM="$cur"
|
|
878
978
|
if planning_complete; then
|
|
979
|
+
# Record ONLY when the plan actually advanced. stage-05 is the terminal planning
|
|
980
|
+
# node, so recording it while an earlier stage is still blocked would make
|
|
981
|
+
# `graph next` answer "complete" for a project flow.sh itself calls BLOCKED.
|
|
982
|
+
_graph_stage_record "$cur"
|
|
879
983
|
echo "PASS: stage $cur gate clean. Planning is COMPLETE."
|
|
880
984
|
echo "All planning stages passed (or were debt-skipped). Run '/flow card' to create build cards."
|
|
881
985
|
if prd_declares_fr; then
|
|
@@ -889,14 +993,39 @@ cmd_next() {
|
|
|
889
993
|
return 0
|
|
890
994
|
fi
|
|
891
995
|
local nxt; nxt="$(stage_name_at "$((idx + 1))")"
|
|
996
|
+
# Walk PAST every debt-skipped successor instead of stopping on one: cmd_skip already
|
|
997
|
+
# recorded the accepted exposure, so re-creating its file would resurrect a stage the
|
|
998
|
+
# DEBT line says is knowingly missing - but simply refusing to scaffold would wedge the
|
|
999
|
+
# project forever when the skipped stage's own successor is also absent (a failed
|
|
1000
|
+
# scaffold in cmd_skip, or an operator deleting the file). Advance to the first stage
|
|
1001
|
+
# that is neither present nor skipped; if that runs off the end, planning is complete.
|
|
1002
|
+
local nidx=$((idx + 1)) skipped_over=""
|
|
1003
|
+
while [ "$nidx" -le "$LAST_STAGE_IDX" ] && stage_skipped "$nxt"; do
|
|
1004
|
+
skipped_over="$skipped_over $nxt"
|
|
1005
|
+
nidx=$((nidx + 1))
|
|
1006
|
+
[ "$nidx" -gt "$LAST_STAGE_IDX" ] && break
|
|
1007
|
+
nxt="$(stage_name_at "$nidx")"
|
|
1008
|
+
done
|
|
1009
|
+
if [ "$nidx" -gt "$LAST_STAGE_IDX" ]; then
|
|
1010
|
+
echo "PASS: stage $cur gate clean. Remaining stages are debt-skipped ($(printf '%s' "${skipped_over# }"))."
|
|
1011
|
+
if planning_complete; then
|
|
1012
|
+
_graph_stage_record "$cur"
|
|
1013
|
+
echo "Planning is COMPLETE. Run '/flow card' to create build cards."
|
|
1014
|
+
else
|
|
1015
|
+
echo "An earlier stage is still BLOCKED (and not debt-skipped) - run '/flow' to see which."
|
|
1016
|
+
fi
|
|
1017
|
+
return 0
|
|
1018
|
+
fi
|
|
892
1019
|
if [ -f "$FLOW_DIR/$nxt.md" ]; then
|
|
893
|
-
echo "PASS: stage $cur gate clean. Stage $
|
|
1020
|
+
echo "PASS: stage $cur gate clean. Stage $nidx ($nxt.md) already exists - not overwritten."
|
|
1021
|
+
_graph_stage_record "$cur"
|
|
894
1022
|
return 0
|
|
895
1023
|
fi
|
|
896
1024
|
cp "$TEMPLATE_DIR/$nxt.md" "$FLOW_DIR/$nxt.md"
|
|
897
1025
|
FLOW_LOG_STAGE_FROM="$cur"; FLOW_LOG_STAGE_TO="$nxt"
|
|
898
|
-
echo "PASS: stage $cur gate clean -> unlocked stage $
|
|
1026
|
+
echo "PASS: stage $cur gate clean -> unlocked stage $nidx (flow/$nxt.md)"
|
|
899
1027
|
gate_durable_hook "$cur"
|
|
1028
|
+
_graph_stage_record "$cur"
|
|
900
1029
|
echo " tip: '/flow recall' surfaces prior debt/retro/friction before you fill this stage."
|
|
901
1030
|
return 0
|
|
902
1031
|
}
|
|
@@ -1045,6 +1174,10 @@ cmd_card_done() { # CLI-owned flip to done, gated by the SAME done-rules as '/fl
|
|
|
1045
1174
|
if ! _set_card_status "$file" done; then echo "FAIL: could not write status for $id"; return 1; fi
|
|
1046
1175
|
if cmd_check "$id"; then # cmd_check enforces evidence/verify AND records the durable done-trace
|
|
1047
1176
|
_inflight_clear "$id"
|
|
1177
|
+
# card-verify-live: the done boundary is the EVIDENCE gate passing, not the merge
|
|
1178
|
+
# (merge != shipped) - dependents unblock on card status, matching cmd_ready.
|
|
1179
|
+
# The nested cmd_check already recorded card-review; this advances past it.
|
|
1180
|
+
_graph_record "$id" card-verify-live --gate-exit 0 --card-status done
|
|
1048
1181
|
return 0
|
|
1049
1182
|
fi
|
|
1050
1183
|
_set_card_status "$file" "${orig:-todo}" # NOT done — never leave a hollow 'done'
|
|
@@ -1161,6 +1294,10 @@ cmd_check() {
|
|
|
1161
1294
|
|
|
1162
1295
|
if [ "$found" -eq 0 ]; then
|
|
1163
1296
|
echo "PASS: $id is valid (status: $st)."
|
|
1297
|
+
# card-review boundary: the gate verdict IS the evidence the executor records
|
|
1298
|
+
# (green here is what unblocks card-merge in the topology).
|
|
1299
|
+
# Typed flags, never a bash-built JSON literal: $st is free text from the card file.
|
|
1300
|
+
_graph_record "$id" card-review --gate-exit 0 --gate-cmd "check $id" --card-status "$st"
|
|
1164
1301
|
case "$st" in
|
|
1165
1302
|
todo) _harness_or_fail story update --id "$id" --status in_progress || return 1 ;;
|
|
1166
1303
|
done)
|
|
@@ -1184,6 +1321,7 @@ cmd_check() {
|
|
|
1184
1321
|
fi
|
|
1185
1322
|
FLOW_LAST_GATE_FAIL="fill:$(grep -c '\[FILL' "$file" 2>/dev/null || echo 0),unchecked:$(grep -cE '^[[:space:]]*- \[ \]' "$file" 2>/dev/null || echo 0)"
|
|
1186
1323
|
echo "FAIL: $id has gate violations (above)."
|
|
1324
|
+
_graph_record "$id" card-review --gate-exit 1 --gate-cmd "check $id" --card-status "$st"
|
|
1187
1325
|
return 1
|
|
1188
1326
|
}
|
|
1189
1327
|
|
|
@@ -1262,6 +1400,34 @@ cmd_skip() {
|
|
|
1262
1400
|
return 0
|
|
1263
1401
|
}
|
|
1264
1402
|
|
|
1403
|
+
cmd_gate() {
|
|
1404
|
+
# Read-only mechanical gate scan (no unlock, no template copy, no durable writes):
|
|
1405
|
+
# the ONLY flow.sh verb the graph executor may run from topology cmd position
|
|
1406
|
+
# (`check` mutates story/trace on pass and stays Must-ask). Exit 0 clean / 1 findings.
|
|
1407
|
+
local target="${1:-}"
|
|
1408
|
+
if [ "$target" = "--card" ]; then
|
|
1409
|
+
local card="${2:-}" f
|
|
1410
|
+
if [ -z "$card" ]; then echo "usage: /flow gate --card C-NNN"; return 2; fi
|
|
1411
|
+
f="$(resolve_card_file "$card" 2>/dev/null)"
|
|
1412
|
+
if [ -z "$f" ]; then echo "usage: /flow gate --card C-NNN"; return 2; fi
|
|
1413
|
+
# A nonexistent card is NOT a gate verdict: reporting it as exit 1 would journal a
|
|
1414
|
+
# typo'd id as a genuine red review and burn a two-strikes repair budget.
|
|
1415
|
+
if [ ! -f "$f" ]; then echo "FAIL: card not found for '$card' (looked for $f)"; return 2; fi
|
|
1416
|
+
echo "GATE card ${card}:"
|
|
1417
|
+
if scan_gate "$f"; then echo " clean"; return 0; fi
|
|
1418
|
+
return 1
|
|
1419
|
+
fi
|
|
1420
|
+
local known=0 s
|
|
1421
|
+
for s in $STAGES; do [ "$s" = "$target" ] && known=1; done
|
|
1422
|
+
if [ "$known" -eq 0 ]; then
|
|
1423
|
+
echo "usage: /flow gate <stage: $STAGES> | /flow gate --card C-NNN"
|
|
1424
|
+
return 2
|
|
1425
|
+
fi
|
|
1426
|
+
echo "GATE stage ${target}:"
|
|
1427
|
+
if scan_gate "$FLOW_DIR/$target.md"; then echo " clean"; return 0; fi
|
|
1428
|
+
return 1
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1265
1431
|
cmd_retro() {
|
|
1266
1432
|
[ -f "$RETRO_FILE" ] || { [ -f "$LAW_DIR/RETRO.md" ] && cp "$LAW_DIR/RETRO.md" "$RETRO_FILE"; }
|
|
1267
1433
|
echo "flow retro - answer these 3, then append ONE honest line to RETRO.md:"
|
|
@@ -2010,6 +2176,10 @@ _ws_add() {
|
|
|
2010
2176
|
done
|
|
2011
2177
|
fi
|
|
2012
2178
|
echo "PASS: created worktree for '$branch' (vendor ${vendor:-?}, port-offset $port) -> $wt"
|
|
2179
|
+
# card-dispatch boundary. NOTE: no FLOW_PROJECT_ROOT export in the enter block — the DB
|
|
2180
|
+
# resolver already maps a linked worktree to the main DB (_db.default_db_path), and
|
|
2181
|
+
# exporting the project root would hijack CARDS_DIR/locks/DEBT for every call in there.
|
|
2182
|
+
_graph_record "$card" card-dispatch --branch "$branch" --worktree "$wt" --vendor "$vendor"
|
|
2013
2183
|
_ws_print_enter "$branch" "$wt" "$vendor" "$port"
|
|
2014
2184
|
echo " (tracked in .flow/workspaces.jsonl; see 'workspace list' / 'workspace doctor')"
|
|
2015
2185
|
return 0
|
|
@@ -2092,6 +2262,20 @@ _ws_remove() {
|
|
|
2092
2262
|
'/^worktree /{p=substr($0,10)} /^branch /{b=$2; sub(/^refs\/heads\//,"",b); if(b==want) print p}')"
|
|
2093
2263
|
[ -n "$wt" ] || wt="$(_ws_get "$rec" worktree_path)"
|
|
2094
2264
|
if [ -z "$wt" ]; then echo "workspace: no worktree found for branch '$branch'."; return 1; fi
|
|
2265
|
+
# Telemetry ingest must happen BEFORE the tree is gone (the sink lives inside it); the
|
|
2266
|
+
# merge boundary is recorded only AFTER a successful removal, so a refused (dirty-tree)
|
|
2267
|
+
# remove never journals a merge for a card still under active work.
|
|
2268
|
+
local _gcard _gcreated
|
|
2269
|
+
_gcard="$(_ws_get "$rec" card_id)"; _gcreated="$(_ws_get_num "$rec" created_at)"
|
|
2270
|
+
if _graph_on && [ -f "$wt/.flow/events.jsonl" ]; then
|
|
2271
|
+
# Lifecycle-unique key: worktree paths are recycled, so a bare-path cursor would
|
|
2272
|
+
# swallow the next lifecycle's lines. Prefix = destination sink (usage/prune group it).
|
|
2273
|
+
# Path and tag stay in separate arguments so the path is still a path when it crosses
|
|
2274
|
+
# into python — pre-joining them with '#' defeats Git-Bash path translation on Windows.
|
|
2275
|
+
harness_capture_checked rollup --src "$wt/.flow/events.jsonl" \
|
|
2276
|
+
--src-key-path "$LOG_DIR/events.jsonl" \
|
|
2277
|
+
--src-key-tag "$branch#${_gcreated:-0}" >/dev/null 2>&1 || true
|
|
2278
|
+
fi
|
|
2095
2279
|
if [ "$force" -eq 1 ]; then
|
|
2096
2280
|
rmout="$(git -C "$ROOT" worktree remove --force "$wt" 2>&1)"; rc=$?
|
|
2097
2281
|
else
|
|
@@ -2102,6 +2286,9 @@ _ws_remove() {
|
|
|
2102
2286
|
echo "FAIL: git worktree remove refused (commit/clean the tree, or re-run with --force)."
|
|
2103
2287
|
return 1
|
|
2104
2288
|
fi
|
|
2289
|
+
# The executor computes the ancestry itself - this verb has no merge knowledge (it only
|
|
2290
|
+
# removes a tree) - and records card-abandon when the branch was never merged.
|
|
2291
|
+
_graph_record "$_gcard" card-merge --merge --branch "$branch"
|
|
2105
2292
|
# tombstone ONLY on clean success (git is truth; a failed remove changed nothing)
|
|
2106
2293
|
local vendor card port
|
|
2107
2294
|
vendor="$(_ws_get "$rec" vendor)"; card="$(_ws_get "$rec" card_id)"; port="$(_ws_get_num "$rec" port_offset)"
|
|
@@ -2175,6 +2362,24 @@ _ws_doctor() {
|
|
|
2175
2362
|
if [ -n "$pdup" ]; then echo " [!] duplicate port_offset across active workspaces (advisory): $(printf '%s ' $pdup)"; warn=1; fi
|
|
2176
2363
|
local maxw="${FLOW_WORKSPACE_MAX:-4}"
|
|
2177
2364
|
if [ "$nact" -gt "$maxw" ]; then echo " [!] $nact active workspaces exceeds FLOW_WORKSPACE_MAX=$maxw (realistic solo ceiling is 3-4; advisory)."; warn=1; fi
|
|
2365
|
+
# Execution-aware (graph executor): a PAUSED execution is waiting on an operator, so
|
|
2366
|
+
# its worktree must not be reconciled away. git + card files stay the authority; the
|
|
2367
|
+
# journal is a derived record, so this only ever WARNS.
|
|
2368
|
+
if _graph_on; then
|
|
2369
|
+
local eid gstat opens
|
|
2370
|
+
# --no-create: doctor is advisory and read-only; it must never mint an execution.
|
|
2371
|
+
eid="$(_graph_exec_id --no-create 2>/dev/null || true)"
|
|
2372
|
+
if [ -n "$eid" ]; then
|
|
2373
|
+
gstat="$(harness_capture_checked graph status --execution "$eid" 2>/dev/null)"
|
|
2374
|
+
opens="$(printf '%s' "$gstat" | tr ',' '\n' | grep -c '"interrupt_id"' || true)"
|
|
2375
|
+
opens="$(printf '%s' "${opens:-0}" | tr -dc '0-9' | head -c 6)"; opens="${opens:-0}"
|
|
2376
|
+
if [ "${opens:-0}" -gt 0 ]; then
|
|
2377
|
+
echo " [!] graph execution $eid has $opens OPEN interrupt(s) - do not remove the backing"
|
|
2378
|
+
echo " worktree(s) until resolved: 'flow harness graph status --execution $eid'"
|
|
2379
|
+
warn=1
|
|
2380
|
+
fi
|
|
2381
|
+
fi
|
|
2382
|
+
fi
|
|
2178
2383
|
rm -rf "$td"
|
|
2179
2384
|
|
|
2180
2385
|
if [ "$drift" -eq 0 ]; then
|
|
@@ -3511,6 +3716,7 @@ usage: bash flow.sh <command> [args]
|
|
|
3511
3716
|
assess Brownfield: scaffold + gate a current-state assessment (flow/00-inspect.md) before planning
|
|
3512
3717
|
card Create the next build card (after planning complete)
|
|
3513
3718
|
check C-NNN Validate a card (FILL/status/sections/done-evidence)
|
|
3719
|
+
gate <stage>|--card C-NNN Read-only mechanical scan (no unlock, no durable writes)
|
|
3514
3720
|
mode [teach|work] Show or set who writes the artifacts
|
|
3515
3721
|
project-type [t] Show or set project type (web|cli|library|skill); adapts done-evidence
|
|
3516
3722
|
skip <stage> --reason Advance past a gate that has a matching open DEBT (non-security only)
|
|
@@ -3576,7 +3782,7 @@ _flow_version() {
|
|
|
3576
3782
|
|
|
3577
3783
|
_log_is_readonly() { # $1 = command -> true|false (does it mutate the flow plan?)
|
|
3578
3784
|
case "$1" in
|
|
3579
|
-
status|resume|recall|ready|usage|tokens|coherence|consistency|contract|constitution|doctor|design|help|-h|--help|"") echo true ;;
|
|
3785
|
+
status|resume|recall|ready|usage|tokens|coherence|consistency|contract|constitution|doctor|design|gate|help|-h|--help|"") echo true ;;
|
|
3580
3786
|
*) echo false ;;
|
|
3581
3787
|
esac
|
|
3582
3788
|
}
|
|
@@ -3684,6 +3890,10 @@ cmd_usage() {
|
|
|
3684
3890
|
}
|
|
3685
3891
|
|
|
3686
3892
|
# ---------- dispatch ----------
|
|
3893
|
+
# Library mode: tests source this file to exercise real helpers instead of regex-slicing
|
|
3894
|
+
# them out (a slice silently breaks the moment a helper gains a dependency, and then the
|
|
3895
|
+
# test reports 127 instead of the behavior it claims to check).
|
|
3896
|
+
if [ -n "${FLOW_LIB_ONLY:-}" ]; then return 0 2>/dev/null || exit 0; fi
|
|
3687
3897
|
cmd="${1:-status}"
|
|
3688
3898
|
shift 2>/dev/null || true
|
|
3689
3899
|
FLOW_LOG_CMD="$cmd"
|
|
@@ -3697,6 +3907,7 @@ case "$cmd" in
|
|
|
3697
3907
|
assess) cmd_assess ;;
|
|
3698
3908
|
card) cmd_card "$@" ;;
|
|
3699
3909
|
check) cmd_check "${1:-}" ;;
|
|
3910
|
+
gate) cmd_gate "$@" ;;
|
|
3700
3911
|
mode) cmd_mode "${1:-}" ;;
|
|
3701
3912
|
project-type) cmd_project_type "${1:-}" ;;
|
|
3702
3913
|
skip) cmd_skip "$@" ;;
|
package/skills-manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "../skills/flow",
|
|
3
3
|
"destRelative": "skills/flow",
|
|
4
|
-
"fileCount":
|
|
5
|
-
"syncedAt": "2026-07-
|
|
4
|
+
"fileCount": 82,
|
|
5
|
+
"syncedAt": "2026-07-27T03:21:41.774Z",
|
|
6
6
|
"fileList": [
|
|
7
7
|
"SKILL.md",
|
|
8
8
|
"_templates/00-idea.md",
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
"harness/_domain.py",
|
|
38
38
|
"harness/_presence.py",
|
|
39
39
|
"harness/flow_harness.py",
|
|
40
|
+
"harness/graph_executor.py",
|
|
41
|
+
"harness/graph_ids.py",
|
|
42
|
+
"harness/graph_predicates.py",
|
|
43
|
+
"harness/pins/flow-topology.sha256",
|
|
40
44
|
"harness/pins/harness-cli-v0.1.17.sha256sums",
|
|
41
45
|
"harness/repo_map.py",
|
|
42
46
|
"harness/schema/001-init.sql",
|
|
@@ -48,6 +52,7 @@
|
|
|
48
52
|
"harness/schema/010-usage-event.sql",
|
|
49
53
|
"harness/schema/011-usage-gate-reason.sql",
|
|
50
54
|
"harness/schema/012-usage-ephemeral.sql",
|
|
55
|
+
"harness/schema/014-graph-executor.sql",
|
|
51
56
|
"law/CLAUDE.md",
|
|
52
57
|
"law/DESIGN.md",
|
|
53
58
|
"law/RETRO.md",
|
|
@@ -68,6 +73,7 @@
|
|
|
68
73
|
"references/debt-and-halts.md",
|
|
69
74
|
"references/design-review-checklist.md",
|
|
70
75
|
"references/flow-catalog.tsv",
|
|
76
|
+
"references/flow-topology.json",
|
|
71
77
|
"references/forge-idea.md",
|
|
72
78
|
"references/gate-eval.md",
|
|
73
79
|
"references/gate-rules.md",
|