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.
- package/CHANGELOG.md +49 -0
- package/README.md +1 -1
- package/dist/agents/git.md +2 -2
- package/dist/cli/agents-view/index.js +1 -1
- package/dist/cli/agents-view/render.js +71 -17
- package/dist/cli/agents-view/state.js +42 -16
- package/dist/cli/agents-view/terminal.js +5 -5
- package/dist/cli/commands/agents.js +142 -51
- package/dist/cli/commands/ambient.js +1 -1
- package/dist/cli/commands/attribution-prompts.js +8 -8
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/compliance-prompts.js +8 -8
- package/dist/cli/commands/compliance.js +8 -7
- package/dist/cli/commands/flags.js +33 -31
- package/dist/cli/commands/hud.js +1 -1
- package/dist/cli/commands/init-seed.js +9 -9
- package/dist/cli/commands/init.js +162 -85
- package/dist/cli/commands/install-report.js +10 -10
- package/dist/cli/commands/learning.js +302 -136
- package/dist/cli/commands/memory.js +36 -15
- package/dist/cli/commands/proxy.js +23 -23
- package/dist/cli/commands/rules.js +6 -5
- package/dist/cli/commands/tracker-prompts.js +6 -6
- package/dist/cli/commands/tracker.js +9 -9
- package/dist/cli/commands/uninstall.js +183 -59
- package/dist/cli/flags-view/render.js +5 -5
- package/dist/cli/flags-view/state.js +9 -9
- package/dist/cli/flags-view/terminal.js +4 -4
- package/dist/cli/tui/cells.js +1 -1
- package/dist/cli/tui/terminal.js +6 -6
- package/dist/commands/code-review.md +0 -2
- package/dist/commands/debug.md +14 -11
- package/dist/commands/dynamic-build.md +51 -47
- package/dist/commands/dynamic-plan.md +27 -7
- package/dist/commands/dynamic-profile.md +17 -3
- package/dist/commands/dynamic-tickets.md +18 -4
- package/dist/commands/explore.md +9 -3
- package/dist/commands/implement.md +20 -16
- package/dist/commands/plan.md +13 -9
- package/dist/commands/release.md +23 -3
- package/dist/commands/research.md +9 -3
- package/dist/commands/resolve.md +9 -12
- package/dist/commands/self-review.md +0 -2
- package/dist/core/agent-frontmatter.js +28 -3
- package/dist/core/agent-models.js +204 -42
- package/dist/core/agent-state.js +28 -6
- package/dist/core/ansi.js +2 -2
- package/dist/core/assets.js +1 -1
- package/dist/core/cache.js +7 -8
- package/dist/core/codex-auth-inspect.js +4 -4
- package/dist/core/compliance-compose.js +3 -3
- package/dist/core/compliance.js +3 -4
- package/dist/core/evidence-policy.js +14 -13
- package/dist/core/external-models.js +1 -1
- package/dist/core/feature-config.js +71 -13
- package/dist/core/feature-switch.js +3 -3
- package/dist/core/flags.js +49 -25
- package/dist/core/fs-atomic.js +6 -7
- package/dist/core/learning-queue-cleanup.js +16 -81
- package/dist/core/learning-store.js +61 -0
- package/dist/core/linked-path.js +46 -0
- package/dist/core/manifest.js +5 -5
- package/dist/core/mds-variants.js +13 -13
- package/dist/core/model-discovery.js +8 -8
- package/dist/core/observations.js +17 -101
- package/dist/core/orphan-sweep.js +4 -4
- package/dist/core/plugins.js +13 -8
- package/dist/core/project-paths.js +9 -13
- package/dist/core/proxy-log.js +8 -8
- package/dist/core/proxy-state.js +3 -3
- package/dist/core/queue-drain.js +31 -0
- package/dist/core/reference-sweep.js +6 -6
- package/dist/core/teammate-mode-cleanup.js +1 -1
- package/dist/core/tracker.js +14 -14
- package/dist/hud/colors.js +2 -2
- package/dist/hud/components/learning-counts.js +54 -22
- package/dist/hud/components/version-badge.js +1 -1
- package/dist/skills/git/references/pr/resolve-review-threads.md +2 -2
- package/dist/skills/git/references/tracker/github/create-release.md +2 -2
- package/dist/skills/git/references/tracker/jira/create-release.md +2 -2
- package/dist/skills/git/references/tracker/linear/create-release.md +2 -2
- package/dist/targets/claude-code/compliance-install.js +17 -15
- package/dist/targets/claude-code/hooks.js +2 -2
- package/dist/targets/claude-code/installer.js +59 -32
- package/dist/targets/claude-code/legacy.js +1 -1
- package/dist/targets/claude-code/post-install.js +135 -45
- package/dist/targets/claude-code/tracker-install.js +2 -2
- package/package.json +1 -1
- package/src/assets/agents/code.md +15 -21
- package/src/assets/agents/design.md +4 -2
- package/src/assets/agents/diagnose.md +3 -1
- package/src/assets/agents/evaluate.md +4 -0
- package/src/assets/agents/git.mds +2 -2
- package/src/assets/agents/knowledge.md +5 -3
- package/src/assets/agents/learning.md +281 -196
- package/src/assets/agents/research.md +3 -1
- package/src/assets/agents/review.md +5 -3
- package/src/assets/agents/scrutinize.md +5 -1
- package/src/assets/agents/simplify.md +4 -0
- package/src/assets/agents/skim.md +4 -2
- package/src/assets/agents/synthesize.md +6 -0
- package/src/assets/agents/test.md +18 -10
- package/src/assets/agents/triage.md +11 -9
- package/src/assets/agents/validate.md +14 -10
- package/src/assets/commands/_partials/_decisions.mds +8 -3
- package/src/assets/commands/_partials/_docs_root.mds +3 -3
- package/src/assets/commands/_partials/_engine.mds +16 -32
- package/src/assets/commands/_partials/_knowledge.mds +0 -2
- package/src/assets/commands/_partials/_preamble.mds +6 -2
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/_partials/_tracker.mds +1 -1
- package/src/assets/commands/code-review.mds +0 -2
- package/src/assets/commands/debug.mds +13 -8
- package/src/assets/commands/dynamic-build.mds +18 -12
- package/src/assets/commands/dynamic-plan.mds +10 -4
- package/src/assets/commands/dynamic-profile.mds +1 -1
- package/src/assets/commands/dynamic-tickets.mds +2 -2
- package/src/assets/commands/explore.mds +9 -1
- package/src/assets/commands/implement.mds +19 -13
- package/src/assets/commands/plan.mds +12 -8
- package/src/assets/commands/release.md +23 -3
- package/src/assets/commands/research.mds +9 -3
- package/src/assets/commands/resolve.mds +9 -10
- package/src/assets/mds/git/_pr.mds +3 -3
- package/src/assets/mds/tracker/_common.mds +1 -1
- package/src/assets/mds/tracker/_github.mds +3 -3
- package/src/assets/mds/tracker/_jira.mds +3 -3
- package/src/assets/mds/tracker/_linear.mds +3 -3
- package/src/assets/mds/tracker/_mcp.mds +6 -5
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -2
- package/src/assets/scripts/hooks/background-memory-update +97 -33
- package/src/assets/scripts/hooks/capture-prompt +4 -3
- package/src/assets/scripts/hooks/capture-question +4 -3
- package/src/assets/scripts/hooks/capture-turn +5 -20
- package/src/assets/scripts/hooks/ensure-devflow-init +14 -2
- package/src/assets/scripts/hooks/ensure-proxy +5 -6
- package/src/assets/scripts/hooks/ensure-root-gitignore +123 -11
- package/src/assets/scripts/hooks/git-marker +71 -0
- package/src/assets/scripts/hooks/is-hex-sha +1 -1
- package/src/assets/scripts/hooks/json-helper.cjs +345 -944
- package/src/assets/scripts/hooks/json-parse +25 -129
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +205 -156
- package/src/assets/scripts/hooks/lib/learning-store.cjs +3207 -0
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +7 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +13 -19
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +253 -226
- package/src/assets/scripts/hooks/memory-worker +10 -0
- package/src/assets/scripts/hooks/pre-compact-memory +66 -14
- package/src/assets/scripts/hooks/preamble +9 -1
- package/src/assets/scripts/hooks/queue-append +55 -23
- package/src/assets/scripts/hooks/resolve-project-root +3 -4
- package/src/assets/scripts/hooks/session-start-context +146 -45
- package/src/assets/scripts/hooks/session-start-memory +33 -11
- package/src/assets/scripts/lib/project-config.cjs +2 -2
- package/src/assets/scripts/pr-evidence.cjs +3 -3
- package/src/assets/scripts/redact-secrets.cjs +20 -20
- package/src/assets/scripts/release-trace.cjs +1 -1
- package/src/assets/scripts/resolve-evidence-policy.cjs +3 -3
- package/src/assets/scripts/resolve-settings.cjs +3 -3
- package/src/assets/scripts/verify-evidence.cjs +2 -2
- package/src/assets/skills/apply-decisions/SKILL.md +37 -17
- package/src/assets/skills/docs-framework/SKILL.md +2 -2
- package/src/assets/skills/feature-knowledge/SKILL.md +6 -5
- package/src/assets/skills/test-driven-development/SKILL.md +6 -4
- package/dist/core/observation-io.js +0 -50
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
296
|
-
|
|
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 [ -
|
|
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
|
-
// (
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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 (
|
|
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
|
|
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
|
-
// (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
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 —
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
31
|
-
ADR-
|
|
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-
|
|
35
|
-
PF-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
82
|
-
4. **Cite** — If the file shows signs of the god-script pattern, note `avoids PF-
|
|
83
|
-
5. **Verbatim** — ID `PF-
|
|
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
|
|
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
|
|
144
|
-
| Pitfalls | `.devflow/learning/pitfalls.md` | Rendered from `decisions-ledger.jsonl` (active PF-NNN
|
|
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
|
|
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
|
|
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
|
-
-
|
|
329
|
-
-
|
|
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
|