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
|
@@ -56,7 +56,7 @@ conflict the contract wins.
|
|
|
56
56
|
Headings below each section's own anchor are `###` by grammar, not by taste: a
|
|
57
57
|
column-0 `## ` line outside a fence terminates the section for every guard that
|
|
58
58
|
reads it through `extractOpSectionFromCorpus`, and everything under it becomes
|
|
59
|
-
invisible while the bytes stay on disk
|
|
59
|
+
invisible while the bytes stay on disk. The one `## ` heading in this
|
|
60
60
|
file is the module-level section below, which sits above every section marker and
|
|
61
61
|
is therefore emitted nowhere.
|
|
62
62
|
|
|
@@ -218,13 +218,13 @@ Over `{{comment_cap()}}` characters the rolling item is closed and a successor i
|
|
|
218
218
|
|
|
219
219
|
Load when the resolved tracker provider is `linear` and the operation is `create-release`.
|
|
220
220
|
|
|
221
|
-
**Mechanics held here:** the
|
|
221
|
+
**Mechanics held here:** the shipped-issues step only. Tag creation, release creation and notes composition stay with the operation and are unchanged — they are release-host mechanics, not tracker mechanics.
|
|
222
222
|
|
|
223
223
|
### Process
|
|
224
224
|
|
|
225
225
|
Inside step 5 (compose release notes):
|
|
226
226
|
|
|
227
|
-
- If `SHIPPED_ISSUES` is provided: append a `##
|
|
227
|
+
- If `SHIPPED_ISSUES` is provided: append a `## Shipped Issues` section rendering each entry through `## Reference Rendering` — **first ≤50 issues** (the same bound `backlink-shipped-issues` applies); if truncated, add a final `…and {n} more issues` line (D4 degrade if enrichment fails).
|
|
228
228
|
- {{common.ref_preflight_list("linear", "**either** anchored form — the team-key form `^[A-Z][A-Z0-9]{0,9}-[1-9][0-9]{0,8}$` or the internal-id form `^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$`", "**ASCII-upper-normalise every entry first.** ", ", never joined into one alternation", " and the section is omitted rather than rendered empty.")}}
|
|
229
229
|
- `## Reference Rendering` absent, or its token discarded by the gate below ⇒ render the reference itself on its own line, and record the discard under `### Substitutions`.
|
|
230
230
|
|
|
@@ -32,16 +32,17 @@ extraction that turns a universal obligation into per-consumer opt-in is the
|
|
|
32
32
|
defect, not the saving.
|
|
33
33
|
|
|
34
34
|
The preamble names it on the SAME physical line that composes the per-operation
|
|
35
|
-
mechanics path, which is what keeps
|
|
36
|
-
|
|
37
|
-
is
|
|
35
|
+
mechanics path, which is what keeps the single convergence point (validation lives
|
|
36
|
+
at the one sink every path passes through, not at each entry) at exactly one line.
|
|
37
|
+
That is sound rather than a loophole: what that rule counts is where a path is
|
|
38
|
+
BUILT, and this one is a fixed literal built from nothing — the validated
|
|
38
39
|
provider token selects the mechanics directory and never reaches this name. A
|
|
39
40
|
per-operation file naming it again is forbidden, and asserted as forbidden.
|
|
40
41
|
|
|
41
42
|
Headings below the first are `###` by grammar, not by taste: a column-0 `## `
|
|
42
43
|
line outside a fence terminates this file's section for every guard that reads it
|
|
43
44
|
through `extractOpSectionFromCorpus`, and everything under it becomes invisible
|
|
44
|
-
while the bytes stay on disk
|
|
45
|
+
while the bytes stay on disk.
|
|
45
46
|
|
|
46
47
|
TWO ROLES, AND THE SECOND ONE IS EMITTED NOWHERE. `@define tool_call_contract()`
|
|
47
48
|
below is the artifact: it becomes `tracker/_mcp.md` and is read once per spawn.
|
|
@@ -81,7 +82,7 @@ to `_common.mds` cost 1.2 s in total. A rule that belongs here by subject and
|
|
|
81
82
|
would be the tenth define belongs in `_common.mds` with its audience stated at
|
|
82
83
|
the define, and this paragraph is the reason.
|
|
83
84
|
|
|
84
|
-
NOTHING MORE GOES INTO THIS MODULE
|
|
85
|
+
NOTHING MORE GOES INTO THIS MODULE. Anything further that wants
|
|
85
86
|
hoisting goes to `_common.mds`, or this module shrinks first; and every importer
|
|
86
87
|
reaches it by ALIAS (`as mcp`), never by a selective import, whose deep copy per
|
|
87
88
|
importing define is the same cliff. `tests/build-mds-compile-time.test.ts` holds
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- ORCHESTRATOR CHARTER ---
|
|
2
2
|
You are the orchestrator of this session: you coordinate, agents produce.
|
|
3
3
|
|
|
4
|
-
Never do work-product mainline: no file edits, no builds, no multi-file reads, no codebase orientation, no debug loops. Delegate all of it.
|
|
4
|
+
Never do work-product mainline: no file edits, no builds, no multi-file reads, no codebase orientation, no debug loops. Delegate all of it, with one bounded exception: the main thread may run only a single git, gh or script command whose output stays under about 40 lines, and never a diff, log or test run.
|
|
5
5
|
|
|
6
6
|
Routing (Agent tool) — pick the roster agent that fits the work:
|
|
7
7
|
- Search and listing: Explore. Codebase orientation: Skim.
|
|
@@ -14,6 +14,8 @@ Stays mainline (judgment work): conversation, decisions, routing, synthesizing a
|
|
|
14
14
|
Operating rules:
|
|
15
15
|
- Decompose mainline. Subagents cannot spawn subagents — you own task breakdown, then delegate leaf tasks.
|
|
16
16
|
- Subagents see none of this conversation. Make every delegation self-contained: goal, constraints, relevant session decisions and facts, exact paths. A deliverable that draws on the conversation (issue, PR, report) needs the substance in the prompt — not a pointer to it.
|
|
17
|
+
- Report cap: ask every direct delegation for a final report of at most about 1,500 tokens — findings, paths and verdicts, not file dumps.
|
|
17
18
|
- Parallelize independent delegations in one message. Git operations stay sequential.
|
|
18
19
|
- Feature knowledge (direct delegations only — workflow skills handle their own): before delegating non-trivial code work, match the task area against .devflow/features/index.md and pass matching KNOWLEDGE.md content as FEATURE_KNOWLEDGE; after delegated changes to a covered area, spawn Knowledge to refresh that KB.
|
|
19
|
-
-
|
|
20
|
+
- Decisions (direct delegations only — workflow skills load their own): pass the index named under PROJECT DECISIONS as DECISIONS_CONTEXT — its content, read once — to every agent that takes it.
|
|
21
|
+
- Plan handoff: if the user's first message begins with `Implement the following plan:`, say so in one sentence, then immediately invoke devflow:implement via the Skill tool with no arguments — the plan is already in this conversation. Do not pause to ask.
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# background-memory-update — Eager working-memory refresh worker
|
|
4
4
|
# Spawned as a detached background process by memory-worker (Stop hook) after the
|
|
5
|
-
# 120s throttle expires. Drains .pending-turns.jsonl → calls `claude -p` (sonnet
|
|
5
|
+
# 120s throttle expires. Drains .pending-turns.jsonl → calls `claude -p` (sonnet 5.5) →
|
|
6
6
|
# rewrites WORKING-MEMORY.md with a git-reconciliation stamp on line 1.
|
|
7
7
|
#
|
|
8
|
-
# Applies
|
|
8
|
+
# Applies the LLM-vs-plumbing principle: artifact content (WORKING-MEMORY.md) is
|
|
9
9
|
# authored by the LLM; this script only does plumbing (lock, queue drain, spawn).
|
|
10
|
-
#
|
|
10
|
+
# Does NOT parse Stop hook JSON. Edit this source file, not the installed copy.
|
|
11
11
|
#
|
|
12
12
|
# Usage: background-memory-update <CWD> [<manifest_path>]
|
|
13
13
|
# <manifest_path> — the devflow-global manifest whose `features.memory` is the
|
|
@@ -73,7 +73,7 @@ MEMORY_DIR="$PROJECT_DEVFLOW_DIR/memory"
|
|
|
73
73
|
QUEUE_FILE="$MEMORY_DIR/.pending-turns.jsonl"
|
|
74
74
|
PROCESSING_FILE="$MEMORY_DIR/.pending-turns.processing"
|
|
75
75
|
MEMORY_FILE="$MEMORY_DIR/WORKING-MEMORY.md"
|
|
76
|
-
STAGED_FILE="$MEMORY_FILE.new" # staging path for CAS write (
|
|
76
|
+
STAGED_FILE="$MEMORY_FILE.new" # staging path for CAS write (D-MEMORY-STAGED-CAS)
|
|
77
77
|
LOCK_DIR="$MEMORY_DIR/.working-memory.lock"
|
|
78
78
|
TRIGGER_FILE="$MEMORY_DIR/.working-memory-last-trigger"
|
|
79
79
|
OK_FILE="$MEMORY_DIR/.last-refresh-ok"
|
|
@@ -99,13 +99,25 @@ fi
|
|
|
99
99
|
# --- Assert cksum availability (required for CAS verification — avoids fail-open swap) ---
|
|
100
100
|
# cksum must be on PATH at startup; a missing binary makes both CAS sentinels collapse
|
|
101
101
|
# to the same "ABSENT" literal, compare equal, and swap unconditionally — reinstating
|
|
102
|
-
# the exact clobber
|
|
103
|
-
# silently degrading.
|
|
102
|
+
# the exact clobber of a human edit the CAS was designed to prevent
|
|
103
|
+
# (D-MEMORY-STAGED-CAS). Fail loudly here rather than silently degrading.
|
|
104
104
|
if ! command -v cksum >/dev/null 2>&1; then
|
|
105
105
|
log "SKIP: cksum not on PATH — CAS verification unavailable, refusing to write"
|
|
106
106
|
exit 0
|
|
107
107
|
fi
|
|
108
108
|
|
|
109
|
+
# --- Never write through a symbolic link (D-HOOKS-NO-SYMLINK, git-marker) ---
|
|
110
|
+
# The lock (`mkdir`), the batch merge (`>>`), the claim heartbeat and the success
|
|
111
|
+
# stamp (`touch`) write these paths in place; the claim and the trim rename onto the
|
|
112
|
+
# batch and the swap onto the working memory, and `mv` moves a file into the folder
|
|
113
|
+
# a link at its target names. The trim's copy is created only where nothing stands.
|
|
114
|
+
# So the run is skipped, before it takes the lock or starts an LLM run, when any of
|
|
115
|
+
# these paths or a folder above them is a link.
|
|
116
|
+
if ! df_no_symlink_below "$PROJECT_ROOT" "$LOCK_DIR" "$QUEUE_FILE" "$PROCESSING_FILE" "$OK_FILE" "$MEMORY_FILE"; then
|
|
117
|
+
log "SKIP: a symbolic link sits on the path to the memory files in $MEMORY_DIR; nothing written"
|
|
118
|
+
exit 0
|
|
119
|
+
fi
|
|
120
|
+
|
|
109
121
|
# --- Worker-level lock (300s stale-break — much longer than learning-lock's 30s) ---
|
|
110
122
|
# This prevents a second worker (spawned 121s later) from double-writing WORKING-MEMORY.md
|
|
111
123
|
# while the first worker's claude -p call (up to 120s) is still in flight.
|
|
@@ -158,7 +170,7 @@ fi
|
|
|
158
170
|
|
|
159
171
|
# Clean up any staged file left by a watchdog-killed prior run.
|
|
160
172
|
# Without this, a stale staged file with a valid stamp would be mistakenly
|
|
161
|
-
# mv-ed to the real path on the NEXT run's CAS check (
|
|
173
|
+
# mv-ed to the real path on the NEXT run's CAS check (D-MEMORY-STAGED-CAS).
|
|
162
174
|
rm -f "$STAGED_FILE" 2>/dev/null || true
|
|
163
175
|
|
|
164
176
|
# --- Orphan-only skip: if queue has no assistant/qa turn, exit and leave the queue ---
|
|
@@ -180,7 +192,7 @@ if [ ! -f "$PROCESSING_FILE" ] && [ -f "$QUEUE_FILE" ] && [ -s "$QUEUE_FILE" ] &
|
|
|
180
192
|
_HAS_CONTENT=$(jq -r 'select(.role=="assistant" or .role=="qa") | .role' "$QUEUE_FILE" 2>/dev/null | head -1 || echo "")
|
|
181
193
|
else
|
|
182
194
|
# SECURITY: pass path via argv, never interpolate into node -e source (avoids shell injection
|
|
183
|
-
# for repo paths containing quotes or special characters
|
|
195
|
+
# for repo paths containing quotes or special characters)
|
|
184
196
|
_HAS_CONTENT=$(node -e '
|
|
185
197
|
const f = process.argv[1];
|
|
186
198
|
const lines = require("fs").readFileSync(f, "utf8").split("\n");
|
|
@@ -210,8 +222,20 @@ if [ -f "$PROCESSING_FILE" ]; then
|
|
|
210
222
|
_PROC_LINES=$(wc -l < "$PROCESSING_FILE" | tr -d ' ')
|
|
211
223
|
if [ "$_PROC_LINES" -gt "$PROC_OVERFLOW_CAP" ]; then
|
|
212
224
|
_PROC_TMP="$PROCESSING_FILE.tmp.$$"
|
|
213
|
-
|
|
214
|
-
|
|
225
|
+
# SEC-2: the batch is the claimed queue, 0600 like the queue itself, and the
|
|
226
|
+
# cold path at session start moves it back to the queue. mv gives it the
|
|
227
|
+
# trimmed copy's inode and mode, so the copy is created under umask 077, and
|
|
228
|
+
# only where nothing stands at its name: builtin tests come first, so an entry
|
|
229
|
+
# already there, a link to a FIFO or a device included, is never opened
|
|
230
|
+
# (noclobber alone would open one, as no regular file stands there), and
|
|
231
|
+
# noclobber then makes the create itself exclusive. Such an entry is removed
|
|
232
|
+
# instead, and the batch is left untrimmed.
|
|
233
|
+
if [ ! -e "$_PROC_TMP" ] && [ ! -L "$_PROC_TMP" ] && (umask 077 && set -o noclobber && tail -"$PROC_OVERFLOW_TARGET" "$PROCESSING_FILE" > "$_PROC_TMP") && mv "$_PROC_TMP" "$PROCESSING_FILE"; then
|
|
234
|
+
log "Processing overflow: truncated from $_PROC_LINES to $PROC_OVERFLOW_TARGET lines"
|
|
235
|
+
else
|
|
236
|
+
rm -f "$_PROC_TMP" 2>/dev/null || true
|
|
237
|
+
log "Processing overflow: not truncated, its copy could not be written ($_PROC_LINES lines)"
|
|
238
|
+
fi
|
|
215
239
|
fi
|
|
216
240
|
elif [ -f "$QUEUE_FILE" ]; then
|
|
217
241
|
mv "$QUEUE_FILE" "$PROCESSING_FILE" 2>/dev/null || { log "SKIP: failed to claim queue (race condition — another worker got it)"; exit 0; }
|
|
@@ -330,37 +354,44 @@ fi
|
|
|
330
354
|
|
|
331
355
|
log "Built $TURN_COUNT turns from queue"
|
|
332
356
|
|
|
333
|
-
# --- Capture pre-run cksum baseline + read existing memory ---
|
|
357
|
+
# --- Capture pre-run cksum baseline (D-MEMORY-STAGED-CAS) + read existing memory ---
|
|
334
358
|
# Baseline captured BEFORE content read so the cksum reflects the exact bytes we
|
|
335
359
|
# synthesised from. ABSENT sentinel when file missing — resolves toward false-conflict,
|
|
336
360
|
# never false-success (a file created externally during the run triggers CONFLICT,
|
|
337
|
-
# which is safer than accepting a write we did not produce).
|
|
361
|
+
# which is safer than accepting a write we did not produce).
|
|
338
362
|
#
|
|
339
363
|
# CKSUM_FAILED: if cksum invocation fails (EACCES, missing binary for this path, etc.)
|
|
340
364
|
# on either side, the CAS must treat it as CONFLICT rather than a match — separating
|
|
341
365
|
# the "cksum invocation failure" sentinel from "file absent" keeps the "resolves toward
|
|
342
366
|
# false-conflict, never false-success" invariant true even when cksum errors.
|
|
367
|
+
#
|
|
368
|
+
# D-HOOKS-NO-SYMLINK (git-marker): the existing memory goes into the prompt, so a
|
|
369
|
+
# working memory a symbolic link leads to is treated as absent. A link at its own
|
|
370
|
+
# path has already skipped the run, since the swap renames onto it (above), so
|
|
371
|
+
# only one that appeared since can reach this read.
|
|
372
|
+
MEMORY_PRESENT=""
|
|
373
|
+
if df_file_below "$PROJECT_ROOT" "$MEMORY_FILE"; then MEMORY_PRESENT="yes"; fi
|
|
343
374
|
CKSUM_FAILED="false"
|
|
344
375
|
PRE_RUN_CKSUM="ABSENT"
|
|
345
|
-
if [ -
|
|
376
|
+
if [ -n "$MEMORY_PRESENT" ]; then
|
|
346
377
|
PRE_RUN_CKSUM=$(cksum "$MEMORY_FILE" 2>/dev/null) || CKSUM_FAILED="true"
|
|
347
378
|
fi
|
|
348
379
|
|
|
349
380
|
EXISTING_MEMORY=""
|
|
350
381
|
MEMORY_READ_LIMIT=65536 # bytes — keep prompt under context limit
|
|
351
|
-
if [ -
|
|
382
|
+
if [ -n "$MEMORY_PRESENT" ]; then
|
|
352
383
|
EXISTING_MEMORY=$(head -c "$MEMORY_READ_LIMIT" "$MEMORY_FILE")
|
|
353
384
|
fi
|
|
354
385
|
|
|
355
386
|
# Sets COMMITS_SINCE_NOTE in caller scope.
|
|
356
387
|
# Reads EXISTING_MEMORY and HEAD_SHA from caller scope (must be set before call).
|
|
357
|
-
# Pure parameter expansion for stamp parsing (
|
|
358
|
-
# with || echo / || true per set -e discipline
|
|
388
|
+
# Pure parameter expansion for stamp parsing (no pipe whose exit status a later stage
|
|
389
|
+
# could mask); git commands are guarded with || echo / || true per set -e discipline.
|
|
359
390
|
compute_commits_since_note() {
|
|
360
391
|
local _stamp_line="${EXISTING_MEMORY%%$'\n'*}" _stamp_sha="" _rest
|
|
361
392
|
case "$_stamp_line" in
|
|
362
393
|
"<!-- memory-head: "[0-9a-f]*" branch: "*)
|
|
363
|
-
# Extract stamp SHA using pure parameter expansion — no subprocess
|
|
394
|
+
# Extract stamp SHA using pure parameter expansion — no subprocess.
|
|
364
395
|
_rest="${_stamp_line#<!-- memory-head: }"
|
|
365
396
|
_stamp_sha="${_rest%% *}"
|
|
366
397
|
;;
|
|
@@ -418,7 +449,7 @@ fi
|
|
|
418
449
|
|
|
419
450
|
# --- Build prompt (passed via STDIN, not argv — turn content may hold secrets) ---
|
|
420
451
|
# SECURITY: argv is visible to ps(1); all user/assistant content goes via stdin.
|
|
421
|
-
# SECURITY:
|
|
452
|
+
# SECURITY: each untrusted block is wrapped in named XML tags and preceded
|
|
422
453
|
# by an explicit containment declaration so injected prose cannot masquerade as operator
|
|
423
454
|
# instructions regardless of positional ordering.
|
|
424
455
|
PROMPT=$(cat <<EOF
|
|
@@ -469,7 +500,7 @@ Instructions:
|
|
|
469
500
|
EOF
|
|
470
501
|
)
|
|
471
502
|
|
|
472
|
-
log "Spawning claude -p (model claude-sonnet-
|
|
503
|
+
log "Spawning claude -p (model claude-sonnet-5-5, ${TURN_COUNT} turns)"
|
|
473
504
|
# SECURITY: never log PROMPT — it contains turn content which may include secrets
|
|
474
505
|
|
|
475
506
|
# --- Watchdog timeout (overridable for tests: DEVFLOW_BG_WATCHDOG_SECS=2) ---
|
|
@@ -481,7 +512,6 @@ WATCHDOG_KILL_GRACE_SECS=5 # grace period between SIGTERM and SIGKILL
|
|
|
481
512
|
# recovery can never evict a still-live worker. If DEVFLOW_BG_WATCHDOG_SECS is raised above
|
|
482
513
|
# 294 (leaving < 1s margin), this will fail loudly rather than silently corrupt the
|
|
483
514
|
# safety guarantee. Checked before spawning claude so a bad override is caught early.
|
|
484
|
-
# avoids PF-011
|
|
485
515
|
_WATCHDOG_TOTAL=$(( WATCHDOG_SECS + WATCHDOG_KILL_GRACE_SECS ))
|
|
486
516
|
if [ "$STALE_THRESHOLD" -le "$_WATCHDOG_TOTAL" ]; then
|
|
487
517
|
echo "[background-memory-update] FATAL: STALE_THRESHOLD ($STALE_THRESHOLD) must exceed watchdog total (${WATCHDOG_SECS}+${WATCHDOG_KILL_GRACE_SECS}=${_WATCHDOG_TOTAL})" >&2
|
|
@@ -509,7 +539,7 @@ fi
|
|
|
509
539
|
# Job-control notifications go to stderr which is redirected to the log — suppress them.
|
|
510
540
|
set -m
|
|
511
541
|
DEVFLOW_BG_UPDATER=1 "$CLAUDE_BIN" -p \
|
|
512
|
-
--model claude-sonnet-
|
|
542
|
+
--model claude-sonnet-5-5 \
|
|
513
543
|
--dangerously-skip-permissions \
|
|
514
544
|
--allowedTools 'Read,Write' \
|
|
515
545
|
--output-format text \
|
|
@@ -547,7 +577,7 @@ if [ "$CLAUDE_EXIT" -gt 128 ]; then
|
|
|
547
577
|
# Retry-count ceiling: this worker is rate-bounded to one spawn per 120s by memory-worker's
|
|
548
578
|
# throttle (.working-memory-last-trigger mtime). Persistent failure is surfaced to the user
|
|
549
579
|
# via session-start-memory State C (stale .last-refresh-ok) rather than a retry counter here,
|
|
550
|
-
# keeping this plumbing script simple
|
|
580
|
+
# keeping this plumbing script simple.
|
|
551
581
|
exit 0
|
|
552
582
|
elif [ "$CLAUDE_EXIT" -ne 0 ]; then
|
|
553
583
|
log "FAIL: claude -p exited with code $CLAUDE_EXIT"
|
|
@@ -555,36 +585,58 @@ elif [ "$CLAUDE_EXIT" -ne 0 ]; then
|
|
|
555
585
|
exit 0
|
|
556
586
|
fi
|
|
557
587
|
|
|
558
|
-
# --- CAS verification (
|
|
559
|
-
#
|
|
560
|
-
#
|
|
561
|
-
#
|
|
588
|
+
# --- CAS verification (staged compare-and-swap) ---
|
|
589
|
+
# D-MEMORY-STAGED-CAS: claude never writes WORKING-MEMORY.md. The prompt names
|
|
590
|
+
# only STAGED_FILE (WORKING-MEMORY.md.new); this function checks that file and
|
|
591
|
+
# moves it into place only when the real file is byte-identical (cksum) to the
|
|
592
|
+
# pre-run read. A changed real file means someone edited it during the run:
|
|
593
|
+
# CONFLICT keeps their version, discards the staged file, leaves .last-refresh-ok
|
|
594
|
+
# untouched and keeps the batch in .processing for the next run. Reason: only our
|
|
595
|
+
# own claude run can create STAGED_FILE between lock-acquire and here, so a valid
|
|
596
|
+
# staged file proves OUR write succeeded. A check on the real file (an mtime bump,
|
|
597
|
+
# a stamped line 1) passes for a human's edit too, and taking that for success
|
|
598
|
+
# would delete an unprocessed batch; a direct write would also overwrite an edit
|
|
599
|
+
# made mid-run.
|
|
562
600
|
#
|
|
563
601
|
# Sets OUTCOME to one of: updated | conflict | failed
|
|
564
602
|
# Reads PRE_RUN_CKSUM, CKSUM_FAILED, STAGED_FILE, MEMORY_FILE from caller scope.
|
|
565
603
|
# Each state is assigned exactly once at the point it is decided (single OUTCOME
|
|
566
|
-
# variable, three states).
|
|
604
|
+
# variable, three states).
|
|
567
605
|
verify_and_swap() {
|
|
568
|
-
|
|
606
|
+
# A symbolic link at the staged path counts as no staged file (D-HOOKS-NO-SYMLINK,
|
|
607
|
+
# git-marker): `head` below would read the file it names, and the swap's `mv`
|
|
608
|
+
# would rename the link itself into the working memory's place. The worker
|
|
609
|
+
# removed that path before the LLM run, so a link there was planted during it.
|
|
610
|
+
# df_file_below checks it before both, and logs the refusal once.
|
|
611
|
+
df_file_below "$PROJECT_ROOT" "$STAGED_FILE" && [ -s "$STAGED_FILE" ] || {
|
|
569
612
|
log "WARN: staged file missing or empty after claude -p run"; OUTCOME="failed"; return; }
|
|
570
613
|
case "$(head -1 "$STAGED_FILE" 2>/dev/null || echo "")" in
|
|
571
614
|
"<!-- memory-head:"*) : ;;
|
|
572
615
|
*) log "WARN: staged file exists but stamp missing on line 1 — treating as failure"
|
|
573
616
|
rm -f "$STAGED_FILE" 2>/dev/null || true; OUTCOME="failed"; return ;;
|
|
574
617
|
esac
|
|
618
|
+
# A symbolic link on the working memory's path refuses the swap
|
|
619
|
+
# (D-HOOKS-NO-SYMLINK, git-marker): the run began without one, so a link there now
|
|
620
|
+
# is a change made during the run, and `mv` would move the staged memory into a
|
|
621
|
+
# folder it names. When the run began with no working memory, the cksum
|
|
622
|
+
# comparison below would take the link for that absent file.
|
|
623
|
+
if ! df_no_symlink_below "$PROJECT_ROOT" "$MEMORY_FILE"; then
|
|
624
|
+
rm -f "$STAGED_FILE" 2>/dev/null || true
|
|
625
|
+
log "CONFLICT: a symbolic link appeared on the path to $MEMORY_FILE during the run — not swapped, leaving .processing for retry"
|
|
626
|
+
OUTCOME="conflict"; return
|
|
627
|
+
fi
|
|
575
628
|
# Re-check real file cksum before swapping.
|
|
576
629
|
# Residual TOCTOU: a human edit landing in the O(ms) window between this
|
|
577
630
|
# cksum re-check and the mv would be clobbered; this is accepted — the
|
|
578
631
|
# staged content was synthesised from the pre-run file, so the next run
|
|
579
632
|
# re-synthesises from the then-current content (no net information loss).
|
|
580
|
-
# applies ADR-023 (staged compare-and-swap)
|
|
581
633
|
local _post="ABSENT" _post_cksum_failed="false"
|
|
582
634
|
if [ -f "$MEMORY_FILE" ]; then
|
|
583
635
|
_post=$(cksum "$MEMORY_FILE" 2>/dev/null) || _post_cksum_failed="true"
|
|
584
636
|
fi
|
|
585
637
|
# If either cksum invocation failed, treat as conflict (fail-closed — resolves toward
|
|
586
638
|
# false-conflict, never false-success; a cksum error must never silently degrade the
|
|
587
|
-
# CAS to "always overwrite", reinstating the clobber
|
|
639
|
+
# CAS to "always overwrite", reinstating the clobber of a human edit it exists to prevent).
|
|
588
640
|
if [ "$CKSUM_FAILED" = "true" ] || [ "$_post_cksum_failed" = "true" ]; then
|
|
589
641
|
rm -f "$STAGED_FILE" 2>/dev/null || true
|
|
590
642
|
log "CONFLICT: cksum failed during CAS verification — leaving .processing for retry (fail-closed)"
|
|
@@ -607,14 +659,26 @@ verify_and_swap() {
|
|
|
607
659
|
OUTCOME="failed"
|
|
608
660
|
verify_and_swap
|
|
609
661
|
|
|
662
|
+
# Each stamp below is a `touch`, which follows a symbolic link, and its path was
|
|
663
|
+
# checked before the LLM run, which can take minutes. So, like the swap's target,
|
|
664
|
+
# it is checked again just before the touch: a link there now is a change made
|
|
665
|
+
# during the run, and the stamp is skipped and logged (D-HOOKS-NO-SYMLINK, git-marker).
|
|
610
666
|
if [ "$OUTCOME" = "updated" ]; then
|
|
611
667
|
rm -f "$PROCESSING_FILE" 2>/dev/null || true
|
|
612
|
-
|
|
613
|
-
|
|
668
|
+
if df_no_symlink_below "$PROJECT_ROOT" "$OK_FILE"; then
|
|
669
|
+
touch "$OK_FILE" 2>/dev/null || true
|
|
670
|
+
log "SUCCESS: queue drained, .last-refresh-ok touched"
|
|
671
|
+
else
|
|
672
|
+
log "SUCCESS: queue drained; .last-refresh-ok not touched, a symbolic link appeared on its path during the run"
|
|
673
|
+
fi
|
|
614
674
|
elif [ "$OUTCOME" = "conflict" ]; then
|
|
615
675
|
# Heartbeat: this batch is still owned by the retry cycle, not orphaned.
|
|
616
676
|
# Keeps session-start-memory's 300s cold path from reclaiming a live retry.
|
|
617
|
-
|
|
677
|
+
if df_no_symlink_below "$PROJECT_ROOT" "$PROCESSING_FILE"; then
|
|
678
|
+
touch "$PROCESSING_FILE" 2>/dev/null || true
|
|
679
|
+
else
|
|
680
|
+
log "Heartbeat skipped: a symbolic link appeared on the path to $PROCESSING_FILE during the run"
|
|
681
|
+
fi
|
|
618
682
|
log "CONFLICT: queue retained in .processing, .last-refresh-ok untouched"
|
|
619
683
|
else
|
|
620
684
|
# Leave .processing for session-start-memory's D56c cold path; do NOT touch .last-refresh-ok
|
|
@@ -84,11 +84,12 @@ source "$SCRIPT_DIR/ensure-devflow-init" "$CWD" || exit 0
|
|
|
84
84
|
source "$SCRIPT_DIR/get-mtime" || { echo "capture-prompt: failed to source get-mtime" >&2; exit 1; }
|
|
85
85
|
source "$SCRIPT_DIR/learning-lock" || { echo "capture-prompt: failed to source learning-lock" >&2; exit 1; }
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
# Each queue is checked from the root it lies below, and its folder created there,
|
|
88
|
+
# by queue_append_row (D-HOOKS-NO-SYMLINK, git-marker).
|
|
89
89
|
TS=$(date +%s)
|
|
90
90
|
queue_append_both \
|
|
91
|
-
"$
|
|
91
|
+
"$PROJECT_ROOT" "$MEMORY_DIR/.pending-turns.jsonl" \
|
|
92
|
+
"$LEDGER_ROOT" "$LEARNING_DIR/.pending-turns.jsonl" \
|
|
92
93
|
"$MEMORY_ENABLED" "$LEARNING_ENABLED" \
|
|
93
94
|
"user" "$PROMPT" "$TS"
|
|
94
95
|
|
|
@@ -140,8 +140,8 @@ source "$SCRIPT_DIR/ensure-devflow-init" "$CWD" || exit 0
|
|
|
140
140
|
source "$SCRIPT_DIR/get-mtime" || { echo "capture-question: failed to source get-mtime" >&2; exit 1; }
|
|
141
141
|
source "$SCRIPT_DIR/learning-lock" || { echo "capture-question: failed to source learning-lock" >&2; exit 1; }
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
# Each queue is checked from the root it lies below, and its folder created there,
|
|
144
|
+
# by queue_append_row (D-HOOKS-NO-SYMLINK, git-marker).
|
|
145
145
|
_QUESTION_COUNT=0
|
|
146
146
|
while IFS=$'\t' read -r Q A; do
|
|
147
147
|
[ -z "$Q" ] && continue
|
|
@@ -150,7 +150,8 @@ while IFS=$'\t' read -r Q A; do
|
|
|
150
150
|
A: ${A}"
|
|
151
151
|
TS=$(date +%s)
|
|
152
152
|
queue_append_both \
|
|
153
|
-
"$
|
|
153
|
+
"$PROJECT_ROOT" "$MEMORY_DIR/.pending-turns.jsonl" \
|
|
154
|
+
"$LEDGER_ROOT" "$LEARNING_DIR/.pending-turns.jsonl" \
|
|
154
155
|
"$MEMORY_ENABLED" "$LEARNING_ENABLED" \
|
|
155
156
|
"qa" "$CONTENT" "$TS"
|
|
156
157
|
_QUESTION_COUNT=$(( _QUESTION_COUNT + 1 ))
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Learning System: capture-turn (Stop Hook)
|
|
4
4
|
# Dual-append: writes the assistant turn to BOTH the memory queue and the
|
|
5
|
-
# learning queue, each independently gated by its own feature flag (AC-F4).
|
|
6
|
-
# the decisions usage scanner (D29 grep-first) regardless of which queue is
|
|
7
|
-
# gated on/off -- memory-disabled projects still run the usage scanner. The
|
|
5
|
+
# learning queue, each independently gated by its own feature flag (AC-F4). The
|
|
8
6
|
# 120s-throttle/nohup-spawn block lives in memory-worker, a separate Stop
|
|
9
7
|
# hook that Claude Code runs in parallel with this one; the worker tolerates a
|
|
10
8
|
# queue this hook has not appended to yet (D-QUEUE-NO-ORPHAN-DELETE).
|
|
@@ -77,20 +75,6 @@ LEARNING_ENABLED="$_QG_LEARNING"
|
|
|
77
75
|
|
|
78
76
|
dbg "MEMORY_ENABLED=$MEMORY_ENABLED LEARNING_ENABLED=$LEARNING_ENABLED"
|
|
79
77
|
|
|
80
|
-
# --- Decisions usage scanner (independent of the memory/learning queue gates below) ---
|
|
81
|
-
# D29: Grep-first reorder -- cheap in-process citation check gates the scanner call.
|
|
82
|
-
# The scanner writes the ledger's usage file, and it runs before ensure-devflow-init,
|
|
83
|
-
# so it takes the D-HOOKS-GIT-ONLY gate itself (df_is_project_root, git-marker via
|
|
84
|
-
# resolve-project-root; zero forks): a `.devflow/` an older devflow left in a
|
|
85
|
-
# non-git directory, or at HOME, is not a project's ledger.
|
|
86
|
-
SCANNER="$SCRIPT_DIR/decisions-usage-scan.cjs"
|
|
87
|
-
if [ -f "$SCANNER" ] && printf '%s' "$ASSISTANT_MSG" | grep -qE 'ADR-[0-9]+|PF-[0-9]+'; then
|
|
88
|
-
if [ "$LEARNING_ENABLED" = "true" ] && df_is_project_root "$PROJECT_ROOT" 2>/dev/null; then
|
|
89
|
-
dbg "Running decisions usage scanner"
|
|
90
|
-
printf '%s' "$ASSISTANT_MSG" | node "$SCANNER" --cwd "$LEDGER_ROOT" 2>/dev/null || true
|
|
91
|
-
fi
|
|
92
|
-
fi
|
|
93
|
-
|
|
94
78
|
if [ "$MEMORY_ENABLED" != "true" ] && [ "$LEARNING_ENABLED" != "true" ]; then
|
|
95
79
|
dbg "EXIT: both features disabled"
|
|
96
80
|
exit 0
|
|
@@ -106,11 +90,12 @@ source "$SCRIPT_DIR/ensure-devflow-init" "$CWD" || exit 0
|
|
|
106
90
|
source "$SCRIPT_DIR/get-mtime" || { echo "capture-turn: failed to source get-mtime" >&2; exit 1; }
|
|
107
91
|
source "$SCRIPT_DIR/learning-lock" || { echo "capture-turn: failed to source learning-lock" >&2; exit 1; }
|
|
108
92
|
|
|
109
|
-
|
|
110
|
-
|
|
93
|
+
# Each queue is checked from the root it lies below, and its folder created there,
|
|
94
|
+
# by queue_append_row (D-HOOKS-NO-SYMLINK, git-marker).
|
|
111
95
|
TS=$(date +%s)
|
|
112
96
|
queue_append_both \
|
|
113
|
-
"$
|
|
97
|
+
"$PROJECT_ROOT" "$MEMORY_DIR/.pending-turns.jsonl" \
|
|
98
|
+
"$LEDGER_ROOT" "$LEARNING_DIR/.pending-turns.jsonl" \
|
|
114
99
|
"$MEMORY_ENABLED" "$LEARNING_ENABLED" \
|
|
115
100
|
"assistant" "$ASSISTANT_MSG" "$TS"
|
|
116
101
|
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
# Ensures .devflow/ and all subdirectories exist and the project root .gitignore
|
|
3
3
|
# ignores .devflow/. Called from capture-prompt, capture-turn, capture-question,
|
|
4
4
|
# memory-worker, and pre-compact-memory. Idempotent.
|
|
5
|
-
# Returns non-zero, having written nothing, outside a git project
|
|
5
|
+
# Returns non-zero, having written nothing, outside a git project, at HOME, or when
|
|
6
|
+
# .devflow is a symbolic link.
|
|
6
7
|
# Usage: source ensure-devflow-init "$CWD"
|
|
7
8
|
|
|
8
9
|
[ -z "$1" ] && return 1
|
|
@@ -25,7 +26,7 @@ _EDI_LEDGER="${DF_LEDGER_ROOT:-$_EDI_ROOT}"
|
|
|
25
26
|
# pre-compact-memory) passes through before it writes, and each of them treats a
|
|
26
27
|
# non-zero return as "stop" (`|| exit 0`), so memory and learning stop together
|
|
27
28
|
# in a non-git directory. ensure-root-gitignore itself stays ungated: it is a
|
|
28
|
-
# pure writer its parity suite runs in plain directories
|
|
29
|
+
# pure writer its parity suite runs in plain directories, and both of
|
|
29
30
|
# its callers now decide before reaching it. A helper that failed to source is a
|
|
30
31
|
# command not found — the refusing branch, so the failure mode writes nothing.
|
|
31
32
|
source "$_EDI_DIR/git-marker" 2>/dev/null || true
|
|
@@ -33,6 +34,17 @@ df_is_project_root "$_EDI_ROOT" 2>/dev/null || return 1
|
|
|
33
34
|
|
|
34
35
|
_DEVFLOW_DIR="$_EDI_ROOT/.devflow"
|
|
35
36
|
|
|
37
|
+
# D-HOOKS-NO-SYMLINK (git-marker): a `.devflow` that is itself a symbolic link
|
|
38
|
+
# would take every folder below and the carve-out marker into the folder it names,
|
|
39
|
+
# so nothing is written and the caller stops, as at any root it may not write to.
|
|
40
|
+
# The skip is logged once, through log() where the caller defines it. A folder below
|
|
41
|
+
# `.devflow` that is a link is left to the write under it: `mkdir -p` creates
|
|
42
|
+
# nothing through a link it ends at.
|
|
43
|
+
if ! df_no_symlink_below "$_EDI_ROOT" "$_DEVFLOW_DIR"; then
|
|
44
|
+
declare -F log >/dev/null 2>&1 && log "Skipped: $_DEVFLOW_DIR is a symbolic link; nothing written under it"
|
|
45
|
+
return 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
36
48
|
# learning/ is scaffolded only where the ledger lives. In a linked worktree whose
|
|
37
49
|
# main checkout holds the ledger (D-LEDGER-MAIN-WORKTREE, resolve-project-root) a
|
|
38
50
|
# learning/ here would be an empty directory nothing writes to; the capture hooks
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
# Branding: "subswitch" is an internal identifier (health check body match, SUBSWITCH_CONFIG env var,
|
|
16
16
|
# spawn args); it MUST NOT appear in user-visible strings or additionalContext messages.
|
|
17
17
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
18
|
+
# Port is digit-validated, falling back to the default, before reaching /dev/tcp or context strings.
|
|
19
|
+
# Hook always exits 0; no fd held across exit.
|
|
20
20
|
|
|
21
21
|
# Safe no-op fallback — must exist before hook-bootstrap is sourced
|
|
22
22
|
dbg() { :; }
|
|
@@ -71,7 +71,7 @@ if [ "$PROXY_ENABLED" != "true" ]; then
|
|
|
71
71
|
exit 0
|
|
72
72
|
fi
|
|
73
73
|
|
|
74
|
-
# Digit-validate port before use in /dev/tcp and string interpolation
|
|
74
|
+
# Digit-validate port before use in /dev/tcp and string interpolation
|
|
75
75
|
PROXY_PORT_RAW=$(json_field_file "$PROXY_STATE_FILE" "port" "4141")
|
|
76
76
|
PROXY_PORT="4141"
|
|
77
77
|
case "$PROXY_PORT_RAW" in
|
|
@@ -189,8 +189,7 @@ dbg "PROXY_BIN=$PROXY_BIN PROXY_CONFIG=$PROXY_CONFIG"
|
|
|
189
189
|
# b) `command -v subswitch` — globally installed CLI.
|
|
190
190
|
# Re-resolution is best-effort: on failure the existing warning is emitted and the
|
|
191
191
|
# hook exits 0 as before. The healed path is used for this session only; the next
|
|
192
|
-
# successful `devflow proxy --enable` persists it to proxy.json.
|
|
193
|
-
# avoids PF-009: non-fatal on failure, always exits 0)
|
|
192
|
+
# successful `devflow proxy --enable` persists it to proxy.json.
|
|
194
193
|
if [ -z "$PROXY_BIN" ] || [ ! -f "$PROXY_BIN" ]; then
|
|
195
194
|
log "prereq: binPath missing or not a file (${PROXY_BIN:-<empty>}) — attempting re-resolution"
|
|
196
195
|
_RESOLVED_BIN=""
|
|
@@ -295,7 +294,7 @@ log "spawning relay: $NODE_BIN $PROXY_BIN serve"
|
|
|
295
294
|
(umask 077 && touch "$LOG_FILE") 2>/dev/null || true
|
|
296
295
|
chmod 600 "$LOG_FILE" 2>/dev/null || true
|
|
297
296
|
|
|
298
|
-
# SEC-2 (allowlist): scope the relay's env to exactly what it needs
|
|
297
|
+
# SEC-2 (allowlist): scope the relay's env to exactly what it needs.
|
|
299
298
|
# NODE_EXTRA_CA_CERTS is included when non-empty so corporate-TLS deployments can
|
|
300
299
|
# supply a CA bundle; an unset/empty value is not injected (an empty path causes
|
|
301
300
|
# TLS errors rather than being a no-op). NODE_OPTIONS is deliberately excluded:
|