devflow-kit 3.0.1 → 3.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.
Files changed (166) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +1 -1
  3. package/dist/agents/git.md +2 -2
  4. package/dist/cli/agents-view/index.js +1 -1
  5. package/dist/cli/agents-view/render.js +71 -17
  6. package/dist/cli/agents-view/state.js +42 -16
  7. package/dist/cli/agents-view/terminal.js +5 -5
  8. package/dist/cli/commands/agents.js +142 -51
  9. package/dist/cli/commands/ambient.js +1 -1
  10. package/dist/cli/commands/attribution-prompts.js +8 -8
  11. package/dist/cli/commands/capture.js +1 -1
  12. package/dist/cli/commands/compliance-prompts.js +8 -8
  13. package/dist/cli/commands/compliance.js +8 -7
  14. package/dist/cli/commands/flags.js +33 -31
  15. package/dist/cli/commands/hud.js +1 -1
  16. package/dist/cli/commands/init-seed.js +9 -9
  17. package/dist/cli/commands/init.js +162 -85
  18. package/dist/cli/commands/install-report.js +10 -10
  19. package/dist/cli/commands/learning.js +302 -136
  20. package/dist/cli/commands/memory.js +36 -15
  21. package/dist/cli/commands/proxy.js +23 -23
  22. package/dist/cli/commands/rules.js +6 -5
  23. package/dist/cli/commands/tracker-prompts.js +6 -6
  24. package/dist/cli/commands/tracker.js +9 -9
  25. package/dist/cli/commands/uninstall.js +183 -59
  26. package/dist/cli/flags-view/render.js +5 -5
  27. package/dist/cli/flags-view/state.js +9 -9
  28. package/dist/cli/flags-view/terminal.js +4 -4
  29. package/dist/cli/tui/cells.js +1 -1
  30. package/dist/cli/tui/terminal.js +6 -6
  31. package/dist/commands/code-review.md +0 -2
  32. package/dist/commands/debug.md +14 -11
  33. package/dist/commands/dynamic-build.md +51 -47
  34. package/dist/commands/dynamic-plan.md +27 -7
  35. package/dist/commands/dynamic-profile.md +17 -3
  36. package/dist/commands/dynamic-tickets.md +18 -4
  37. package/dist/commands/explore.md +9 -3
  38. package/dist/commands/implement.md +20 -16
  39. package/dist/commands/plan.md +13 -9
  40. package/dist/commands/release.md +23 -3
  41. package/dist/commands/research.md +9 -3
  42. package/dist/commands/resolve.md +9 -12
  43. package/dist/commands/self-review.md +0 -2
  44. package/dist/core/agent-frontmatter.js +28 -3
  45. package/dist/core/agent-models.js +204 -42
  46. package/dist/core/agent-state.js +28 -6
  47. package/dist/core/ansi.js +2 -2
  48. package/dist/core/assets.js +1 -1
  49. package/dist/core/cache.js +7 -8
  50. package/dist/core/codex-auth-inspect.js +4 -4
  51. package/dist/core/compliance-compose.js +3 -3
  52. package/dist/core/compliance.js +3 -4
  53. package/dist/core/evidence-policy.js +14 -13
  54. package/dist/core/external-models.js +1 -1
  55. package/dist/core/feature-config.js +71 -13
  56. package/dist/core/feature-switch.js +3 -3
  57. package/dist/core/flags.js +49 -25
  58. package/dist/core/fs-atomic.js +6 -7
  59. package/dist/core/learning-queue-cleanup.js +16 -81
  60. package/dist/core/learning-store.js +61 -0
  61. package/dist/core/linked-path.js +46 -0
  62. package/dist/core/manifest.js +5 -5
  63. package/dist/core/mds-variants.js +13 -13
  64. package/dist/core/model-discovery.js +8 -8
  65. package/dist/core/observations.js +17 -101
  66. package/dist/core/orphan-sweep.js +4 -4
  67. package/dist/core/plugins.js +13 -8
  68. package/dist/core/project-paths.js +9 -13
  69. package/dist/core/proxy-log.js +8 -8
  70. package/dist/core/proxy-state.js +3 -3
  71. package/dist/core/queue-drain.js +31 -0
  72. package/dist/core/reference-sweep.js +6 -6
  73. package/dist/core/teammate-mode-cleanup.js +1 -1
  74. package/dist/core/tracker.js +14 -14
  75. package/dist/hud/colors.js +2 -2
  76. package/dist/hud/components/learning-counts.js +54 -22
  77. package/dist/hud/components/version-badge.js +1 -1
  78. package/dist/skills/git/references/pr/resolve-review-threads.md +2 -2
  79. package/dist/skills/git/references/tracker/github/create-release.md +2 -2
  80. package/dist/skills/git/references/tracker/jira/create-release.md +2 -2
  81. package/dist/skills/git/references/tracker/linear/create-release.md +2 -2
  82. package/dist/targets/claude-code/compliance-install.js +17 -15
  83. package/dist/targets/claude-code/hooks.js +2 -2
  84. package/dist/targets/claude-code/installer.js +59 -32
  85. package/dist/targets/claude-code/legacy.js +1 -1
  86. package/dist/targets/claude-code/post-install.js +135 -45
  87. package/dist/targets/claude-code/tracker-install.js +2 -2
  88. package/package.json +1 -1
  89. package/src/assets/agents/code.md +15 -21
  90. package/src/assets/agents/design.md +4 -2
  91. package/src/assets/agents/diagnose.md +3 -1
  92. package/src/assets/agents/evaluate.md +4 -0
  93. package/src/assets/agents/git.mds +2 -2
  94. package/src/assets/agents/knowledge.md +5 -3
  95. package/src/assets/agents/learning.md +281 -196
  96. package/src/assets/agents/research.md +3 -1
  97. package/src/assets/agents/review.md +5 -3
  98. package/src/assets/agents/scrutinize.md +5 -1
  99. package/src/assets/agents/simplify.md +4 -0
  100. package/src/assets/agents/skim.md +4 -2
  101. package/src/assets/agents/synthesize.md +6 -0
  102. package/src/assets/agents/test.md +18 -10
  103. package/src/assets/agents/triage.md +11 -9
  104. package/src/assets/agents/validate.md +14 -10
  105. package/src/assets/commands/_partials/_decisions.mds +8 -3
  106. package/src/assets/commands/_partials/_docs_root.mds +3 -3
  107. package/src/assets/commands/_partials/_engine.mds +16 -32
  108. package/src/assets/commands/_partials/_knowledge.mds +0 -2
  109. package/src/assets/commands/_partials/_preamble.mds +6 -2
  110. package/src/assets/commands/_partials/_settings.mds +2 -2
  111. package/src/assets/commands/_partials/_tracker.mds +1 -1
  112. package/src/assets/commands/code-review.mds +0 -2
  113. package/src/assets/commands/debug.mds +13 -8
  114. package/src/assets/commands/dynamic-build.mds +18 -12
  115. package/src/assets/commands/dynamic-plan.mds +10 -4
  116. package/src/assets/commands/dynamic-profile.mds +1 -1
  117. package/src/assets/commands/dynamic-tickets.mds +2 -2
  118. package/src/assets/commands/explore.mds +9 -1
  119. package/src/assets/commands/implement.mds +19 -13
  120. package/src/assets/commands/plan.mds +12 -8
  121. package/src/assets/commands/release.md +23 -3
  122. package/src/assets/commands/research.mds +9 -3
  123. package/src/assets/commands/resolve.mds +9 -10
  124. package/src/assets/mds/git/_pr.mds +3 -3
  125. package/src/assets/mds/tracker/_common.mds +1 -1
  126. package/src/assets/mds/tracker/_github.mds +3 -3
  127. package/src/assets/mds/tracker/_jira.mds +3 -3
  128. package/src/assets/mds/tracker/_linear.mds +3 -3
  129. package/src/assets/mds/tracker/_mcp.mds +6 -5
  130. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -2
  131. package/src/assets/scripts/hooks/background-memory-update +97 -33
  132. package/src/assets/scripts/hooks/capture-prompt +4 -3
  133. package/src/assets/scripts/hooks/capture-question +4 -3
  134. package/src/assets/scripts/hooks/capture-turn +5 -20
  135. package/src/assets/scripts/hooks/ensure-devflow-init +14 -2
  136. package/src/assets/scripts/hooks/ensure-proxy +5 -6
  137. package/src/assets/scripts/hooks/ensure-root-gitignore +123 -11
  138. package/src/assets/scripts/hooks/git-marker +71 -0
  139. package/src/assets/scripts/hooks/is-hex-sha +1 -1
  140. package/src/assets/scripts/hooks/json-helper.cjs +345 -944
  141. package/src/assets/scripts/hooks/json-parse +25 -129
  142. package/src/assets/scripts/hooks/lib/decisions-format.cjs +205 -156
  143. package/src/assets/scripts/hooks/lib/learning-store.cjs +3207 -0
  144. package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +7 -5
  145. package/src/assets/scripts/hooks/lib/project-paths.cjs +13 -19
  146. package/src/assets/scripts/hooks/lib/render-decisions.cjs +253 -226
  147. package/src/assets/scripts/hooks/memory-worker +10 -0
  148. package/src/assets/scripts/hooks/pre-compact-memory +66 -14
  149. package/src/assets/scripts/hooks/preamble +9 -1
  150. package/src/assets/scripts/hooks/queue-append +55 -23
  151. package/src/assets/scripts/hooks/resolve-project-root +3 -4
  152. package/src/assets/scripts/hooks/session-start-context +146 -45
  153. package/src/assets/scripts/hooks/session-start-memory +33 -11
  154. package/src/assets/scripts/lib/project-config.cjs +2 -2
  155. package/src/assets/scripts/pr-evidence.cjs +3 -3
  156. package/src/assets/scripts/redact-secrets.cjs +20 -20
  157. package/src/assets/scripts/release-trace.cjs +1 -1
  158. package/src/assets/scripts/resolve-evidence-policy.cjs +3 -3
  159. package/src/assets/scripts/resolve-settings.cjs +3 -3
  160. package/src/assets/scripts/verify-evidence.cjs +2 -2
  161. package/src/assets/skills/apply-decisions/SKILL.md +37 -17
  162. package/src/assets/skills/docs-framework/SKILL.md +2 -2
  163. package/src/assets/skills/feature-knowledge/SKILL.md +6 -5
  164. package/src/assets/skills/test-driven-development/SKILL.md +6 -4
  165. package/dist/core/observation-io.js +0 -50
  166. package/src/assets/scripts/hooks/decisions-usage-scan.cjs +0 -131
