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
|
@@ -24,7 +24,12 @@
|
|
|
24
24
|
// session-output <context> Build SessionStart output envelope
|
|
25
25
|
// prompt-output <context> Build UserPromptSubmit output envelope
|
|
26
26
|
// backup-construct Build pre-compact backup JSON from --arg pairs
|
|
27
|
-
// assign-anchor <type> <obs_id>
|
|
27
|
+
// assign-anchor <type> <obs_id> [--allow-collision]
|
|
28
|
+
// Claim next ADR/PF number, render both .md files.
|
|
29
|
+
// Refuses on a pre-mint citation collision (E4) unless
|
|
30
|
+
// --allow-collision is passed.
|
|
31
|
+
// next-anchor <type> Read-only: print the next candidate ADR/PF id and
|
|
32
|
+
// any pre-mint collision hits; mutates nothing (E4)
|
|
28
33
|
// retire-anchor <anchor_id> <status> Flip ledger row status, re-render both .md files
|
|
29
34
|
// refresh-anchor <anchor_id> Re-project log obs onto ledger row, re-render
|
|
30
35
|
// rotate-observations [<log>] [<arch>] Archive observing rows older than 30 days
|
|
@@ -33,6 +38,7 @@
|
|
|
33
38
|
|
|
34
39
|
const fs = require('fs');
|
|
35
40
|
const path = require('path');
|
|
41
|
+
const { execFileSync } = require('child_process');
|
|
36
42
|
|
|
37
43
|
const op = process.argv[2];
|
|
38
44
|
const args = process.argv.slice(3);
|
|
@@ -156,6 +162,154 @@ function nextAnchorFromLedger(ledgerRows, type) {
|
|
|
156
162
|
return { anchorId: `${prefix}-${nextN}`, nextN };
|
|
157
163
|
}
|
|
158
164
|
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Pre-mint collision guard (E4).
|
|
167
|
+
//
|
|
168
|
+
// A design doc can cite a design-local number ("PF-017") in tracked source
|
|
169
|
+
// before the ledger ever mints that same number for an unrelated entry — the
|
|
170
|
+
// two silently collide and nothing catches it until a human notices the text
|
|
171
|
+
// doesn't match. This scans the project tree for a whole-word citation of the
|
|
172
|
+
// candidate id BEFORE assign-anchor writes it, and refuses to mint over a hit.
|
|
173
|
+
// The consumer-side counterpart lives in mdl's scripts/verify-ledger-citations.mjs.
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
/** Directory names excluded from collision scanning at any depth (E4). */
|
|
177
|
+
const COLLISION_SCAN_EXCLUDED_SEGMENTS = new Set(['.git', 'node_modules', 'target', 'dist']);
|
|
178
|
+
|
|
179
|
+
/** Files larger than this are skipped during collision scanning — bounds the scan (E4). */
|
|
180
|
+
const COLLISION_SCAN_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* True when a project-relative path must be excluded from collision scanning:
|
|
184
|
+
* the ledger's own files (`.devflow/learning/**`, self-citation is expected,
|
|
185
|
+
* not a collision) or any of the excluded directory segments.
|
|
186
|
+
*
|
|
187
|
+
* @param {string} relPath - path relative to the project root, either separator style
|
|
188
|
+
* @returns {boolean}
|
|
189
|
+
*/
|
|
190
|
+
function isCollisionScanExcluded(relPath) {
|
|
191
|
+
const norm = relPath.split(path.sep).join('/');
|
|
192
|
+
if (norm === '.devflow/learning' || norm.startsWith('.devflow/learning/')) return true;
|
|
193
|
+
return norm.split('/').some(seg => COLLISION_SCAN_EXCLUDED_SEGMENTS.has(seg));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* List tracked files via `git ls-files` (respects .gitignore; args passed as an
|
|
198
|
+
* array — never shelled through a string-built command). Throws when the
|
|
199
|
+
* project root is not a git working tree or the `git` binary is unavailable;
|
|
200
|
+
* callers fall back to `listFsWalkFiles`.
|
|
201
|
+
*
|
|
202
|
+
* D-NO-FSMONITOR: `ls-files` reads the index, and reading the index runs the
|
|
203
|
+
* command a repository's config names in `core.fsmonitor` — code chosen by the
|
|
204
|
+
* repository this hook runs inside. The call turns it off for itself
|
|
205
|
+
* (`-c core.fsmonitor=false`), so the listing stays a pure read.
|
|
206
|
+
*
|
|
207
|
+
* @param {string} projectRoot
|
|
208
|
+
* @returns {string[]} project-relative paths
|
|
209
|
+
*/
|
|
210
|
+
function listGitTrackedFiles(projectRoot) {
|
|
211
|
+
const out = execFileSync('git', ['-c', 'core.fsmonitor=false', 'ls-files', '-z'], {
|
|
212
|
+
cwd: projectRoot,
|
|
213
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
214
|
+
});
|
|
215
|
+
return out.toString('utf8').split('\0').filter(Boolean);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Bounded, non-recursing-into-excluded-dirs fs walk — fallback for a project
|
|
220
|
+
* root that is not a git working tree. The walk is bounded by construction:
|
|
221
|
+
* it only descends into directories actually present on disk, and never
|
|
222
|
+
* descends into an excluded directory at all (E4).
|
|
223
|
+
*
|
|
224
|
+
* @param {string} projectRoot
|
|
225
|
+
* @returns {string[]} project-relative paths
|
|
226
|
+
*/
|
|
227
|
+
function listFsWalkFiles(projectRoot) {
|
|
228
|
+
const results = [];
|
|
229
|
+
const stack = [''];
|
|
230
|
+
while (stack.length > 0) {
|
|
231
|
+
const relDir = stack.pop();
|
|
232
|
+
const absDir = relDir ? path.join(projectRoot, relDir) : projectRoot;
|
|
233
|
+
let entries;
|
|
234
|
+
try {
|
|
235
|
+
entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
236
|
+
} catch {
|
|
237
|
+
continue; // unreadable dir — best-effort scan, skip
|
|
238
|
+
}
|
|
239
|
+
for (const entry of entries) {
|
|
240
|
+
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
241
|
+
if (isCollisionScanExcluded(relPath)) continue;
|
|
242
|
+
if (entry.isDirectory()) {
|
|
243
|
+
stack.push(relPath);
|
|
244
|
+
} else if (entry.isFile()) {
|
|
245
|
+
results.push(relPath);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return results;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Scan the project tree for a whole-word citation of `id` (e.g. `ADR-042`),
|
|
254
|
+
* excluding the ledger's own files and common vendored/build directories.
|
|
255
|
+
* Prefers tracked files (`git ls-files`) when the project root is a git
|
|
256
|
+
* working tree; falls back to a bounded fs walk otherwise. Best-effort:
|
|
257
|
+
* unreadable, binary, or oversized files are skipped rather than failing
|
|
258
|
+
* the scan.
|
|
259
|
+
*
|
|
260
|
+
* @param {string} projectRoot
|
|
261
|
+
* @param {string} id - e.g. 'ADR-042' or 'PF-017'
|
|
262
|
+
* @returns {{ file: string, line: number }[]} hits, empty when no collision
|
|
263
|
+
*/
|
|
264
|
+
function scanForAnchorCollision(projectRoot, id) {
|
|
265
|
+
let files;
|
|
266
|
+
try {
|
|
267
|
+
files = listGitTrackedFiles(projectRoot);
|
|
268
|
+
} catch {
|
|
269
|
+
files = listFsWalkFiles(projectRoot);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
273
|
+
const pattern = new RegExp(`\\b${escaped}\\b`);
|
|
274
|
+
const hits = [];
|
|
275
|
+
for (const relPath of files) {
|
|
276
|
+
if (isCollisionScanExcluded(relPath)) continue;
|
|
277
|
+
const absPath = path.join(projectRoot, relPath);
|
|
278
|
+
let stat;
|
|
279
|
+
try {
|
|
280
|
+
stat = fs.statSync(absPath);
|
|
281
|
+
} catch {
|
|
282
|
+
continue; // race: listed then removed — best-effort scan, skip
|
|
283
|
+
}
|
|
284
|
+
if (!stat.isFile() || stat.size > COLLISION_SCAN_MAX_FILE_BYTES) continue;
|
|
285
|
+
let content;
|
|
286
|
+
try {
|
|
287
|
+
content = fs.readFileSync(absPath, 'utf8');
|
|
288
|
+
} catch {
|
|
289
|
+
continue; // unreadable or invalid utf8 — best-effort scan, skip
|
|
290
|
+
}
|
|
291
|
+
if (content.includes('\u0000')) continue; // binary heuristic
|
|
292
|
+
const lines = content.split('\n');
|
|
293
|
+
for (let i = 0; i < lines.length; i++) {
|
|
294
|
+
if (pattern.test(lines[i])) {
|
|
295
|
+
hits.push({ file: relPath, line: i + 1 });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return hits;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Format collision hits for a stderr/stdout report — one `file:line` per line,
|
|
304
|
+
* two-space indented (E4).
|
|
305
|
+
*
|
|
306
|
+
* @param {{ file: string, line: number }[]} hits
|
|
307
|
+
* @returns {string}
|
|
308
|
+
*/
|
|
309
|
+
function formatCollisionHits(hits) {
|
|
310
|
+
return hits.map(h => ` ${h.file}:${h.line}`).join('\n');
|
|
311
|
+
}
|
|
312
|
+
|
|
159
313
|
/**
|
|
160
314
|
* Read .decisions-usage.json. Returns {version, entries} or empty default.
|
|
161
315
|
* @param {string} projectRoot - Path to project root (cwd)
|
|
@@ -533,20 +687,36 @@ try {
|
|
|
533
687
|
}
|
|
534
688
|
|
|
535
689
|
// -------------------------------------------------------------------------
|
|
536
|
-
// assign-anchor <type> <obs_id>
|
|
690
|
+
// assign-anchor <type> <obs_id> [--allow-collision]
|
|
537
691
|
// AC-A2: Assign next anchor ID for the given type (decision|pitfall) to the
|
|
538
692
|
// observation identified by obs_id in decisions-log.jsonl. Atomic under a
|
|
539
693
|
// single .decisions.lock acquisition. Registers usage, re-renders both .md.
|
|
540
694
|
//
|
|
695
|
+
// E4: before writing, refuses if the candidate id is already cited as a
|
|
696
|
+
// whole word somewhere in tracked source (a pre-mint collision — see
|
|
697
|
+
// scanForAnchorCollision above). --allow-collision skips the scan and
|
|
698
|
+
// mints anyway, for the human-ruled case where the citation should be
|
|
699
|
+
// superseded by the ledger's number.
|
|
700
|
+
//
|
|
541
701
|
// Locking discipline: holds ONLY .decisions.lock (never .observations.lock).
|
|
542
702
|
// O(anchored) — single pass for max numeric suffix (AC-P2).
|
|
543
703
|
// -------------------------------------------------------------------------
|
|
544
704
|
case 'assign-anchor': {
|
|
545
|
-
const
|
|
546
|
-
const
|
|
705
|
+
const aaKnownFlags = new Set(['--allow-collision']);
|
|
706
|
+
const aaFlags = args.filter(a => a.startsWith('--'));
|
|
707
|
+
const aaUnknownFlags = aaFlags.filter(f => !aaKnownFlags.has(f));
|
|
708
|
+
if (aaUnknownFlags.length > 0) {
|
|
709
|
+
process.stderr.write(`assign-anchor: unknown flag(s): ${aaUnknownFlags.join(', ')}\n`);
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
712
|
+
const aaAllowCollision = aaFlags.includes('--allow-collision');
|
|
713
|
+
const aaPositional = args.filter(a => !a.startsWith('--'));
|
|
714
|
+
|
|
715
|
+
const assignType = aaPositional[0]; // 'decision' or 'pitfall'
|
|
716
|
+
const assignObsId = aaPositional[1];
|
|
547
717
|
|
|
548
718
|
if (!assignType || !assignObsId) {
|
|
549
|
-
process.stderr.write('assign-anchor: usage: assign-anchor <type> <obs_id
|
|
719
|
+
process.stderr.write('assign-anchor: usage: assign-anchor <type> <obs_id> [--allow-collision]\n');
|
|
550
720
|
process.exit(1);
|
|
551
721
|
}
|
|
552
722
|
if (assignType !== 'decision' && assignType !== 'pitfall') {
|
|
@@ -565,6 +735,22 @@ try {
|
|
|
565
735
|
// Compute next anchor — O(anchored), single pass
|
|
566
736
|
const { anchorId: aaAnchorId } = nextAnchorFromLedger(aaLedgerRows, assignType);
|
|
567
737
|
|
|
738
|
+
// E4: pre-mint collision guard — refuse if the candidate id is already
|
|
739
|
+
// cited (as a whole word) somewhere in tracked source with a different
|
|
740
|
+
// meaning, before any ledger write. Never auto-skip to the next free
|
|
741
|
+
// number — the collision is a human call (rename the citation, or
|
|
742
|
+
// rerun with --allow-collision to mint over it deliberately).
|
|
743
|
+
if (!aaAllowCollision) {
|
|
744
|
+
const aaCollisionHits = scanForAnchorCollision(aaProjectRoot, aaAnchorId);
|
|
745
|
+
if (aaCollisionHits.length > 0) {
|
|
746
|
+
throw new Error(
|
|
747
|
+
`assign-anchor: '${aaAnchorId}' is already cited in source with a different ` +
|
|
748
|
+
`meaning; resolve the collision before minting (or pass --allow-collision):\n` +
|
|
749
|
+
formatCollisionHits(aaCollisionHits)
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
568
754
|
// Read observation from log
|
|
569
755
|
let aaLogEntries = parseLedger(aaLogPath);
|
|
570
756
|
const aaObsIdx = aaLogEntries.findIndex(e => e.id === assignObsId);
|
|
@@ -648,6 +834,41 @@ try {
|
|
|
648
834
|
break;
|
|
649
835
|
}
|
|
650
836
|
|
|
837
|
+
// -------------------------------------------------------------------------
|
|
838
|
+
// next-anchor <type>
|
|
839
|
+
// E4: Read-only preview of what assign-anchor would mint next — no lock
|
|
840
|
+
// acquired, no file written, no usage entry registered. Prints the
|
|
841
|
+
// candidate id and, when a pre-mint collision guard would fire, its
|
|
842
|
+
// file:line hits — so a caller can check before committing to assign-anchor.
|
|
843
|
+
// -------------------------------------------------------------------------
|
|
844
|
+
case 'next-anchor': {
|
|
845
|
+
const naType = args[0];
|
|
846
|
+
|
|
847
|
+
if (!naType) {
|
|
848
|
+
process.stderr.write('next-anchor: usage: next-anchor <type>\n');
|
|
849
|
+
process.exit(1);
|
|
850
|
+
}
|
|
851
|
+
if (naType !== 'decision' && naType !== 'pitfall') {
|
|
852
|
+
process.stderr.write(`next-anchor: type must be 'decision' or 'pitfall', got '${naType}'\n`);
|
|
853
|
+
process.exit(1);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const naProjectRoot = process.cwd();
|
|
857
|
+
const naLedgerRows = parseLedger(getDecisionsLedgerPath(naProjectRoot));
|
|
858
|
+
const { anchorId: naAnchorId } = nextAnchorFromLedger(naLedgerRows, naType);
|
|
859
|
+
const naHits = scanForAnchorCollision(naProjectRoot, naAnchorId);
|
|
860
|
+
|
|
861
|
+
process.stdout.write(naAnchorId + '\n');
|
|
862
|
+
if (naHits.length > 0) {
|
|
863
|
+
process.stderr.write(
|
|
864
|
+
`next-anchor: '${naAnchorId}' is already cited in source — collision hits:\n` +
|
|
865
|
+
formatCollisionHits(naHits) + '\n'
|
|
866
|
+
);
|
|
867
|
+
process.exit(1);
|
|
868
|
+
}
|
|
869
|
+
break;
|
|
870
|
+
}
|
|
871
|
+
|
|
651
872
|
// -------------------------------------------------------------------------
|
|
652
873
|
// retire-anchor <anchor_id> <status>
|
|
653
874
|
// AC-A3, AC-F5, AC-F7: Flip decisions_status on the ledger row. Idempotent.
|
|
@@ -906,5 +1127,7 @@ if (typeof module !== 'undefined' && module.exports) {
|
|
|
906
1127
|
initDecisionsContent,
|
|
907
1128
|
nextAnchorFromLedger,
|
|
908
1129
|
rotateObservations,
|
|
1130
|
+
scanForAnchorCollision,
|
|
1131
|
+
isCollisionScanExcluded,
|
|
909
1132
|
};
|
|
910
1133
|
}
|
|
@@ -43,7 +43,7 @@ function getDocsDir(projectRoot) {
|
|
|
43
43
|
// Feature config (neutral .devflow root — not inside learning/)
|
|
44
44
|
// ---------------------------------------------------------------------------
|
|
45
45
|
|
|
46
|
-
/** .devflow/config.json —
|
|
46
|
+
/** .devflow/config.json — per-repo facts {reviewPublication, tracker override} */
|
|
47
47
|
function getFeatureConfigPath(projectRoot) {
|
|
48
48
|
return path.join(projectRoot, '.devflow', 'config.json');
|
|
49
49
|
}
|
|
@@ -174,23 +174,6 @@ function getHandoffPath(projectRoot, branchSlug) {
|
|
|
174
174
|
return path.join(projectRoot, '.devflow', 'docs', `handoff-${branchSlug}.md`);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
// ---------------------------------------------------------------------------
|
|
178
|
-
// Gitignore entries
|
|
179
|
-
// ---------------------------------------------------------------------------
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* The canonical list of generic gitignore entries Devflow adds to a project's
|
|
183
|
-
* root .gitignore for LOCAL-scope installs. Currently just `.claude/`.
|
|
184
|
-
*
|
|
185
|
-
* `.devflow/` is intentionally NOT here: it is managed by ensureDevflowGitignore
|
|
186
|
-
* (TS) / ensure-root-gitignore (hook), which write the feature-knowledge carve-out
|
|
187
|
-
* for ALL scopes. Adding a bare `.devflow/` here would append a wholesale-ignore
|
|
188
|
-
* line after the carve-out and re-bury it (last match wins in .gitignore).
|
|
189
|
-
*/
|
|
190
|
-
function getGitignoreEntries() {
|
|
191
|
-
return ['.claude/'];
|
|
192
|
-
}
|
|
193
|
-
|
|
194
177
|
module.exports = {
|
|
195
178
|
// Core directories
|
|
196
179
|
getMemoryDir,
|
|
@@ -225,6 +208,4 @@ module.exports = {
|
|
|
225
208
|
getDesignDir,
|
|
226
209
|
getResearchDir,
|
|
227
210
|
getHandoffPath,
|
|
228
|
-
// Gitignore entries
|
|
229
|
-
getGitignoreEntries,
|
|
230
211
|
};
|
|
@@ -2,11 +2,88 @@
|
|
|
2
2
|
# Shared log path computation for Devflow hooks.
|
|
3
3
|
# Source this file to get devflow_log_dir function.
|
|
4
4
|
|
|
5
|
+
# D-LOG-DIR-CAP (hook side). Every hook logs under ~/.devflow/logs/<cwd-slug>/,
|
|
6
|
+
# one folder per working directory, and `devflow init` prunes them to the
|
|
7
|
+
# MAX_HOOK_LOG_DIRS most recent (src/core/hook-log-dirs.ts). A machine that never
|
|
8
|
+
# re-inits would still grow without bound, so devflow_log_dir also prunes — but
|
|
9
|
+
# ONLY when it creates a new folder, which is rare: the common path (the folder
|
|
10
|
+
# already exists) pays nothing. The pass is bounded (one `ls`, at most
|
|
11
|
+
# _DF_LOG_DIRS_SCANNED_MAX entries read, _DF_LOG_DIRS_PRUNED_PER_CALL removed in
|
|
12
|
+
# one `rm`), bash 3.2 compatible, and never spawns node. Only directories are
|
|
13
|
+
# counted or removed: root files (proxy.log) and symbolic links are left alone.
|
|
14
|
+
#
|
|
15
|
+
# Recency: folders are ranked by their own mtime (`ls -t`), which an append to a
|
|
16
|
+
# log inside does not move, so a candidate is spared when any of its first
|
|
17
|
+
# _DF_LOG_FILES_READ_PER_DIR entries is newer than the oldest folder the cap
|
|
18
|
+
# keeps (`-nt`, a shell builtin). A spared folder can leave the count briefly
|
|
19
|
+
# above the cap; init's exact prune settles it.
|
|
20
|
+
#
|
|
21
|
+
# _DF_MAX_HOOK_LOG_DIRS must equal MAX_HOOK_LOG_DIRS (tests/hook-log-paths.test.ts).
|
|
22
|
+
_DF_MAX_HOOK_LOG_DIRS=200
|
|
23
|
+
_DF_LOG_DIRS_PRUNED_PER_CALL=50
|
|
24
|
+
_DF_LOG_DIRS_SCANNED_MAX=100000
|
|
25
|
+
_DF_LOG_FILES_READ_PER_DIR=64
|
|
26
|
+
|
|
5
27
|
# Cache the computed log dir path to avoid spawning 4 subprocesses (sed+tr+mkdir+chmod)
|
|
6
28
|
# on every call within the same hook process.
|
|
7
29
|
_LOG_DIR_CACHED=""
|
|
8
30
|
_LOG_DIR_CACHED_CWD=""
|
|
9
31
|
|
|
32
|
+
# _df_log_dir_is_recent <dir> <cutoff>
|
|
33
|
+
# True when one of the first _DF_LOG_FILES_READ_PER_DIR entries in <dir> is newer
|
|
34
|
+
# than <cutoff>. Globbing must be on.
|
|
35
|
+
_df_log_dir_is_recent() {
|
|
36
|
+
local _dir="$1" _cutoff="$2" _f _read=0
|
|
37
|
+
for _f in "$_dir"/.[!.]* "$_dir"/..?* "$_dir"/*; do
|
|
38
|
+
[ "$_read" -lt "$_DF_LOG_FILES_READ_PER_DIR" ] || break
|
|
39
|
+
[ -e "$_f" ] || [ -L "$_f" ] || continue
|
|
40
|
+
_read=$((_read + 1))
|
|
41
|
+
if [ "$_f" -nt "$_cutoff" ]; then return 0; fi
|
|
42
|
+
done
|
|
43
|
+
return 1
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# _df_prune_log_dirs <logs-root> <just-created-dir>
|
|
47
|
+
# Remove the oldest folders beyond _DF_MAX_HOOK_LOG_DIRS, at most
|
|
48
|
+
# _DF_LOG_DIRS_PRUNED_PER_CALL of them. Never fails the caller.
|
|
49
|
+
_df_prune_log_dirs() {
|
|
50
|
+
local _logs="$1" _new="$2"
|
|
51
|
+
local _listing _name _n=0 _i _removed=0 _cutoff _d _glob_off=0
|
|
52
|
+
local -a _dirs _batch
|
|
53
|
+
_listing=$(ls -1At "$_logs" 2>/dev/null) || return 0
|
|
54
|
+
|
|
55
|
+
case $- in *f*) _glob_off=1 ;; esac
|
|
56
|
+
local IFS='
|
|
57
|
+
'
|
|
58
|
+
set -f
|
|
59
|
+
for _name in $_listing; do
|
|
60
|
+
[ "$_n" -lt "$_DF_LOG_DIRS_SCANNED_MAX" ] || break
|
|
61
|
+
case "$_name" in */*) continue ;; esac
|
|
62
|
+
[ -L "$_logs/$_name" ] && continue
|
|
63
|
+
[ -d "$_logs/$_name" ] || continue
|
|
64
|
+
_dirs[_n]="$_logs/$_name"
|
|
65
|
+
_n=$((_n + 1))
|
|
66
|
+
done
|
|
67
|
+
set +f
|
|
68
|
+
|
|
69
|
+
if [ "$_n" -gt "$_DF_MAX_HOOK_LOG_DIRS" ]; then
|
|
70
|
+
_cutoff="${_dirs[_DF_MAX_HOOK_LOG_DIRS - 1]}"
|
|
71
|
+
_i=$((_n - 1))
|
|
72
|
+
while [ "$_i" -ge "$_DF_MAX_HOOK_LOG_DIRS" ] && [ "$_removed" -lt "$_DF_LOG_DIRS_PRUNED_PER_CALL" ]; do
|
|
73
|
+
_d="${_dirs[_i]}"
|
|
74
|
+
if [ "$_d" != "$_new" ] && ! _df_log_dir_is_recent "$_d" "$_cutoff"; then
|
|
75
|
+
_batch[_removed]="$_d"
|
|
76
|
+
_removed=$((_removed + 1))
|
|
77
|
+
fi
|
|
78
|
+
_i=$((_i - 1))
|
|
79
|
+
done
|
|
80
|
+
if [ "$_removed" -gt 0 ]; then rm -rf -- "${_batch[@]}" 2>/dev/null || true; fi
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if [ "$_glob_off" = 1 ]; then set -f; fi
|
|
84
|
+
return 0
|
|
85
|
+
}
|
|
86
|
+
|
|
10
87
|
devflow_log_dir() {
|
|
11
88
|
local cwd="$1"
|
|
12
89
|
if [ "$cwd" = "$_LOG_DIR_CACHED_CWD" ] && [ -n "$_LOG_DIR_CACHED" ]; then
|
|
@@ -16,8 +93,11 @@ devflow_log_dir() {
|
|
|
16
93
|
local slug
|
|
17
94
|
slug=$(echo "$cwd" | sed 's|^/||' | tr '/' '-')
|
|
18
95
|
local dir="$HOME/.devflow/logs/$slug"
|
|
96
|
+
local created=0
|
|
97
|
+
[ -d "$dir" ] || created=1
|
|
19
98
|
mkdir -p "$dir"
|
|
20
99
|
chmod 700 "$dir"
|
|
100
|
+
if [ "$created" = 1 ]; then _df_prune_log_dirs "$HOME/.devflow/logs" "$dir" || true; fi
|
|
21
101
|
_LOG_DIR_CACHED="$dir"
|
|
22
102
|
_LOG_DIR_CACHED_CWD="$cwd"
|
|
23
103
|
echo "$dir"
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# Memory pipeline: memory-worker (Stop Hook)
|
|
4
4
|
# Owns the 120s-throttle + nohup-spawn logic for background-memory-update.
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# Claude Code runs a Stop event's hooks in parallel, so this hook may fire
|
|
6
|
+
# before capture-turn has appended this turn's assistant row. That is safe:
|
|
7
|
+
# background-memory-update leaves a user-only queue in place and skips the LLM
|
|
8
|
+
# run (D-QUEUE-NO-ORPHAN-DELETE), and the next run takes the whole turn. This
|
|
9
|
+
# hook does NOT append to any queue itself.
|
|
8
10
|
|
|
9
11
|
# Safe no-op fallback: must exist before set -e and before hook-bootstrap is sourced.
|
|
10
12
|
dbg() { :; }
|
|
@@ -32,15 +34,19 @@ source "$SCRIPT_DIR/resolve-project-root" 2>/dev/null || true
|
|
|
32
34
|
PROJECT_ROOT="$(df_resolve_root "$CWD" 2>/dev/null || true)"
|
|
33
35
|
[ -n "$PROJECT_ROOT" ] || PROJECT_ROOT="$CWD"
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
# The machine-wide manifest (the memory switch, D-FEATURES-NARROW-ONLY in
|
|
38
|
+
# queue-append) lives at the machine root,
|
|
39
|
+
# $HOME/.devflow (D-ONE-HOME).
|
|
40
|
+
DEVFLOW_MANIFEST="$HOME/.devflow/manifest.json"
|
|
41
|
+
PROJECT_DEVFLOW_DIR="$PROJECT_ROOT/.devflow"
|
|
42
|
+
MEMORY_DIR="$PROJECT_DEVFLOW_DIR/memory"
|
|
37
43
|
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
# Memory gate: the machine switch, narrowed by this checkout's project.json /
|
|
45
|
+
# config.json `features.memory`, gates this hook entirely -- read by the helper
|
|
46
|
+
# every memory/learning gate shares (D-FEATURES-NARROW-ONLY, see queue-append).
|
|
47
|
+
source "$SCRIPT_DIR/queue-append" || { echo "memory-worker: failed to source queue-append" >&2; exit 1; }
|
|
48
|
+
queue_read_gates "$DEVFLOW_MANIFEST" "$PROJECT_ROOT"
|
|
49
|
+
MEMORY_ENABLED="$_QG_MEMORY"
|
|
44
50
|
|
|
45
51
|
dbg "MEMORY_ENABLED=$MEMORY_ENABLED"
|
|
46
52
|
|
|
@@ -93,8 +99,11 @@ dbg "Throttle passed: TRIGGER_AGE=${TRIGGER_AGE}s >= 120s — spawning worker"
|
|
|
93
99
|
# Touch trigger BEFORE spawning (prevents concurrent Stop hooks from double-spawning)
|
|
94
100
|
touch "$TRIGGER_FILE" 2>/dev/null || true
|
|
95
101
|
|
|
96
|
-
# Spawn detached worker — nohup + disown so it survives the Stop hook process exit
|
|
97
|
-
|
|
102
|
+
# Spawn detached worker — nohup + disown so it survives the Stop hook process exit.
|
|
103
|
+
# The manifest path is handed over explicitly: the worker re-checks the memory
|
|
104
|
+
# switch after spawn against the same manifest this hook gated on (and the same
|
|
105
|
+
# checkout's repository files, from the project root it resolves itself).
|
|
106
|
+
nohup "$UPDATER" "$CWD" "$DEVFLOW_MANIFEST" </dev/null >>/dev/null 2>&1 & disown
|
|
98
107
|
|
|
99
108
|
log "Spawned background-memory-update worker (CWD=$CWD)"
|
|
100
109
|
dbg "=== HOOK COMPLETE ==="
|
|
@@ -43,20 +43,24 @@ source "$SCRIPT_DIR/resolve-project-root" 2>/dev/null || true
|
|
|
43
43
|
PROJECT_ROOT="$(df_resolve_root "$CWD" 2>/dev/null || true)"
|
|
44
44
|
[ -n "$PROJECT_ROOT" ] || PROJECT_ROOT="$CWD"
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
# The machine-wide manifest (the memory switch, D-FEATURES-NARROW-ONLY in
|
|
47
|
+
# queue-append) lives at the machine root,
|
|
48
|
+
# $HOME/.devflow (D-ONE-HOME).
|
|
49
|
+
DEVFLOW_MANIFEST="$HOME/.devflow/manifest.json"
|
|
50
|
+
PROJECT_DEVFLOW_DIR="$PROJECT_ROOT/.devflow"
|
|
51
|
+
MEMORY_DIR="$PROJECT_DEVFLOW_DIR/memory"
|
|
48
52
|
|
|
49
53
|
# Normal logging
|
|
50
54
|
source "$SCRIPT_DIR/hook-log-init" "pre-compact-memory"
|
|
51
55
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
# Memory gate: the machine switch, narrowed by this checkout's project.json /
|
|
57
|
+
# config.json `features.memory`, read by the helper every memory/learning gate
|
|
58
|
+
# shares (D-FEATURES-NARROW-ONLY, see queue-append).
|
|
59
|
+
source "$SCRIPT_DIR/queue-append" || { echo "pre-compact-memory: failed to source queue-append" >&2; exit 1; }
|
|
60
|
+
queue_read_gates "$DEVFLOW_MANIFEST" "$PROJECT_ROOT"
|
|
61
|
+
if [ "$_QG_MEMORY" != "true" ]; then
|
|
62
|
+
dbg "EXIT: memory disabled"
|
|
63
|
+
exit 0
|
|
60
64
|
fi
|
|
61
65
|
|
|
62
66
|
# Auto-create .devflow/ and ensure .gitignore entries (idempotent after first run)
|
|
@@ -75,9 +79,26 @@ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
|
75
79
|
if cd "$CWD" 2>/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then
|
|
76
80
|
GIT_HEAD_SHA=$(git rev-parse HEAD 2>/dev/null || echo "")
|
|
77
81
|
GIT_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
78
|
-
|
|
82
|
+
# D-DETACHED-HEAD: a checkout of a commit rather than a branch (`git checkout
|
|
83
|
+
# <sha>`, bisect, a rebase stop, `git worktree add --detach`, a CI checkout)
|
|
84
|
+
# prints no branch name. It is labelled `(detached)`, echoing git's own
|
|
85
|
+
# `(HEAD detached at …)` display and holding no space, so the stamp's
|
|
86
|
+
# one-token `branch:` field still parses; the backup names it and the bootstrap
|
|
87
|
+
# below still stamps memory, keyed on the commit it describes. (A branch really
|
|
88
|
+
# named `(detached)` is legal git and would read the same — accepted: nothing
|
|
89
|
+
# decides on the label, it only names.) An unborn branch has no HEAD commit,
|
|
90
|
+
# keeps the empty label, and is still skipped.
|
|
91
|
+
if [ -z "$GIT_BRANCH" ] && is_hex_sha "$GIT_HEAD_SHA" 40 40; then
|
|
92
|
+
GIT_BRANCH="(detached)"
|
|
93
|
+
fi
|
|
94
|
+
# D-NO-FSMONITOR: `status` and `diff` read the index, and reading the index
|
|
95
|
+
# runs the command the repository's config names in `core.fsmonitor` — code
|
|
96
|
+
# chosen by the repository this hook runs inside. Each index read turns it
|
|
97
|
+
# off for itself (`-c core.fsmonitor=false`); rev-parse, branch and log never
|
|
98
|
+
# read the index.
|
|
99
|
+
GIT_STATUS=$(git -c core.fsmonitor=false status --porcelain 2>/dev/null | head -30 || echo "")
|
|
79
100
|
GIT_LOG=$(git log --oneline -10 2>/dev/null || echo "")
|
|
80
|
-
GIT_DIFF_STAT=$(git diff --stat HEAD 2>/dev/null || echo "")
|
|
101
|
+
GIT_DIFF_STAT=$(git -c core.fsmonitor=false diff --stat HEAD 2>/dev/null || echo "")
|
|
81
102
|
dbg "GIT_BRANCH=$GIT_BRANCH HEAD=$GIT_HEAD_SHA"
|
|
82
103
|
fi
|
|
83
104
|
|
|
@@ -101,8 +122,12 @@ json_backup_construct \
|
|
|
101
122
|
log "Wrote backup: $BACKUP_FILE"
|
|
102
123
|
dbg "Wrote backup: $BACKUP_FILE"
|
|
103
124
|
|
|
104
|
-
# Bootstrap minimal WORKING-MEMORY.md if absent; skip on
|
|
105
|
-
#
|
|
125
|
+
# Bootstrap minimal WORKING-MEMORY.md if absent; skip on an unborn branch or a
|
|
126
|
+
# malformed SHA. is_hex_sha 40 40: exactly 40 lowercase hex chars required. A detached
|
|
127
|
+
# HEAD bootstraps with a `branch: (detached)` stamp (D-DETACHED-HEAD, above): the
|
|
128
|
+
# stamp's memory-head SHA is what drift detection keys on, and the Context line names
|
|
129
|
+
# the commit by its short SHA, so the first compaction there no longer leaves the
|
|
130
|
+
# session with nothing to restore.
|
|
106
131
|
# avoids REL-5: O_EXCL-style atomic create via noclobber so the existence test and the
|
|
107
132
|
# create are one operation — if the worker's CAS mv lands in the window, noclobber fails
|
|
108
133
|
# (file already exists) and we skip the bootstrap rather than truncating fresh memory.
|
|
@@ -122,7 +147,11 @@ if [ -n "$GIT_BRANCH" ] && is_hex_sha "$GIT_HEAD_SHA" 40 40; then
|
|
|
122
147
|
echo "- (none recorded)"
|
|
123
148
|
echo ""
|
|
124
149
|
echo "## Context"
|
|
125
|
-
|
|
150
|
+
if [ "$GIT_BRANCH" = "(detached)" ]; then
|
|
151
|
+
echo "- Branch: (detached) @ ${GIT_HEAD_SHA:0:7}"
|
|
152
|
+
else
|
|
153
|
+
echo "- Branch: $GIT_BRANCH"
|
|
154
|
+
fi
|
|
126
155
|
echo "$GIT_LOG" | head -3 | while IFS= read -r line; do
|
|
127
156
|
[ -n "$line" ] && echo "- $line"
|
|
128
157
|
done
|
|
@@ -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
|
|