devflow-kit 2.4.0 → 2.5.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 +156 -0
- package/README.md +86 -18
- package/dist/agents/git.md +824 -0
- package/dist/cli/commands/agents.js +6 -1
- package/dist/cli/commands/attribution-prompts.js +1 -1
- package/dist/cli/commands/compliance-prompts.js +1 -1
- package/dist/cli/commands/compliance.js +23 -1
- package/dist/cli/commands/init-seed.js +24 -26
- package/dist/cli/commands/init.js +502 -71
- package/dist/cli/commands/install-report.js +205 -0
- package/dist/cli/commands/knowledge/index.js +2 -2
- package/dist/cli/commands/knowledge/toggle.js +27 -37
- package/dist/cli/commands/learning.js +37 -30
- package/dist/cli/commands/memory.js +79 -69
- package/dist/cli/commands/prompt-io.js +4 -4
- package/dist/cli/commands/security.js +76 -16
- package/dist/cli/commands/skills.js +53 -7
- package/dist/cli/commands/tracker-prompts.js +145 -0
- package/dist/cli/commands/tracker.js +405 -0
- package/dist/cli/commands/uninstall.js +211 -65
- package/dist/cli.js +2 -0
- package/dist/commands/bug-analysis.md +22 -4
- package/dist/commands/code-review.md +44 -15
- package/dist/commands/debug.md +20 -6
- package/dist/commands/dynamic-build.md +289 -67
- package/dist/commands/dynamic-plan.md +60 -21
- package/dist/commands/dynamic-profile.md +1 -1
- package/dist/commands/dynamic-tickets.md +58 -8
- package/dist/commands/explore.md +2 -2
- package/dist/commands/implement.md +241 -53
- package/dist/commands/plan.md +88 -17
- package/dist/commands/release.md +64 -17
- package/dist/commands/resolve.md +138 -58
- package/dist/commands/self-review.md +2 -2
- package/dist/core/agent-models.js +55 -12
- package/dist/core/assets.js +58 -2
- package/dist/core/evidence-policy.js +147 -0
- package/dist/core/feature-config.js +130 -64
- package/dist/core/feature-switch.js +112 -0
- package/dist/core/flags.js +4 -4
- package/dist/core/manifest.js +33 -7
- package/dist/core/mds-variants.js +861 -0
- package/dist/core/model-discovery.js +12 -1
- package/dist/core/plugins.js +357 -9
- package/dist/core/project-paths.js +1 -1
- package/dist/core/proxy-log.js +8 -6
- package/dist/core/proxy-state.js +11 -8
- package/dist/core/reference-sweep.js +136 -0
- package/dist/core/tracker.js +407 -0
- package/dist/skills/git/references/decision-markers.md +19 -0
- package/dist/skills/git/references/learn-conventions.md +56 -0
- package/dist/skills/git/references/pr/check-ci-status.md +14 -0
- package/dist/skills/git/references/pr/check-merge-readiness.md +28 -0
- package/dist/skills/git/references/pr/ensure-pr-ready.md +24 -0
- package/dist/skills/git/references/pr/fetch-review-threads.md +22 -0
- package/dist/skills/git/references/pr/post-resolution-summary.md +40 -0
- package/dist/skills/git/references/pr/post-review-summary.md +42 -0
- package/dist/skills/git/references/pr/resolve-review-threads.md +35 -0
- package/dist/skills/git/references/pr/update-pr-evidence.md +14 -0
- package/dist/skills/git/references/pr/validate-branch.md +18 -0
- package/dist/skills/git/references/publication-gate.md +13 -0
- package/dist/skills/git/references/tracker/_mcp.md +153 -0
- package/dist/skills/git/references/tracker/github/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +40 -0
- package/dist/skills/git/references/tracker/github/create-release.md +11 -0
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +16 -0
- package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +69 -0
- package/dist/skills/git/references/tracker/github/fetch-issue.md +32 -0
- package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +17 -0
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +19 -0
- package/dist/skills/git/references/tracker/github/manage-debt.md +101 -0
- package/dist/skills/git/references/tracker/github/post-wave-report.md +28 -0
- package/dist/skills/git/references/tracker/github/setup-task.md +26 -0
- package/dist/skills/git/references/tracker/jira/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +49 -0
- package/dist/skills/git/references/tracker/jira/create-release.md +17 -0
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +22 -0
- package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +53 -0
- package/dist/skills/git/references/tracker/jira/fetch-issue.md +14 -0
- package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +15 -0
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +18 -0
- package/dist/skills/git/references/tracker/jira/manage-debt.md +37 -0
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +33 -0
- package/dist/skills/git/references/tracker/jira/setup-task.md +31 -0
- package/dist/skills/git/references/tracker/linear/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +53 -0
- package/dist/skills/git/references/tracker/linear/create-release.md +17 -0
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +22 -0
- package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +53 -0
- package/dist/skills/git/references/tracker/linear/fetch-issue.md +14 -0
- package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +15 -0
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +18 -0
- package/dist/skills/git/references/tracker/linear/manage-debt.md +37 -0
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +33 -0
- package/dist/skills/git/references/tracker/linear/setup-task.md +32 -0
- package/dist/skills/git/references/trust-rule.md +7 -0
- package/dist/targets/claude-code/installer.js +1213 -31
- package/dist/targets/claude-code/legacy.js +5 -0
- package/dist/targets/claude-code/post-install.js +196 -74
- package/dist/targets/claude-code/tracker-install.js +161 -0
- package/package.json +4 -3
- package/src/assets/agents/code.md +42 -4
- package/src/assets/agents/design.md +1 -1
- package/src/assets/agents/git.mds +827 -0
- package/src/assets/agents/knowledge.md +1 -1
- package/src/assets/agents/learning.md +11 -0
- package/src/assets/agents/synthesize.md +1 -1
- package/src/assets/agents/test.md +16 -5
- package/src/assets/agents/tracker.md +467 -0
- package/src/assets/agents/validate.md +7 -5
- package/src/assets/commands/_partials/_engine.mds +11 -9
- package/src/assets/commands/_partials/_evidence_policy.mds +30 -0
- package/src/assets/commands/_partials/_knowledge.mds +2 -2
- package/src/assets/commands/_partials/_plan_contract.mds +22 -7
- package/src/assets/commands/_partials/_preamble.mds +1 -1
- package/src/assets/commands/_partials/_publication.mds +3 -1
- package/src/assets/commands/_partials/_ticket_template.mds +3 -2
- package/src/assets/commands/_partials/_tracker.mds +18 -0
- package/src/assets/commands/_partials/_wave.mds +16 -10
- package/src/assets/commands/bug-analysis.mds +15 -5
- package/src/assets/commands/code-review.mds +34 -14
- package/src/assets/commands/debug.mds +11 -4
- package/src/assets/commands/dynamic-build.mds +227 -41
- package/src/assets/commands/dynamic-plan.mds +35 -13
- package/src/assets/commands/dynamic-tickets.mds +47 -5
- package/src/assets/commands/implement.mds +206 -52
- package/src/assets/commands/plan.mds +70 -17
- package/src/assets/commands/release.md +64 -17
- package/src/assets/commands/resolve.mds +126 -56
- package/src/assets/mds/git/_pr.mds +331 -0
- package/src/assets/mds/git/_references.mds +135 -0
- package/src/assets/mds/tracker/_common.mds +156 -0
- package/src/assets/mds/tracker/_github.mds +472 -0
- package/src/assets/mds/tracker/_jira.mds +407 -0
- package/src/assets/mds/tracker/_linear.mds +449 -0
- package/src/assets/mds/tracker/_mcp.mds +299 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +5 -8
- package/src/assets/scripts/hooks/background-memory-update +14 -9
- package/src/assets/scripts/hooks/capture-prompt +6 -2
- package/src/assets/scripts/hooks/capture-question +6 -2
- package/src/assets/scripts/hooks/capture-turn +6 -2
- package/src/assets/scripts/hooks/ensure-devflow-init +1 -1
- package/src/assets/scripts/hooks/ensure-root-gitignore +161 -60
- package/src/assets/scripts/hooks/hook-log-init +3 -1
- package/src/assets/scripts/hooks/json-helper.cjs +223 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +1 -1
- package/src/assets/scripts/hooks/memory-worker +15 -8
- package/src/assets/scripts/hooks/pre-compact-memory +12 -8
- package/src/assets/scripts/hooks/preamble +1 -4
- package/src/assets/scripts/hooks/queue-append +68 -24
- package/src/assets/scripts/hooks/session-start-context +355 -8
- package/src/assets/scripts/hooks/session-start-memory +12 -8
- package/src/assets/scripts/pr-evidence.cjs +1961 -0
- package/src/assets/scripts/redact-secrets.cjs +490 -62
- package/src/assets/scripts/release-trace.cjs +1143 -0
- package/src/assets/scripts/resolve-evidence-policy.cjs +1065 -0
- package/src/assets/scripts/verify-evidence.cjs +1822 -0
- package/src/assets/skills/compliance/SKILL.md +2 -0
- package/src/assets/skills/docs-framework/SKILL.md +5 -3
- package/src/assets/skills/git/SKILL.md +8 -78
- package/src/assets/skills/git/references/github-api.md +179 -141
- package/src/assets/skills/git/references/patterns.md +11 -6
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/references/patterns.md +6 -61
- package/src/assets/skills/review-methodology/references/violations.md +14 -22
- package/src/assets/agents/git.md +0 -938
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
# rules that govern .devflow/.
|
|
4
4
|
#
|
|
5
5
|
# .devflow/ holds per-developer runtime state (memory, learning, docs, locks) —
|
|
6
|
-
# local by default.
|
|
6
|
+
# local by default. THREE exceptions are shared via git:
|
|
7
7
|
# 1. Feature knowledge bases under .devflow/features/: index.md and every
|
|
8
8
|
# {slug}/KNOWLEDGE.md (tracked, committed by the Knowledge agent at workflow end).
|
|
9
9
|
# 2. .devflow/conventions.md — naming-convention authority written by the Git
|
|
10
10
|
# learn-conventions operation; shared so the team uses one naming source.
|
|
11
|
+
# 3. .devflow/policy.json — the team-owned evidence policy, read from the default
|
|
12
|
+
# branch by resolve-evidence-policy.cjs. Devflow never writes it.
|
|
11
13
|
#
|
|
12
|
-
# Everything else under .devflow/ stays local. A user opts back out
|
|
13
|
-
# `.devflow/features/` or `.devflow/conventions.md` to their own .gitignore.
|
|
14
|
+
# Everything else under .devflow/ stays local. A user opts back out of the first two
|
|
15
|
+
# by re-adding `.devflow/features/` or `.devflow/conventions.md` to their own .gitignore.
|
|
14
16
|
#
|
|
15
17
|
# Re-including files under an ignored tree requires un-ignoring each level with a
|
|
16
18
|
# `dir/*` + `!dir/keep` pair: a bare `.devflow/` excludes the directory outright,
|
|
@@ -23,9 +25,23 @@
|
|
|
23
25
|
# Both reach this one writer so the rule is applied identically everywhere; this
|
|
24
26
|
# decouples git-tracking of .devflow/ from any single feature toggle (avoids PF-014).
|
|
25
27
|
#
|
|
26
|
-
# Idempotent and O(1) after the first run via the .root-gitignore-configured-
|
|
27
|
-
# marker. The marker is
|
|
28
|
-
#
|
|
28
|
+
# Idempotent and O(1) after the first run via the .root-gitignore-configured-v5
|
|
29
|
+
# marker under .devflow/ (project-local). The marker is a claim, not proof, so the
|
|
30
|
+
# fast path also requires the block's own sentinel, the policy line AND a
|
|
31
|
+
# .claudeignore entry to be present; a .gitignore that lost any of them (a merge
|
|
32
|
+
# resolution, a hand edit) is healed on the next run. A v4-marked project misses the
|
|
33
|
+
# fast path, gains the policy line and is re-stamped v5.
|
|
34
|
+
#
|
|
35
|
+
# D-GITIGNORE-V5: the block is detected ONLY by its devflow-unique sentinel
|
|
36
|
+
# `!.devflow/conventions.md` — NEVER by `.claudeignore` or `!.devflow/policy.json`,
|
|
37
|
+
# which users legitimately author themselves. Those two are completion lines: each is
|
|
38
|
+
# topped up when missing, in block order (policy, then .claudeignore). Keying presence
|
|
39
|
+
# off a user-authored line inverts both halves of the contract: projects that already
|
|
40
|
+
# carry that line never receive the carve-out (so .devflow/ runtime files leak into
|
|
41
|
+
# git), and re-appending `.claudeignore` after a user's `!.claudeignore` reverses
|
|
42
|
+
# their intent under gitignore's last-match-wins. Hence: a `.claudeignore` OR
|
|
43
|
+
# `!.claudeignore` entry means "the user owns that line" — emit the block without it.
|
|
44
|
+
# All matching is whole-line and whitespace-tolerant; never substring.
|
|
29
45
|
#
|
|
30
46
|
# Usage: source ensure-root-gitignore "$PROJECT_ROOT"
|
|
31
47
|
# Sourced helper: uses `return` (never exit), _ERG_-prefixed locals (never clobbers
|
|
@@ -34,31 +50,59 @@
|
|
|
34
50
|
[ -z "$1" ] && return 1
|
|
35
51
|
|
|
36
52
|
_ERG_DEVFLOW_DIR="$1/.devflow"
|
|
37
|
-
_ERG_MARKER="$_ERG_DEVFLOW_DIR/.root-gitignore-configured-
|
|
53
|
+
_ERG_MARKER="$_ERG_DEVFLOW_DIR/.root-gitignore-configured-v5"
|
|
38
54
|
_ERG_GITIGNORE="$1/.gitignore"
|
|
39
55
|
|
|
40
|
-
#
|
|
41
|
-
# the
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
# Whole-line, whitespace-tolerant matchers. `*` and `.` are escaped so the ERE
|
|
57
|
+
# matches the literal gitignore patterns.
|
|
58
|
+
_ERG_RE_OPTOUT='^[[:space:]]*/\.devflow/[[:space:]]*$'
|
|
59
|
+
_ERG_RE_LEGACY='^[[:space:]]*\.devflow/[[:space:]]*$'
|
|
60
|
+
_ERG_RE_SENTINEL='^[[:space:]]*!\.devflow/conventions\.md[[:space:]]*$'
|
|
61
|
+
_ERG_RE_SENTINEL_V2='^[[:space:]]*!\.devflow/features/\*/KNOWLEDGE\.md[[:space:]]*$'
|
|
62
|
+
_ERG_RE_POLICY='^[[:space:]]*!\.devflow/policy\.json[[:space:]]*$'
|
|
63
|
+
_ERG_RE_CLAUDEIGNORE='^[[:space:]]*!?\.claudeignore[[:space:]]*$'
|
|
64
|
+
|
|
65
|
+
# Drop every legacy marker beside v5 — builtin `[ -e ]` tests, so rm forks only for a
|
|
66
|
+
# marker that exists. Called from the fast path and from the on-success stamp below,
|
|
67
|
+
# so an older devflow's marker is dropped whichever path a run takes.
|
|
68
|
+
# Always returns 0 (never fails the caller under `set -e`): the last iteration's
|
|
69
|
+
# `[ -e ]` test is false whenever the unversioned marker is absent (the common case),
|
|
70
|
+
# and a function call's own exit status is NOT exempt from errexit the way an inlined
|
|
71
|
+
# for-loop's is — unlike this same loop when it sat directly inside the caller's `{ }`.
|
|
72
|
+
_erg_drop_legacy_markers() {
|
|
73
|
+
for _ERG_OLD in -v4 -v3 -v2 ''; do
|
|
74
|
+
[ -e "$_ERG_DEVFLOW_DIR/.root-gitignore-configured$_ERG_OLD" ] \
|
|
75
|
+
&& rm -f "$_ERG_DEVFLOW_DIR/.root-gitignore-configured$_ERG_OLD" 2>/dev/null
|
|
76
|
+
done
|
|
77
|
+
return 0
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Fast path: converged only when the marker is stamped AND the block sentinel is
|
|
81
|
+
# present AND the policy line is present AND a .claudeignore entry exists. Every
|
|
82
|
+
# other state falls through and recomputes.
|
|
83
|
+
if [ -f "$_ERG_MARKER" ] \
|
|
84
|
+
&& grep -qE "$_ERG_RE_SENTINEL" "$_ERG_GITIGNORE" 2>/dev/null \
|
|
85
|
+
&& grep -qE "$_ERG_RE_POLICY" "$_ERG_GITIGNORE" 2>/dev/null \
|
|
86
|
+
&& grep -qE "$_ERG_RE_CLAUDEIGNORE" "$_ERG_GITIGNORE" 2>/dev/null; then
|
|
87
|
+
_erg_drop_legacy_markers
|
|
88
|
+
return 0
|
|
47
89
|
fi
|
|
48
90
|
|
|
49
91
|
# The marker lives under .devflow/, so the directory must exist before we touch it.
|
|
50
92
|
# (ensure-devflow-init creates it earlier; session-start-context may reach here first.)
|
|
51
93
|
mkdir -p "$_ERG_DEVFLOW_DIR" 2>/dev/null || return 1
|
|
52
94
|
|
|
53
|
-
# The carve-out block, built once
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
|
|
95
|
+
# The carve-out block, built once. _ERG_BLOCK_NO_CI is the block a project that owns
|
|
96
|
+
# its own .claudeignore entry receives; _ERG_BLOCK is that plus the final line.
|
|
97
|
+
# Both trail a newline. Keep byte-identical to DEVFLOW_GITIGNORE_BLOCK and
|
|
98
|
+
# DEVFLOW_GITIGNORE_BLOCK_WITHOUT_CLAUDEIGNORE in
|
|
99
|
+
# src/targets/claude-code/post-install.ts.
|
|
100
|
+
printf -v _ERG_BLOCK_NO_CI '%s\n' \
|
|
57
101
|
'# Devflow runtime data — local by default (memory, learning, docs, locks).' \
|
|
58
|
-
'#
|
|
59
|
-
'#
|
|
60
|
-
'#
|
|
61
|
-
'# to your own .gitignore.' \
|
|
102
|
+
'# Shared via git: feature knowledge bases under .devflow/features/ (index.md and' \
|
|
103
|
+
'# every {slug}/KNOWLEDGE.md), .devflow/conventions.md (naming authority) and' \
|
|
104
|
+
'# .devflow/policy.json (evidence policy). To stop sharing the first two, re-add' \
|
|
105
|
+
'# `.devflow/features/` or `.devflow/conventions.md` to your own .gitignore.' \
|
|
62
106
|
'.devflow/*' \
|
|
63
107
|
'!.devflow/features/' \
|
|
64
108
|
'.devflow/features/*' \
|
|
@@ -66,51 +110,108 @@ printf -v _ERG_BLOCK '%s\n' \
|
|
|
66
110
|
'!.devflow/features/*/' \
|
|
67
111
|
'.devflow/features/*/*' \
|
|
68
112
|
'!.devflow/features/*/KNOWLEDGE.md' \
|
|
69
|
-
'!.devflow/conventions.md'
|
|
113
|
+
'!.devflow/conventions.md' \
|
|
114
|
+
'!.devflow/policy.json'
|
|
115
|
+
printf -v _ERG_LINE_SENTINEL '%s\n' '!.devflow/conventions.md'
|
|
116
|
+
printf -v _ERG_LINE_POLICY '%s\n' '!.devflow/policy.json'
|
|
117
|
+
printf -v _ERG_LINE_CLAUDEIGNORE '%s\n' '.claudeignore'
|
|
118
|
+
_ERG_BLOCK="$_ERG_BLOCK_NO_CI$_ERG_LINE_CLAUDEIGNORE"
|
|
70
119
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
120
|
+
# Does the file already carry the policy line, or a .claudeignore entry of the user's
|
|
121
|
+
# own (either form)? Computed BEFORE the legacy filter below, which rewrites the file.
|
|
122
|
+
_ERG_HAS_POLICY=0
|
|
123
|
+
grep -qE "$_ERG_RE_POLICY" "$_ERG_GITIGNORE" 2>/dev/null && _ERG_HAS_POLICY=1
|
|
124
|
+
_ERG_HAS_CI=0
|
|
125
|
+
grep -qE "$_ERG_RE_CLAUDEIGNORE" "$_ERG_GITIGNORE" 2>/dev/null && _ERG_HAS_CI=1
|
|
126
|
+
|
|
127
|
+
# The completion lines this file lacks, in block order (policy, then .claudeignore).
|
|
128
|
+
# Mirrors missingCompletionLines in the TS twin.
|
|
129
|
+
_ERG_COMPLETION=''
|
|
130
|
+
if [ "$_ERG_HAS_POLICY" = 0 ]; then
|
|
131
|
+
_ERG_COMPLETION="$_ERG_LINE_POLICY"
|
|
132
|
+
fi
|
|
133
|
+
if [ "$_ERG_HAS_CI" = 0 ]; then
|
|
134
|
+
_ERG_COMPLETION="$_ERG_COMPLETION$_ERG_LINE_CLAUDEIGNORE"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Classify: noop (nothing to do) | continue (extend an existing block) |
|
|
138
|
+
# block (install a fresh block) | fail (a rewrite failed; leave the file alone).
|
|
139
|
+
_ERG_MODE=noop
|
|
140
|
+
_ERG_APPEND=''
|
|
141
|
+
if grep -qE "$_ERG_RE_OPTOUT" "$_ERG_GITIGNORE" 2>/dev/null; then
|
|
79
142
|
# User-authored `/.devflow/` (leading slash) — respect it; don't force the carve-out.
|
|
80
|
-
# Checked
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# branches below get this for free by emitting a leading '\n'. `tail -c 1` yields the
|
|
88
|
-
# empty string when the file already ends in a newline (command substitution strips it).
|
|
89
|
-
if [ -n "$(tail -c 1 "$_ERG_GITIGNORE" 2>/dev/null)" ]; then
|
|
90
|
-
printf '\n' >> "$_ERG_GITIGNORE"
|
|
143
|
+
# Checked FIRST so a file containing both it and a sentinel keeps the user's entry.
|
|
144
|
+
_ERG_MODE=noop
|
|
145
|
+
elif grep -qE "$_ERG_RE_SENTINEL" "$_ERG_GITIGNORE" 2>/dev/null; then
|
|
146
|
+
# Block installed (v3 and later) — top up only the completion lines it lacks.
|
|
147
|
+
if [ -n "$_ERG_COMPLETION" ]; then
|
|
148
|
+
_ERG_MODE=continue
|
|
149
|
+
_ERG_APPEND="$_ERG_COMPLETION"
|
|
91
150
|
fi
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# which differs on BSD/GNU). An empty filter result is fine — the block is appended
|
|
97
|
-
# regardless; a leading blank is added only when prior content survived.
|
|
98
|
-
_ERG_TMP="$_ERG_GITIGNORE.devflow-tmp.$$"
|
|
99
|
-
grep -vE '^\.devflow/[[:space:]]*$' "$_ERG_GITIGNORE" 2>/dev/null \
|
|
100
|
-
| grep -vF '# Devflow runtime data (local by default; remove to share via git)' \
|
|
101
|
-
> "$_ERG_TMP" 2>/dev/null
|
|
102
|
-
{ [ -s "$_ERG_TMP" ] && printf '\n'; printf '%s' "$_ERG_BLOCK"; } >> "$_ERG_TMP" \
|
|
103
|
-
&& mv "$_ERG_TMP" "$_ERG_GITIGNORE" && _ERG_OK=1
|
|
104
|
-
[ "$_ERG_OK" = 1 ] || rm -f "$_ERG_TMP" 2>/dev/null
|
|
151
|
+
elif grep -qE "$_ERG_RE_SENTINEL_V2" "$_ERG_GITIGNORE" 2>/dev/null; then
|
|
152
|
+
# v2 block installed — append the lines it lacks, in block order.
|
|
153
|
+
_ERG_MODE=continue
|
|
154
|
+
_ERG_APPEND="$_ERG_LINE_SENTINEL$_ERG_COMPLETION"
|
|
105
155
|
else
|
|
106
|
-
#
|
|
107
|
-
|
|
156
|
+
# No devflow block — install one, omitting the .claudeignore line the user owns.
|
|
157
|
+
_ERG_MODE=block
|
|
158
|
+
if [ "$_ERG_HAS_CI" = 1 ]; then
|
|
159
|
+
_ERG_APPEND="$_ERG_BLOCK_NO_CI"
|
|
160
|
+
else
|
|
161
|
+
_ERG_APPEND="$_ERG_BLOCK"
|
|
162
|
+
fi
|
|
163
|
+
if grep -qE "$_ERG_RE_LEGACY" "$_ERG_GITIGNORE" 2>/dev/null; then
|
|
164
|
+
# Upgrade our legacy wholesale entry: strip the bare `.devflow/` line and our old
|
|
165
|
+
# comment first, then fall through to the shared append below. Portable (grep
|
|
166
|
+
# filter + mv; no sed -i, which differs on BSD/GNU).
|
|
167
|
+
# The pipeline's own status is deliberately ignored: grep -v exits 1 when it
|
|
168
|
+
# emits no lines, which is the correct outcome for a .gitignore whose only
|
|
169
|
+
# content was the legacy entry. The redirect creates _ERG_TMP either way.
|
|
170
|
+
_ERG_TMP="$_ERG_GITIGNORE.devflow-tmp.$$"
|
|
171
|
+
grep -vE "$_ERG_RE_LEGACY" "$_ERG_GITIGNORE" 2>/dev/null \
|
|
172
|
+
| grep -vF '# Devflow runtime data (local by default; remove to share via git)' \
|
|
173
|
+
> "$_ERG_TMP" 2>/dev/null
|
|
174
|
+
if [ -f "$_ERG_TMP" ] && mv "$_ERG_TMP" "$_ERG_GITIGNORE"; then
|
|
175
|
+
:
|
|
176
|
+
else
|
|
177
|
+
rm -f "$_ERG_TMP" 2>/dev/null
|
|
178
|
+
_ERG_MODE=fail
|
|
179
|
+
fi
|
|
180
|
+
fi
|
|
108
181
|
fi
|
|
109
182
|
|
|
110
|
-
#
|
|
183
|
+
# The two append shapes, mirrored byte-for-byte by the TS twin's appendLines and
|
|
184
|
+
# appendBlock. `tail -c 1` yields the empty string when the file already ends in a
|
|
185
|
+
# newline (command substitution strips it), so a file whose last byte is not a
|
|
186
|
+
# newline gets one before the appended text fuses onto its last line. Existing
|
|
187
|
+
# trailing newlines are preserved verbatim — no trimming, no blank-line dedupe.
|
|
188
|
+
_ERG_OK=0
|
|
189
|
+
case "$_ERG_MODE" in
|
|
190
|
+
noop)
|
|
191
|
+
_ERG_OK=1
|
|
192
|
+
;;
|
|
193
|
+
continue)
|
|
194
|
+
if [ -n "$(tail -c 1 "$_ERG_GITIGNORE" 2>/dev/null)" ]; then
|
|
195
|
+
printf '\n' >> "$_ERG_GITIGNORE"
|
|
196
|
+
fi
|
|
197
|
+
printf '%s' "$_ERG_APPEND" >> "$_ERG_GITIGNORE" && _ERG_OK=1
|
|
198
|
+
;;
|
|
199
|
+
block)
|
|
200
|
+
if [ ! -s "$_ERG_GITIGNORE" ]; then
|
|
201
|
+
# Absent or empty — the block is the whole file, with no leading blank line.
|
|
202
|
+
printf '%s' "$_ERG_APPEND" > "$_ERG_GITIGNORE" && _ERG_OK=1
|
|
203
|
+
else
|
|
204
|
+
if [ -n "$(tail -c 1 "$_ERG_GITIGNORE" 2>/dev/null)" ]; then
|
|
205
|
+
printf '\n' >> "$_ERG_GITIGNORE"
|
|
206
|
+
fi
|
|
207
|
+
{ printf '\n'; printf '%s' "$_ERG_APPEND"; } >> "$_ERG_GITIGNORE" && _ERG_OK=1
|
|
208
|
+
fi
|
|
209
|
+
;;
|
|
210
|
+
esac
|
|
211
|
+
|
|
212
|
+
# On success, stamp the current-format v5 marker and drop legacy markers.
|
|
111
213
|
[ "$_ERG_OK" = 1 ] && {
|
|
112
214
|
touch "$_ERG_MARKER"
|
|
113
|
-
|
|
114
|
-
rm -f "$_ERG_DEVFLOW_DIR/.root-gitignore-configured" 2>/dev/null
|
|
215
|
+
_erg_drop_legacy_markers
|
|
115
216
|
}
|
|
116
217
|
return 0
|
|
@@ -29,9 +29,11 @@ LOG_FILE="$LOG_DIR/.${_HOOK_LOG_NAME}.log"
|
|
|
29
29
|
|
|
30
30
|
# Size guard: truncate log to tail 1MB when it exceeds 2MB (runs once per hook invocation)
|
|
31
31
|
# stat -f%z (macOS/BSD) → stat -c%s (GNU/Linux) → wc -c (last resort), matching debug-trace pattern.
|
|
32
|
+
# `2>/dev/null` precedes `<`: redirections apply left to right, so the shell's own
|
|
33
|
+
# "No such file or directory" for a missing log (first run for a cwd) lands in /dev/null.
|
|
32
34
|
_LOG_SIZE=$(stat -f%z "$LOG_FILE" 2>/dev/null) \
|
|
33
35
|
|| _LOG_SIZE=$(stat -c%s "$LOG_FILE" 2>/dev/null) \
|
|
34
|
-
|| _LOG_SIZE=$(wc -c < "$LOG_FILE"
|
|
36
|
+
|| _LOG_SIZE=$(wc -c 2>/dev/null < "$LOG_FILE") \
|
|
35
37
|
|| _LOG_SIZE=0
|
|
36
38
|
if [ -f "$LOG_FILE" ] && [ "$_LOG_SIZE" -gt 2097152 ]; then
|
|
37
39
|
_LTMP="$LOG_FILE.tmp.$$"
|
|
@@ -24,7 +24,12 @@
|
|
|
24
24
|
// session-output <context> Build SessionStart output envelope
|
|
25
25
|
// prompt-output <context> Build UserPromptSubmit output envelope
|
|
26
26
|
// backup-construct Build pre-compact backup JSON from --arg pairs
|
|
27
|
-
// assign-anchor <type> <obs_id>
|
|
27
|
+
// assign-anchor <type> <obs_id> [--allow-collision]
|
|
28
|
+
// Claim next ADR/PF number, render both .md files.
|
|
29
|
+
// Refuses on a pre-mint citation collision (E4) unless
|
|
30
|
+
// --allow-collision is passed.
|
|
31
|
+
// next-anchor <type> Read-only: print the next candidate ADR/PF id and
|
|
32
|
+
// any pre-mint collision hits; mutates nothing (E4)
|
|
28
33
|
// retire-anchor <anchor_id> <status> Flip ledger row status, re-render both .md files
|
|
29
34
|
// refresh-anchor <anchor_id> Re-project log obs onto ledger row, re-render
|
|
30
35
|
// rotate-observations [<log>] [<arch>] Archive observing rows older than 30 days
|
|
@@ -33,6 +38,7 @@
|
|
|
33
38
|
|
|
34
39
|
const fs = require('fs');
|
|
35
40
|
const path = require('path');
|
|
41
|
+
const { execFileSync } = require('child_process');
|
|
36
42
|
|
|
37
43
|
const op = process.argv[2];
|
|
38
44
|
const args = process.argv.slice(3);
|
|
@@ -156,6 +162,149 @@ function nextAnchorFromLedger(ledgerRows, type) {
|
|
|
156
162
|
return { anchorId: `${prefix}-${nextN}`, nextN };
|
|
157
163
|
}
|
|
158
164
|
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Pre-mint collision guard (E4).
|
|
167
|
+
//
|
|
168
|
+
// A design doc can cite a design-local number ("PF-017") in tracked source
|
|
169
|
+
// before the ledger ever mints that same number for an unrelated entry — the
|
|
170
|
+
// two silently collide and nothing catches it until a human notices the text
|
|
171
|
+
// doesn't match. This scans the project tree for a whole-word citation of the
|
|
172
|
+
// candidate id BEFORE assign-anchor writes it, and refuses to mint over a hit.
|
|
173
|
+
// The consumer-side counterpart lives in mdl's scripts/verify-ledger-citations.mjs.
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
/** Directory names excluded from collision scanning at any depth (E4). */
|
|
177
|
+
const COLLISION_SCAN_EXCLUDED_SEGMENTS = new Set(['.git', 'node_modules', 'target', 'dist']);
|
|
178
|
+
|
|
179
|
+
/** Files larger than this are skipped during collision scanning — bounds the scan (E4). */
|
|
180
|
+
const COLLISION_SCAN_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* True when a project-relative path must be excluded from collision scanning:
|
|
184
|
+
* the ledger's own files (`.devflow/learning/**`, self-citation is expected,
|
|
185
|
+
* not a collision) or any of the excluded directory segments.
|
|
186
|
+
*
|
|
187
|
+
* @param {string} relPath - path relative to the project root, either separator style
|
|
188
|
+
* @returns {boolean}
|
|
189
|
+
*/
|
|
190
|
+
function isCollisionScanExcluded(relPath) {
|
|
191
|
+
const norm = relPath.split(path.sep).join('/');
|
|
192
|
+
if (norm === '.devflow/learning' || norm.startsWith('.devflow/learning/')) return true;
|
|
193
|
+
return norm.split('/').some(seg => COLLISION_SCAN_EXCLUDED_SEGMENTS.has(seg));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* List tracked files via `git ls-files` (respects .gitignore; args passed as an
|
|
198
|
+
* array — never shelled through a string-built command). Throws when the
|
|
199
|
+
* project root is not a git working tree or the `git` binary is unavailable;
|
|
200
|
+
* callers fall back to `listFsWalkFiles`.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} projectRoot
|
|
203
|
+
* @returns {string[]} project-relative paths
|
|
204
|
+
*/
|
|
205
|
+
function listGitTrackedFiles(projectRoot) {
|
|
206
|
+
const out = execFileSync('git', ['ls-files', '-z'], {
|
|
207
|
+
cwd: projectRoot,
|
|
208
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
209
|
+
});
|
|
210
|
+
return out.toString('utf8').split('\0').filter(Boolean);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Bounded, non-recursing-into-excluded-dirs fs walk — fallback for a project
|
|
215
|
+
* root that is not a git working tree. The walk is bounded by construction:
|
|
216
|
+
* it only descends into directories actually present on disk, and never
|
|
217
|
+
* descends into an excluded directory at all (E4).
|
|
218
|
+
*
|
|
219
|
+
* @param {string} projectRoot
|
|
220
|
+
* @returns {string[]} project-relative paths
|
|
221
|
+
*/
|
|
222
|
+
function listFsWalkFiles(projectRoot) {
|
|
223
|
+
const results = [];
|
|
224
|
+
const stack = [''];
|
|
225
|
+
while (stack.length > 0) {
|
|
226
|
+
const relDir = stack.pop();
|
|
227
|
+
const absDir = relDir ? path.join(projectRoot, relDir) : projectRoot;
|
|
228
|
+
let entries;
|
|
229
|
+
try {
|
|
230
|
+
entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
231
|
+
} catch {
|
|
232
|
+
continue; // unreadable dir — best-effort scan, skip
|
|
233
|
+
}
|
|
234
|
+
for (const entry of entries) {
|
|
235
|
+
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
236
|
+
if (isCollisionScanExcluded(relPath)) continue;
|
|
237
|
+
if (entry.isDirectory()) {
|
|
238
|
+
stack.push(relPath);
|
|
239
|
+
} else if (entry.isFile()) {
|
|
240
|
+
results.push(relPath);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return results;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Scan the project tree for a whole-word citation of `id` (e.g. `ADR-042`),
|
|
249
|
+
* excluding the ledger's own files and common vendored/build directories.
|
|
250
|
+
* Prefers tracked files (`git ls-files`) when the project root is a git
|
|
251
|
+
* working tree; falls back to a bounded fs walk otherwise. Best-effort:
|
|
252
|
+
* unreadable, binary, or oversized files are skipped rather than failing
|
|
253
|
+
* the scan.
|
|
254
|
+
*
|
|
255
|
+
* @param {string} projectRoot
|
|
256
|
+
* @param {string} id - e.g. 'ADR-042' or 'PF-017'
|
|
257
|
+
* @returns {{ file: string, line: number }[]} hits, empty when no collision
|
|
258
|
+
*/
|
|
259
|
+
function scanForAnchorCollision(projectRoot, id) {
|
|
260
|
+
let files;
|
|
261
|
+
try {
|
|
262
|
+
files = listGitTrackedFiles(projectRoot);
|
|
263
|
+
} catch {
|
|
264
|
+
files = listFsWalkFiles(projectRoot);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
268
|
+
const pattern = new RegExp(`\\b${escaped}\\b`);
|
|
269
|
+
const hits = [];
|
|
270
|
+
for (const relPath of files) {
|
|
271
|
+
if (isCollisionScanExcluded(relPath)) continue;
|
|
272
|
+
const absPath = path.join(projectRoot, relPath);
|
|
273
|
+
let stat;
|
|
274
|
+
try {
|
|
275
|
+
stat = fs.statSync(absPath);
|
|
276
|
+
} catch {
|
|
277
|
+
continue; // race: listed then removed — best-effort scan, skip
|
|
278
|
+
}
|
|
279
|
+
if (!stat.isFile() || stat.size > COLLISION_SCAN_MAX_FILE_BYTES) continue;
|
|
280
|
+
let content;
|
|
281
|
+
try {
|
|
282
|
+
content = fs.readFileSync(absPath, 'utf8');
|
|
283
|
+
} catch {
|
|
284
|
+
continue; // unreadable or invalid utf8 — best-effort scan, skip
|
|
285
|
+
}
|
|
286
|
+
if (content.includes('\u0000')) continue; // binary heuristic
|
|
287
|
+
const lines = content.split('\n');
|
|
288
|
+
for (let i = 0; i < lines.length; i++) {
|
|
289
|
+
if (pattern.test(lines[i])) {
|
|
290
|
+
hits.push({ file: relPath, line: i + 1 });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return hits;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Format collision hits for a stderr/stdout report — one `file:line` per line,
|
|
299
|
+
* two-space indented (E4).
|
|
300
|
+
*
|
|
301
|
+
* @param {{ file: string, line: number }[]} hits
|
|
302
|
+
* @returns {string}
|
|
303
|
+
*/
|
|
304
|
+
function formatCollisionHits(hits) {
|
|
305
|
+
return hits.map(h => ` ${h.file}:${h.line}`).join('\n');
|
|
306
|
+
}
|
|
307
|
+
|
|
159
308
|
/**
|
|
160
309
|
* Read .decisions-usage.json. Returns {version, entries} or empty default.
|
|
161
310
|
* @param {string} projectRoot - Path to project root (cwd)
|
|
@@ -533,20 +682,36 @@ try {
|
|
|
533
682
|
}
|
|
534
683
|
|
|
535
684
|
// -------------------------------------------------------------------------
|
|
536
|
-
// assign-anchor <type> <obs_id>
|
|
685
|
+
// assign-anchor <type> <obs_id> [--allow-collision]
|
|
537
686
|
// AC-A2: Assign next anchor ID for the given type (decision|pitfall) to the
|
|
538
687
|
// observation identified by obs_id in decisions-log.jsonl. Atomic under a
|
|
539
688
|
// single .decisions.lock acquisition. Registers usage, re-renders both .md.
|
|
540
689
|
//
|
|
690
|
+
// E4: before writing, refuses if the candidate id is already cited as a
|
|
691
|
+
// whole word somewhere in tracked source (a pre-mint collision — see
|
|
692
|
+
// scanForAnchorCollision above). --allow-collision skips the scan and
|
|
693
|
+
// mints anyway, for the human-ruled case where the citation should be
|
|
694
|
+
// superseded by the ledger's number.
|
|
695
|
+
//
|
|
541
696
|
// Locking discipline: holds ONLY .decisions.lock (never .observations.lock).
|
|
542
697
|
// O(anchored) — single pass for max numeric suffix (AC-P2).
|
|
543
698
|
// -------------------------------------------------------------------------
|
|
544
699
|
case 'assign-anchor': {
|
|
545
|
-
const
|
|
546
|
-
const
|
|
700
|
+
const aaKnownFlags = new Set(['--allow-collision']);
|
|
701
|
+
const aaFlags = args.filter(a => a.startsWith('--'));
|
|
702
|
+
const aaUnknownFlags = aaFlags.filter(f => !aaKnownFlags.has(f));
|
|
703
|
+
if (aaUnknownFlags.length > 0) {
|
|
704
|
+
process.stderr.write(`assign-anchor: unknown flag(s): ${aaUnknownFlags.join(', ')}\n`);
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
const aaAllowCollision = aaFlags.includes('--allow-collision');
|
|
708
|
+
const aaPositional = args.filter(a => !a.startsWith('--'));
|
|
709
|
+
|
|
710
|
+
const assignType = aaPositional[0]; // 'decision' or 'pitfall'
|
|
711
|
+
const assignObsId = aaPositional[1];
|
|
547
712
|
|
|
548
713
|
if (!assignType || !assignObsId) {
|
|
549
|
-
process.stderr.write('assign-anchor: usage: assign-anchor <type> <obs_id
|
|
714
|
+
process.stderr.write('assign-anchor: usage: assign-anchor <type> <obs_id> [--allow-collision]\n');
|
|
550
715
|
process.exit(1);
|
|
551
716
|
}
|
|
552
717
|
if (assignType !== 'decision' && assignType !== 'pitfall') {
|
|
@@ -565,6 +730,22 @@ try {
|
|
|
565
730
|
// Compute next anchor — O(anchored), single pass
|
|
566
731
|
const { anchorId: aaAnchorId } = nextAnchorFromLedger(aaLedgerRows, assignType);
|
|
567
732
|
|
|
733
|
+
// E4: pre-mint collision guard — refuse if the candidate id is already
|
|
734
|
+
// cited (as a whole word) somewhere in tracked source with a different
|
|
735
|
+
// meaning, before any ledger write. Never auto-skip to the next free
|
|
736
|
+
// number — the collision is a human call (rename the citation, or
|
|
737
|
+
// rerun with --allow-collision to mint over it deliberately).
|
|
738
|
+
if (!aaAllowCollision) {
|
|
739
|
+
const aaCollisionHits = scanForAnchorCollision(aaProjectRoot, aaAnchorId);
|
|
740
|
+
if (aaCollisionHits.length > 0) {
|
|
741
|
+
throw new Error(
|
|
742
|
+
`assign-anchor: '${aaAnchorId}' is already cited in source with a different ` +
|
|
743
|
+
`meaning; resolve the collision before minting (or pass --allow-collision):\n` +
|
|
744
|
+
formatCollisionHits(aaCollisionHits)
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
568
749
|
// Read observation from log
|
|
569
750
|
let aaLogEntries = parseLedger(aaLogPath);
|
|
570
751
|
const aaObsIdx = aaLogEntries.findIndex(e => e.id === assignObsId);
|
|
@@ -648,6 +829,41 @@ try {
|
|
|
648
829
|
break;
|
|
649
830
|
}
|
|
650
831
|
|
|
832
|
+
// -------------------------------------------------------------------------
|
|
833
|
+
// next-anchor <type>
|
|
834
|
+
// E4: Read-only preview of what assign-anchor would mint next — no lock
|
|
835
|
+
// acquired, no file written, no usage entry registered. Prints the
|
|
836
|
+
// candidate id and, when a pre-mint collision guard would fire, its
|
|
837
|
+
// file:line hits — so a caller can check before committing to assign-anchor.
|
|
838
|
+
// -------------------------------------------------------------------------
|
|
839
|
+
case 'next-anchor': {
|
|
840
|
+
const naType = args[0];
|
|
841
|
+
|
|
842
|
+
if (!naType) {
|
|
843
|
+
process.stderr.write('next-anchor: usage: next-anchor <type>\n');
|
|
844
|
+
process.exit(1);
|
|
845
|
+
}
|
|
846
|
+
if (naType !== 'decision' && naType !== 'pitfall') {
|
|
847
|
+
process.stderr.write(`next-anchor: type must be 'decision' or 'pitfall', got '${naType}'\n`);
|
|
848
|
+
process.exit(1);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const naProjectRoot = process.cwd();
|
|
852
|
+
const naLedgerRows = parseLedger(getDecisionsLedgerPath(naProjectRoot));
|
|
853
|
+
const { anchorId: naAnchorId } = nextAnchorFromLedger(naLedgerRows, naType);
|
|
854
|
+
const naHits = scanForAnchorCollision(naProjectRoot, naAnchorId);
|
|
855
|
+
|
|
856
|
+
process.stdout.write(naAnchorId + '\n');
|
|
857
|
+
if (naHits.length > 0) {
|
|
858
|
+
process.stderr.write(
|
|
859
|
+
`next-anchor: '${naAnchorId}' is already cited in source — collision hits:\n` +
|
|
860
|
+
formatCollisionHits(naHits) + '\n'
|
|
861
|
+
);
|
|
862
|
+
process.exit(1);
|
|
863
|
+
}
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
|
|
651
867
|
// -------------------------------------------------------------------------
|
|
652
868
|
// retire-anchor <anchor_id> <status>
|
|
653
869
|
// AC-A3, AC-F5, AC-F7: Flip decisions_status on the ledger row. Idempotent.
|
|
@@ -906,5 +1122,7 @@ if (typeof module !== 'undefined' && module.exports) {
|
|
|
906
1122
|
initDecisionsContent,
|
|
907
1123
|
nextAnchorFromLedger,
|
|
908
1124
|
rotateObservations,
|
|
1125
|
+
scanForAnchorCollision,
|
|
1126
|
+
isCollisionScanExcluded,
|
|
909
1127
|
};
|
|
910
1128
|
}
|
|
@@ -43,7 +43,7 @@ function getDocsDir(projectRoot) {
|
|
|
43
43
|
// Feature config (neutral .devflow root — not inside learning/)
|
|
44
44
|
// ---------------------------------------------------------------------------
|
|
45
45
|
|
|
46
|
-
/** .devflow/config.json —
|
|
46
|
+
/** .devflow/config.json — per-repo facts {reviewPublication, tracker override} */
|
|
47
47
|
function getFeatureConfigPath(projectRoot) {
|
|
48
48
|
return path.join(projectRoot, '.devflow', 'config.json');
|
|
49
49
|
}
|
|
@@ -32,15 +32,19 @@ source "$SCRIPT_DIR/resolve-project-root" 2>/dev/null || true
|
|
|
32
32
|
PROJECT_ROOT="$(df_resolve_root "$CWD" 2>/dev/null || true)"
|
|
33
33
|
[ -n "$PROJECT_ROOT" ] || PROJECT_ROOT="$CWD"
|
|
34
34
|
|
|
35
|
+
# The machine-wide manifest (the memory switch, D-FEATURES-MACHINE-WIDE in
|
|
36
|
+
# queue-append) is user-scope: resolve it from the inherited DEVFLOW_DIR (or
|
|
37
|
+
# ~/.devflow) BEFORE the project-scoped assignment below shadows that value.
|
|
38
|
+
DEVFLOW_MANIFEST="${DEVFLOW_DIR:-$HOME/.devflow}/manifest.json"
|
|
35
39
|
DEVFLOW_DIR="$PROJECT_ROOT/.devflow"
|
|
36
40
|
MEMORY_DIR="$DEVFLOW_DIR/memory"
|
|
37
41
|
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
# Memory gate: the machine-wide switch gates this hook entirely, read by the
|
|
43
|
+
# helper every memory/learning gate shares (D-FEATURES-MACHINE-WIDE, see
|
|
44
|
+
# queue-append). The per-repo config is never consulted.
|
|
45
|
+
source "$SCRIPT_DIR/queue-append" || { echo "memory-worker: failed to source queue-append" >&2; exit 1; }
|
|
46
|
+
queue_read_gates "$DEVFLOW_MANIFEST"
|
|
47
|
+
MEMORY_ENABLED="$_QG_MEMORY"
|
|
44
48
|
|
|
45
49
|
dbg "MEMORY_ENABLED=$MEMORY_ENABLED"
|
|
46
50
|
|
|
@@ -93,8 +97,11 @@ dbg "Throttle passed: TRIGGER_AGE=${TRIGGER_AGE}s >= 120s — spawning worker"
|
|
|
93
97
|
# Touch trigger BEFORE spawning (prevents concurrent Stop hooks from double-spawning)
|
|
94
98
|
touch "$TRIGGER_FILE" 2>/dev/null || true
|
|
95
99
|
|
|
96
|
-
# Spawn detached worker — nohup + disown so it survives the Stop hook process exit
|
|
97
|
-
|
|
100
|
+
# Spawn detached worker — nohup + disown so it survives the Stop hook process exit.
|
|
101
|
+
# The manifest path is handed over explicitly: the worker re-checks the memory
|
|
102
|
+
# switch after spawn, and by now DEVFLOW_DIR names the project .devflow (and is
|
|
103
|
+
# what the worker would inherit, were it exported), not the devflow-global root.
|
|
104
|
+
nohup "$UPDATER" "$CWD" "$DEVFLOW_MANIFEST" </dev/null >>/dev/null 2>&1 & disown
|
|
98
105
|
|
|
99
106
|
log "Spawned background-memory-update worker (CWD=$CWD)"
|
|
100
107
|
dbg "=== HOOK COMPLETE ==="
|
|
@@ -43,20 +43,24 @@ source "$SCRIPT_DIR/resolve-project-root" 2>/dev/null || true
|
|
|
43
43
|
PROJECT_ROOT="$(df_resolve_root "$CWD" 2>/dev/null || true)"
|
|
44
44
|
[ -n "$PROJECT_ROOT" ] || PROJECT_ROOT="$CWD"
|
|
45
45
|
|
|
46
|
+
# The machine-wide manifest (the memory switch, D-FEATURES-MACHINE-WIDE in
|
|
47
|
+
# queue-append) is user-scope: resolve it from the inherited DEVFLOW_DIR (or
|
|
48
|
+
# ~/.devflow) BEFORE the project-scoped assignment below shadows that value.
|
|
49
|
+
DEVFLOW_MANIFEST="${DEVFLOW_DIR:-$HOME/.devflow}/manifest.json"
|
|
46
50
|
DEVFLOW_DIR="$PROJECT_ROOT/.devflow"
|
|
47
51
|
MEMORY_DIR="$DEVFLOW_DIR/memory"
|
|
48
52
|
|
|
49
53
|
# Normal logging
|
|
50
54
|
source "$SCRIPT_DIR/hook-log-init" "pre-compact-memory"
|
|
51
55
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
# Memory gate: the machine-wide switch, read by the helper every memory/learning
|
|
57
|
+
# gate shares (D-FEATURES-MACHINE-WIDE, see queue-append). The per-repo config
|
|
58
|
+
# is never consulted.
|
|
59
|
+
source "$SCRIPT_DIR/queue-append" || { echo "pre-compact-memory: failed to source queue-append" >&2; exit 1; }
|
|
60
|
+
queue_read_gates "$DEVFLOW_MANIFEST"
|
|
61
|
+
if [ "$_QG_MEMORY" != "true" ]; then
|
|
62
|
+
dbg "EXIT: memory disabled machine-wide"
|
|
63
|
+
exit 0
|
|
60
64
|
fi
|
|
61
65
|
|
|
62
66
|
# Auto-create .devflow/ and ensure .gitignore entries (idempotent after first run)
|