@@ -70,7 +70,11 @@ fi
70
70
  # re-spawns (memory disabled mid-flight, host offline, etc.): a SessionStart
71
71
  # arriving >300s after the crash with no intervening Stop hook.
72
72
  # Non-clobber: only recovers when .pending-turns.jsonl does NOT already exist,
73
- # so a concurrent session's fresh queue is never overwritten.
73
+ # so a concurrent session's fresh queue is never overwritten. Nor where a
74
+ # symbolic link sits on the path to the batch or to the queue it is renamed onto
75
+ # (D-HOOKS-NO-SYMLINK, git-marker): `mv` would move the batch into a folder a
76
+ # linked memory folder or queue path names, and a linked batch would become a
77
+ # linked queue.
74
78
  source "$SCRIPT_DIR/get-mtime" || { echo "session-start-memory: failed to source get-mtime" >&2; exit 1; }
75
79
  source "$SCRIPT_DIR/is-hex-sha" || { echo "session-start-memory: failed to source is-hex-sha" >&2; exit 1; }
76
80
  _SSM_PT_PROC="$MEMORY_DIR/.pending-turns.processing"
@@ -81,7 +85,10 @@ if [ -f "$_SSM_PT_PROC" ]; then
81
85
  _SSM_NOW_PT=$(date +%s)
82
86
  _SSM_PT_AGE=$(( _SSM_NOW_PT - _SSM_PT_MTIME ))
83
87
  if [ "$_SSM_PT_AGE" -gt 300 ]; then
84
- if [ ! -f "$_SSM_PT_JSONL" ]; then
88
+ if ! df_no_symlink_below "$PROJECT_ROOT" "$_SSM_PT_PROC" "$_SSM_PT_JSONL"; then
89
+ dbg "Stale .pending-turns.processing skipped — a symbolic link sits on its path or the queue's"
90
+ log "Stale .pending-turns.processing skipped (cold path): a symbolic link sits on the path to $_SSM_PT_PROC or $_SSM_PT_JSONL"
91
+ elif [ ! -f "$_SSM_PT_JSONL" ]; then
85
92
  mv "$_SSM_PT_PROC" "$_SSM_PT_JSONL" 2>/dev/null || true
