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
|
@@ -66,11 +66,8 @@ elif [[ "$HEAD" == "Implement the following plan:"* ]]; then
|
|
|
66
66
|
elif [[ "$HEAD" == "/"* ]]; then
|
|
67
67
|
dbg "EXIT: slash command — no reminder"
|
|
68
68
|
else
|
|
69
|
-
# Model-tier taxonomy (haiku=mechanical, sonnet=defined execution, opus=analysis/design/research)
|
|
70
|
-
# is cross-referenced with the full routing table in orchestrator-charter.md.
|
|
71
|
-
# Update both together if routing changes.
|
|
72
69
|
dbg "ORCHESTRATOR_REMINDER injected"
|
|
73
|
-
json_prompt_output "Orchestrator reminder: coordinate, don't produce — delegate edits, builds, multi-file reads, and debug loops
|
|
70
|
+
json_prompt_output "Orchestrator reminder: coordinate, don't produce — delegate edits, builds, multi-file reads, and debug loops to the fitting roster agent (Agent tool) or the matching devflow workflow skill.
|
|
74
71
|
Keep only judgment work mainline: conversation, decisions, routing, synthesis of agent reports."
|
|
75
72
|
fi
|
|
76
73
|
|
|
@@ -22,18 +22,41 @@
|
|
|
22
22
|
# queue_append_both <memory_queue> <learning_queue> <memory_enabled> <learning_enabled> <role> <content> <ts>
|
|
23
23
|
# Calls queue_append_row for each queue whose *_enabled flag is "true". Each
|
|
24
24
|
# queue is gated independently -- callers compute memory_enabled/learning_enabled
|
|
25
|
-
#
|
|
26
|
-
# combined read that keeps this to a single config subprocess per hook).
|
|
25
|
+
# themselves (see queue_read_gates below for the at-most-one-fork read of both).
|
|
27
26
|
#
|
|
28
|
-
# queue_read_gates <
|
|
29
|
-
# Reads BOTH
|
|
30
|
-
#
|
|
31
|
-
# hook, not two
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# they are always the literal
|
|
35
|
-
# content, so a plain newline split is
|
|
36
|
-
# invisible bytes hiding in the source).
|
|
27
|
+
# queue_read_gates <manifest_path>
|
|
28
|
+
# Reads BOTH `features.memory` and `features.learning` from the devflow-global
|
|
29
|
+
# ~/.devflow/manifest.json in at most ONE subprocess fork (AC-P1 -- at most one
|
|
30
|
+
# gate-read fork per capture hook, not two; none when D-GATES-FAST-PATH
|
|
31
|
+
# settles it). Sets _QG_MEMORY and _QG_LEARNING ("true"/"false") in the
|
|
32
|
+
# caller's scope. The two values are newline-separated
|
|
33
|
+
# rather than using a control-character delimiter: they are always the literal
|
|
34
|
+
# strings "true"/"false", never arbitrary content, so a plain newline split is
|
|
35
|
+
# unambiguous and easy to review (no invisible bytes hiding in the source).
|
|
36
|
+
#
|
|
37
|
+
# D-FEATURES-MACHINE-WIDE (src/core/feature-switch.ts): memory and learning are
|
|
38
|
+
# switched for the WHOLE MACHINE by the manifest alone -- `devflow init` and
|
|
39
|
+
# `devflow memory|learning --enable/--disable` both write it -- so the answer is
|
|
40
|
+
# the same in every repo and every non-git cwd. The per-repo
|
|
41
|
+
# .devflow/config.json is never read here: its old memory/learning keys are
|
|
42
|
+
# retired, and reading them is what let a feature keep running in every repo
|
|
43
|
+
# but the one `init --no-<feature>` ran in (#378). Only an explicit boolean
|
|
44
|
+
# `false` switches a feature off -- an absent, unreadable or malformed manifest,
|
|
45
|
+
# a missing key, or a non-boolean value leaves it ON (fail-open, ADR-028), the
|
|
46
|
+
# same rule isMachineFeatureOn() applies in the CLI.
|
|
47
|
+
#
|
|
48
|
+
# D-LEARNING-LEGACY-DECISIONS: learning reads `features.learning` when it is a
|
|
49
|
+
# boolean, else the pre-rename `features.decisions` (ADR-011) -- readManifest's
|
|
50
|
+
# migration precedence exactly, so a legacy `decisions: false` switches
|
|
51
|
+
# learning off before any command has healed the file. Only a boolean `false`
|
|
52
|
+
# in whichever key decides is off, as above.
|
|
53
|
+
#
|
|
54
|
+
# Every hook that gates on memory or learning reads it through here: the
|
|
55
|
+
# capture hooks, session-start-context (Sections 1-2), memory-worker,
|
|
56
|
+
# session-start-memory, pre-compact-memory and background-memory-update. The
|
|
57
|
+
# callers resolve <manifest_path> from ${DEVFLOW_DIR:-$HOME/.devflow} BEFORE
|
|
58
|
+
# they shadow DEVFLOW_DIR with the project-scoped .devflow (memory-worker hands
|
|
59
|
+
# its resolved path to the background worker it spawns).
|
|
37
60
|
|
|
38
61
|
queue_append_row() {
|
|
39
62
|
local _qar_file="$1" _qar_role="$2" _qar_content="$3" _qar_ts="$4"
|
|
@@ -84,27 +107,48 @@ queue_append_both() {
|
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
queue_read_gates() {
|
|
87
|
-
local
|
|
110
|
+
local _qg_manifest="${1:-}" _qg_text="" _qg_parse="false"
|
|
88
111
|
_QG_MEMORY="true"
|
|
89
112
|
_QG_LEARNING="true"
|
|
90
113
|
|
|
91
|
-
if [ -f "$
|
|
114
|
+
if [ -n "$_qg_manifest" ] && [ -f "$_qg_manifest" ]; then
|
|
115
|
+
# D-GATES-FAST-PATH: every capture hook and every session start pays this
|
|
116
|
+
# read, and on most machines both switches are on. Only a JSON boolean
|
|
117
|
+
# `false` under a "memory", "learning" or "decisions" key can switch anything
|
|
118
|
+
# off, and a `\u` escape is the only JSON spelling of a letter other than the
|
|
119
|
+
# letter itself, so without one such a key appears literally. A text holding
|
|
120
|
+
# no `\u` and no key-then-`false` sequence therefore cannot switch anything
|
|
121
|
+
# off -- valid or not, it reads both-on either way -- and is settled by shell
|
|
122
|
+
# builtins (read, case, [[ =~ ]]) with no jq/node fork; everything else takes
|
|
123
|
+
# the full parse below. `read -d ''` succeeds only on reaching a NUL byte,
|
|
124
|
+
# i.e. before the end of the file, so a text it cut short is parsed in full
|
|
125
|
+
# too. An unreadable file reads empty and fails open, as the parser would.
|
|
126
|
+
local _qg_re='"(memory|learning|decisions)"[[:space:]]*:[[:space:]]*false'
|
|
127
|
+
IFS= read -r -d '' _qg_text 2>/dev/null < "$_qg_manifest" && _qg_parse="true"
|
|
128
|
+
case "$_qg_text" in *'\u'*) _qg_parse="true" ;; esac
|
|
129
|
+
if [[ $_qg_text =~ $_qg_re ]]; then _qg_parse="true"; fi
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
if [ "$_qg_parse" = "true" ]; then
|
|
92
133
|
local _qg_fields
|
|
93
134
|
if [ "$_HAS_JQ" = "true" ]; then
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
135
|
+
# `try` covers a manifest whose top level or `features` is not an object; a
|
|
136
|
+
# parse failure empties the output, which reads as "not switched off". The
|
|
137
|
+
# comma produces two raw-output lines (newline-separated) in one jq process.
|
|
97
138
|
_qg_fields=$(jq -r '
|
|
98
|
-
(if (.memory
|
|
99
|
-
(if (.learning | type) == "
|
|
100
|
-
|
|
139
|
+
(if (try .features.memory catch null) == false then "false" else "true" end),
|
|
140
|
+
(if (try (.features | if (.learning | type) == "boolean" then .learning else .decisions end) catch null) == false
|
|
141
|
+
then "false" else "true" end)
|
|
142
|
+
' "$_qg_manifest" 2>/dev/null) || _qg_fields=""
|
|
101
143
|
else
|
|
102
144
|
_qg_fields=$(node -e "
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
145
|
+
const m = JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'));
|
|
146
|
+
const f = m !== null && typeof m === 'object' ? m.features : undefined;
|
|
147
|
+
const isObj = f !== null && typeof f === 'object';
|
|
148
|
+
const on = (k) => !(isObj && f[k] === false);
|
|
149
|
+
const learnKey = isObj && typeof f.learning !== 'boolean' ? 'decisions' : 'learning';
|
|
150
|
+
process.stdout.write(String(on('memory')) + String.fromCharCode(10) + String(on(learnKey)));
|
|
151
|
+
" -- "$_qg_manifest" 2>/dev/null) || _qg_fields=""
|
|
108
152
|
fi
|
|
109
153
|
if [ -n "$_qg_fields" ]; then
|
|
110
154
|
_QG_MEMORY="${_qg_fields%%$'\n'*}"
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# SessionStart Hook: Cross-Feature Context Injection
|
|
4
4
|
# Always-on hook that injects learning context as additionalContext. Sections
|
|
5
|
-
# are gated by the `learning`
|
|
6
|
-
# this hook itself is never
|
|
5
|
+
# 1–2 are gated by the machine-wide `features.learning` switch in
|
|
6
|
+
# ~/.devflow/manifest.json (D-FEATURES-MACHINE-WIDE) — this hook itself is never
|
|
7
|
+
# disabled.
|
|
7
8
|
#
|
|
8
9
|
# Section 1: Project decisions TL;DR (decisions.md / pitfalls.md header lines).
|
|
9
10
|
# Section 2: Learning maintenance directive — when captured turns are pending in
|
|
@@ -11,6 +12,10 @@
|
|
|
11
12
|
# the main model to spawn the background Learning agent with the resolved model.
|
|
12
13
|
# The agent claims the queue itself and queue emptiness is the natural gate,
|
|
13
14
|
# so there is no throttle here.
|
|
15
|
+
# Section 3: Tracker setup directive — when the machine's manifest names a
|
|
16
|
+
# non-GitHub issue tracker and no ~/.devflow/tracker.md has been inferred for it
|
|
17
|
+
# yet, instructs the main model to spawn the background Tracker agent. Gated on a
|
|
18
|
+
# zero-byte presence sentinel so a GitHub user pays one stat and zero forks.
|
|
14
19
|
|
|
15
20
|
# Safe no-op fallback: must exist before hook-bootstrap is sourced.
|
|
16
21
|
dbg() { :; }
|
|
@@ -61,18 +66,89 @@ PROJECT_ROOT="$(df_resolve_root "$CWD" 2>/dev/null || true)"
|
|
|
61
66
|
|
|
62
67
|
CONTEXT=""
|
|
63
68
|
|
|
69
|
+
# The devflow-GLOBAL root (~/.devflow), honouring the DEVFLOW_DIR env override —
|
|
70
|
+
# the ensure-proxy:58-60 idiom. Captured HERE, above the project-scoped
|
|
71
|
+
# DEVFLOW_DIR assignment that shadows the inherited value, because Section 3's
|
|
72
|
+
# tracker files and the manifest the learning gate reads are user-scope and not
|
|
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}"
|
|
77
|
+
|
|
78
|
+
# The shape gate for every path this hook interpolates into a directive.
|
|
79
|
+
#
|
|
80
|
+
# Sections 2 and 3 embed $PROJECT_ROOT — and Section 3 also $TRACKER_DEVFLOW_DIR —
|
|
81
|
+
# inside a double-quoted `prompt: "..."` string the model reads out of
|
|
82
|
+
# additionalContext. The provider and model TOKENS in those directives are admitted
|
|
83
|
+
# by positive allowlists, and so are these: the gate is the POSITIVE shape
|
|
84
|
+
# ^[A-Za-z0-9/._-]+$, expressed as "rejects if any character falls outside it".
|
|
85
|
+
#
|
|
86
|
+
# Positive, not a denylist of the characters that are known to hurt. A denylist
|
|
87
|
+
# enumerates the injections someone thought of — `"` closes the prompt string, `\`
|
|
88
|
+
# reads as an escape, LF and CR put the rest of the path on its own line as free
|
|
89
|
+
# text — and admits every one that was not on the list. An allowlist admits only
|
|
90
|
+
# what is known to be inert, so the next escape nobody has thought of is refused
|
|
91
|
+
# by construction rather than by a later amendment.
|
|
92
|
+
#
|
|
93
|
+
# The narrowing is real and deliberate: a project root containing a space, a
|
|
94
|
+
# quote, a backtick, `$` or `;` now suppresses the directive that embeds it
|
|
95
|
+
# rather than interpolating an unproven value. That is the fail-closed direction
|
|
96
|
+
# — the directive is an optimisation, and `dbg` names the reason on the debug
|
|
97
|
+
# path.
|
|
98
|
+
#
|
|
99
|
+
# What the range does NOT promise is "ASCII only". A `case` bracket range
|
|
100
|
+
# collates under LC_COLLATE, so `A-Za-z0-9` admits an accented letter under a
|
|
101
|
+
# UTF-8 locale and refuses it under C. Every byte the gate exists to refuse —
|
|
102
|
+
# quote, backslash, CR, LF, space, backtick, `$`, `;` — is outside the range in
|
|
103
|
+
# both, so the security property holds either way; only the exact width of the
|
|
104
|
+
# admitted set is locale-dependent, and no rule here rests on it.
|
|
105
|
+
#
|
|
106
|
+
# Checked ONCE, here, where both values are resolved and above every section that
|
|
107
|
+
# interpolates them, so no sink can embed a value no gate saw (PF-023 — the
|
|
108
|
+
# invariant belongs at the convergence point all callers pass through, not in
|
|
109
|
+
# 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
|
+
# PROJECT_ROOT must not read as "no forbidden character, therefore safe".
|
|
112
|
+
#
|
|
113
|
+
# ONE gate per VALUE, not one over their concatenation. Section 2 interpolates
|
|
114
|
+
# $PROJECT_ROOT alone; only Section 3 also interpolates $TRACKER_DEVFLOW_DIR.
|
|
115
|
+
# Gating the two values jointly made a rejected ~/.devflow shape suppress the
|
|
116
|
+
# Learning directive as well — a value Section 2 never embeds, silently
|
|
117
|
+
# disabling the whole learning pipeline for any machine whose home directory
|
|
118
|
+
# carries a space. A gate must refuse a sink its value actually reaches and no
|
|
119
|
+
# other, or the fail-closed direction stops being the safe one.
|
|
120
|
+
DIRECTIVE_ROOT_SAFE="yes"
|
|
121
|
+
case "$PROJECT_ROOT" in
|
|
122
|
+
''|*[!A-Za-z0-9/._-]*)
|
|
123
|
+
DIRECTIVE_ROOT_SAFE=""
|
|
124
|
+
;;
|
|
125
|
+
esac
|
|
126
|
+
|
|
127
|
+
# Section 3's flag: BOTH values, because Section 3 interpolates both. Seeded from
|
|
128
|
+
# the root flag so it can only ever be narrower — a root the sections may not
|
|
129
|
+
# embed is not embeddable by the section that embeds more of them.
|
|
130
|
+
DIRECTIVE_PATHS_SAFE="$DIRECTIVE_ROOT_SAFE"
|
|
131
|
+
case "$TRACKER_DEVFLOW_DIR" in
|
|
132
|
+
''|*[!A-Za-z0-9/._-]*)
|
|
133
|
+
DIRECTIVE_PATHS_SAFE=""
|
|
134
|
+
;;
|
|
135
|
+
esac
|
|
136
|
+
|
|
64
137
|
DEVFLOW_DIR="$PROJECT_ROOT/.devflow"
|
|
65
138
|
LEARNING_DIR="$DEVFLOW_DIR/learning"
|
|
66
139
|
|
|
67
140
|
# Normal logging
|
|
68
141
|
source "$SCRIPT_DIR/hook-log-init" "session-start-context"
|
|
69
142
|
|
|
70
|
-
# --- Learning gate
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
143
|
+
# --- Learning gate: the machine-wide switch ---
|
|
144
|
+
# D-FEATURES-MACHINE-WIDE (see queue-append): ~/.devflow/manifest.json's
|
|
145
|
+
# features.learning is read by the same helper the capture hooks use, so the
|
|
146
|
+
# directive and the queue it drains can never disagree about whether learning
|
|
147
|
+
# is on. The manifest lives under the devflow-global root captured above, not
|
|
148
|
+
# the project-scoped DEVFLOW_DIR.
|
|
149
|
+
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"
|
|
151
|
+
LEARNING_ENABLED="$_QG_LEARNING"
|
|
76
152
|
|
|
77
153
|
# --- Section 1: Project Decisions TL;DR ---
|
|
78
154
|
if [ "$LEARNING_ENABLED" = "true" ]; then
|
|
@@ -130,6 +206,13 @@ if [ "$LEARNING_ENABLED" = "true" ]; then
|
|
|
130
206
|
LEARNING_WORK="queue"
|
|
131
207
|
fi
|
|
132
208
|
|
|
209
|
+
# $PROJECT_ROOT is the only path this directive interpolates (line below), so
|
|
210
|
+
# it is the only one whose shape may suppress it.
|
|
211
|
+
if [ -n "$LEARNING_WORK" ] && [ -z "$DIRECTIVE_ROOT_SAFE" ]; then
|
|
212
|
+
dbg "learning directive suppressed: interpolated path shape rejected"
|
|
213
|
+
LEARNING_WORK=""
|
|
214
|
+
fi
|
|
215
|
+
|
|
133
216
|
if [ -n "$LEARNING_WORK" ]; then
|
|
134
217
|
# Model resolution: project learning.json → global ~/.devflow/learning.json → opus
|
|
135
218
|
LEARNING_MODEL=""
|
|
@@ -164,6 +247,270 @@ ${LEARNING_SECTION}"
|
|
|
164
247
|
fi
|
|
165
248
|
fi
|
|
166
249
|
|
|
250
|
+
# --- Section 3: Tracker setup directive ---
|
|
251
|
+
# Emitted when this machine's manifest names a non-GitHub issue tracker and no
|
|
252
|
+
# conventions file has been inferred for it yet: a silent, non-blocking
|
|
253
|
+
# instruction to spawn the background Tracker agent, which writes
|
|
254
|
+
# ~/.devflow/tracker.md exactly once.
|
|
255
|
+
#
|
|
256
|
+
# [DR-10] The gate below is TWO shell builtins and nothing else, so a GitHub user
|
|
257
|
+
# — the default, and every user until someone chooses otherwise — pays one stat
|
|
258
|
+
# and ZERO forks per session. The sentinel is what makes that possible:
|
|
259
|
+
# tracker.md is written only for jira/linear, so a bare "does tracker.md exist"
|
|
260
|
+
# early exit would never fire on the default provider and every SessionStart
|
|
261
|
+
# would fall through to the manifest read below — one jq (or one node) fork, per
|
|
262
|
+
# session, forever, for 100% of users who never chose a tracker.
|
|
263
|
+
#
|
|
264
|
+
# Also gated on the project root being inside a git repository: the agent
|
|
265
|
+
# infers every repo-derived value from that history, and it writes the
|
|
266
|
+
# conventions file once and only once, so a session started outside a checkout
|
|
267
|
+
# would fix this machine's conventions at the unresolved sentinel for good.
|
|
268
|
+
#
|
|
269
|
+
# Not gated by the learning feature toggle: a user who turned learning off did
|
|
270
|
+
# not turn their issue tracker off.
|
|
271
|
+
TRACKER_SENTINEL="$TRACKER_DEVFLOW_DIR/.tracker.enabled"
|
|
272
|
+
TRACKER_CONVENTIONS="$TRACKER_DEVFLOW_DIR/tracker.md"
|
|
273
|
+
if [ -f "$TRACKER_SENTINEL" ] && [ ! -f "$TRACKER_CONVENTIONS" ]; then
|
|
274
|
+
TRACKER_ATTEMPTS_FILE="$TRACKER_DEVFLOW_DIR/.tracker.attempts"
|
|
275
|
+
TRACKER_CLAIM_FILE="$TRACKER_DEVFLOW_DIR/.tracker.processing"
|
|
276
|
+
# OD-14 — the attempt cap. A permanently broken tracker connection is routine;
|
|
277
|
+
# without a cap the hook respawns a background agent at every startup forever.
|
|
278
|
+
TRACKER_ATTEMPTS_MAX=5
|
|
279
|
+
# Its OWN literal, deliberately NOT shared with Learning's 900 above: one
|
|
280
|
+
# shared constant would make a change to either feature silently reclassify the
|
|
281
|
+
# other's live runs as crashed. 600s is longer than the memory worker's 300s
|
|
282
|
+
# lock (a Tracker run does more — a capability probe plus bounded git scans)
|
|
283
|
+
# and shorter than Learning's 900s (no multi-part curation phase). Too long
|
|
284
|
+
# costs one session's delay before a crashed run retries; too short burns an
|
|
285
|
+
# attempt against the cap.
|
|
286
|
+
TRACKER_PROCESSING_STALE_SECS=600
|
|
287
|
+
|
|
288
|
+
# tracker_gates_open — every gate after the sentinel, cheapest first, each one
|
|
289
|
+
# an early `return 1`.
|
|
290
|
+
#
|
|
291
|
+
# A plain `sh` block has no early exit: a gate there can suppress only by
|
|
292
|
+
# clearing a flag that every later gate must then re-test, which turns the
|
|
293
|
+
# cheapest-first ordering that delivers [DR-10]'s zero-fork guarantee into a
|
|
294
|
+
# stack of re-entries rather than the linear sequence it is. A function supplies
|
|
295
|
+
# the early exit: each gate states its own condition once, and the order below
|
|
296
|
+
# IS the execution order, read top to bottom.
|
|
297
|
+
#
|
|
298
|
+
# TRACKER_ATTEMPTS and TRACKER_PROVIDER are deliberately not `local`: they are
|
|
299
|
+
# this function's output, interpolated by the emitting block below.
|
|
300
|
+
tracker_gates_open() {
|
|
301
|
+
# Gate 1 — the attempt cap. Read with the `read` builtin: no fork.
|
|
302
|
+
#
|
|
303
|
+
# The counter's shape is ONE decimal integer line and nothing else (PF-062 —
|
|
304
|
+
# document the shape of any file that gates an action, and keep absent and
|
|
305
|
+
# malformed distinct from a value). Absent means "no attempt yet" = 0.
|
|
306
|
+
# Malformed self-heals to 0 and is overwritten with a well-formed count on
|
|
307
|
+
# emission below, so a stray byte can never recur: refusing forever would
|
|
308
|
+
# disable inference permanently with no user-visible reason, and treating it as
|
|
309
|
+
# uncapped would defeat the cap. `read` returns non-zero at an EOF with no
|
|
310
|
+
# trailing newline but HAS assigned the variable, so its status is deliberately
|
|
311
|
+
# not consulted — only the value's shape is.
|
|
312
|
+
#
|
|
313
|
+
# `-n 16` bounds the BYTES consumed, which the `case` below cannot: the file is
|
|
314
|
+
# user-scope and hand-editable, and one very long line would otherwise be pulled
|
|
315
|
+
# whole into a shell variable and pattern-matched whole on the SessionStart
|
|
316
|
+
# critical path. Sixteen characters is past every arm's decision point — a
|
|
317
|
+
# well-formed count never reaches two digits, and the out-of-range arm fires at
|
|
318
|
+
# seven — so every arm keeps the verdict it would have reached unbounded.
|
|
319
|
+
# `2>/dev/null` is spelled BEFORE the input redirect for the same reason it is
|
|
320
|
+
# on the write below: redirections apply left to right, and a failed open is
|
|
321
|
+
# reported by the shell itself, so silencing stderr afterwards would be too late.
|
|
322
|
+
TRACKER_ATTEMPTS=""
|
|
323
|
+
if [ -f "$TRACKER_ATTEMPTS_FILE" ]; then
|
|
324
|
+
if [ -r "$TRACKER_ATTEMPTS_FILE" ]; then
|
|
325
|
+
IFS= read -r -n 16 TRACKER_ATTEMPTS 2>/dev/null < "$TRACKER_ATTEMPTS_FILE"
|
|
326
|
+
else
|
|
327
|
+
# Present but unreadable is NOT a fresh start. Leaving the variable empty
|
|
328
|
+
# would take the '' arm below and read as "no attempt yet", so an EACCES on
|
|
329
|
+
# the counter would emit at every startup forever — the unbounded retry the
|
|
330
|
+
# cap exists to prevent, triggered by exactly the broken-I/O condition that
|
|
331
|
+
# also stops the agent from ever writing tracker.md. Fails CLOSED; the
|
|
332
|
+
# re-arm path (devflow init / devflow tracker --set) owns recovery, as it
|
|
333
|
+
# does for any counter the hook leaves sitting at the cap.
|
|
334
|
+
dbg "tracker attempt counter unreadable — treated as at the cap"
|
|
335
|
+
TRACKER_ATTEMPTS="$TRACKER_ATTEMPTS_MAX"
|
|
336
|
+
fi
|
|
337
|
+
fi
|
|
338
|
+
case "$TRACKER_ATTEMPTS" in
|
|
339
|
+
'') TRACKER_ATTEMPTS=0 ;;
|
|
340
|
+
*[!0-9]*)
|
|
341
|
+
dbg "tracker attempt counter malformed — self-healed to 0"
|
|
342
|
+
TRACKER_ATTEMPTS=0
|
|
343
|
+
;;
|
|
344
|
+
0) ;; # a bare zero is a well-formed count, not a padded one
|
|
345
|
+
0*)
|
|
346
|
+
# A zero-padded count is ONE string that this hook's two consumers of it read
|
|
347
|
+
# in DIFFERENT BASES. `[ "$N" -ge "$MAX" ]` parses base 10, so `08` compares
|
|
348
|
+
# as eight; the `$(( N + 1 ))` that writes the next count is shell arithmetic,
|
|
349
|
+
# where a leading `0` means OCTAL and `08` is "value too great for base" — an
|
|
350
|
+
# error that escapes the write's own `2>/dev/null`, because expansion runs
|
|
351
|
+
# before redirection. Nothing in the padded shape says which reading was
|
|
352
|
+
# meant, so it self-heals to 0 with every other malformed value instead of
|
|
353
|
+
# being carried into the disagreement. This hook writes a bare decimal, so
|
|
354
|
+
# padding came from elsewhere. Placed BEFORE the digit-count arm so a
|
|
355
|
+
# six-character `000008` heals rather than reading as "six digits, at the cap".
|
|
356
|
+
dbg "tracker attempt counter zero-padded — self-healed to 0"
|
|
357
|
+
TRACKER_ATTEMPTS=0
|
|
358
|
+
;;
|
|
359
|
+
???????*)
|
|
360
|
+
# Bounded before the comparison: `[ "$N" -ge 5 ]` on a value past intmax_t
|
|
361
|
+
# prints "integer expression expected" to stderr and takes the FALSE branch,
|
|
362
|
+
# so an unbounded digit string would fail OPEN — uncapped — and leak a shell
|
|
363
|
+
# error. `-n 16` above already puts that state out of reach; this arm is the
|
|
364
|
+
# semantic half and survives a change to the read bound. It matches SEVEN
|
|
365
|
+
# digits or more (the earlier arms have taken every non-digit and every
|
|
366
|
+
# zero-padded value), which is the shape CLAUDE.md documents as "at the cap",
|
|
367
|
+
# and a count that matters needs one digit, so anything reaching here is past
|
|
368
|
+
# the cap by six orders of magnitude. Six digits and fewer are compared as
|
|
369
|
+
# the integers they are.
|
|
370
|
+
dbg "tracker attempt counter out of range — treated as at the cap"
|
|
371
|
+
TRACKER_ATTEMPTS="$TRACKER_ATTEMPTS_MAX"
|
|
372
|
+
;;
|
|
373
|
+
esac
|
|
374
|
+
if [ "$TRACKER_ATTEMPTS" -ge "$TRACKER_ATTEMPTS_MAX" ]; then
|
|
375
|
+
dbg "tracker directive suppressed: attempt cap reached ($TRACKER_ATTEMPTS/$TRACKER_ATTEMPTS_MAX)"
|
|
376
|
+
return 1
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
# Gate 2 — the repository. Every repo-derived value the Tracker agent writes
|
|
380
|
+
# comes from the project root's history, and the agent refuses to infer from
|
|
381
|
+
# history at all when the root carries no git marker — outside a checkout it
|
|
382
|
+
# would write its unresolved sentinel into every one of those sections. It
|
|
383
|
+
# writes the conventions file ONCE, create-exclusive, so that degraded file
|
|
384
|
+
# would be this machine's conventions for good, and the attempt that produced
|
|
385
|
+
# it would be spent against the cap.
|
|
386
|
+
#
|
|
387
|
+
# The helper is sourced HERE rather than beside the hook's other sources so
|
|
388
|
+
# that reading it is a cost only a machine with a tracker pays: the sentinel
|
|
389
|
+
# above has already turned the GitHub path away [DR-10]. Its bounded walk of
|
|
390
|
+
# `-e` tests answers the question with no subprocess, which is what keeps this
|
|
391
|
+
# gate ahead of the three forking gates below. A helper that failed to source
|
|
392
|
+
# is a command that is not found, which takes the suppressing branch — fail
|
|
393
|
+
# closed, and silently, since the redirect covers the lookup failure too.
|
|
394
|
+
source "$SCRIPT_DIR/git-marker" 2>/dev/null || true
|
|
395
|
+
if ! df_has_git_marker "$PROJECT_ROOT" 2>/dev/null; then
|
|
396
|
+
dbg "tracker directive suppressed: project root is not a git repository"
|
|
397
|
+
return 1
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
# Gate 3 — source. Only a fresh session (startup) and a cleared one (clear)
|
|
401
|
+
# begin work that needs conventions; resume and compact continue a session that
|
|
402
|
+
# already had its chance, so re-asking there would spawn an agent mid-flight.
|
|
403
|
+
TRACKER_SOURCE=$(printf '%s' "$INPUT" | json_field "source" "")
|
|
404
|
+
case "$TRACKER_SOURCE" in
|
|
405
|
+
startup|clear) ;;
|
|
406
|
+
*)
|
|
407
|
+
dbg "tracker directive suppressed: not a session start"
|
|
408
|
+
return 1
|
|
409
|
+
;;
|
|
410
|
+
esac
|
|
411
|
+
|
|
412
|
+
# Gate 4 — the claim file, mirroring Section 2's freshness check. A FRESH
|
|
413
|
+
# claim means a live Tracker agent owns the run. A STALE one means a previous
|
|
414
|
+
# run crashed, so re-arm — but never delete it: re-claiming is the agent's job
|
|
415
|
+
# (it touches the file), and a hook that deleted it would race a slow-but-live
|
|
416
|
+
# run. An unreadable mtime falls to the suppressing branch (fail closed).
|
|
417
|
+
if [ -f "$TRACKER_CLAIM_FILE" ]; then
|
|
418
|
+
source "$SCRIPT_DIR/get-mtime" 2>/dev/null || true
|
|
419
|
+
_SC_TRACKER_MTIME=$(get_mtime "$TRACKER_CLAIM_FILE" 2>/dev/null || true)
|
|
420
|
+
_SC_TRACKER_NOW=$(date +%s)
|
|
421
|
+
if [ -n "$_SC_TRACKER_MTIME" ] && [ $(( _SC_TRACKER_NOW - _SC_TRACKER_MTIME )) -ge "$TRACKER_PROCESSING_STALE_SECS" ]; then
|
|
422
|
+
dbg "tracker claim file is stale — previous run crashed, re-arming"
|
|
423
|
+
else
|
|
424
|
+
dbg "tracker directive suppressed: fresh .tracker.processing (live agent owns the run)"
|
|
425
|
+
return 1
|
|
426
|
+
fi
|
|
427
|
+
fi
|
|
428
|
+
|
|
429
|
+
# Gate 5 — the provider, admitted by a POSITIVE allowlist that runs BEFORE any
|
|
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
|
|
455
|
+
# allowlisted tokens. Decided once at the top of the file, where both values are
|
|
456
|
+
# resolved; consulted here because this is one of the two sinks that interpolate
|
|
457
|
+
# them, and a control stated once for the file is not a control at a sink that
|
|
458
|
+
# never consults it (PF-023).
|
|
459
|
+
if [ -z "$DIRECTIVE_PATHS_SAFE" ]; then
|
|
460
|
+
dbg "tracker directive suppressed: interpolated path shape rejected"
|
|
461
|
+
return 1
|
|
462
|
+
fi
|
|
463
|
+
|
|
464
|
+
# Gate 7 — the increment must LAND. [DR-02] Increment on EMISSION, not on the
|
|
465
|
+
# agent's completion: a crashed agent never reaches its own increment, so without
|
|
466
|
+
# this the crash-loop case stays uncapped even with the agent-side counter. The
|
|
467
|
+
# agent DELETES the counter on a successful write, so a healthy path never
|
|
468
|
+
# accumulates. printf is a builtin and the redirect is the shell's — no fork.
|
|
469
|
+
# `2>/dev/null` is spelled FIRST: redirections are applied left to right, and a
|
|
470
|
+
# failed open on the counter path is reported by the shell itself, so silencing
|
|
471
|
+
# stderr after the failing redirect would be too late to keep the hook quiet.
|
|
472
|
+
#
|
|
473
|
+
# Emission is CONDITIONAL on the write: an increment that cannot persist is a cap
|
|
474
|
+
# that can never engage, and the very same unwritable ~/.devflow also stops the
|
|
475
|
+
# agent writing tracker.md while leaving an earlier install's sentinel in place.
|
|
476
|
+
# Emitting anyway would spawn a background agent at every startup, forever — an
|
|
477
|
+
# unbounded retry driven by the one condition the cap exists to bound.
|
|
478
|
+
if ! printf '%s\n' "$(( TRACKER_ATTEMPTS + 1 ))" 2>/dev/null > "$TRACKER_ATTEMPTS_FILE"; then
|
|
479
|
+
dbg "tracker directive suppressed: attempt counter not writable"
|
|
480
|
+
return 1
|
|
481
|
+
fi
|
|
482
|
+
|
|
483
|
+
return 0
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if tracker_gates_open; then
|
|
487
|
+
# Allowlisted the same way LEARNING_MODEL is. The tier is
|
|
488
|
+
# a constant today — there is no tracker tuning config — so this `case` is an
|
|
489
|
+
# assertion of the closed domain rather than a sanitiser, and it is the single
|
|
490
|
+
# place the tier is validated, so a later config read cannot be wired in
|
|
491
|
+
# without passing through it. The literal must equal the Tracker agent's
|
|
492
|
+
# frontmatter `model:` (pinned against loadShippedDefaults in shell-hooks).
|
|
493
|
+
TRACKER_MODEL="sonnet"
|
|
494
|
+
case "$TRACKER_MODEL" in
|
|
495
|
+
opus|sonnet|haiku) ;;
|
|
496
|
+
*) TRACKER_MODEL="sonnet" ;;
|
|
497
|
+
esac
|
|
498
|
+
|
|
499
|
+
dbg "tracker directive emitted (provider=$TRACKER_PROVIDER model=$TRACKER_MODEL attempts=$TRACKER_ATTEMPTS/$TRACKER_ATTEMPTS_MAX)"
|
|
500
|
+
TRACKER_SECTION="--- TRACKER SETUP ---
|
|
501
|
+
No conventions have been inferred yet for this machine's configured issue tracker. Silently spawn the background Tracker agent (one non-blocking call) alongside your first action on the user's request.
|
|
502
|
+
Agent(subagent_type=\"Tracker\", model=\"$TRACKER_MODEL\", run_in_background: true, prompt: \"Infer this machine's tracker conventions and write the conventions file per your agent instructions. Provider: $TRACKER_PROVIDER. Devflow directory: $TRACKER_DEVFLOW_DIR. Project root: $PROJECT_ROOT\")
|
|
503
|
+
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
|
+
if [ -n "$CONTEXT" ]; then
|
|
505
|
+
CONTEXT="${CONTEXT}
|
|
506
|
+
|
|
507
|
+
${TRACKER_SECTION}"
|
|
508
|
+
else
|
|
509
|
+
CONTEXT="$TRACKER_SECTION"
|
|
510
|
+
fi
|
|
511
|
+
fi
|
|
512
|
+
fi
|
|
513
|
+
|
|
167
514
|
# --- Output ---
|
|
168
515
|
|
|
169
516
|
# Only output if we have something to inject
|
|
@@ -42,20 +42,24 @@ source "$SCRIPT_DIR/resolve-project-root" 2>/dev/null || true
|
|
|
42
42
|
PROJECT_ROOT="$(df_resolve_root "$CWD" 2>/dev/null || true)"
|
|
43
43
|
[ -n "$PROJECT_ROOT" ] || PROJECT_ROOT="$CWD"
|
|
44
44
|
|
|
45
|
+
# The machine-wide manifest (the memory switch, D-FEATURES-MACHINE-WIDE in
|
|
46
|
+
# queue-append) is user-scope: resolve it from the inherited DEVFLOW_DIR (or
|
|
47
|
+
# ~/.devflow) BEFORE the project-scoped assignment below shadows that value.
|
|
48
|
+
DEVFLOW_MANIFEST="${DEVFLOW_DIR:-$HOME/.devflow}/manifest.json"
|
|
45
49
|
DEVFLOW_DIR="$PROJECT_ROOT/.devflow"
|
|
46
50
|
MEMORY_DIR="$DEVFLOW_DIR/memory"
|
|
47
51
|
|
|
48
52
|
# Normal logging
|
|
49
53
|
source "$SCRIPT_DIR/hook-log-init" "session-start-memory"
|
|
50
54
|
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
# Memory gate: the machine-wide switch, read by the helper every memory/learning
|
|
56
|
+
# gate shares (D-FEATURES-MACHINE-WIDE, see queue-append). The per-repo config
|
|
57
|
+
# is never consulted.
|
|
58
|
+
source "$SCRIPT_DIR/queue-append" || { echo "session-start-memory: failed to source queue-append" >&2; exit 1; }
|
|
59
|
+
queue_read_gates "$DEVFLOW_MANIFEST"
|
|
60
|
+
if [ "$_QG_MEMORY" != "true" ]; then
|
|
61
|
+
dbg "EXIT: memory disabled machine-wide"
|
|
62
|
+
exit 0
|
|
59
63
|
fi
|
|
60
64
|
|
|
61
65
|
# --- D56c cold-path recovery: orphaned .pending-turns.processing ---
|