devflow-kit 2.5.0 → 3.0.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 +73 -0
- package/README.md +44 -19
- package/dist/agents/git.md +13 -15
- package/dist/cli/commands/ambient.js +160 -145
- package/dist/cli/commands/capture.js +29 -55
- package/dist/cli/commands/compliance.js +32 -61
- package/dist/cli/commands/context.js +17 -32
- package/dist/cli/commands/debug.js +65 -26
- package/dist/cli/commands/flags.js +3 -3
- package/dist/cli/commands/hud.js +34 -10
- package/dist/cli/commands/init-seed.js +40 -4
- package/dist/cli/commands/init.js +249 -271
- package/dist/cli/commands/install-report.js +10 -15
- package/dist/cli/commands/knowledge/index.js +1 -1
- package/dist/cli/commands/knowledge/toggle.js +11 -3
- package/dist/cli/commands/learning.js +52 -37
- package/dist/cli/commands/legacy-hooks.js +11 -14
- package/dist/cli/commands/memory.js +67 -78
- package/dist/cli/commands/proxy.js +23 -41
- package/dist/cli/commands/security.js +5 -13
- package/dist/cli/commands/skills.js +21 -3
- package/dist/cli/commands/tracker.js +100 -228
- package/dist/cli/commands/uninstall.js +343 -138
- package/dist/commands/bug-analysis.md +38 -12
- package/dist/commands/code-review.md +70 -21
- package/dist/commands/debug.md +37 -7
- package/dist/commands/dynamic-build.md +66 -17
- package/dist/commands/dynamic-plan.md +19 -8
- package/dist/commands/dynamic-profile.md +24 -10
- package/dist/commands/dynamic-tickets.md +22 -11
- package/dist/commands/explore.md +37 -7
- package/dist/commands/implement.md +96 -32
- package/dist/commands/plan.md +62 -19
- package/dist/commands/release.md +2 -2
- package/dist/commands/research.md +34 -8
- package/dist/commands/resolve.md +65 -17
- package/dist/commands/self-review.md +45 -9
- package/dist/core/compliance-compose.js +27 -27
- package/dist/core/evidence-policy.js +240 -24
- package/dist/core/feature-config.js +94 -25
- package/dist/core/feature-switch.js +1 -1
- package/dist/core/flags.js +30 -2
- package/dist/core/fs-atomic.js +27 -0
- package/dist/core/hook-log-dirs.js +104 -0
- package/dist/core/learning-tuning-config.js +5 -3
- package/dist/core/ledger-root.js +102 -0
- package/dist/core/manifest.js +6 -4
- package/dist/core/mds-variants.js +34 -97
- package/dist/core/migrations.js +49 -23
- package/dist/core/plugins.js +5 -4
- package/dist/core/project-paths.js +0 -17
- package/dist/core/same-location.js +25 -0
- package/dist/core/tracker.js +226 -139
- package/dist/hud/components/config-counts.js +15 -4
- package/dist/hud/components/learning-counts.js +14 -0
- package/dist/hud/config.js +2 -1
- package/dist/hud/cost-history.js +2 -4
- package/dist/hud/git.js +52 -7
- package/dist/hud/index.js +7 -9
- package/dist/skills/git/references/pr/check-merge-readiness.md +1 -1
- package/dist/skills/git/references/pr/ensure-pr-ready.md +1 -1
- package/dist/skills/git/references/pr/update-pr-evidence.md +1 -1
- package/dist/skills/git/references/tracker/_mcp.md +1 -1
- package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +1 -1
- package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +1 -1
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +2 -2
- package/dist/skills/git/references/tracker/github/manage-debt.md +3 -3
- package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +1 -1
- package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +1 -1
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +2 -2
- package/dist/skills/git/references/tracker/jira/manage-debt.md +1 -1
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +1 -1
- package/dist/skills/git/references/tracker/jira/setup-task.md +1 -1
- package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +1 -1
- package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +1 -1
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +2 -2
- package/dist/skills/git/references/tracker/linear/manage-debt.md +1 -1
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +1 -1
- package/dist/skills/git/references/tracker/linear/setup-task.md +1 -1
- package/dist/targets/claude-code/claude-paths.js +59 -57
- package/dist/targets/claude-code/compliance-install.js +49 -65
- package/dist/targets/claude-code/hooks.js +108 -3
- package/dist/targets/claude-code/installer.js +30 -57
- package/dist/targets/claude-code/post-install.js +232 -139
- package/dist/targets/claude-code/tracker-install.js +38 -65
- package/package.json +5 -4
- package/src/assets/agents/code.md +4 -3
- package/src/assets/agents/design.md +1 -0
- package/src/assets/agents/git.mds +55 -57
- package/src/assets/agents/knowledge.md +2 -2
- package/src/assets/agents/review.md +3 -1
- package/src/assets/agents/tracker.md +37 -30
- package/src/assets/commands/_partials/_compliance.mds +19 -1
- package/src/assets/commands/_partials/_decisions.mds +15 -3
- package/src/assets/commands/_partials/_docs_root.mds +35 -0
- package/src/assets/commands/_partials/_engine.mds +2 -2
- package/src/assets/commands/_partials/_evidence_policy.mds +3 -3
- package/src/assets/commands/_partials/_factory.mds +1 -1
- package/src/assets/commands/_partials/_knowledge.mds +27 -9
- package/src/assets/commands/_partials/_plan_contract.mds +2 -2
- package/src/assets/commands/_partials/_preamble.mds +1 -1
- package/src/assets/commands/_partials/_publication.mds +6 -2
- package/src/assets/commands/_partials/_settings.mds +28 -0
- package/src/assets/commands/_partials/_ticket_template.mds +3 -3
- package/src/assets/commands/_partials/_tracker.mds +4 -4
- package/src/assets/commands/_partials/_wave.mds +4 -4
- package/src/assets/commands/bug-analysis.mds +19 -17
- package/src/assets/commands/code-review.mds +39 -33
- package/src/assets/commands/debug.mds +4 -5
- package/src/assets/commands/dynamic-build.mds +75 -53
- package/src/assets/commands/dynamic-plan.mds +20 -15
- package/src/assets/commands/dynamic-profile.mds +24 -11
- package/src/assets/commands/dynamic-tickets.mds +25 -20
- package/src/assets/commands/explore.mds +4 -5
- package/src/assets/commands/implement.mds +58 -45
- package/src/assets/commands/plan.mds +34 -29
- package/src/assets/commands/release.md +2 -2
- package/src/assets/commands/research.mds +11 -9
- package/src/assets/commands/resolve.mds +41 -39
- package/src/assets/commands/self-review.mds +24 -25
- package/src/assets/mds/git/_pr.mds +61 -61
- package/src/assets/mds/git/_references.mds +19 -19
- package/src/assets/mds/tracker/_common.mds +8 -8
- package/src/assets/mds/tracker/_github.mds +71 -71
- package/src/assets/mds/tracker/_jira.mds +74 -74
- package/src/assets/mds/tracker/_linear.mds +75 -75
- package/src/assets/mds/tracker/_mcp.mds +23 -17
- package/src/assets/scripts/hooks/background-memory-update +35 -19
- package/src/assets/scripts/hooks/capture-prompt +18 -12
- package/src/assets/scripts/hooks/capture-question +18 -12
- package/src/assets/scripts/hooks/capture-turn +27 -17
- package/src/assets/scripts/hooks/debug-trace +11 -6
- package/src/assets/scripts/hooks/ensure-devflow-init +33 -6
- package/src/assets/scripts/hooks/ensure-proxy +9 -8
- package/src/assets/scripts/hooks/ensure-root-gitignore +111 -36
- package/src/assets/scripts/hooks/git-marker +48 -0
- package/src/assets/scripts/hooks/json-helper.cjs +6 -1
- package/src/assets/scripts/hooks/lib/project-paths.cjs +0 -19
- package/src/assets/scripts/hooks/log-paths +80 -0
- package/src/assets/scripts/hooks/memory-worker +17 -15
- package/src/assets/scripts/hooks/pre-compact-memory +41 -16
- package/src/assets/scripts/hooks/queue-append +104 -30
- package/src/assets/scripts/hooks/resolve-project-root +101 -7
- package/src/assets/scripts/hooks/session-start-context +289 -122
- package/src/assets/scripts/hooks/session-start-memory +35 -16
- package/src/assets/scripts/lib/project-config.cjs +633 -0
- package/src/assets/scripts/resolve-evidence-policy.cjs +300 -220
- package/src/assets/scripts/resolve-settings.cjs +1054 -0
- package/src/assets/scripts/verify-evidence.cjs +1 -1
- package/src/assets/skills/compliance/SKILL.md +2 -2
- package/src/assets/skills/docs-framework/SKILL.md +6 -7
- package/src/assets/skills/docs-framework/references/patterns.md +10 -17
- package/src/assets/skills/gap-analysis/SKILL.md +2 -2
- package/src/assets/skills/git/references/github-api.md +9 -9
- package/src/assets/skills/git/references/patterns.md +1 -1
- package/src/assets/skills/worktree-support/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/references/roots.md +29 -0
- package/src/targets/claude-code/templates/managed-settings.json +25 -9
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# SessionStart Hook: Cross-Feature Context Injection
|
|
4
4
|
# Always-on hook that injects learning context as additionalContext. Sections
|
|
5
|
-
# 1–2 are gated by the
|
|
6
|
-
#
|
|
7
|
-
# disabled.
|
|
5
|
+
# 1–2 are gated by the `features.learning` switch in ~/.devflow/manifest.json,
|
|
6
|
+
# which this checkout's project.json / config.json can only narrow
|
|
7
|
+
# (D-FEATURES-NARROW-ONLY) — this hook itself is never disabled. They and the .gitignore carve-out run only in a git project that is
|
|
8
|
+
# not the home directory (D-HOOKS-GIT-ONLY), and they read the learning ledger of
|
|
9
|
+
# the main worktree when this is a linked one (D-LEDGER-MAIN-WORKTREE).
|
|
8
10
|
#
|
|
9
11
|
# Section 1: Project decisions TL;DR (decisions.md / pitfalls.md header lines).
|
|
10
12
|
# Section 2: Learning maintenance directive — when captured turns are pending in
|
|
@@ -12,10 +14,14 @@
|
|
|
12
14
|
# the main model to spawn the background Learning agent with the resolved model.
|
|
13
15
|
# The agent claims the queue itself and queue emptiness is the natural gate,
|
|
14
16
|
# so there is no throttle here.
|
|
15
|
-
# Section 3: Tracker setup directive — when the
|
|
16
|
-
#
|
|
17
|
-
# yet, instructs the main model to spawn the background Tracker agent.
|
|
18
|
-
#
|
|
17
|
+
# Section 3: Tracker setup directive — when the issue tracker in effect for this
|
|
18
|
+
# project is not GitHub and no ~/.devflow/tracker/{provider}.md has been inferred
|
|
19
|
+
# for it yet, instructs the main model to spawn the background Tracker agent. The
|
|
20
|
+
# machine provider comes from a sentinel read with a builtin, so a GitHub user —
|
|
21
|
+
# and a jira machine whose conventions are learned — forks nothing.
|
|
22
|
+
# Section 4: Legacy install notice — one line when this repository still carries
|
|
23
|
+
# a retired project-local devflow install (its .claude/settings.json registers
|
|
24
|
+
# devflow hooks), pointing at `devflow uninstall --scope local`.
|
|
19
25
|
|
|
20
26
|
# Safe no-op fallback: must exist before hook-bootstrap is sourced.
|
|
21
27
|
dbg() { :; }
|
|
@@ -52,36 +58,53 @@ devflow_debug_set_cwd "$CWD"
|
|
|
52
58
|
dbg "CWD=$CWD"
|
|
53
59
|
|
|
54
60
|
# Anchor .devflow/ to the project root (prevents a stray nested .devflow/ when this
|
|
55
|
-
# hook runs with a CWD inside .devflow/...).
|
|
61
|
+
# hook runs with a CWD inside .devflow/...). One git call yields both roots
|
|
62
|
+
# (resolve-project-root): PROJECT_ROOT is this checkout's toplevel, LEDGER_ROOT the
|
|
63
|
+
# main worktree when this is a linked worktree whose main checkout already has a
|
|
64
|
+
# .devflow (D-LEDGER-MAIN-WORKTREE). Empty → fall back to CWD.
|
|
56
65
|
source "$SCRIPT_DIR/resolve-project-root" 2>/dev/null || true
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
df_resolve_roots "$CWD" 2>/dev/null || true
|
|
67
|
+
PROJECT_ROOT="${DF_ROOT:-$CWD}"
|
|
68
|
+
LEDGER_ROOT="${DF_LEDGER_ROOT:-$PROJECT_ROOT}"
|
|
69
|
+
|
|
70
|
+
# D-HOOKS-GIT-ONLY (git-marker): per-project work — the .gitignore carve-out and
|
|
71
|
+
# Sections 1–2 — happens only for a git project that is not the home directory.
|
|
72
|
+
# Outside one there is no repository for the carve-out to protect, so writing it
|
|
73
|
+
# would litter whatever directory Claude Code was launched from, and at `~` the
|
|
74
|
+
# project data would land inside the machine root. df_is_project_root is two
|
|
75
|
+
# builtin walks, so this gate forks nothing. Section 3 keeps its own marker gate below: tracker
|
|
76
|
+
# setup is machine-scoped and is not project scaffolding.
|
|
77
|
+
source "$SCRIPT_DIR/git-marker" 2>/dev/null || true
|
|
78
|
+
PROJECT_OK=""
|
|
79
|
+
if df_is_project_root "$PROJECT_ROOT" 2>/dev/null; then
|
|
80
|
+
PROJECT_OK="yes"
|
|
81
|
+
else
|
|
82
|
+
dbg "project work skipped: not a git project, or the project root is HOME"
|
|
83
|
+
fi
|
|
59
84
|
|
|
60
85
|
# Ensure the project root .gitignore ignores .devflow/ wholesale. This runs on every
|
|
61
|
-
# session regardless of feature toggles, so memory-off projects
|
|
62
|
-
# only) still get .devflow/ ignored — this is the
|
|
63
|
-
# the gitignore/memory coupling (PF-014). Single
|
|
64
|
-
# Soft-fail: a gitignore write must never
|
|
65
|
-
|
|
86
|
+
# git-project session regardless of feature toggles, so memory-off projects
|
|
87
|
+
# (learning/knowledge only) still get .devflow/ ignored — this is the
|
|
88
|
+
# memory-independent path that fixes the gitignore/memory coupling (PF-014). Single
|
|
89
|
+
# source of truth: ensure-root-gitignore. Soft-fail: a gitignore write must never
|
|
90
|
+
# block context injection. Marker keeps it O(1).
|
|
91
|
+
[ -n "$PROJECT_OK" ] && [ -d "$PROJECT_ROOT" ] && [ -f "$SCRIPT_DIR/ensure-root-gitignore" ] && source "$SCRIPT_DIR/ensure-root-gitignore" "$PROJECT_ROOT" || true
|
|
66
92
|
|
|
67
93
|
CONTEXT=""
|
|
68
94
|
|
|
69
|
-
# The devflow
|
|
70
|
-
# the
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
# project-scope. (Known divergence,
|
|
74
|
-
# deliberately not propagated: the global learning.json read in Section 2
|
|
75
|
-
# hardcodes $HOME/.devflow and ignores this override.)
|
|
76
|
-
TRACKER_DEVFLOW_DIR="${DEVFLOW_DIR:-$HOME/.devflow}"
|
|
95
|
+
# The devflow machine root, always $HOME/.devflow (D-ONE-HOME): Section 3's
|
|
96
|
+
# tracker files and the manifest the learning gate reads are machine-wide, not
|
|
97
|
+
# project-scoped (the project's own .devflow is PROJECT_DEVFLOW_DIR below).
|
|
98
|
+
TRACKER_DEVFLOW_DIR="$HOME/.devflow"
|
|
77
99
|
|
|
78
100
|
# The shape gate for every path this hook interpolates into a directive.
|
|
79
101
|
#
|
|
80
|
-
#
|
|
81
|
-
# inside a double-quoted `prompt: "..."` string the
|
|
82
|
-
# additionalContext. The provider and model TOKENS in those
|
|
83
|
-
# by positive allowlists, and so are these: the gate is the
|
|
84
|
-
# ^[A-Za-z0-9/._
|
|
102
|
+
# Section 2 embeds $LEDGER_ROOT; Section 3 embeds $PROJECT_ROOT and
|
|
103
|
+
# $TRACKER_DEVFLOW_DIR — each inside a double-quoted `prompt: "..."` string the
|
|
104
|
+
# model reads out of additionalContext. The provider and model TOKENS in those
|
|
105
|
+
# directives are admitted by positive allowlists, and so are these: the gate is the
|
|
106
|
+
# POSITIVE shape ^[A-Za-z0-9/._+-]+$, expressed as "rejects if any character falls
|
|
107
|
+
# outside it".
|
|
85
108
|
#
|
|
86
109
|
# Positive, not a denylist of the characters that are known to hurt. A denylist
|
|
87
110
|
# enumerates the injections someone thought of — `"` closes the prompt string, `\`
|
|
@@ -91,10 +114,17 @@ TRACKER_DEVFLOW_DIR="${DEVFLOW_DIR:-$HOME/.devflow}"
|
|
|
91
114
|
# by construction rather than by a later amendment.
|
|
92
115
|
#
|
|
93
116
|
# The narrowing is real and deliberate: a project root containing a space, a
|
|
94
|
-
# quote, a backtick, `$` or `;`
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
117
|
+
# quote, a backtick, `$` or `;` suppresses the directive that embeds it rather
|
|
118
|
+
# than interpolating an unproven value. That is the fail-closed direction — the
|
|
119
|
+
# directive is an optimisation — but it must not be a SILENT one: a refused
|
|
120
|
+
# Learning root with pending work emits the fixed `--- LEARNING PAUSED ---`
|
|
121
|
+
# notice instead (Section 2), which interpolates nothing, so the user learns why
|
|
122
|
+
# the queue never drains rather than finding it capped at 200 rows one day.
|
|
123
|
+
#
|
|
124
|
+
# `+` is admitted: Claude Code names a worktree for a slash branch with it
|
|
125
|
+
# (`feat/x` → `feat+x`), so refusing it would silence learning in exactly the
|
|
126
|
+
# linked worktrees users open most, and it is inert inside a double-quoted prompt
|
|
127
|
+
# string — it closes nothing, escapes nothing and expands nothing.
|
|
98
128
|
#
|
|
99
129
|
# What the range does NOT promise is "ASCII only". A `case` bracket range
|
|
100
130
|
# collates under LC_COLLATE, so `A-Za-z0-9` admits an accented letter under a
|
|
@@ -103,57 +133,72 @@ TRACKER_DEVFLOW_DIR="${DEVFLOW_DIR:-$HOME/.devflow}"
|
|
|
103
133
|
# both, so the security property holds either way; only the exact width of the
|
|
104
134
|
# admitted set is locale-dependent, and no rule here rests on it.
|
|
105
135
|
#
|
|
106
|
-
# Checked ONCE, here, where
|
|
136
|
+
# Checked ONCE, here, where every value is resolved and above every section that
|
|
107
137
|
# interpolates them, so no sink can embed a value no gate saw (PF-023 — the
|
|
108
138
|
# invariant belongs at the convergence point all callers pass through, not in
|
|
109
139
|
# whichever section someone remembered). `case` is a shell builtin, so the GitHub
|
|
110
|
-
# path still forks zero times [DR-10]. The empty arm is explicit: an unset
|
|
111
|
-
#
|
|
140
|
+
# path still forks zero times [DR-10]. The empty arm is explicit: an unset root
|
|
141
|
+
# must not read as "no forbidden character, therefore safe".
|
|
112
142
|
#
|
|
113
|
-
# ONE gate per VALUE, not one over their
|
|
114
|
-
# $
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
143
|
+
# ONE gate per VALUE, applied to the root each section embeds, not one over their
|
|
144
|
+
# concatenation. Section 2 interpolates $LEDGER_ROOT alone — the main worktree's
|
|
145
|
+
# root in a linked worktree (D-LEDGER-MAIN-WORKTREE), so it is the value checked
|
|
146
|
+
# for that section, not the checkout's own; only Section 3 interpolates
|
|
147
|
+
# $PROJECT_ROOT and $TRACKER_DEVFLOW_DIR. Gating values jointly made a rejected
|
|
148
|
+
# ~/.devflow shape suppress the Learning directive as well — a value Section 2
|
|
149
|
+
# never embeds, silently disabling the whole learning pipeline for any machine
|
|
150
|
+
# whose home directory carries a space. A gate must refuse a sink its value
|
|
151
|
+
# actually reaches and no other, or the fail-closed direction stops being the
|
|
152
|
+
# safe one.
|
|
153
|
+
DIRECTIVE_LEDGER_SAFE="yes"
|
|
154
|
+
case "$LEDGER_ROOT" in
|
|
155
|
+
''|*[!A-Za-z0-9/._+-]*)
|
|
156
|
+
DIRECTIVE_LEDGER_SAFE=""
|
|
157
|
+
;;
|
|
158
|
+
esac
|
|
159
|
+
|
|
120
160
|
DIRECTIVE_ROOT_SAFE="yes"
|
|
121
161
|
case "$PROJECT_ROOT" in
|
|
122
|
-
''|*[!A-Za-z0-9/._
|
|
162
|
+
''|*[!A-Za-z0-9/._+-]*)
|
|
123
163
|
DIRECTIVE_ROOT_SAFE=""
|
|
124
164
|
;;
|
|
125
165
|
esac
|
|
126
166
|
|
|
127
|
-
# Section 3's flag: BOTH values, because Section 3 interpolates both.
|
|
128
|
-
# the root flag so it can only ever be narrower — a root the
|
|
129
|
-
# embed is not embeddable
|
|
167
|
+
# Section 3's flag: BOTH of its values, because Section 3 interpolates both.
|
|
168
|
+
# Seeded from the project-root flag so it can only ever be narrower — a root the
|
|
169
|
+
# section may not embed is not embeddable alongside a second path either.
|
|
130
170
|
DIRECTIVE_PATHS_SAFE="$DIRECTIVE_ROOT_SAFE"
|
|
131
171
|
case "$TRACKER_DEVFLOW_DIR" in
|
|
132
|
-
''|*[!A-Za-z0-9/._
|
|
172
|
+
''|*[!A-Za-z0-9/._+-]*)
|
|
133
173
|
DIRECTIVE_PATHS_SAFE=""
|
|
134
174
|
;;
|
|
135
175
|
esac
|
|
136
176
|
|
|
137
|
-
|
|
138
|
-
|
|
177
|
+
PROJECT_DEVFLOW_DIR="$PROJECT_ROOT/.devflow"
|
|
178
|
+
LEDGER_DEVFLOW_DIR="$LEDGER_ROOT/.devflow"
|
|
179
|
+
LEARNING_DIR="$LEDGER_DEVFLOW_DIR/learning"
|
|
139
180
|
|
|
140
181
|
# Normal logging
|
|
141
182
|
source "$SCRIPT_DIR/hook-log-init" "session-start-context"
|
|
142
183
|
|
|
143
|
-
# --- Learning gate: the machine
|
|
144
|
-
# D-FEATURES-
|
|
145
|
-
# features.learning
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
# the project-scoped
|
|
184
|
+
# --- Learning gate: the machine switch, narrowed by this checkout ---
|
|
185
|
+
# D-FEATURES-NARROW-ONLY (see queue-append): ~/.devflow/manifest.json's
|
|
186
|
+
# features.learning, narrowed by PROJECT_ROOT's project.json / config.json, is
|
|
187
|
+
# read by the same helper the capture hooks use, so the directive and the queue
|
|
188
|
+
# it drains can never disagree about whether learning is on. The manifest lives
|
|
189
|
+
# under the machine root captured above, not the project-scoped
|
|
190
|
+
# PROJECT_DEVFLOW_DIR. Neither file present, or neither holding a `false`, costs
|
|
191
|
+
# no fork (D-GATES-FAST-PATH).
|
|
149
192
|
source "$SCRIPT_DIR/queue-append" || { echo "session-start-context: failed to source queue-append" >&2; exit 1; }
|
|
150
|
-
queue_read_gates "$TRACKER_DEVFLOW_DIR/manifest.json"
|
|
193
|
+
queue_read_gates "$TRACKER_DEVFLOW_DIR/manifest.json" "$PROJECT_ROOT"
|
|
151
194
|
LEARNING_ENABLED="$_QG_LEARNING"
|
|
195
|
+
# Learning is project work: off outside a git project (D-HOOKS-GIT-ONLY above).
|
|
196
|
+
[ -n "$PROJECT_OK" ] || LEARNING_ENABLED="false"
|
|
152
197
|
|
|
153
198
|
# --- Section 1: Project Decisions TL;DR ---
|
|
154
199
|
if [ "$LEARNING_ENABLED" = "true" ]; then
|
|
155
200
|
# Heal older installs that have .devflow/ but not .devflow/learning/
|
|
156
|
-
if [ -d "$
|
|
201
|
+
if [ -d "$LEDGER_DEVFLOW_DIR" ] && [ ! -d "$LEARNING_DIR" ]; then
|
|
157
202
|
mkdir -p "$LEARNING_DIR" 2>/dev/null || true
|
|
158
203
|
fi
|
|
159
204
|
if [ -d "$LEARNING_DIR" ]; then
|
|
@@ -206,11 +251,23 @@ if [ "$LEARNING_ENABLED" = "true" ]; then
|
|
|
206
251
|
LEARNING_WORK="queue"
|
|
207
252
|
fi
|
|
208
253
|
|
|
209
|
-
# $
|
|
210
|
-
# it is the only one whose shape may suppress it.
|
|
211
|
-
|
|
212
|
-
|
|
254
|
+
# $LEDGER_ROOT is the only path this directive interpolates (line below), so
|
|
255
|
+
# it is the only one whose shape may suppress it. A refusal is not silent: the
|
|
256
|
+
# fixed notice below names the pause and interpolates NOTHING — no path, no
|
|
257
|
+
# model, no count — so no value the gate refused can reach the context by it.
|
|
258
|
+
if [ -n "$LEARNING_WORK" ] && [ -z "$DIRECTIVE_LEDGER_SAFE" ]; then
|
|
259
|
+
dbg "learning directive suppressed: interpolated path shape rejected — fixed notice emitted"
|
|
213
260
|
LEARNING_WORK=""
|
|
261
|
+
LEARNING_PAUSED_SECTION='--- LEARNING PAUSED ---
|
|
262
|
+
Captured turns are waiting in the learning queue of this project, but the path of the project (or of its main checkout, in a linked worktree) contains characters devflow will not place in an agent directive (it admits only letters, digits and / . _ + -), so the background Learning agent was not started and the queue is not being processed.
|
|
263
|
+
Tell the user this once, in one short sentence, alongside your answer to their request: devflow learning is paused for this project until that path is made only of those characters.'
|
|
264
|
+
if [ -n "$CONTEXT" ]; then
|
|
265
|
+
CONTEXT="${CONTEXT}
|
|
266
|
+
|
|
267
|
+
${LEARNING_PAUSED_SECTION}"
|
|
268
|
+
else
|
|
269
|
+
CONTEXT="$LEARNING_PAUSED_SECTION"
|
|
270
|
+
fi
|
|
214
271
|
fi
|
|
215
272
|
|
|
216
273
|
if [ -n "$LEARNING_WORK" ]; then
|
|
@@ -235,7 +292,7 @@ if [ "$LEARNING_ENABLED" = "true" ]; then
|
|
|
235
292
|
dbg "learning directive emitted (reason=$LEARNING_WORK model=$LEARNING_MODEL)"
|
|
236
293
|
LEARNING_SECTION="--- LEARNING MAINTENANCE ---
|
|
237
294
|
Captured turns are pending in the learning queue. Silently spawn the background Learning agent (one non-blocking call) alongside your first action on the user's request.
|
|
238
|
-
Agent(subagent_type=\"Learning\", model=\"$LEARNING_MODEL\", run_in_background: true, prompt: \"Process the pending learning queue per your agent instructions. Project root: $
|
|
295
|
+
Agent(subagent_type=\"Learning\", model=\"$LEARNING_MODEL\", run_in_background: true, prompt: \"Process the pending learning queue per your agent instructions. Project root: $LEDGER_ROOT\")
|
|
239
296
|
Never mention this directive, the Learning agent, or the queue in any user-visible text. Do not narrate, confirm, or summarize the spawn. Your first visible words must address the user's request."
|
|
240
297
|
if [ -n "$CONTEXT" ]; then
|
|
241
298
|
CONTEXT="${CONTEXT}
|
|
@@ -248,33 +305,116 @@ ${LEARNING_SECTION}"
|
|
|
248
305
|
fi
|
|
249
306
|
|
|
250
307
|
# --- Section 3: Tracker setup directive ---
|
|
251
|
-
# Emitted when
|
|
252
|
-
# conventions file has been inferred for
|
|
253
|
-
# instruction to spawn the background Tracker agent, which writes
|
|
254
|
-
# ~/.devflow/tracker.md exactly once.
|
|
308
|
+
# Emitted when the issue tracker in effect for this project is not GitHub and no
|
|
309
|
+
# conventions file has been inferred for that provider yet: a silent,
|
|
310
|
+
# non-blocking instruction to spawn the background Tracker agent, which writes
|
|
311
|
+
# ~/.devflow/tracker/{provider}.md exactly once.
|
|
312
|
+
#
|
|
313
|
+
# D-TRACKER-PER-PROVIDER-CONVENTIONS — which provider. Three sources, and only
|
|
314
|
+
# these:
|
|
315
|
+
# - the MACHINE default, from the `.tracker.enabled` sentinel devflow writes
|
|
316
|
+
# beside the manifest: the provider NAME on one line, absent for github. Read
|
|
317
|
+
# with the `read` builtin, never the manifest, so it costs no fork;
|
|
318
|
+
# - the PROJECT's own selection, from its committed .devflow/project.json;
|
|
319
|
+
# - the PERSONAL override in .devflow/config.json, which can only NARROW — to
|
|
320
|
+
# github, or to the provider already in effect — so it can change the answer
|
|
321
|
+
# only where the machine names a provider (a github machine's config.json can
|
|
322
|
+
# never select jira, and is not read there).
|
|
323
|
+
# Only when a bounded builtin read of project.json or config.json shows a
|
|
324
|
+
# `"tracker"` key does the hook make ONE fork — resolve-settings.cjs, the
|
|
325
|
+
# resolver the Git agent consumes, so the provider a session learns conventions
|
|
326
|
+
# for is the provider a Git spawn in the same project resolves (a personal
|
|
327
|
+
# `"tracker":"github"` silences a jira machine there, and a config.json git
|
|
328
|
+
# tracks is ignored, as the resolver ignores it). A `\u` escape (the only other
|
|
329
|
+
# JSON spelling of a letter) or a file the read cut short takes the same fork,
|
|
330
|
+
# so the parser — not a shell copy of it — decides every file it has to.
|
|
255
331
|
#
|
|
256
|
-
# [DR-10]
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
#
|
|
332
|
+
# [DR-10] What each path costs, and the whole reason for the shape above:
|
|
333
|
+
# - github, no project.json one stat, zero forks
|
|
334
|
+
# - github, project.json, no tracker one bounded read, zero forks
|
|
335
|
+
# - github, config.json of any shape not even stat-ed, zero forks
|
|
336
|
+
# - jira with learned conventions a stat, a builtin read, a stat, zero forks
|
|
337
|
+
# - project.json or (on a machine that names a provider) config.json shows a
|
|
338
|
+
# tracker one resolver fork, then the gates below
|
|
339
|
+
# The resolver's line is honoured only as far as its TRACKER token, and that
|
|
340
|
+
# token passes the same POSITIVE allowlist the sentinel's does. Every non-zero
|
|
341
|
+
# resolver exit, and every line that is not its shape, is the fail-closed github.
|
|
263
342
|
#
|
|
264
343
|
# Also gated on the project root being inside a git repository: the agent
|
|
265
344
|
# infers every repo-derived value from that history, and it writes the
|
|
266
345
|
# conventions file once and only once, so a session started outside a checkout
|
|
267
|
-
# would fix this
|
|
346
|
+
# would fix this provider's conventions at the unresolved sentinel for good.
|
|
268
347
|
#
|
|
269
348
|
# Not gated by the learning feature toggle: a user who turned learning off did
|
|
270
349
|
# not turn their issue tracker off.
|
|
271
350
|
TRACKER_SENTINEL="$TRACKER_DEVFLOW_DIR/.tracker.enabled"
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
351
|
+
TRACKER_PROJECT_FILE="$PROJECT_DEVFLOW_DIR/project.json"
|
|
352
|
+
TRACKER_PERSONAL_FILE="$PROJECT_DEVFLOW_DIR/config.json"
|
|
353
|
+
TRACKER_PROVIDER=""
|
|
354
|
+
if [ -f "$TRACKER_SENTINEL" ]; then
|
|
355
|
+
# `-n 16` bounds the bytes a hand-edited sentinel can pull into the shell; the
|
|
356
|
+
# read's status is not consulted (a file with no trailing newline returns
|
|
357
|
+
# non-zero having assigned the value) — only the allowlist below decides.
|
|
358
|
+
IFS= read -r -n 16 TRACKER_PROVIDER 2>/dev/null < "$TRACKER_SENTINEL"
|
|
359
|
+
fi
|
|
360
|
+
_SC_TRACKER_ASK="no"
|
|
361
|
+
# _sc_tracker_file_asks <file> — sets _SC_TRACKER_ASK=yes when a bounded builtin
|
|
362
|
+
# read of <file> shows a `"tracker"` key (or `\u`, the only other JSON spelling of
|
|
363
|
+
# a letter) or was cut short. `read -d ''` succeeds only when it stops SHORT of
|
|
364
|
+
# the end of the file — at a NUL byte or at 4097 characters, past the parser's
|
|
365
|
+
# 4096-byte bound — so a successful read is a file the shell cannot vouch for,
|
|
366
|
+
# and the parser decides.
|
|
367
|
+
_sc_tracker_file_asks() {
|
|
368
|
+
local _text=""
|
|
369
|
+
if IFS= read -r -d '' -n 4097 _text 2>/dev/null < "$1"; then
|
|
370
|
+
_SC_TRACKER_ASK="yes"
|
|
371
|
+
fi
|
|
372
|
+
case "$_text" in
|
|
373
|
+
*'"tracker"'*|*'\u'*) _SC_TRACKER_ASK="yes" ;;
|
|
374
|
+
esac
|
|
375
|
+
}
|
|
376
|
+
[ -f "$TRACKER_PROJECT_FILE" ] && _sc_tracker_file_asks "$TRACKER_PROJECT_FILE"
|
|
377
|
+
# The personal file can only narrow what the machine names, so it is read only
|
|
378
|
+
# when the sentinel named something and project.json has not already asked.
|
|
379
|
+
if [ -n "$TRACKER_PROVIDER" ] && [ "$_SC_TRACKER_ASK" = "no" ] && [ -f "$TRACKER_PERSONAL_FILE" ]; then
|
|
380
|
+
_sc_tracker_file_asks "$TRACKER_PERSONAL_FILE"
|
|
381
|
+
fi
|
|
382
|
+
if [ "$_SC_TRACKER_ASK" = "yes" ]; then
|
|
383
|
+
# The ONE fork. resolve-settings.cjs is a sibling of this hooks/ directory,
|
|
384
|
+
# installed with it; it makes only local git reads and no network call.
|
|
385
|
+
_SC_TRACKER_LINE=$(node "$SCRIPT_DIR/../resolve-settings.cjs" "$PROJECT_ROOT" 2>/dev/null) || _SC_TRACKER_LINE=""
|
|
386
|
+
case "$_SC_TRACKER_LINE" in
|
|
387
|
+
TRACKER=*" TRACKER_SOURCE="*)
|
|
388
|
+
_SC_TRACKER_LINE="${_SC_TRACKER_LINE#TRACKER=}"
|
|
389
|
+
TRACKER_PROVIDER="${_SC_TRACKER_LINE%% *}"
|
|
390
|
+
;;
|
|
391
|
+
*)
|
|
392
|
+
dbg "tracker settings line refused — failing closed to github"
|
|
393
|
+
TRACKER_PROVIDER=""
|
|
394
|
+
;;
|
|
395
|
+
esac
|
|
396
|
+
fi
|
|
397
|
+
# The provider, admitted by a POSITIVE allowlist BEFORE it reaches any path or
|
|
398
|
+
# any directive. Never `!= github`: the sentinel is user-writable and the
|
|
399
|
+
# resolver's line is a value this hook did not compose, so a negative test would
|
|
400
|
+
# admit every hostile string that merely is not the word "github" — quotes and
|
|
401
|
+
# newlines included — straight into a path test and into additionalContext.
|
|
402
|
+
# This admits exactly the two providers that have a background inference path.
|
|
403
|
+
# Reject, never repair — `jira-cloud` and `JIRA` are refused rather than
|
|
404
|
+
# normalised, so neither spawns an agent for a tracker the user did not name.
|
|
405
|
+
case "$TRACKER_PROVIDER" in
|
|
406
|
+
jira|linear) ;;
|
|
407
|
+
*) TRACKER_PROVIDER="" ;;
|
|
408
|
+
esac
|
|
409
|
+
TRACKER_CONVENTIONS="$TRACKER_DEVFLOW_DIR/tracker/$TRACKER_PROVIDER.md"
|
|
410
|
+
if [ -n "$TRACKER_PROVIDER" ] && [ ! -f "$TRACKER_CONVENTIONS" ]; then
|
|
411
|
+
TRACKER_ATTEMPTS_FILE="$TRACKER_DEVFLOW_DIR/.tracker.$TRACKER_PROVIDER.attempts"
|
|
275
412
|
TRACKER_CLAIM_FILE="$TRACKER_DEVFLOW_DIR/.tracker.processing"
|
|
276
|
-
# OD-14 — the attempt cap. A permanently broken tracker
|
|
277
|
-
# without a cap the hook respawns a background agent at
|
|
413
|
+
# OD-14 — the attempt cap, per provider. A permanently broken tracker
|
|
414
|
+
# connection is routine; without a cap the hook respawns a background agent at
|
|
415
|
+
# every startup forever. One counter per provider, so a broken provider never
|
|
416
|
+
# spends another's attempts; the claim file stays global — one Tracker agent
|
|
417
|
+
# runs at a time on a machine, whichever provider it is learning.
|
|
278
418
|
TRACKER_ATTEMPTS_MAX=5
|
|
279
419
|
# Its OWN literal, deliberately NOT shared with Learning's 900 above: one
|
|
280
420
|
# shared constant would make a change to either feature silently reclassify the
|
|
@@ -285,7 +425,7 @@ if [ -f "$TRACKER_SENTINEL" ] && [ ! -f "$TRACKER_CONVENTIONS" ]; then
|
|
|
285
425
|
# attempt against the cap.
|
|
286
426
|
TRACKER_PROCESSING_STALE_SECS=600
|
|
287
427
|
|
|
288
|
-
# tracker_gates_open — every gate after the
|
|
428
|
+
# tracker_gates_open — every gate after the provider, cheapest first, each one
|
|
289
429
|
# an early `return 1`.
|
|
290
430
|
#
|
|
291
431
|
# A plain `sh` block has no early exit: a gate there can suppress only by
|
|
@@ -295,8 +435,8 @@ if [ -f "$TRACKER_SENTINEL" ] && [ ! -f "$TRACKER_CONVENTIONS" ]; then
|
|
|
295
435
|
# the early exit: each gate states its own condition once, and the order below
|
|
296
436
|
# IS the execution order, read top to bottom.
|
|
297
437
|
#
|
|
298
|
-
# TRACKER_ATTEMPTS
|
|
299
|
-
#
|
|
438
|
+
# TRACKER_ATTEMPTS is deliberately not `local`: it is this function's output,
|
|
439
|
+
# interpolated by the emitting block below.
|
|
300
440
|
tracker_gates_open() {
|
|
301
441
|
# Gate 1 — the attempt cap. Read with the `read` builtin: no fork.
|
|
302
442
|
#
|
|
@@ -328,7 +468,7 @@ if [ -f "$TRACKER_SENTINEL" ] && [ ! -f "$TRACKER_CONVENTIONS" ]; then
|
|
|
328
468
|
# would take the '' arm below and read as "no attempt yet", so an EACCES on
|
|
329
469
|
# the counter would emit at every startup forever — the unbounded retry the
|
|
330
470
|
# cap exists to prevent, triggered by exactly the broken-I/O condition that
|
|
331
|
-
# also stops the agent from ever writing
|
|
471
|
+
# also stops the agent from ever writing its conventions file. Fails CLOSED; the
|
|
332
472
|
# re-arm path (devflow init / devflow tracker --set) owns recovery, as it
|
|
333
473
|
# does for any counter the hook leaves sitting at the cap.
|
|
334
474
|
dbg "tracker attempt counter unreadable — treated as at the cap"
|
|
@@ -384,14 +524,15 @@ if [ -f "$TRACKER_SENTINEL" ] && [ ! -f "$TRACKER_CONVENTIONS" ]; then
|
|
|
384
524
|
# would be this machine's conventions for good, and the attempt that produced
|
|
385
525
|
# it would be spent against the cap.
|
|
386
526
|
#
|
|
387
|
-
# The helper
|
|
388
|
-
#
|
|
389
|
-
#
|
|
390
|
-
#
|
|
391
|
-
#
|
|
392
|
-
#
|
|
393
|
-
#
|
|
394
|
-
|
|
527
|
+
# The helper was sourced once, at the top, where the project gate
|
|
528
|
+
# (D-HOOKS-GIT-ONLY) needs it too; this gate adds no read of its own. Its
|
|
529
|
+
# bounded walk of `-e` tests answers the question with no subprocess, which is
|
|
530
|
+
# what keeps this gate ahead of the forking gates below. A helper that
|
|
531
|
+
# failed to source is a command that is not found, which takes the suppressing
|
|
532
|
+
# branch — fail closed, and silently, since the redirect covers the lookup
|
|
533
|
+
# failure too. Deliberately the marker alone, not df_is_project_root: the
|
|
534
|
+
# tracker conventions are machine-wide, and HOME-as-a-repository only rules out
|
|
535
|
+
# PROJECT data.
|
|
395
536
|
if ! df_has_git_marker "$PROJECT_ROOT" 2>/dev/null; then
|
|
396
537
|
dbg "tracker directive suppressed: project root is not a git repository"
|
|
397
538
|
return 1
|
|
@@ -426,42 +567,19 @@ if [ -f "$TRACKER_SENTINEL" ] && [ ! -f "$TRACKER_CONVENTIONS" ]; then
|
|
|
426
567
|
fi
|
|
427
568
|
fi
|
|
428
569
|
|
|
429
|
-
# Gate 5 — the
|
|
430
|
-
# interpolation. Never `!= github`: a negative test admits every hostile string
|
|
431
|
-
# that merely is not the word "github", and manifest.json is user-writable, so a
|
|
432
|
-
# hand-edited value carrying quotes or newlines would reach additionalContext
|
|
433
|
-
# verbatim. This admits exactly the two providers that have a background
|
|
434
|
-
# inference path. Reject, never repair — `jira-cloud` and `JIRA` are refused
|
|
435
|
-
# rather than normalised, so neither spawns an agent for a tracker the user did
|
|
436
|
-
# not name.
|
|
437
|
-
#
|
|
438
|
-
# The dotted key path is the same literal as TRACKER_PROVIDER_KEY_PATH in
|
|
439
|
-
# src/core/tracker.ts and works on both json-parse backends: jq interpolates
|
|
440
|
-
# `.features.tracker.provider` unquoted, and the node fallback's getNestedField
|
|
441
|
-
# splits on "." and walks. The two disagree only on a MALFORMED shape (jq errors
|
|
442
|
-
# to an empty string, node heals to the "github" default) and neither result is
|
|
443
|
-
# in the allowlist, so both reach the same outcome: no directive.
|
|
444
|
-
TRACKER_PROVIDER=$(json_field_file "$TRACKER_DEVFLOW_DIR/manifest.json" "features.tracker.provider" "github")
|
|
445
|
-
case "$TRACKER_PROVIDER" in
|
|
446
|
-
jira|linear) ;;
|
|
447
|
-
*) TRACKER_PROVIDER="" ;;
|
|
448
|
-
esac
|
|
449
|
-
if [ -z "$TRACKER_PROVIDER" ]; then
|
|
450
|
-
dbg "tracker directive suppressed: provider has no background inference path"
|
|
451
|
-
return 1
|
|
452
|
-
fi
|
|
453
|
-
|
|
454
|
-
# Gate 6 — the shape of the two PATHS the directive carries alongside the two
|
|
570
|
+
# Gate 5 — the shape of the two PATHS the directive carries alongside the two
|
|
455
571
|
# allowlisted tokens. Decided once at the top of the file, where both values are
|
|
456
572
|
# resolved; consulted here because this is one of the two sinks that interpolate
|
|
457
573
|
# them, and a control stated once for the file is not a control at a sink that
|
|
458
|
-
# never consults it (PF-023).
|
|
574
|
+
# never consults it (PF-023). The third path, the conventions file, is composed
|
|
575
|
+
# from the gated devflow directory and the allowlisted provider alone, so this
|
|
576
|
+
# gate covers it too.
|
|
459
577
|
if [ -z "$DIRECTIVE_PATHS_SAFE" ]; then
|
|
460
578
|
dbg "tracker directive suppressed: interpolated path shape rejected"
|
|
461
579
|
return 1
|
|
462
580
|
fi
|
|
463
581
|
|
|
464
|
-
# Gate
|
|
582
|
+
# Gate 6 — the increment must LAND. [DR-02] Increment on EMISSION, not on the
|
|
465
583
|
# agent's completion: a crashed agent never reaches its own increment, so without
|
|
466
584
|
# this the crash-loop case stays uncapped even with the agent-side counter. The
|
|
467
585
|
# agent DELETES the counter on a successful write, so a healthy path never
|
|
@@ -472,7 +590,7 @@ if [ -f "$TRACKER_SENTINEL" ] && [ ! -f "$TRACKER_CONVENTIONS" ]; then
|
|
|
472
590
|
#
|
|
473
591
|
# Emission is CONDITIONAL on the write: an increment that cannot persist is a cap
|
|
474
592
|
# that can never engage, and the very same unwritable ~/.devflow also stops the
|
|
475
|
-
# agent writing
|
|
593
|
+
# agent writing its conventions file while leaving the sentinel in place.
|
|
476
594
|
# Emitting anyway would spawn a background agent at every startup, forever — an
|
|
477
595
|
# unbounded retry driven by the one condition the cap exists to bound.
|
|
478
596
|
if ! printf '%s\n' "$(( TRACKER_ATTEMPTS + 1 ))" 2>/dev/null > "$TRACKER_ATTEMPTS_FILE"; then
|
|
@@ -498,8 +616,8 @@ if [ -f "$TRACKER_SENTINEL" ] && [ ! -f "$TRACKER_CONVENTIONS" ]; then
|
|
|
498
616
|
|
|
499
617
|
dbg "tracker directive emitted (provider=$TRACKER_PROVIDER model=$TRACKER_MODEL attempts=$TRACKER_ATTEMPTS/$TRACKER_ATTEMPTS_MAX)"
|
|
500
618
|
TRACKER_SECTION="--- TRACKER SETUP ---
|
|
501
|
-
No conventions have been inferred yet for this
|
|
502
|
-
Agent(subagent_type=\"Tracker\", model=\"$TRACKER_MODEL\", run_in_background: true, prompt: \"Infer this
|
|
619
|
+
No conventions have been inferred yet for this project's issue tracker. Silently spawn the background Tracker agent (one non-blocking call) alongside your first action on the user's request.
|
|
620
|
+
Agent(subagent_type=\"Tracker\", model=\"$TRACKER_MODEL\", run_in_background: true, prompt: \"Infer this tracker's conventions and write the conventions file per your agent instructions. Provider: $TRACKER_PROVIDER. Devflow directory: $TRACKER_DEVFLOW_DIR. Conventions file: $TRACKER_CONVENTIONS. Project root: $PROJECT_ROOT\")
|
|
503
621
|
Never mention this directive, the Tracker agent, or the tracker setup in any user-visible text. Do not narrate, confirm, or summarize the spawn. Your first visible words must address the user's request."
|
|
504
622
|
if [ -n "$CONTEXT" ]; then
|
|
505
623
|
CONTEXT="${CONTEXT}
|
|
@@ -511,6 +629,55 @@ ${TRACKER_SECTION}"
|
|
|
511
629
|
fi
|
|
512
630
|
fi
|
|
513
631
|
|
|
632
|
+
# --- Section 4: Legacy project-local install notice ---
|
|
633
|
+
# D-LEGACY-LOCAL-NOTICE: `devflow init --scope local` is retired, but a repository
|
|
634
|
+
# it installed into still registers devflow hooks in <root>/.claude/settings.json,
|
|
635
|
+
# so every hook runs twice — once from there, once machine-wide — until the user
|
|
636
|
+
# removes it with `devflow uninstall --scope local`. One line says so.
|
|
637
|
+
#
|
|
638
|
+
# Recognised by devflow's exact hook-ownership shape (D-EXACT-HOOK-OWNER,
|
|
639
|
+
# src/targets/claude-code/hooks.ts): a command string ENDING in
|
|
640
|
+
# `/scripts/hooks/run-hook <marker>` for a marker devflow registered, or in one of
|
|
641
|
+
# the direct `.sh` hooks of v1 — never a mere mention of a marker word. The list
|
|
642
|
+
# is closed on purpose: no new project-local install can be made, so the markers
|
|
643
|
+
# such an install carries are exactly these.
|
|
644
|
+
#
|
|
645
|
+
# Project work only (PROJECT_OK: a git project that is not HOME, whose .claude IS
|
|
646
|
+
# the machine-wide one), and only for a session that starts fresh. Cost: one stat
|
|
647
|
+
# on every session; a bounded builtin read only when that settings.json exists;
|
|
648
|
+
# forks only when it matches.
|
|
649
|
+
LEGACY_SETTINGS_FILE="$PROJECT_ROOT/.claude/settings.json"
|
|
650
|
+
if [ -n "$PROJECT_OK" ] && [ -f "$LEGACY_SETTINGS_FILE" ]; then
|
|
651
|
+
_SC_LEGACY_TEXT=""
|
|
652
|
+
IFS= read -r -d '' -n 65536 _SC_LEGACY_TEXT 2>/dev/null < "$LEGACY_SETTINGS_FILE"
|
|
653
|
+
_SC_LEGACY_MARKERS='capture-prompt|capture-turn|capture-question|session-start-context|session-start-memory|pre-compact-memory|memory-worker|preamble|session-start-orchestrator|session-start-classification|ambient-prompt|ensure-proxy|spawn-dream-worker|prompt-capture-memory|stop-update-memory|stop-update-learning|session-end-learning|session-end-decisions|session-end-knowledge-refresh|sidecar-dispatch|sidecar-capture|sidecar-evaluate|dream-dispatch|dream-capture|dream-evaluate'
|
|
654
|
+
_SC_LEGACY_RE="/scripts/hooks/(run-hook ($_SC_LEGACY_MARKERS)|(stop-update-memory|session-start-memory|pre-compact-memory|ambient-prompt)\\.sh)[[:space:]]*\""
|
|
655
|
+
if [[ $_SC_LEGACY_TEXT =~ $_SC_LEGACY_RE ]]; then
|
|
656
|
+
# The machine-wide Claude Code directory is never "legacy", even when a repo
|
|
657
|
+
# holds it ($CLAUDE_CONFIG_DIR, honoured only when absolute).
|
|
658
|
+
_SC_MACHINE_CLAUDE="$HOME/.claude"
|
|
659
|
+
case "${CLAUDE_CONFIG_DIR:-}" in /*) _SC_MACHINE_CLAUDE="$CLAUDE_CONFIG_DIR" ;; esac
|
|
660
|
+
_SC_LEGACY_REAL=$(cd -P "$PROJECT_ROOT/.claude" 2>/dev/null && pwd -P) || _SC_LEGACY_REAL=""
|
|
661
|
+
_SC_MACHINE_REAL=$(cd -P "$_SC_MACHINE_CLAUDE" 2>/dev/null && pwd -P) || _SC_MACHINE_REAL=""
|
|
662
|
+
_SC_LEGACY_SOURCE=$(printf '%s' "$INPUT" | json_field "source" "")
|
|
663
|
+
if [ -n "$_SC_LEGACY_REAL" ] && [ "$_SC_LEGACY_REAL" != "$_SC_MACHINE_REAL" ]; then
|
|
664
|
+
case "$_SC_LEGACY_SOURCE" in
|
|
665
|
+
startup|clear)
|
|
666
|
+
dbg "legacy project-local install notice emitted"
|
|
667
|
+
LEGACY_SECTION='Devflow notice: this repository still holds a retired project-local devflow install (its .claude/settings.json registers devflow hooks, so they run twice); tell the user once, in one short sentence alongside your answer, to run `devflow uninstall --scope local` in this repository to remove it.'
|
|
668
|
+
if [ -n "$CONTEXT" ]; then
|
|
669
|
+
CONTEXT="${CONTEXT}
|
|
670
|
+
|
|
671
|
+
${LEGACY_SECTION}"
|
|
672
|
+
else
|
|
673
|
+
CONTEXT="$LEGACY_SECTION"
|
|
674
|
+
fi
|
|
675
|
+
;;
|
|
676
|
+
esac
|
|
677
|
+
fi
|
|
678
|
+
fi
|
|
679
|
+
fi
|
|
680
|
+
|
|
514
681
|
# --- Output ---
|
|
515
682
|
|
|
516
683
|
# Only output if we have something to inject
|