86
93
  dbg "Recovered orphaned .pending-turns.processing (age ${_SSM_PT_AGE}s)"
87
94
  log "Recovered orphaned .pending-turns.processing → .pending-turns.jsonl (cold path)"
@@ -135,27 +142,31 @@ parse_and_validate_stamp() {
135
142
  esac
136
143
  }
137
144
 
138
- # --- detect_refresh_failing: sets REFRESH_FAILING in caller's scope.
145
+ # --- detect_refresh_failing <now> <memory_dir> <log_dir> <root>: sets
146
+ # REFRESH_FAILING in caller's scope. <root> is the project root the memory folder
147
+ # lies below.
139
148
  # Condition: queue non-empty AND (.last-refresh-ok missing OR >600s old)
140
149
  # orphaned-processing depth fix: count both .pending-turns.jsonl AND
141
150
  # .pending-turns.processing toward _queue_depth. Before this fix, an orphaned
142
151
  # .processing (whose mtime is between 0s and the D56c 300s cold-path gate)
143
152
  # was invisible to State-C — a crashed worker's batch would sit silently with
144
- # no user-visible warning.
153
+ # no user-visible warning. The count reaches the session, so a batch a symbolic
154
+ # link leads to counts as absent (D-HOOKS-NO-SYMLINK, git-marker).
145
155
  detect_refresh_failing() {
146
156
  local _now="$1"
147
157
  local _memory_dir="$2"
148
158
  local _log_dir="$3"
159
+ local _root="$4"
149
160
  local _queue_file="$_memory_dir/.pending-turns.jsonl"
150
161
  local _proc_file="$_memory_dir/.pending-turns.processing"
151
162
  local _ok_file="$_memory_dir/.last-refresh-ok"
152
163
  local _queue_depth=0
153
- if [ -f "$_queue_file" ] && [ -s "$_queue_file" ]; then
164
+ if df_file_below "$_root" "$_queue_file" && [ -s "$_queue_file" ]; then
154
165
  _queue_depth=$(wc -l < "$_queue_file" | tr -d ' ')
155
166
  fi
156
167
  # Also count lines in .processing — an orphaned batch left by a crashed worker
157
168
  # is unprocessed content even if .jsonl is empty (applies B4 blind-spot fix)
158
- if [ -f "$_proc_file" ] && [ -s "$_proc_file" ]; then
169
+ if df_file_below "$_root" "$_proc_file" && [ -s "$_proc_file" ]; then
159
170
  local _proc_depth
160
171
  _proc_depth=$(wc -l < "$_proc_file" | tr -d ' ')
161
172
  _queue_depth=$(( _queue_depth + _proc_depth ))
@@ -180,7 +191,14 @@ detect_refresh_failing() {
180
191
  fi
181
192
  }
182
193
 
183
- if [ -f "$MEMORY_FILE" ]; then
194
+ # D-HOOKS-NO-SYMLINK (git-marker): the working memory, the batches State C counts
195
+ # and the pre-compact backup are all read into the session, so each is read only
196
+ # where no symbolic link sits on its path; a file a link leads to is treated as
197
+ # absent, and its refusal logged once.
198
+ MEMORY_PRESENT=""
199
+ if df_file_below "$PROJECT_ROOT" "$MEMORY_FILE"; then MEMORY_PRESENT="yes"; fi
200
+
201
+ if [ -n "$MEMORY_PRESENT" ]; then
184
202
  dbg "MEMORY_FILE exists: $MEMORY_FILE"
185
203
  MEMORY_CONTENT=$(head -c 65536 "$MEMORY_FILE")
186
204
 
@@ -239,7 +257,7 @@ if [ -f "$MEMORY_FILE" ]; then
239
257
  fi
240
258
 
241
259
  # --- State C detection: refresh failing ---
242
- detect_refresh_failing "$NOW" "$MEMORY_DIR" "$LOG_DIR"
260
+ detect_refresh_failing "$NOW" "$MEMORY_DIR" "$LOG_DIR" "$PROJECT_ROOT"
243
261
 
244
262
  # --- Build state-specific header ---
245
263
  # D-DETACHED-HEAD: where the session is, as one phrase. On a branch it is
@@ -292,8 +310,12 @@ fi
292
310
  # Re-inject compaction snapshot when it's more recent than the memory file (compact overwrote
293
311
  # WORKING-MEMORY.md with a summary, but the pre-compact snapshot may have richer context).
294
312
  BACKUP_FILE="$MEMORY_DIR/backup.json"
295
- if [ -f "$BACKUP_FILE" ]; then
296
- BACKUP_MEMORY=$(json_field_file "$BACKUP_FILE" "memory_snapshot" "")
313
+ if df_file_below "$PROJECT_ROOT" "$BACKUP_FILE"; then
314
+ # Under set -e a failed read would end the hook before it prints the working
315
+ # memory built above, so a backup that does not parse is read as no snapshot,
316
+ # like an absent one, discarding what jq printed before it failed. The read
317
+ # below runs only once this one has parsed the whole file.
318
+ BACKUP_MEMORY=$(json_field_file "$BACKUP_FILE" "memory_snapshot" "") || BACKUP_MEMORY=""
297
319
  if [ -n "$BACKUP_MEMORY" ]; then
298
320
  BACKUP_TS=$(json_field_file "$BACKUP_FILE" "timestamp" "")
299
321
  BACKUP_EPOCH=0
@@ -303,7 +325,7 @@ if [ -f "$BACKUP_FILE" ]; then
303
325
  || echo "0")
304
326
  fi
305
327
  FILE_MTIME_CK=0
306
- if [ -f "$MEMORY_FILE" ]; then
328
+ if [ -n "$MEMORY_PRESENT" ]; then
307
329
  FILE_MTIME_CK=$(get_mtime "$MEMORY_FILE" 2>/dev/null || echo "0")
308
330
  fi
309
331
  if [ "$BACKUP_EPOCH" -gt "$FILE_MTIME_CK" ]; then
@@ -2,14 +2,14 @@
2
2
  //
