devflow-kit 2.4.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 +229 -0
- package/README.md +111 -18
- package/dist/agents/git.md +822 -0
- package/dist/cli/commands/agents.js +6 -1
- package/dist/cli/commands/ambient.js +160 -145
- package/dist/cli/commands/attribution-prompts.js +1 -1
- package/dist/cli/commands/capture.js +29 -55
- package/dist/cli/commands/compliance-prompts.js +1 -1
- package/dist/cli/commands/compliance.js +48 -55
- 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 +61 -27
- package/dist/cli/commands/init.js +649 -240
- package/dist/cli/commands/install-report.js +200 -0
- package/dist/cli/commands/knowledge/index.js +2 -2
- package/dist/cli/commands/knowledge/toggle.js +35 -37
- package/dist/cli/commands/learning.js +79 -57
- package/dist/cli/commands/legacy-hooks.js +11 -14
- package/dist/cli/commands/memory.js +134 -135
- package/dist/cli/commands/prompt-io.js +4 -4
- package/dist/cli/commands/proxy.js +23 -41
- package/dist/cli/commands/security.js +81 -29
- package/dist/cli/commands/skills.js +71 -7
- package/dist/cli/commands/tracker-prompts.js +145 -0
- package/dist/cli/commands/tracker.js +277 -0
- package/dist/cli/commands/uninstall.js +520 -169
- package/dist/cli.js +2 -0
- package/dist/commands/bug-analysis.md +58 -14
- package/dist/commands/code-review.md +110 -32
- package/dist/commands/debug.md +55 -11
- package/dist/commands/dynamic-build.md +344 -73
- package/dist/commands/dynamic-plan.md +77 -27
- package/dist/commands/dynamic-profile.md +25 -11
- package/dist/commands/dynamic-tickets.md +76 -15
- package/dist/commands/explore.md +37 -7
- package/dist/commands/implement.md +314 -62
- package/dist/commands/plan.md +146 -32
- package/dist/commands/release.md +64 -17
- package/dist/commands/research.md +34 -8
- package/dist/commands/resolve.md +196 -68
- package/dist/commands/self-review.md +45 -9
- package/dist/core/agent-models.js +55 -12
- package/dist/core/assets.js +58 -2
- package/dist/core/compliance-compose.js +27 -27
- package/dist/core/evidence-policy.js +363 -0
- package/dist/core/feature-config.js +200 -65
- package/dist/core/feature-switch.js +112 -0
- package/dist/core/flags.js +34 -6
- 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 +38 -10
- package/dist/core/mds-variants.js +798 -0
- package/dist/core/migrations.js +49 -23
- package/dist/core/model-discovery.js +12 -1
- package/dist/core/plugins.js +361 -12
- package/dist/core/project-paths.js +1 -18
- 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/same-location.js +25 -0
- package/dist/core/tracker.js +494 -0
- 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/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/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 +1187 -32
- package/dist/targets/claude-code/legacy.js +5 -0
- package/dist/targets/claude-code/post-install.js +366 -151
- package/dist/targets/claude-code/tracker-install.js +134 -0
- package/package.json +8 -6
- package/src/assets/agents/code.md +45 -6
- package/src/assets/agents/design.md +2 -1
- package/src/assets/agents/git.mds +825 -0
- package/src/assets/agents/knowledge.md +3 -3
- package/src/assets/agents/learning.md +11 -0
- package/src/assets/agents/review.md +3 -1
- package/src/assets/agents/synthesize.md +1 -1
- package/src/assets/agents/test.md +16 -5
- package/src/assets/agents/tracker.md +474 -0
- package/src/assets/agents/validate.md +7 -5
- 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 +13 -11
- package/src/assets/commands/_partials/_evidence_policy.mds +30 -0
- 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 +22 -7
- package/src/assets/commands/_partials/_preamble.mds +2 -2
- package/src/assets/commands/_partials/_publication.mds +8 -2
- package/src/assets/commands/_partials/_settings.mds +28 -0
- 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 +31 -19
- package/src/assets/commands/code-review.mds +67 -41
- package/src/assets/commands/debug.mds +13 -7
- package/src/assets/commands/dynamic-build.mds +274 -66
- package/src/assets/commands/dynamic-plan.mds +50 -23
- package/src/assets/commands/dynamic-profile.mds +24 -11
- package/src/assets/commands/dynamic-tickets.mds +63 -16
- package/src/assets/commands/explore.mds +4 -5
- package/src/assets/commands/implement.mds +234 -67
- package/src/assets/commands/plan.mds +91 -33
- package/src/assets/commands/release.md +64 -17
- package/src/assets/commands/research.mds +11 -9
- package/src/assets/commands/resolve.mds +150 -78
- package/src/assets/commands/self-review.mds +24 -25
- 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 +305 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +5 -8
- package/src/assets/scripts/hooks/background-memory-update +40 -19
- package/src/assets/scripts/hooks/capture-prompt +18 -8
- package/src/assets/scripts/hooks/capture-question +18 -8
- package/src/assets/scripts/hooks/capture-turn +27 -13
- 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 +236 -60
- package/src/assets/scripts/hooks/git-marker +48 -0
- package/src/assets/scripts/hooks/hook-log-init +3 -1
- package/src/assets/scripts/hooks/json-helper.cjs +228 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +1 -20
- package/src/assets/scripts/hooks/log-paths +80 -0
- package/src/assets/scripts/hooks/memory-worker +22 -13
- package/src/assets/scripts/hooks/pre-compact-memory +44 -15
- package/src/assets/scripts/hooks/preamble +1 -4
- package/src/assets/scripts/hooks/queue-append +146 -28
- package/src/assets/scripts/hooks/resolve-project-root +101 -7
- package/src/assets/scripts/hooks/session-start-context +534 -20
- package/src/assets/scripts/hooks/session-start-memory +38 -15
- package/src/assets/scripts/lib/project-config.cjs +633 -0
- 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 +1145 -0
- package/src/assets/scripts/resolve-settings.cjs +1054 -0
- package/src/assets/scripts/verify-evidence.cjs +1822 -0
- package/src/assets/skills/compliance/SKILL.md +4 -2
- package/src/assets/skills/docs-framework/SKILL.md +11 -10
- 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/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/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
- package/src/assets/agents/git.md +0 -938
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# Ensures .devflow/ and all subdirectories exist and the project root .gitignore
|
|
3
3
|
# ignores .devflow/. Called from capture-prompt, capture-turn, capture-question,
|
|
4
4
|
# memory-worker, and pre-compact-memory. Idempotent.
|
|
5
|
+
# Returns non-zero, having written nothing, outside a git project or at HOME.
|
|
5
6
|
# Usage: source ensure-devflow-init "$CWD"
|
|
6
7
|
|
|
7
8
|
[ -z "$1" ] && return 1
|
|
@@ -11,23 +12,49 @@
|
|
|
11
12
|
# by a hook or directly by tests). Empty → fall back to "$1" (old behavior).
|
|
12
13
|
_EDI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
14
|
source "$_EDI_DIR/resolve-project-root" 2>/dev/null || true
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
df_resolve_roots "$1" 2>/dev/null || true
|
|
16
|
+
_EDI_ROOT="${DF_ROOT:-$1}"
|
|
17
|
+
_EDI_LEDGER="${DF_LEDGER_ROOT:-$_EDI_ROOT}"
|
|
18
|
+
|
|
19
|
+
# D-HOOKS-GIT-ONLY (git-marker): scaffold nothing outside a git project, and
|
|
20
|
+
# nothing at a repository rooted at HOME. D-HOOKS-TOPLEVEL-ONLY (git-marker): nor
|
|
21
|
+
# below a checkout's toplevel — when git refuses the cwd (dubious ownership,
|
|
22
|
+
# GIT_CEILING_DIRECTORIES) the root above is that cwd, and a subdirectory holds no
|
|
23
|
+
# `.git` of its own, so it is refused rather than scaffolded. This is the one gate every capture and
|
|
24
|
+
# memory caller (capture-prompt, capture-turn, capture-question, memory-worker,
|
|
25
|
+
# pre-compact-memory) passes through before it writes, and each of them treats a
|
|
26
|
+
# non-zero return as "stop" (`|| exit 0`), so memory and learning stop together
|
|
27
|
+
# in a non-git directory. ensure-root-gitignore itself stays ungated: it is a
|
|
28
|
+
# pure writer its parity suite runs in plain directories (PF-059), and both of
|
|
29
|
+
# its callers now decide before reaching it. A helper that failed to source is a
|
|
30
|
+
# command not found — the refusing branch, so the failure mode writes nothing.
|
|
31
|
+
source "$_EDI_DIR/git-marker" 2>/dev/null || true
|
|
32
|
+
df_is_project_root "$_EDI_ROOT" 2>/dev/null || return 1
|
|
16
33
|
|
|
17
34
|
_DEVFLOW_DIR="$_EDI_ROOT/.devflow"
|
|
18
35
|
|
|
19
|
-
#
|
|
36
|
+
# learning/ is scaffolded only where the ledger lives. In a linked worktree whose
|
|
37
|
+
# main checkout holds the ledger (D-LEDGER-MAIN-WORKTREE, resolve-project-root) a
|
|
38
|
+
# learning/ here would be an empty directory nothing writes to; the capture hooks
|
|
39
|
+
# create the ledger's own learning/ at the ledger root when they append to it.
|
|
40
|
+
_EDI_LEARNING="$_DEVFLOW_DIR/learning"
|
|
41
|
+
[ "$_EDI_LEDGER" = "$_EDI_ROOT" ] || _EDI_LEARNING=""
|
|
42
|
+
|
|
43
|
+
# Fast-path: if all subdirectories already exist, skip mkdir and gitignore setup.
|
|
44
|
+
# The marker is the CURRENT carve-out version (D-GITIGNORE-V6), bumped with both
|
|
45
|
+
# writers' stamps, so a v5-stamped project misses this once and gains the
|
|
46
|
+
# `!.devflow/project.json` line.
|
|
20
47
|
if [ -d "$_DEVFLOW_DIR/memory" ] && [ -d "$_DEVFLOW_DIR/docs" ] && \
|
|
21
|
-
[ -d "$
|
|
48
|
+
{ [ -z "$_EDI_LEARNING" ] || [ -d "$_EDI_LEARNING" ]; } && \
|
|
22
49
|
[ -d "$_DEVFLOW_DIR/features" ] && \
|
|
23
|
-
[ -f "$_DEVFLOW_DIR/.root-gitignore-configured-
|
|
50
|
+
[ -f "$_DEVFLOW_DIR/.root-gitignore-configured-v6" ]; then
|
|
24
51
|
return 0
|
|
25
52
|
fi
|
|
26
53
|
|
|
27
54
|
# Create all subdirectories
|
|
28
55
|
mkdir -p \
|
|
29
56
|
"$_DEVFLOW_DIR/memory" \
|
|
30
|
-
"$
|
|
57
|
+
${_EDI_LEARNING:+"$_EDI_LEARNING"} \
|
|
31
58
|
"$_DEVFLOW_DIR/features" \
|
|
32
59
|
"$_DEVFLOW_DIR/docs" \
|
|
33
60
|
2>/dev/null || return 1
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# ensure-proxy — SessionStart + UserPromptSubmit hook
|
|
4
4
|
# Ensures the Devflow proxy relay is running when external model routing is enabled.
|
|
5
5
|
# NOT git-gated (proxy is a global user-scope feature, not per-project).
|
|
6
|
-
# NOT project-scoped: proxy state lives at $
|
|
6
|
+
# NOT project-scoped: proxy state lives at $HOME/.devflow/proxy.json (machine-wide).
|
|
7
7
|
#
|
|
8
8
|
# SessionStart: probe port → if DOWN attempt spawn → inject additionalContext warning if still down
|
|
9
9
|
# UserPromptSubmit: fast exit (both port-up and port-down) — silent; SessionStart handles all warnings
|
|
@@ -55,9 +55,10 @@ if [ "$HOOK_EVENT" = "UserPromptSubmit" ]; then
|
|
|
55
55
|
fi
|
|
56
56
|
|
|
57
57
|
# ── Proxy state ────────────────────────────────────────────────────────────────
|
|
58
|
-
# Proxy is
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
# Proxy is machine-wide, not project-scoped: state lives at the machine root,
|
|
59
|
+
# always $HOME/.devflow (D-ONE-HOME).
|
|
60
|
+
MACHINE_DEVFLOW_DIR="$HOME/.devflow"
|
|
61
|
+
PROXY_STATE_FILE="$MACHINE_DEVFLOW_DIR/proxy.json"
|
|
61
62
|
|
|
62
63
|
if [ ! -f "$PROXY_STATE_FILE" ]; then
|
|
63
64
|
dbg "EXIT: no proxy state file"
|
|
@@ -93,7 +94,7 @@ if [ "${DEVFLOW_HOOK_DEBUG:-}" = "1" ]; then
|
|
|
93
94
|
fi
|
|
94
95
|
|
|
95
96
|
# ── Log setup (SessionStart only) ──────────────────────────────────────────────
|
|
96
|
-
LOG_DIR="$
|
|
97
|
+
LOG_DIR="$MACHINE_DEVFLOW_DIR/logs"
|
|
97
98
|
mkdir -p "$LOG_DIR" 2>/dev/null || true
|
|
98
99
|
# SEC-2: harden log directory to 0700 (best-effort; pre-existing dirs may be wider).
|
|
99
100
|
chmod 700 "$LOG_DIR" 2>/dev/null || true
|
|
@@ -101,7 +102,7 @@ LOG_FILE="$LOG_DIR/proxy.log"
|
|
|
101
102
|
|
|
102
103
|
# Size guard: 2MB max → truncate to 1MB tail (matches hook-log-init guard pattern).
|
|
103
104
|
# Sharing hook-log-init is not feasible here: hook-log-init requires $CWD and targets
|
|
104
|
-
# the per-project log path; ensure-proxy uses the
|
|
105
|
+
# the per-project log path; ensure-proxy uses the machine-wide $MACHINE_DEVFLOW_DIR/logs path.
|
|
105
106
|
# The existence guard is required: wc -c uses a shell redirect whose failure is emitted
|
|
106
107
|
# by bash itself (before wc starts), bypassing 2>/dev/null.
|
|
107
108
|
_LOG_MAX_BYTES=2097152
|
|
@@ -268,7 +269,7 @@ fi
|
|
|
268
269
|
source "$SCRIPT_DIR/get-mtime" 2>/dev/null || true
|
|
269
270
|
source "$SCRIPT_DIR/learning-lock" 2>/dev/null || true
|
|
270
271
|
|
|
271
|
-
SPAWN_LOCK="$
|
|
272
|
+
SPAWN_LOCK="$MACHINE_DEVFLOW_DIR/.proxy-spawn.lock"
|
|
272
273
|
|
|
273
274
|
if ! learning_lock_acquire "$SPAWN_LOCK" 2 2>/dev/null; then
|
|
274
275
|
# Another session is racing to start the proxy — give it a moment then re-probe
|
|
@@ -324,7 +325,7 @@ log "relay spawned with pid $_RELAY_PID"
|
|
|
324
325
|
# Best-effort pid record for `devflow proxy --status` — mirrors the CLI enable path.
|
|
325
326
|
# Written unconditionally after spawn (like the CLI): a stale pid from a relay that
|
|
326
327
|
# never came up is harmless, since --status liveness-checks it before display.
|
|
327
|
-
printf '%s' "$_RELAY_PID" > "$
|
|
328
|
+
printf '%s' "$_RELAY_PID" > "$MACHINE_DEVFLOW_DIR/proxy.pid" 2>/dev/null || \
|
|
328
329
|
log "warn: could not write proxy.pid (non-fatal)"
|
|
329
330
|
|
|
330
331
|
# Bounded wait: 80×0.1s = 8s maximum (well within 15s hook timeout)
|
|
@@ -3,14 +3,19 @@
|
|
|
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. FOUR 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 retired evidence-policy file: never parsed, but
|
|
12
|
+
# where project.json has no `evidence` its presence holds the repository at
|
|
13
|
+
# required (D-POLICY-JSON-RETIRED). Devflow never writes it.
|
|
14
|
+
# 4. .devflow/project.json — the team-committed settings both resolvers read
|
|
15
|
+
# (D-GITIGNORE-V6). Devflow never writes it.
|
|
11
16
|
#
|
|
12
|
-
# Everything else under .devflow/ stays local. A user opts back out
|
|
13
|
-
# `.devflow/features/` or `.devflow/conventions.md` to their own .gitignore.
|
|
17
|
+
# Everything else under .devflow/ stays local. A user opts back out of the first two
|
|
18
|
+
# by re-adding `.devflow/features/` or `.devflow/conventions.md` to their own .gitignore.
|
|
14
19
|
#
|
|
15
20
|
# Re-including files under an ignored tree requires un-ignoring each level with a
|
|
16
21
|
# `dir/*` + `!dir/keep` pair: a bare `.devflow/` excludes the directory outright,
|
|
@@ -23,9 +28,35 @@
|
|
|
23
28
|
# Both reach this one writer so the rule is applied identically everywhere; this
|
|
24
29
|
# decouples git-tracking of .devflow/ from any single feature toggle (avoids PF-014).
|
|
25
30
|
#
|
|
26
|
-
# Idempotent and O(1) after the first run via the .root-gitignore-configured-
|
|
27
|
-
# marker. The marker is
|
|
28
|
-
#
|
|
31
|
+
# Idempotent and O(1) after the first run via the .root-gitignore-configured-v6
|
|
32
|
+
# marker under .devflow/ (project-local). The marker is a claim, not proof, so the
|
|
33
|
+
# fast path also requires the block's own sentinel, the policy line, the project
|
|
34
|
+
# line AND a .claudeignore entry to be present; a .gitignore that lost any of them (a
|
|
35
|
+
# merge resolution, a hand edit) is healed on the next run. A v5-marked project
|
|
36
|
+
# misses the fast path, gains the project line just before its .claudeignore line
|
|
37
|
+
# and is re-stamped v6 (D-GITIGNORE-V6); a v4-marked one gains the policy and
|
|
38
|
+
# project lines.
|
|
39
|
+
#
|
|
40
|
+
# D-GITIGNORE-IN-BLOCK: a line topped up into an existing block goes INSIDE the
|
|
41
|
+
# block, where a fresh block holds it, never at the end of the file. gitignore is
|
|
42
|
+
# last-match-wins, so a `!.devflow/project.json` appended after a user's own later
|
|
43
|
+
# `.devflow/project.json` would silently override their re-ignore. The missing
|
|
44
|
+
# lines are inserted as one run, in block order, right after the block sentinel
|
|
45
|
+
# and the block lines that precede the first missing one in a fresh block (at most
|
|
46
|
+
# three: the sentinel, the policy line, the project line). Every other byte of the
|
|
47
|
+
# file is kept.
|
|
48
|
+
#
|
|
49
|
+
# D-GITIGNORE-V5 / D-GITIGNORE-V6: the block is detected ONLY by its devflow-unique
|
|
50
|
+
# sentinel `!.devflow/conventions.md` — NEVER by `.claudeignore`,
|
|
51
|
+
# `!.devflow/policy.json` or `!.devflow/project.json`, which users legitimately author
|
|
52
|
+
# themselves. Those three are completion lines: each is topped up when missing, in
|
|
53
|
+
# block order (policy, project, then .claudeignore). Keying presence
|
|
54
|
+
# off a user-authored line inverts both halves of the contract: projects that already
|
|
55
|
+
# carry that line never receive the carve-out (so .devflow/ runtime files leak into
|
|
56
|
+
# git), and re-appending `.claudeignore` after a user's `!.claudeignore` reverses
|
|
57
|
+
# their intent under gitignore's last-match-wins. Hence: a `.claudeignore` OR
|
|
58
|
+
# `!.claudeignore` entry means "the user owns that line" — emit the block without it.
|
|
59
|
+
# All matching is whole-line and whitespace-tolerant; never substring.
|
|
29
60
|
#
|
|
30
61
|
# Usage: source ensure-root-gitignore "$PROJECT_ROOT"
|
|
31
62
|
# Sourced helper: uses `return` (never exit), _ERG_-prefixed locals (never clobbers
|
|
@@ -34,31 +65,62 @@
|
|
|
34
65
|
[ -z "$1" ] && return 1
|
|
35
66
|
|
|
36
67
|
_ERG_DEVFLOW_DIR="$1/.devflow"
|
|
37
|
-
_ERG_MARKER="$_ERG_DEVFLOW_DIR/.root-gitignore-configured-
|
|
68
|
+
_ERG_MARKER="$_ERG_DEVFLOW_DIR/.root-gitignore-configured-v6"
|
|
38
69
|
_ERG_GITIGNORE="$1/.gitignore"
|
|
39
70
|
|
|
40
|
-
#
|
|
41
|
-
# the
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
71
|
+
# Whole-line, whitespace-tolerant matchers. `*` and `.` are escaped so the ERE
|
|
72
|
+
# matches the literal gitignore patterns.
|
|
73
|
+
_ERG_RE_OPTOUT='^[[:space:]]*/\.devflow/[[:space:]]*$'
|
|
74
|
+
_ERG_RE_LEGACY='^[[:space:]]*\.devflow/[[:space:]]*$'
|
|
75
|
+
_ERG_RE_SENTINEL='^[[:space:]]*!\.devflow/conventions\.md[[:space:]]*$'
|
|
76
|
+
_ERG_RE_SENTINEL_V2='^[[:space:]]*!\.devflow/features/\*/KNOWLEDGE\.md[[:space:]]*$'
|
|
77
|
+
_ERG_RE_POLICY='^[[:space:]]*!\.devflow/policy\.json[[:space:]]*$'
|
|
78
|
+
_ERG_RE_PROJECT='^[[:space:]]*!\.devflow/project\.json[[:space:]]*$'
|
|
79
|
+
_ERG_RE_CLAUDEIGNORE='^[[:space:]]*!?\.claudeignore[[:space:]]*$'
|
|
80
|
+
|
|
81
|
+
# Drop every legacy marker beside v6 — builtin `[ -e ]` tests, so rm forks only for a
|
|
82
|
+
# marker that exists. Called from the fast path and from the on-success stamp below,
|
|
83
|
+
# so an older devflow's marker is dropped whichever path a run takes.
|
|
84
|
+
# Always returns 0 (never fails the caller under `set -e`): the last iteration's
|
|
85
|
+
# `[ -e ]` test is false whenever the unversioned marker is absent (the common case),
|
|
86
|
+
# and a function call's own exit status is NOT exempt from errexit the way an inlined
|
|
87
|
+
# for-loop's is — unlike this same loop when it sat directly inside the caller's `{ }`.
|
|
88
|
+
_erg_drop_legacy_markers() {
|
|
89
|
+
for _ERG_OLD in -v5 -v4 -v3 -v2 ''; do
|
|
90
|
+
[ -e "$_ERG_DEVFLOW_DIR/.root-gitignore-configured$_ERG_OLD" ] \
|
|
91
|
+
&& rm -f "$_ERG_DEVFLOW_DIR/.root-gitignore-configured$_ERG_OLD" 2>/dev/null
|
|
92
|
+
done
|
|
93
|
+
return 0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Fast path: converged only when the marker is stamped AND the block sentinel is
|
|
97
|
+
# present AND the policy and project lines are present AND a .claudeignore entry
|
|
98
|
+
# exists. Every other state falls through and recomputes.
|
|
99
|
+
if [ -f "$_ERG_MARKER" ] \
|
|
100
|
+
&& grep -qE "$_ERG_RE_SENTINEL" "$_ERG_GITIGNORE" 2>/dev/null \
|
|
101
|
+
&& grep -qE "$_ERG_RE_POLICY" "$_ERG_GITIGNORE" 2>/dev/null \
|
|
102
|
+
&& grep -qE "$_ERG_RE_PROJECT" "$_ERG_GITIGNORE" 2>/dev/null \
|
|
103
|
+
&& grep -qE "$_ERG_RE_CLAUDEIGNORE" "$_ERG_GITIGNORE" 2>/dev/null; then
|
|
104
|
+
_erg_drop_legacy_markers
|
|
105
|
+
return 0
|
|
47
106
|
fi
|
|
48
107
|
|
|
49
108
|
# The marker lives under .devflow/, so the directory must exist before we touch it.
|
|
50
109
|
# (ensure-devflow-init creates it earlier; session-start-context may reach here first.)
|
|
51
110
|
mkdir -p "$_ERG_DEVFLOW_DIR" 2>/dev/null || return 1
|
|
52
111
|
|
|
53
|
-
# The carve-out block, built once
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
|
|
112
|
+
# The carve-out block, built once. _ERG_BLOCK_NO_CI is the block a project that owns
|
|
113
|
+
# its own .claudeignore entry receives; _ERG_BLOCK is that plus the final line.
|
|
114
|
+
# Both trail a newline. Keep byte-identical to DEVFLOW_GITIGNORE_BLOCK and
|
|
115
|
+
# DEVFLOW_GITIGNORE_BLOCK_WITHOUT_CLAUDEIGNORE in
|
|
116
|
+
# src/targets/claude-code/post-install.ts.
|
|
117
|
+
printf -v _ERG_BLOCK_NO_CI '%s\n' \
|
|
57
118
|
'# Devflow runtime data — local by default (memory, learning, docs, locks).' \
|
|
58
|
-
'#
|
|
59
|
-
'#
|
|
60
|
-
'#
|
|
61
|
-
'#
|
|
119
|
+
'# Shared via git: feature knowledge bases under .devflow/features/ (index.md and' \
|
|
120
|
+
'# every {slug}/KNOWLEDGE.md), .devflow/conventions.md (naming authority),' \
|
|
121
|
+
'# .devflow/policy.json (retired; presence only) and .devflow/project.json (team settings).' \
|
|
122
|
+
'# To stop sharing the first two, re-add `.devflow/features/` or' \
|
|
123
|
+
'# `.devflow/conventions.md` to your own .gitignore.' \
|
|
62
124
|
'.devflow/*' \
|
|
63
125
|
'!.devflow/features/' \
|
|
64
126
|
'.devflow/features/*' \
|
|
@@ -66,51 +128,165 @@ printf -v _ERG_BLOCK '%s\n' \
|
|
|
66
128
|
'!.devflow/features/*/' \
|
|
67
129
|
'.devflow/features/*/*' \
|
|
68
130
|
'!.devflow/features/*/KNOWLEDGE.md' \
|
|
69
|
-
'!.devflow/conventions.md'
|
|
131
|
+
'!.devflow/conventions.md' \
|
|
132
|
+
'!.devflow/policy.json' \
|
|
133
|
+
'!.devflow/project.json'
|
|
134
|
+
printf -v _ERG_LINE_SENTINEL '%s\n' '!.devflow/conventions.md'
|
|
135
|
+
printf -v _ERG_LINE_POLICY '%s\n' '!.devflow/policy.json'
|
|
136
|
+
printf -v _ERG_LINE_PROJECT '%s\n' '!.devflow/project.json'
|
|
137
|
+
printf -v _ERG_LINE_CLAUDEIGNORE '%s\n' '.claudeignore'
|
|
138
|
+
_ERG_BLOCK="$_ERG_BLOCK_NO_CI$_ERG_LINE_CLAUDEIGNORE"
|
|
70
139
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
140
|
+
# Does the file already carry the policy line, the project line, or a .claudeignore
|
|
141
|
+
# entry of the user's own (either form)? Computed BEFORE the legacy filter below,
|
|
142
|
+
# which rewrites the file.
|
|
143
|
+
_ERG_HAS_POLICY=0
|
|
144
|
+
grep -qE "$_ERG_RE_POLICY" "$_ERG_GITIGNORE" 2>/dev/null && _ERG_HAS_POLICY=1
|
|
145
|
+
_ERG_HAS_PROJECT=0
|
|
146
|
+
grep -qE "$_ERG_RE_PROJECT" "$_ERG_GITIGNORE" 2>/dev/null && _ERG_HAS_PROJECT=1
|
|
147
|
+
_ERG_HAS_CI=0
|
|
148
|
+
grep -qE "$_ERG_RE_CLAUDEIGNORE" "$_ERG_GITIGNORE" 2>/dev/null && _ERG_HAS_CI=1
|
|
149
|
+
|
|
150
|
+
# The completion lines this file lacks, in block order (policy, project, then
|
|
151
|
+
# .claudeignore). Mirrors missingCompletionLines in the TS twin.
|
|
152
|
+
_ERG_COMPLETION=''
|
|
153
|
+
if [ "$_ERG_HAS_POLICY" = 0 ]; then
|
|
154
|
+
_ERG_COMPLETION="$_ERG_LINE_POLICY"
|
|
155
|
+
fi
|
|
156
|
+
if [ "$_ERG_HAS_PROJECT" = 0 ]; then
|
|
157
|
+
_ERG_COMPLETION="$_ERG_COMPLETION$_ERG_LINE_PROJECT"
|
|
158
|
+
fi
|
|
159
|
+
if [ "$_ERG_HAS_CI" = 0 ]; then
|
|
160
|
+
_ERG_COMPLETION="$_ERG_COMPLETION$_ERG_LINE_CLAUDEIGNORE"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# The block lines a top-up run is inserted after: the sentinel, then whichever of
|
|
164
|
+
# the policy and project lines precede the first missing line in a fresh block.
|
|
165
|
+
# Mirrors blockRunBefore in the TS twin.
|
|
166
|
+
if [ "$_ERG_HAS_POLICY" = 0 ]; then
|
|
167
|
+
_ERG_RUN_RE='^[[:space:]]*(!\.devflow/conventions\.md)[[:space:]]*$'
|
|
168
|
+
elif [ "$_ERG_HAS_PROJECT" = 0 ]; then
|
|
169
|
+
_ERG_RUN_RE='^[[:space:]]*(!\.devflow/conventions\.md|!\.devflow/policy\.json)[[:space:]]*$'
|
|
170
|
+
else
|
|
171
|
+
_ERG_RUN_RE='^[[:space:]]*(!\.devflow/conventions\.md|!\.devflow/policy\.json|!\.devflow/project\.json)[[:space:]]*$'
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# Classify: noop (nothing to do) | continue (extend an existing block) |
|
|
175
|
+
# block (install a fresh block) | fail (a rewrite failed; leave the file alone).
|
|
176
|
+
# continue inserts _ERG_APPEND after the first line matching _ERG_ANCHOR_RE and the
|
|
177
|
+
# run of lines matching _ERG_RUN_RE right after it (empty: no run).
|
|
178
|
+
_ERG_MODE=noop
|
|
179
|
+
_ERG_APPEND=''
|
|
180
|
+
_ERG_ANCHOR_RE=''
|
|
181
|
+
if grep -qE "$_ERG_RE_OPTOUT" "$_ERG_GITIGNORE" 2>/dev/null; then
|
|
79
182
|
# User-authored `/.devflow/` (leading slash) — respect it; don't force the carve-out.
|
|
80
|
-
# Checked
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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"
|
|
183
|
+
# Checked FIRST so a file containing both it and a sentinel keeps the user's entry.
|
|
184
|
+
_ERG_MODE=noop
|
|
185
|
+
elif grep -qE "$_ERG_RE_SENTINEL" "$_ERG_GITIGNORE" 2>/dev/null; then
|
|
186
|
+
# Block installed (v3 and later) — top up only the completion lines it lacks.
|
|
187
|
+
if [ -n "$_ERG_COMPLETION" ]; then
|
|
188
|
+
_ERG_MODE=continue
|
|
189
|
+
_ERG_APPEND="$_ERG_COMPLETION"
|
|
190
|
+
_ERG_ANCHOR_RE="$_ERG_RE_SENTINEL"
|
|
91
191
|
fi
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
192
|
+
elif grep -qE "$_ERG_RE_SENTINEL_V2" "$_ERG_GITIGNORE" 2>/dev/null; then
|
|
193
|
+
# v2 block installed — insert the lines it lacks, in block order, right after
|
|
194
|
+
# its sentinel, the last carve-out line it has.
|
|
195
|
+
_ERG_MODE=continue
|
|
196
|
+
_ERG_APPEND="$_ERG_LINE_SENTINEL$_ERG_COMPLETION"
|
|
197
|
+
_ERG_ANCHOR_RE="$_ERG_RE_SENTINEL_V2"
|
|
198
|
+
_ERG_RUN_RE=''
|
|
105
199
|
else
|
|
106
|
-
#
|
|
107
|
-
|
|
200
|
+
# No devflow block — install one, omitting the .claudeignore line the user owns.
|
|
201
|
+
_ERG_MODE=block
|
|
202
|
+
if [ "$_ERG_HAS_CI" = 1 ]; then
|
|
203
|
+
_ERG_APPEND="$_ERG_BLOCK_NO_CI"
|
|
204
|
+
else
|
|
205
|
+
_ERG_APPEND="$_ERG_BLOCK"
|
|
206
|
+
fi
|
|
207
|
+
if grep -qE "$_ERG_RE_LEGACY" "$_ERG_GITIGNORE" 2>/dev/null; then
|
|
208
|
+
# Upgrade our legacy wholesale entry: strip the bare `.devflow/` line and our old
|
|
209
|
+
# comment first, then fall through to the shared append below. Portable (grep
|
|
210
|
+
# filter + mv; no sed -i, which differs on BSD/GNU).
|
|
211
|
+
# The pipeline's own status is deliberately ignored: grep -v exits 1 when it
|
|
212
|
+
# emits no lines, which is the correct outcome for a .gitignore whose only
|
|
213
|
+
# content was the legacy entry. The redirect creates _ERG_TMP either way.
|
|
214
|
+
_ERG_TMP="$_ERG_GITIGNORE.devflow-tmp.$$"
|
|
215
|
+
grep -vE "$_ERG_RE_LEGACY" "$_ERG_GITIGNORE" 2>/dev/null \
|
|
216
|
+
| grep -vF '# Devflow runtime data (local by default; remove to share via git)' \
|
|
217
|
+
> "$_ERG_TMP" 2>/dev/null
|
|
218
|
+
if [ -f "$_ERG_TMP" ] && mv "$_ERG_TMP" "$_ERG_GITIGNORE"; then
|
|
219
|
+
:
|
|
220
|
+
else
|
|
221
|
+
rm -f "$_ERG_TMP" 2>/dev/null
|
|
222
|
+
_ERG_MODE=fail
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
108
225
|
fi
|
|
109
226
|
|
|
110
|
-
#
|
|
227
|
+
# _erg_insert_in_block <anchor-ERE> <run-ERE or ''> <text> — rewrite the file with
|
|
228
|
+
# <text> inserted after the first line matching <anchor-ERE> and the run of up to
|
|
229
|
+
# three lines matching <run-ERE> right after it (D-GITIGNORE-IN-BLOCK). Mirrors
|
|
230
|
+
# insertInBlock in the TS twin byte for byte: head and tail copy every other byte
|
|
231
|
+
# unchanged, and when the run ends on a last line with no newline, one is added
|
|
232
|
+
# before <text>. Each read ends in a trailing `x`, so command substitution cannot
|
|
233
|
+
# strip the file's own trailing newlines. The file is rewritten in place with one
|
|
234
|
+
# `>`, as the TS twin's writeFile does, so a symlinked .gitignore keeps its link.
|
|
235
|
+
# Returns non-zero, having written nothing, when the anchor line cannot be found.
|
|
236
|
+
_erg_insert_in_block() {
|
|
237
|
+
local _anchor="$1" _run="$2" _text="$3" _hit="" _n="" _next="" _line="" _i=0 _head="" _tail=""
|
|
238
|
+
_hit="$(grep -n -m 1 -E "$_anchor" "$_ERG_GITIGNORE" 2>/dev/null)" || return 1
|
|
239
|
+
_n="${_hit%%:*}"
|
|
240
|
+
case "$_n" in ''|*[!0-9]*) return 1 ;; esac
|
|
241
|
+
if [ -n "$_run" ]; then
|
|
242
|
+
_next="$(sed -n "$((_n + 1)),$((_n + 3))p" "$_ERG_GITIGNORE" 2>/dev/null)"
|
|
243
|
+
while [ "$_i" -lt 3 ] && [ -n "$_next" ]; do
|
|
244
|
+
_line="${_next%%$'\n'*}"
|
|
245
|
+
if [ "$_line" = "$_next" ]; then _next=''; else _next="${_next#*$'\n'}"; fi
|
|
246
|
+
[[ $_line =~ $_run ]] || break
|
|
247
|
+
_n=$((_n + 1))
|
|
248
|
+
_i=$((_i + 1))
|
|
249
|
+
done
|
|
250
|
+
fi
|
|
251
|
+
# The `x` is printed only when the read succeeded, so a failed read leaves none
|
|
252
|
+
# and nothing is written — never a truncated file.
|
|
253
|
+
_head="$(head -n "$_n" "$_ERG_GITIGNORE" 2>/dev/null && printf x)"
|
|
254
|
+
case "$_head" in *x) _head="${_head%x}" ;; *) return 1 ;; esac
|
|
255
|
+
_tail="$(tail -n "+$((_n + 1))" "$_ERG_GITIGNORE" 2>/dev/null && printf x)"
|
|
256
|
+
case "$_tail" in *x) _tail="${_tail%x}" ;; *) return 1 ;; esac
|
|
257
|
+
case "$_head" in *$'\n') ;; *) _head="$_head"$'\n' ;; esac
|
|
258
|
+
printf '%s%s%s' "$_head" "$_text" "$_tail" > "$_ERG_GITIGNORE"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# The fresh-block append shape, mirrored byte-for-byte by the TS twin's appendBlock.
|
|
262
|
+
# `tail -c 1` yields the empty string when the file already ends in a newline
|
|
263
|
+
# (command substitution strips it), so a file whose last byte is not a newline gets
|
|
264
|
+
# one before the appended text fuses onto its last line. Existing trailing newlines
|
|
265
|
+
# are preserved verbatim — no trimming, no blank-line dedupe.
|
|
266
|
+
_ERG_OK=0
|
|
267
|
+
case "$_ERG_MODE" in
|
|
268
|
+
noop)
|
|
269
|
+
_ERG_OK=1
|
|
270
|
+
;;
|
|
271
|
+
continue)
|
|
272
|
+
_erg_insert_in_block "$_ERG_ANCHOR_RE" "$_ERG_RUN_RE" "$_ERG_APPEND" && _ERG_OK=1
|
|
273
|
+
;;
|
|
274
|
+
block)
|
|
275
|
+
if [ ! -s "$_ERG_GITIGNORE" ]; then
|
|
276
|
+
# Absent or empty — the block is the whole file, with no leading blank line.
|
|
277
|
+
printf '%s' "$_ERG_APPEND" > "$_ERG_GITIGNORE" && _ERG_OK=1
|
|
278
|
+
else
|
|
279
|
+
if [ -n "$(tail -c 1 "$_ERG_GITIGNORE" 2>/dev/null)" ]; then
|
|
280
|
+
printf '\n' >> "$_ERG_GITIGNORE"
|
|
281
|
+
fi
|
|
282
|
+
{ printf '\n'; printf '%s' "$_ERG_APPEND"; } >> "$_ERG_GITIGNORE" && _ERG_OK=1
|
|
283
|
+
fi
|
|
284
|
+
;;
|
|
285
|
+
esac
|
|
286
|
+
|
|
287
|
+
# On success, stamp the current-format v6 marker and drop legacy markers.
|
|
111
288
|
[ "$_ERG_OK" = 1 ] && {
|
|
112
289
|
touch "$_ERG_MARKER"
|
|
113
|
-
|
|
114
|
-
rm -f "$_ERG_DEVFLOW_DIR/.root-gitignore-configured" 2>/dev/null
|
|
290
|
+
_erg_drop_legacy_markers
|
|
115
291
|
}
|
|
116
292
|
return 0
|
|
@@ -14,3 +14,51 @@ df_has_git_marker() {
|
|
|
14
14
|
done
|
|
15
15
|
return 1
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
# df_is_project_root <root> — returns 0 when <root> may hold per-project devflow
|
|
19
|
+
# data: it is a checkout's toplevel — it holds its own `.git` entry — AND it is not
|
|
20
|
+
# the home directory.
|
|
21
|
+
#
|
|
22
|
+
# D-HOOKS-GIT-ONLY: the hooks that scaffold `.devflow/` and the root `.gitignore`
|
|
23
|
+
# carve-out — session-start-context and ensure-devflow-init, which every capture
|
|
24
|
+
# and memory hook reaches — run only for a git project. Outside one there is no
|
|
25
|
+
# repository for the carve-out to protect and no branch for memory to describe,
|
|
26
|
+
# and scaffolding there would leave `.devflow/` and a `.gitignore` block in `~`,
|
|
27
|
+
# Downloads or a folder of repos — wherever Claude Code happened to start.
|
|
28
|
+
# A repository rooted at HOME (a dotfiles repo) is refused too: its `.devflow` IS
|
|
29
|
+
# the machine root, so per-project data would land inside `~/.devflow` and its
|
|
30
|
+
# carve-out in the user's home `.gitignore`.
|
|
31
|
+
#
|
|
32
|
+
# D-HOOKS-TOPLEVEL-ONLY: the `.git` entry must sit AT <root>, not merely above it.
|
|
33
|
+
# Callers pass the root df_resolve_roots resolved, and when `git rev-parse` fails
|
|
34
|
+
# from inside a checkout — the repository has dubious ownership, or
|
|
35
|
+
# GIT_CEILING_DIRECTORIES stops git's search — that root is the raw cwd, possibly a
|
|
36
|
+
# subdirectory. An ancestor walk would still find the checkout's `.git` and admit
|
|
37
|
+
# it, and `.devflow/` plus a `.gitignore` block would land in that subdirectory.
|
|
38
|
+
# Requiring the marker at the root makes the gate and the resolver agree: git's
|
|
39
|
+
# toplevel always holds `.git` (a directory, or the file a linked worktree or
|
|
40
|
+
# submodule carries), a subdirectory never does. A directory that holds its own
|
|
41
|
+
# `.git` still passes when git cannot read it — the marker contract is the zero-fork
|
|
42
|
+
# one above, and writing there scatters nothing, because it is the checkout's root.
|
|
43
|
+
#
|
|
44
|
+
# The comparison is between PHYSICAL paths, because the two sides reach it by
|
|
45
|
+
# different routes: git reports a toplevel with every symlink resolved, while
|
|
46
|
+
# $HOME is whatever the login shell was handed — on macOS the temp tree is
|
|
47
|
+
# /var → /private/var, and a home directory can itself sit behind a symlink. A
|
|
48
|
+
# textual compare would miss exactly the case it exists for.
|
|
49
|
+
#
|
|
50
|
+
# Zero forks, like the marker walk above: a builtin `-e` test, then `cd -P`, which
|
|
51
|
+
# resolves a path physically and publishes it in $PWD — both shell builtins, so no
|
|
52
|
+
# `$(...)` subshell and no realpath/readlink binary is involved. The caller's directory is restored before
|
|
53
|
+
# returning. An unresolvable <root> is refused (fail closed: nothing is written);
|
|
54
|
+
# an unset or unresolvable HOME cannot equal a resolved root, so it refuses nothing.
|
|
55
|
+
df_is_project_root() {
|
|
56
|
+
local _root="$1" _back="$PWD" _root_real="" _home_real=""
|
|
57
|
+
[ -n "$_root" ] || return 1
|
|
58
|
+
[ -e "$_root/.git" ] || return 1
|
|
59
|
+
if cd -P "$_root" 2>/dev/null; then _root_real="$PWD"; fi
|
|
60
|
+
if [ -n "${HOME:-}" ] && cd -P "$HOME" 2>/dev/null; then _home_real="$PWD"; fi
|
|
61
|
+
cd "$_back" 2>/dev/null || true
|
|
62
|
+
[ -n "$_root_real" ] || return 1
|
|
63
|
+
[ "$_root_real" != "$_home_real" ]
|
|
64
|
+
}
|
|
@@ -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.$$"
|