@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.
@@ -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; else break; fi
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 $((idx + 1)) ($nxt.md) already exists - not overwritten."
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 $((idx + 1)) (flow/$nxt.md)"
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 "$@" ;;
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "source": "../skills/flow",
3
3
  "destRelative": "skills/flow",
4
- "fileCount": 76,
5
- "syncedAt": "2026-07-19T14:46:19.032Z",
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",