3
3
  // The ONE parser of devflow's per-repository config files — the team-committed
4
4
  // `.devflow/project.json` and the personal, uncommitted `.devflow/config.json`
5
- // (applies PF-023: the invariant lives at the sink every reader passes through).
5
+ // (the invariant lives at the sink every reader passes through).
6
6
  // Installed beside its two callers as ~/.devflow/scripts/lib/project-config.cjs:
7
7
  // resolve-evidence-policy.cjs reads `evidence` and `compliance` at every source
8
8
  // resolve-settings.cjs reads every key of both files, locally
9
9
  //
10
10
  // Pure except the two readers (readBoundedRegularFile, readMachineManifest), which
11
11
  // only ever read. Nothing here writes, spawns or prints: devflow never writes
12
- // project.json (applies ADR-024).
12
+ // project.json, which the team owns and commits.
13
13
  //
14
14
  // D-PROJECT-CONFIG: `.devflow/project.json` is a JSON object whose every key is
15
15
  // optional and whose unknown keys are ignored:
@@ -898,7 +898,7 @@ function needsDiff(x) {
898
898
  /**
899
899
  * D-LADDER: the arms, in the order they are tried. PRECEDENCE is derived from
900
900
  * this table. Every arm but the last is a POSITIVE match; the last is the
901
- * conservative default (applies PF-075: a verified state is only ever reached by
901
+ * conservative default (a verified state is only ever reached by
902
902
  * a positive conjunction, never because nothing else matched).
903
903
  *
904
904
  * @type {readonly Arm[]}
@@ -978,7 +978,7 @@ const ARMS = Object.freeze([
978
978
  /**
979
979
  * D-LADDER: the order `classify` tries its arms in — first match wins, and the
980
980
  * terminal arm is the conservative UNVERIFIED, so no state is ever reached by
981
- * exhaustion (applies PF-075). DERIVED from ARMS, so the order the contract states
981
+ * exhaustion. DERIVED from ARMS, so the order the contract states
982
982
  * (parity-pinned to this list) and the order the code runs cannot drift apart.
983
983
  */
984
984
  const PRECEDENCE = Object.freeze(/** @type {State[]} */ (ARMS.map(arm => arm.state)));
@@ -1446,7 +1446,7 @@ function parseEvidenceComment(text) {
1446
1446
  }
1447
1447
 
1448
1448
  // ---------------------------------------------------------------------------
1449
- // splice — only the bytes between the markers are devflow's (applies ADR-024)
1449
+ // splice — only the bytes between the markers are devflow's
1450
1450
  // ---------------------------------------------------------------------------
1451
1451
 
1452
1452
  /**
@@ -40,19 +40,19 @@
40
40
  // posted" from "the script broke": the first is final, the second is retried.
41
41
  //
42
42
  // Design constraints (binding):
43
- // PF-011 the file sink — the one file this script writes — goes via
44
- // temp-sibling + rename (atomic same-fs write; readers see old-or-new,
45
- // never a momentarily absent file). `--emit` prints to stdout and
46
- // touches no file, so it has nothing to protect
47
- // PF-014 never call process.exit() inside any scope with pending cleanup or
48
- // buffered output; main() returns an exit code; the single top-level
49
- // boundary writes stdout SYNCHRONOUSLY then sets process.exitCode so
50
- // nothing is truncated and no finally block is skipped
51
- // PF-018 all regexes are bounded (no unbounded [\s\S]*); skip-list checked
52
- // before any replacement; the unterminated-header pattern uses a
53
- // character class instead of a lazy quantifier for bounded scan
54
- // PF-023 self-contained sink-side control — never assumes upstream masking
55
- // happened; the scrubber is authoritative for its own rule set
43
+ // - the file sink — the one file this script writes — goes via
44
+ // temp-sibling + rename (atomic same-fs write; readers see old-or-new,
45
+ // never a momentarily absent file). `--emit` prints to stdout and
46
+ // touches no file, so it has nothing to protect
47
+ // - never call process.exit() inside any scope with pending cleanup or
48
+ // buffered output; main() returns an exit code; the single top-level
49
+ // boundary writes stdout SYNCHRONOUSLY then sets process.exitCode so
50
+ // nothing is truncated and no finally block is skipped
51
+ // - all regexes are bounded (no unbounded [\s\S]*); skip-list checked
52
+ // before any replacement; the unterminated-header pattern uses a
53
+ // character class instead of a lazy quantifier for bounded scan
54
+ // - self-contained sink-side control — never assumes upstream masking
55
+ // happened; the scrubber is authoritative for its own rule set
56
56
 
57
57
  'use strict';
58
58
 
@@ -98,7 +98,7 @@ const NONCE_HEX_CHARS = 32;
98
98
  * rule then describes a property of every body this script can emit, instead of
99
99
  * an obligation nine documents have to restate correctly.
100
100
  *
101
- * PF-018: bounded — a fixed alternation over two literals, anchored per line by
101
+ * Bounded — a fixed alternation over two literals, anchored per line by
102
102
  * the `m` flag, with no quantifier to backtrack through. The trailing space is
103
103
  * load-bearing: it is what keeps prose such as `D11-FAILURE` out of the refusal.
104
104
  */
@@ -268,7 +268,7 @@ function hasMixedAlphanumerics(s) {
268
268
  // Scrubbing pass
269
269
  //
270
270
  // Rules are applied in the declared order. Each rule uses a bounded regex to
271
- // avoid catastrophic backtracking (PF-018). The final secret-assignment rule
271
+ // avoid catastrophic backtracking. The final secret-assignment rule
272
272
  // operates line-by-line and applies entropy + character-class heuristics to
273
273
  // avoid false positives on config references.
274
274
  // ---------------------------------------------------------------------------
@@ -375,7 +375,7 @@ function scrub(content) {
375
375
  // (`this.apiToken`, `api-key`). Without these the rule only ever fired on a
376
376
  // bare column-0 assignment, which is the rarest form inside a review finding.
377
377
  // The declarator group is bounded {0,3} and each iteration must consume a
378
- // literal keyword, so the added alternation cannot backtrack (PF-018).
378
+ // literal keyword, so the added alternation cannot backtrack.
379
379
  //
380
380
  // Conditions for replacement (all must hold):
381
381
  // (a) The KEY matches SECRET_KEY_RE — not merely the line, so a prose line
@@ -590,7 +590,7 @@ function frameEmit(scrubbed, scrubLine, nonceSource) {
590
590
  // ---------------------------------------------------------------------------
591
591
  // main — returns an exit code (never calls process.exit() internally)
592
592
  //
593
- // PF-014: no process.exit() inside any scope that has pending cleanup or
593
+ // No process.exit() inside any scope that has pending cleanup or
594
594
  // buffered output. main() returns a numeric code for error paths or an
595
595
  // object {scrubLine} for the success path. The single top-level boundary
596
596
  // writes stdout synchronously and sets process.exitCode — nothing is
@@ -656,7 +656,7 @@ function readInput(inputPath) {
656
656
  function runFileMode(args, content) {
657
657
  const { result, counts } = scrub(content);
658
658
 
659
- // ---- atomic write (PF-011: temp-sibling + rename) ----
659
+ // ---- atomic write (temp-sibling + rename) ----
660
660
  const tmpPath = args.outputPath + '.tmp';
661
661
  try {
662
662
  fs.writeFileSync(tmpPath, result, 'utf8');
@@ -686,7 +686,7 @@ function runFileMode(args, content) {
686
686
  * prints them, so there is nothing for a write discipline to protect: a scrubbed
687
687
  * comment body put on disk is a second copy with the input directory's lifetime,
688
688
  * and proving that directory writable would let a filesystem property refuse a
689
- * clean, fully gated body. The file mode's temp-sibling + rename (PF-011) guards
689
+ * clean, fully gated body. The file mode's temp-sibling + rename guards
690
690
  * the one file this script does write.
691
691
  *
692
692
  * @param {string} content
@@ -762,7 +762,7 @@ function main(argv, deps) {
762
762
  //
763
763
  // This is the ONLY place that writes to stdout and sets process.exitCode.
764
764
  // No other code path may call process.exit() or write to stdout.
765
- // (PF-014: single synchronous write, no pending cleanup, no buffered output)
765
+ // (single synchronous write, no pending cleanup, no buffered output)
766
766
  //
767
767
  // AMENDED for --emit, not bypassed. main()'s return widened from
768
768
  // `number | {scrubLine}` to also carry `{emitLine, body, code}`, and the write
@@ -445,7 +445,7 @@ function isChangelogOnly(paths) {
445
445
  /**
446
446
  * D-TRACE-CLASSIFY: one first-parent commit's class. First match wins, in
447
447
  * CLASSES order, and the terminal arm is `untraced` — an input no rule
448
- * recognises surfaces in the confirm rather than passing silently (avoids PF-075).
448
+ * recognises surfaces in the confirm rather than passing silently.
449
449
  *
450
450
  * @param {Commit} commit
451
451
  * @param {ClassifyContext} ctx
@@ -11,7 +11,7 @@
11
11
  // The policy is plumbing, decided once by the caller: this script prints it plus
12
12
  // the mechanism inputs, and operations only ever see the inputs. It WRITES NOTHING
13
13
  // — no file, no git ref, no remote state — so `.devflow/project.json` stays a
14
- // team-owned file that only the team commits (applies ADR-024). A committed
14
+ // team-owned file that only the team commits. A committed
15
15
  // `.devflow/policy.json` is detected by presence and never parsed
16
16
  // (D-POLICY-JSON-RETIRED).
17
17
  //
@@ -432,7 +432,7 @@ function readManifestCompliance() {
432
432
  * starting (ENOENT), timing out, overflowing its buffer or being killed. Only an
433
433
  * answered non-zero exit is a real "no" ("not a repository", "no such path",
434
434
  * "no such ref"); an unanswered call is NOT KNOWING, and a local-git step that
435
- * does not know must not read as the permissive answer (avoids PF-075).
435
+ * does not know must not read as the permissive answer.
436
436
  *
437
437
  * @param {CallResult} r
438
438
  * @returns {boolean}
@@ -621,7 +621,7 @@ function lsRemoteDefaultBranch(ctx, root) {
621
621
  * unreadable git did not answer, or origin/HEAD exists but its answer names
622
622
  * no safe branch (another remote, a hostile or unparseable name).
623
623
  * Either is a failure, never the residual case: gatherFacts reads
624
- * it as an invalid base, which resolves required (avoids PF-075).
624
+ * it as an invalid base, which resolves required.
625
625
  * resolve-settings' defaultBranchCompliance fails the same answer
626
626
  * closed, to `generic`.
627
627
  *
@@ -35,7 +35,7 @@
35
35
  // publication can only lower it (D-PUBLICATION-CEILING) — a branch's
36
36
  // `reviewPublication: "full"` raises nothing, since only a personal value asks
37
37
  // for more than `auto`. The tracker (provider, site, key) is taken as the
38
- // branch states it. It WRITES NOTHING (applies ADR-024).
38
+ // branch states it. It WRITES NOTHING: each layer it folds is changed only by its owner.
39
39
  //
40
40
  // stdout is exactly one line plus "\n", or empty (D-SETTINGS-LINE):
41
41
  // TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default>
@@ -750,7 +750,7 @@ function readRepoLayers(root, deps) {
750
750
  * (D-LENS-UNION, D-SETTINGS-LOCAL-ONLY): origin/HEAD names the branch D, and
751
751
  * `refs/remotes/origin/<D>:.devflow/project.json` is read through the same
752
752
  * parser. The lens only adds, so every state that cannot be read is a malformed
753
- * declaration (`generic`), never "declares nothing" (avoids PF-075):
753
+ * declaration (`generic`), never "declares nothing":
754
754
  * absent origin/HEAD is not recorded, or D holds no project.json
755
755
  * malformed a git call did not answer, origin/HEAD names no safe branch, the
756
756
  * blob overflows its bound, the file is unreadable, or its
@@ -898,7 +898,7 @@ function settleOutcome(outcome) {
898
898
  }
899
899
 
900
900
  // ---------------------------------------------------------------------------
901
- // The suggestion the CLI prints (never writes — ADR-024)
901
+ // The suggestion the CLI prints (never writes — the team commits it)
902
902
  // ---------------------------------------------------------------------------
903
903
 
904
904
  /**
@@ -459,7 +459,7 @@ function runCall(io, file, args, timeout, maxBuffer) {
459
459
  /**
460
460
  * Whether a call ran to completion and exited on its own. Only an answered exit
461
461
  * is a real "no"; a call that never started, timed out, overflowed or was refused
462
- * is NOT KNOWING, and never reads as the permissive answer (avoids PF-075).
462
+ * is NOT KNOWING, and never reads as the permissive answer.
463
463
  *
464
464
  * @param {CallResult} r
465
465
  * @returns {boolean}
@@ -1530,7 +1530,7 @@ function gateEvidenceLine(deps, fields, total) {
1530
1530
  }
1531
1531
 
1532
1532
  // ---------------------------------------------------------------------------
1533
- // splice — the compare-and-swap (D-VERIFY-CAS, applies ADR-023 and ADR-024)
1533
+ // splice — the compare-and-swap (D-VERIFY-CAS)
1534
1534
  // ---------------------------------------------------------------------------
1535
1535
 
1536
1536
  /**
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: apply-decisions
3
- description: Canonical algorithm for consuming DECISIONS_CONTEXT index — scan index, identify relevant entries, Read full bodies on demand, cite verbatim IDs inline.
3
+ description: Canonical algorithm for consuming DECISIONS_CONTEXT index — scan index, identify relevant entries, Read full bodies on demand, cite verbatim IDs in-session and state the rule in words in anything committed or posted.
4
4
  user-invocable: false
5
5
  allowed-tools: Read
6
6
  ---
7
7
 
8
8
  # Apply Decisions
9
9
 
10
- Canonical consumer algorithm for the `DECISIONS_CONTEXT` index passed by orchestrators. The index lists each ADR/PF entry with ID, truncated title, status, and area — not the full body. Use this skill to surface the right decisions and pitfalls for your task without loading the entire corpus.
10
+ Canonical consumer algorithm for the `DECISIONS_CONTEXT` index passed by orchestrators. The index lists each active ADR/PF entry with its ID and title — plus a status tag and an area on a v1 line, or the entry's scope on a v2 line — not the full body. Use this skill to surface the right decisions and pitfalls for your task without loading the entire corpus.
11
11
 
12
12
  ## Iron Law
13
13
 
@@ -23,35 +23,52 @@ Canonical consumer algorithm for the `DECISIONS_CONTEXT` index passed by orchest
23
23
 
24
24
  ### Step 1: Scan the index
25
25
 
26
- Read through all entries in `DECISIONS_CONTEXT`. The index format is:
26
+ Read through all entries in `DECISIONS_CONTEXT`. The index lists active entries only, in two line kinds:
27
27
 
28
28
  ```
29
29
  Decisions (N):
30
- ADR-001 Title truncated to 60 chars [Active]
31
- ADR-002 Another decision [Active]
30
+ ADR-NNN Return Result types from every fallible operation [Accepted]
31
+ ADR-NNN Claim the learning queue with an op, never with mv — src/assets/scripts/hooks/**, area:learning
32
32
 
33
33
  Pitfalls (M):
34
- PF-004 Background hook god scripts [Active] — src/assets/scripts/hooks/foo.cjs
35
- PF-011 DECISIONS_CONTEXT fan-out [Active] — src/assets/commands/...
34
+ PF-NNN Background hook god scripts [Active] — src/assets/scripts/hooks/foo.cjs
35
+ PF-NNN A rename claims a shared file only while nothing re-creates it — area:hooks
36
36
 
37
37
  ADR-NNN entries live in {worktree}/.devflow/learning/decisions.md
38
38
  PF-NNN entries live in {worktree}/.devflow/learning/pitfalls.md
39
39
  Read the relevant file and locate the matching `## ADR-NNN:` or `## PF-NNN:` heading for the full body.
40
40
  ```
41
41
 
42
+ - **A v1 line** ends in a status tag — `[Accepted]` on a decision, `[Active]` on a pitfall — after a title cut to 60 characters, and a pitfall adds its area after `—`.
43
+ - **A v2 line** has no tag. Its title is whole, and after `—` comes its scope: the globs and `area:` tags the rule governs, cut to 80 characters.
44
+
42
45
  ### Step 2: Identify plausibly-relevant entries
43
46
 
44
- From the index, identify entries whose title or area plausibly overlaps with:
45
- - The files you are modifying or reviewing
47
+ From the index, identify entries whose title, area or scope plausibly overlaps with:
48
+ - The files you are modifying or reviewing — a v2 scope glob that matches one of them is a strong signal
46
49
  - The category of issue you are addressing (e.g., error handling, hook scripts, JSON parsing)
47
50
  - The architectural area of your change
48
51
 
49
- Titles are truncated to 60 characters — if a truncated title looks relevant, proceed to Step 3.
52
+ A v1 title may be cut short — if a truncated title looks relevant, proceed to Step 3.
50
53
 
51
54
  ### Step 3: Read the full body
52
55
 
53
56
  For each plausibly-relevant entry, use the Read tool to open the decisions file listed in the `DECISIONS_CONTEXT` footer and locate the matching `## ADR-NNN:` or `## PF-NNN:` heading. Read the full section to confirm relevance and understand the decision or pitfall completely.
54
57
 
58
+ A v2 body reads:
59
+
60
+ ```
61
+ ## ADR-NNN: {title}
62
+
63
+ - **Status**: Accepted · verified {date}
64
+ - **Scope**: `{glob}`, `area:{tag}`
65
+ - **Decision**: {the rule}
66
+ - **Why**: {why it holds}
67
+ - **Source**: {where it was learned}
68
+ ```
69
+
70
+ A pitfall's Status is `Active` and its rule line is labelled `**Rule**`. The verified date is the day the entry was last confirmed true; a body without one has not been confirmed since it was written. A v1 body carries `**Date**`, `**Status**`, `**Context**`, `**Decision**` and `**Consequences**` for a decision, or `**Area**`, `**Issue**`, `**Impact**`, `**Resolution**` and `**Status**` for a pitfall.
71
+
55
72
  **The footer is the single source of truth for file paths.** Never substitute hardcoded paths — the footer resolves to the correct worktree, which may differ from your cwd in multi-worktree flows.
56
73
 
57
74
  ```
@@ -62,9 +79,11 @@ Use the exact paths from the DECISIONS_CONTEXT footer, e.g.:
62
79
 
63
80
  Only cite an entry after you have read its full body and confirmed it applies.
64
81
 
65
- ### Step 4: Cite inline
82
+ ### Step 4: Cite inline — in-session handoffs only
83
+
84
+ When applying a prior decision, cite as `applies ADR-NNN`. When avoiding a known pitfall, cite as `avoids PF-NNN`. Place these citations only where they stay in the session: your reasoning, decision tables, prompts to downstream agents, and your report back to the caller.
66
85
 
67
- When applying a prior decision, cite as `applies ADR-NNN` in your reasoning or output. When avoiding a known pitfall, cite as `avoids PF-NNN`. Place citations in the Reasoning column of decision tables, in inline comments, or in your structured output — wherever your agent's output format captures rationale.
86
+ Anything committed, pushed or posted states the rule in words, never its ID: code, comments, tests, docs, KNOWLEDGE.md files, commit messages, PR and issue text, review and PR comments, resolution summaries — and any report a later step copies into one of those. The ledger is gitignored and numbered per machine, so no other clone can resolve an ID, and numbers get reused.
68
87
 
69
88
  ### Step 5: Use verbatim IDs only
70
89
 
@@ -76,17 +95,17 @@ Cite only IDs that appear verbatim in `DECISIONS_CONTEXT`. Do not guess at IDs t
76
95
 
77
96
  **Scenario**: Reviewing `src/assets/scripts/hooks/background-learning` for issues.
78
97
 
79
- 1. **Scan** — Index shows `PF-004 Background hook god scripts [Active] — src/assets/scripts/hooks/foo.cjs`
98
+ 1. **Scan** — Index shows `PF-NNN Background hook god scripts [Active] — src/assets/scripts/hooks/foo.cjs`
80
99
  2. **Identify** — Area field includes `src/assets/scripts/hooks/` which overlaps with the file under review
81
- 3. **Read** — Open the pitfalls file at the path given in the DECISIONS_CONTEXT footer (e.g., `<worktree>/.devflow/learning/pitfalls.md`), find `## PF-004:` section, read full body
82
- 4. **Cite** — If the file shows signs of the god-script pattern, note `avoids PF-004` in reasoning
83
- 5. **Verbatim** — ID `PF-004` appeared in the index; citation is valid
100
+ 3. **Read** — Open the pitfalls file at the path given in the DECISIONS_CONTEXT footer (e.g., `<worktree>/.devflow/learning/pitfalls.md`), find `## PF-NNN:` section, read full body
101
+ 4. **Cite** — If the file shows signs of the god-script pattern, note `avoids PF-NNN` in reasoning; a comment you commit for the fix says why in words ("hooks stay thin dispatchers"), never the ID
102
+ 5. **Verbatim** — ID `PF-NNN` appeared in the index; citation is valid
84
103
 
85
104
  ---
86
105
 
87
106
  ## Skip Guard
88
107
 
89
- When `DECISIONS_CONTEXT` is empty, `(none)`, or not provided: skip this skill entirely. Do not attempt to load decisions files independently. Do not speculate about what decisions or pitfalls might exist.
108
+ When `DECISIONS_CONTEXT` is empty, `(none)`, or not provided: skip this skill entirely — unless your agent instructions tell you to read the decisions index yourself, in which case the index you read is your `DECISIONS_CONTEXT`. Do not otherwise load decisions files independently. Do not speculate about what decisions or pitfalls might exist.
90
109
 
91
110
  ---
92
111
 
@@ -98,3 +117,4 @@ When `DECISIONS_CONTEXT` is empty, `(none)`, or not provided: skip this skill en
98
117
  | Avoiding a known pitfall | `avoids PF-NNN` |
99
118
  | Entry not in index | (no citation — silence is correct) |
100
119
  | Entry in index but not read yet | (no citation — read first) |
120
+ | Text that is committed, pushed or posted | (no ID — state the rule in words) |
@@ -140,8 +140,8 @@ ensure_docs_dir() { mkdir -p "$(get_docs_root)/.devflow/docs/$1"; }
140
140
  | Resolve cmd | `.devflow/docs/reviews/{branch-slug}/{timestamp}/resolution-summary.md` | Written by /resolve orchestrator (Phase 5) |
141
141
  | Code-review cmd | `.devflow/docs/reviews/{branch-slug}/.last-review-head` | Overwrites with HEAD SHA |
142
142
  | Working Memory | `.devflow/memory/WORKING-MEMORY.md` | Overwrites (auto-maintained by Stop hook) |
143
- | Decisions | `.devflow/learning/decisions.md` | Rendered from `decisions-ledger.jsonl` (active ADR-NNN rows; retired rows dropped) |
144
- | Pitfalls | `.devflow/learning/pitfalls.md` | Rendered from `decisions-ledger.jsonl` (active PF-NNN rows; retired rows dropped) |
143
+ | Decisions | `.devflow/learning/decisions.md` | Rendered from `decisions-ledger.jsonl` (active ADR-NNN entries in full; inactive ones listed in its Inactive table) |
144
+ | Pitfalls | `.devflow/learning/pitfalls.md` | Rendered from `decisions-ledger.jsonl` (active PF-NNN entries in full; inactive ones listed in its Inactive table) |
145
145
  | Design agent (via /plan) | `.devflow/docs/design/{ISSUE_ID}-{topic-slug}.{timestamp}.md` | Creates new design artifact |
146
146
  | Research agent | `.devflow/docs/research/{topic-slug}/{timestamp}/{type}.md` | Creates new in timestamped dir |
147
147
  | Synthesize agent (research) | `.devflow/docs/research/{topic-slug}/{timestamp}/research-summary.md` | Creates new in timestamped dir |
@@ -117,7 +117,7 @@ updated: {ISO date}
117
117
  [Most important files with one-line descriptions]
118
118
 
119
119
  ## Related
120
- [Links to ADR/PF entries, other feature knowledge entries, key source files]
120
+ [Links to other feature knowledge entries and key source files — never an ADR/PF ID]
121
121
  ```
122
122
 
123
123
  ### Category Templates
@@ -167,9 +167,10 @@ Bad: `"Integration stuff"`
167
167
 
168
168
  Knowledge files must not exist in isolation. The **Related** section must link to:
169
169
  - Other feature knowledge entries that cover related topics
170
- - ADR/PF entries from DECISIONS_CONTEXT (if provided)
171
170
  - Key source files referenced in the knowledge
172
171
 
172
+ A decision or pitfall from DECISIONS_CONTEXT that shapes this area is stated in words in the section it governs, never by its ADR/PF ID: KNOWLEDGE.md is git-tracked and shared with every clone, and ledger IDs resolve only on the machine that recorded them.
173
+
173
174
  References should be bidirectional — when creating a new feature knowledge entry that relates to an existing one, note the connection.
174
175
 
175
176
  ---
@@ -230,7 +231,7 @@ Run through this before writing. If any check fails, go back and fix it.
230
231
  - [ ] File stays under 500 lines (split if necessary)
231
232
 
232
233
  **Connections:**
233
- - [ ] Cross-references to related feature knowledge entries and ADR/PF entries in Related section
234
+ - [ ] Cross-references to related feature knowledge entries in Related section; decisions and pitfalls stated in words, with no ADR/PF ID anywhere in the file
234
235
  - [ ] No isolated knowledge islands — file connects to the broader knowledge network
235
236
  - [ ] Key source files listed in Key Files section
236
237
 
@@ -325,8 +326,8 @@ All constants use `SCREAMING_SNAKE_CASE` with a descriptive prefix. If a value c
325
326
 
326
327
  ## Related
327
328
 
328
- - ADR-003: Lib/command separation principle
329
- - PF-001: Config drift when constants are scattered
329
+ - `.devflow/features/cli-commands/KNOWLEDGE.md` — the command side of the lib/command split: catching errors and routing them to `showError()`
330
+ - `src/commands/` — where each integration is wired into a command
330
331
  ````
331
332
 
332
333
  ---
@@ -34,6 +34,8 @@ Enforce the RED-GREEN-REFACTOR cycle for all implementation work. Tests define t
34
34
 
35
35
  ## The Cycle
36
36
 
37
+ **Affected tests**: the tests that cover the changed files, selected by the runner's related-test option (Jest's `--findRelatedTests`, Vitest's `related`) or, where the runner has none, the package or path that owns them. Every "run the tests" step below means the affected tests; the full suite belongs to Validate.
38
+
37
39
  ### Step 1: RED — Write a Failing Test
38
40
 
39
41
  Write a test that describes the behavior you want. Run it. Watch it fail. The failure message IS your specification.
@@ -56,19 +58,19 @@ Don't write code "you'll need later." Write code the test demands NOW.
56
58
  Don't optimize. Don't refactor. Don't clean up. Just pass the test.
57
59
  ```
58
60
 
59
- **Checkpoint:** All tests pass. If any test fails, fix it before moving on.
61
+ **Checkpoint:** All affected tests pass. If any test fails, fix it before moving on.
60
62
 
61
63
  ### Step 3: REFACTOR — Improve Without Changing Behavior
62
64
 
63
65
  Now clean up. Extract helpers, rename variables, simplify logic. Tests stay green throughout.
64
66
 
65
67
  ```
66
- Run tests after every refactoring step.
68
+ Run the affected tests after every refactoring step.
67
69
  If a test breaks during refactor, undo immediately — you changed behavior.
68
70
  Apply DRY, extract patterns, improve readability.
69
71
  ```
70
72
 
71
- **Checkpoint:** All tests still pass. Code is clean. Repeat from Step 1 for next behavior.
73
+ **Checkpoint:** All affected tests still pass. Code is clean. Repeat from Step 1 for next behavior.
72
74
 
73
75
  ---
74
76
 
@@ -79,7 +81,7 @@ After each RED-GREEN-REFACTOR cycle, ALL must hold:
79
81
  - [ ] Test existed BEFORE production code (not concurrent, not after)
80
82
  - [ ] Test failed for the RIGHT reason (expected behavior absent, not syntax/import error)
81
83
  - [ ] Production code is minimal — no speculative additions beyond what the test demands
82
- - [ ] ALL tests pass, not just the new one
84
+ - [ ] ALL affected tests pass, not just the new one
83
85
  - [ ] Refactoring happened in Step 3 (or code is already clean — state explicitly)
84
86
  - [ ] No untested production code remains
85
87
 
@@ -1,50 +0,0 @@
1
- import { promises as fs } from 'fs';
2
- import * as p from '@clack/prompts';
3
- import { writeFileAtomicExclusive } from './fs-atomic.js';
4
- import { loadAndCountObservations } from './observations.js';
5
- /**
6
- * @file observation-io.ts
7
- *
8
- * File I/O for observations and user-facing warnings.
9
- * Bridges the pure data module (observations.ts) with the filesystem.
10
- *
11
- * The `.md` files are a pure render of the decisions ledger — never edit them
12
- * directly. To change the status of a decision or pitfall, use the
13
- * `retire-anchor` op in `json-helper.cjs`, which flips `decisions_status` on
14
- * the ledger row and re-renders both `.md` files atomically.
15
- */
16
- /**
17
- * Read and parse observations from a log file.
18
- * Returns empty results if the file does not exist.
19
- */
20
- export async function readObservations(logPath) {
21
- try {
22
- const logContent = await fs.readFile(logPath, 'utf-8');
23
- return loadAndCountObservations(logContent);
24
- }
25
- catch {
26
- return { observations: [], invalidCount: 0 };
27
- }
28
- }
29
- /**
30
- * Write observations back to a log file atomically.
31
- * Each observation is serialized as a JSON line. Uses a `.tmp` sibling + rename so
32
- * concurrent readers (e.g. background-learning during a race) never observe a
33
- * half-written file. Delegates to `writeFileAtomicExclusive` in fs-atomic.ts
34
- * (D34/D39: canonical TS atomic-write helper).
35
- */
36
- export async function writeObservations(logPath, observations) {
37
- const lines = observations.map(o => JSON.stringify(o));
38
- const content = lines.join('\n') + (lines.length ? '\n' : '');
39
- await writeFileAtomicExclusive(logPath, content);
40
- }
41
- /**
42
- * Warn the user if invalid entries were found in a log file.
43
- * Invalid entries are cleaned up automatically by the background curation pass.
44
- */
45
- export function warnIfInvalid(invalidCount) {
46
- if (invalidCount > 0) {
47
- p.log.warn(`Note: ${invalidCount} invalid entry(ies) found. They will be cleaned up automatically.`);
48
- }
49
- }
50
- //# sourceMappingURL=observation-io.js.map