devflow-kit 3.0.1 → 3.2.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 +49 -0
- package/README.md +1 -1
- package/dist/agents/git.md +2 -2
- package/dist/cli/agents-view/index.js +1 -1
- package/dist/cli/agents-view/render.js +71 -17
- package/dist/cli/agents-view/state.js +42 -16
- package/dist/cli/agents-view/terminal.js +5 -5
- package/dist/cli/commands/agents.js +142 -51
- package/dist/cli/commands/ambient.js +1 -1
- package/dist/cli/commands/attribution-prompts.js +8 -8
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/compliance-prompts.js +8 -8
- package/dist/cli/commands/compliance.js +8 -7
- package/dist/cli/commands/flags.js +33 -31
- package/dist/cli/commands/hud.js +1 -1
- package/dist/cli/commands/init-seed.js +9 -9
- package/dist/cli/commands/init.js +162 -85
- package/dist/cli/commands/install-report.js +10 -10
- package/dist/cli/commands/learning.js +302 -136
- package/dist/cli/commands/memory.js +36 -15
- package/dist/cli/commands/proxy.js +23 -23
- package/dist/cli/commands/rules.js +6 -5
- package/dist/cli/commands/tracker-prompts.js +6 -6
- package/dist/cli/commands/tracker.js +9 -9
- package/dist/cli/commands/uninstall.js +183 -59
- package/dist/cli/flags-view/render.js +5 -5
- package/dist/cli/flags-view/state.js +9 -9
- package/dist/cli/flags-view/terminal.js +4 -4
- package/dist/cli/tui/cells.js +1 -1
- package/dist/cli/tui/terminal.js +6 -6
- package/dist/commands/code-review.md +0 -2
- package/dist/commands/debug.md +14 -11
- package/dist/commands/dynamic-build.md +51 -47
- package/dist/commands/dynamic-plan.md +27 -7
- package/dist/commands/dynamic-profile.md +17 -3
- package/dist/commands/dynamic-tickets.md +18 -4
- package/dist/commands/explore.md +9 -3
- package/dist/commands/implement.md +20 -16
- package/dist/commands/plan.md +13 -9
- package/dist/commands/release.md +23 -3
- package/dist/commands/research.md +9 -3
- package/dist/commands/resolve.md +9 -12
- package/dist/commands/self-review.md +0 -2
- package/dist/core/agent-frontmatter.js +28 -3
- package/dist/core/agent-models.js +204 -42
- package/dist/core/agent-state.js +28 -6
- package/dist/core/ansi.js +2 -2
- package/dist/core/assets.js +1 -1
- package/dist/core/cache.js +7 -8
- package/dist/core/codex-auth-inspect.js +4 -4
- package/dist/core/compliance-compose.js +3 -3
- package/dist/core/compliance.js +3 -4
- package/dist/core/evidence-policy.js +14 -13
- package/dist/core/external-models.js +1 -1
- package/dist/core/feature-config.js +71 -13
- package/dist/core/feature-switch.js +3 -3
- package/dist/core/flags.js +49 -25
- package/dist/core/fs-atomic.js +6 -7
- package/dist/core/learning-queue-cleanup.js +16 -81
- package/dist/core/learning-store.js +61 -0
- package/dist/core/linked-path.js +46 -0
- package/dist/core/manifest.js +5 -5
- package/dist/core/mds-variants.js +13 -13
- package/dist/core/model-discovery.js +8 -8
- package/dist/core/observations.js +17 -101
- package/dist/core/orphan-sweep.js +4 -4
- package/dist/core/plugins.js +13 -8
- package/dist/core/project-paths.js +9 -13
- package/dist/core/proxy-log.js +8 -8
- package/dist/core/proxy-state.js +3 -3
- package/dist/core/queue-drain.js +31 -0
- package/dist/core/reference-sweep.js +6 -6
- package/dist/core/teammate-mode-cleanup.js +1 -1
- package/dist/core/tracker.js +14 -14
- package/dist/hud/colors.js +2 -2
- package/dist/hud/components/learning-counts.js +54 -22
- package/dist/hud/components/version-badge.js +1 -1
- package/dist/skills/git/references/pr/resolve-review-threads.md +2 -2
- package/dist/skills/git/references/tracker/github/create-release.md +2 -2
- package/dist/skills/git/references/tracker/jira/create-release.md +2 -2
- package/dist/skills/git/references/tracker/linear/create-release.md +2 -2
- package/dist/targets/claude-code/compliance-install.js +17 -15
- package/dist/targets/claude-code/hooks.js +2 -2
- package/dist/targets/claude-code/installer.js +59 -32
- package/dist/targets/claude-code/legacy.js +1 -1
- package/dist/targets/claude-code/post-install.js +135 -45
- package/dist/targets/claude-code/tracker-install.js +2 -2
- package/package.json +1 -1
- package/src/assets/agents/code.md +15 -21
- package/src/assets/agents/design.md +4 -2
- package/src/assets/agents/diagnose.md +3 -1
- package/src/assets/agents/evaluate.md +4 -0
- package/src/assets/agents/git.mds +2 -2
- package/src/assets/agents/knowledge.md +5 -3
- package/src/assets/agents/learning.md +281 -196
- package/src/assets/agents/research.md +3 -1
- package/src/assets/agents/review.md +5 -3
- package/src/assets/agents/scrutinize.md +5 -1
- package/src/assets/agents/simplify.md +4 -0
- package/src/assets/agents/skim.md +4 -2
- package/src/assets/agents/synthesize.md +6 -0
- package/src/assets/agents/test.md +18 -10
- package/src/assets/agents/triage.md +11 -9
- package/src/assets/agents/validate.md +14 -10
- package/src/assets/commands/_partials/_decisions.mds +8 -3
- package/src/assets/commands/_partials/_docs_root.mds +3 -3
- package/src/assets/commands/_partials/_engine.mds +16 -32
- package/src/assets/commands/_partials/_knowledge.mds +0 -2
- package/src/assets/commands/_partials/_preamble.mds +6 -2
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/_partials/_tracker.mds +1 -1
- package/src/assets/commands/code-review.mds +0 -2
- package/src/assets/commands/debug.mds +13 -8
- package/src/assets/commands/dynamic-build.mds +18 -12
- package/src/assets/commands/dynamic-plan.mds +10 -4
- package/src/assets/commands/dynamic-profile.mds +1 -1
- package/src/assets/commands/dynamic-tickets.mds +2 -2
- package/src/assets/commands/explore.mds +9 -1
- package/src/assets/commands/implement.mds +19 -13
- package/src/assets/commands/plan.mds +12 -8
- package/src/assets/commands/release.md +23 -3
- package/src/assets/commands/research.mds +9 -3
- package/src/assets/commands/resolve.mds +9 -10
- package/src/assets/mds/git/_pr.mds +3 -3
- package/src/assets/mds/tracker/_common.mds +1 -1
- package/src/assets/mds/tracker/_github.mds +3 -3
- package/src/assets/mds/tracker/_jira.mds +3 -3
- package/src/assets/mds/tracker/_linear.mds +3 -3
- package/src/assets/mds/tracker/_mcp.mds +6 -5
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -2
- package/src/assets/scripts/hooks/background-memory-update +97 -33
- package/src/assets/scripts/hooks/capture-prompt +4 -3
- package/src/assets/scripts/hooks/capture-question +4 -3
- package/src/assets/scripts/hooks/capture-turn +5 -20
- package/src/assets/scripts/hooks/ensure-devflow-init +14 -2
- package/src/assets/scripts/hooks/ensure-proxy +5 -6
- package/src/assets/scripts/hooks/ensure-root-gitignore +123 -11
- package/src/assets/scripts/hooks/git-marker +71 -0
- package/src/assets/scripts/hooks/is-hex-sha +1 -1
- package/src/assets/scripts/hooks/json-helper.cjs +345 -944
- package/src/assets/scripts/hooks/json-parse +25 -129
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +205 -156
- package/src/assets/scripts/hooks/lib/learning-store.cjs +3207 -0
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +7 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +13 -19
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +253 -226
- package/src/assets/scripts/hooks/memory-worker +10 -0
- package/src/assets/scripts/hooks/pre-compact-memory +66 -14
- package/src/assets/scripts/hooks/preamble +9 -1
- package/src/assets/scripts/hooks/queue-append +55 -23
- package/src/assets/scripts/hooks/resolve-project-root +3 -4
- package/src/assets/scripts/hooks/session-start-context +146 -45
- package/src/assets/scripts/hooks/session-start-memory +33 -11
- package/src/assets/scripts/lib/project-config.cjs +2 -2
- package/src/assets/scripts/pr-evidence.cjs +3 -3
- package/src/assets/scripts/redact-secrets.cjs +20 -20
- package/src/assets/scripts/release-trace.cjs +1 -1
- package/src/assets/scripts/resolve-evidence-policy.cjs +3 -3
- package/src/assets/scripts/resolve-settings.cjs +3 -3
- package/src/assets/scripts/verify-evidence.cjs +2 -2
- package/src/assets/skills/apply-decisions/SKILL.md +37 -17
- package/src/assets/skills/docs-framework/SKILL.md +2 -2
- package/src/assets/skills/feature-knowledge/SKILL.md +6 -5
- package/src/assets/skills/test-driven-development/SKILL.md +6 -4
- package/dist/core/observation-io.js +0 -50
- package/src/assets/scripts/hooks/decisions-usage-scan.cjs +0 -131
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file queue-drain.ts
|
|
3
|
+
*
|
|
4
|
+
* `drainQueueFiles`, the delete the queue drains share — `drainLearningQueue`
|
|
5
|
+
* (learning-queue-cleanup.ts) and `drainMemoryQueue` (cli/commands/memory.ts) —
|
|
6
|
+
* and `formatRefusedDrain`, the line the CLI prints for a drain it refused.
|
|
7
|
+
*/
|
|
8
|
+
import { promises as fs } from 'fs';
|
|
9
|
+
import { firstSymbolicLink } from './linked-path.js';
|
|
10
|
+
/**
|
|
11
|
+
* Delete each of `files` unless one of `folders` — `.devflow` and the folder that
|
|
12
|
+
* holds the files, outermost first — is a symbolic link (D-CLI-NO-SYMLINK): through a
|
|
13
|
+
* linked folder, each delete would remove a same-named file wherever the link points.
|
|
14
|
+
* A file that is already gone is not an error; any other failure propagates, as the
|
|
15
|
+
* check's own does, so the command reports it.
|
|
16
|
+
*/
|
|
17
|
+
export async function drainQueueFiles(folders, files) {
|
|
18
|
+
const linkedFolder = await firstSymbolicLink(folders);
|
|
19
|
+
if (linkedFolder !== null)
|
|
20
|
+
return { drained: false, linkedFolder };
|
|
21
|
+
await Promise.all(files.map(file => fs.unlink(file).catch((error) => {
|
|
22
|
+
if (error.code !== 'ENOENT')
|
|
23
|
+
throw error;
|
|
24
|
+
})));
|
|
25
|
+
return { drained: true };
|
|
26
|
+
}
|
|
27
|
+
/** The warning for a drain refused at `linkedFolder`: which queue kept its files, and why. */
|
|
28
|
+
export function formatRefusedDrain(queue, linkedFolder) {
|
|
29
|
+
return `The ${queue} queue was not drained: ${linkedFolder} is a symbolic link, and devflow deletes nothing through one`;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=queue-drain.js.map
|
|
@@ -7,14 +7,14 @@ import * as path from 'path';
|
|
|
7
7
|
*
|
|
8
8
|
* Sibling of {@link sweepOrphanedAssets} in orphan-sweep.ts and deliberately the same
|
|
9
9
|
* {@link SweepResult} shape — `scanned` is the non-vacuity counter, removals and
|
|
10
|
-
* per-item failures are reported rather than thrown
|
|
10
|
+
* per-item failures are reported rather than thrown.
|
|
11
11
|
*
|
|
12
12
|
* What is genuinely new is the KEY. `sweepOrphanedAssets` keys a flat directory by
|
|
13
13
|
* registry name through `mdEntryName`, which cannot express `tracker/{provider}/{op}.md`:
|
|
14
14
|
* two providers may legitimately both carry a `comment.md`, so the registry name has to
|
|
15
15
|
* be the relative PATH, and the walk has to descend.
|
|
16
16
|
*
|
|
17
|
-
* Never writes, only removes
|
|
17
|
+
* Never writes, only removes — no delete-then-write window.
|
|
18
18
|
*/
|
|
19
19
|
/**
|
|
20
20
|
* Descent bound for every walk over the generated reference tree — this sweep and the
|
|
@@ -32,7 +32,7 @@ import * as path from 'path';
|
|
|
32
32
|
* The two walkers answer a breach differently by design, and both answer out loud: the
|
|
33
33
|
* build throws (a generated tree that deep is a build bug, and dist/ is still the
|
|
34
34
|
* build's own to fail), while this sweep records the unvisited subtree in `failed`
|
|
35
|
-
* (
|
|
35
|
+
* (an install is not abandoned over one subtree). Neither returns
|
|
36
36
|
* quietly: a subtree the walk never entered must not be summarised as converged.
|
|
37
37
|
*/
|
|
38
38
|
export const MAX_REFERENCE_SWEEP_DEPTH = 8;
|
|
@@ -76,7 +76,7 @@ async function sweepDirectory(dir, prefix, depth, known, knownDirPrefixes, acc)
|
|
|
76
76
|
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
77
77
|
}
|
|
78
78
|
catch {
|
|
79
|
-
return; /* absent or unreadable — not an error
|
|
79
|
+
return; /* absent or unreadable — not an error */
|
|
80
80
|
}
|
|
81
81
|
for (const entry of entries) {
|
|
82
82
|
const relPath = prefix === '' ? entry.name : `${prefix}/${entry.name}`;
|
|
@@ -96,7 +96,7 @@ async function sweepDirectory(dir, prefix, depth, known, knownDirPrefixes, acc)
|
|
|
96
96
|
acc.removed.push(relPath);
|
|
97
97
|
}
|
|
98
98
|
catch (err) {
|
|
99
|
-
acc.failed.push({ name: relPath, error: err }); /* per-item isolation
|
|
99
|
+
acc.failed.push({ name: relPath, error: err }); /* per-item isolation */
|
|
100
100
|
}
|
|
101
101
|
continue;
|
|
102
102
|
}
|
|
@@ -108,7 +108,7 @@ async function sweepDirectory(dir, prefix, depth, known, knownDirPrefixes, acc)
|
|
|
108
108
|
acc.removed.push(relPath);
|
|
109
109
|
}
|
|
110
110
|
catch (err) {
|
|
111
|
-
acc.failed.push({ name: relPath, error: err }); /* per-item isolation
|
|
111
|
+
acc.failed.push({ name: relPath, error: err }); /* per-item isolation */
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Tolerant: malformed JSON is returned unchanged (no-op). Non-object roots
|
|
11
11
|
* (null, arrays, primitives) are treated as a no-op — only object roots can
|
|
12
|
-
* carry the key (
|
|
12
|
+
* carry the key (a TypeError here would escape the function
|
|
13
13
|
* and surface as an unhandled error during uninstall).
|
|
14
14
|
*/
|
|
15
15
|
export function stripDevflowTeammateModeFromJson(settingsJson) {
|
package/dist/core/tracker.js
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* migrateLegacyTrackerConventions) owns the ~/.devflow tracker files.
|
|
10
10
|
* It sits here rather than in a target adapter because ~/.devflow is
|
|
11
11
|
* devflow-global, not Claude-Code-specific — the same reason manifest.ts's
|
|
12
|
-
* read/write live in src/core
|
|
12
|
+
* read/write live in src/core/.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* Nothing here calls process.exit() and nothing throws; every
|
|
15
15
|
* fallible path returns a Result, so callers own their own error rendering
|
|
16
16
|
* and a try/finally in a caller is never skipped.
|
|
17
17
|
*
|
|
@@ -31,7 +31,7 @@ import * as path from 'path';
|
|
|
31
31
|
/**
|
|
32
32
|
* Canonical issue-tracker provider registry.
|
|
33
33
|
*
|
|
34
|
-
* D-TRACKER-ONE-DOMAIN
|
|
34
|
+
* D-TRACKER-ONE-DOMAIN: this table is the SINGLE authority on the closed
|
|
35
35
|
* provider set, and `TrackerProvider` below is a projection of it. A provider
|
|
36
36
|
* therefore exists for the type system exactly when it has a row here: there is no
|
|
37
37
|
* hand-listed union that can admit an id `parseTrackerId` rejects and the wizard
|
|
@@ -63,7 +63,7 @@ export const DEFAULT_TRACKER_PROVIDER = 'github';
|
|
|
63
63
|
// Artifact basenames — one spelling for every TypeScript reader
|
|
64
64
|
//
|
|
65
65
|
// NOT the only spelling in the repository, and a rename that assumes it is will
|
|
66
|
-
// miss the places these names are hardcoded
|
|
66
|
+
// miss the places these names are hardcoded: the SessionStart hook's
|
|
67
67
|
// Section 3 (shell) and the Tracker agent's prompt (prose), neither of which can
|
|
68
68
|
// import from here. Each is cross-pinned against these constants by tests —
|
|
69
69
|
// shell-hooks-tracker, tracker-agent, uninstall-logic and core/tracker — so the
|
|
@@ -117,7 +117,7 @@ export const TRACKER_CLAIM_FILE = '.tracker.processing';
|
|
|
117
117
|
* no user-authored content (it is a scrubbed, unplaced copy of what the agent
|
|
118
118
|
* was about to write), so it is an install artifact, never user content.
|
|
119
119
|
*
|
|
120
|
-
* Spelled twice for the reason the basenames above are
|
|
120
|
+
* Spelled twice for the reason the basenames above are: the agent's
|
|
121
121
|
* prompt cannot import from here, so the mktemp template is also a literal in
|
|
122
122
|
* src/assets/agents/tracker.md, and tests/core/tracker.test.ts pins the two
|
|
123
123
|
* spellings together.
|
|
@@ -142,7 +142,7 @@ export const TRACKER_ATTEMPTS_NAMES = TRACKER_PROVIDER_IDS.map(id => trackerAtte
|
|
|
142
142
|
* How many background inference attempts a machine gets before the SessionStart
|
|
143
143
|
* hook stops emitting the setup directive.
|
|
144
144
|
*
|
|
145
|
-
* Spelled twice for the reason the basenames above are
|
|
145
|
+
* Spelled twice for the reason the basenames above are: the hook is the
|
|
146
146
|
* enforcer and cannot import from here, so `TRACKER_ATTEMPTS_MAX=5` is also a
|
|
147
147
|
* literal in src/assets/scripts/hooks/session-start-context. This constant is the
|
|
148
148
|
* number `devflow tracker --status` quotes back when it re-arms the counter, and
|
|
@@ -187,7 +187,7 @@ export function isTrackerProvider(value) {
|
|
|
187
187
|
* `parseTrackerId`'s error text and by `devflow tracker --status`.
|
|
188
188
|
*
|
|
189
189
|
* Takes `unknown`, and a non-string renders as its TYPE: the module's
|
|
190
|
-
* never-throws contract
|
|
190
|
+
* never-throws contract has to hold for what reaches this sink, not
|
|
191
191
|
* only for what the signature says does — `devflow tracker --status` reads
|
|
192
192
|
* a conventions file's hand-editable frontmatter, and a caller-side guard is one edit
|
|
193
193
|
* from being gone. Naming the type also keeps the render total, where `String()`
|
|
@@ -242,12 +242,12 @@ export function parseTrackerId(input) {
|
|
|
242
242
|
* Tolerant sink normaliser for a raw `manifest.features.tracker` value.
|
|
243
243
|
*
|
|
244
244
|
* Absent, null, malformed, a bare string, or an unknown provider → the default
|
|
245
|
-
* `{provider:'github'}` (
|
|
245
|
+
* `{provider:'github'}` (self-heal). Drop-not-error: a manifest
|
|
246
246
|
* written by a newer devflow, or hand-edited, degrades to what this build
|
|
247
247
|
* understands instead of failing the read.
|
|
248
248
|
*
|
|
249
249
|
* D-TRACKER-SELF-HEAL [DR-26]: self-healing here is SILENT and emits no
|
|
250
|
-
* DEGRADED — that is the correct
|
|
250
|
+
* DEGRADED — that is the correct self-heal behaviour, and it is a different
|
|
251
251
|
* condition from a per-repo config value outside the domain (which does emit
|
|
252
252
|
* `unknown tracker provider`). The two must not be conflated.
|
|
253
253
|
*/
|
|
@@ -317,8 +317,8 @@ export function parseTrackerFrontmatter(head) {
|
|
|
317
317
|
/**
|
|
318
318
|
* How many leading BYTES of a conventions file any reader takes — the bound the
|
|
319
319
|
* Tracker agent writes to and the Git agent loads. A line cap alone bounds the
|
|
320
|
-
* SCAN, not the read: the file's size is not devflow's to assume
|
|
321
|
-
* a bound is only real at the sink).
|
|
320
|
+
* SCAN, not the read: the file's size is not devflow's to assume
|
|
321
|
+
* (a bound is only real at the sink).
|
|
322
322
|
*/
|
|
323
323
|
export const TRACKER_CONVENTIONS_READ_BYTES = 8000;
|
|
324
324
|
/**
|
|
@@ -327,7 +327,7 @@ export const TRACKER_CONVENTIONS_READ_BYTES = 8000;
|
|
|
327
327
|
* `undefined` for an absent, unreadable or non-regular path — a FIFO or a device
|
|
328
328
|
* is refused before it is opened, so a hostile entry can never block a read.
|
|
329
329
|
* Follows a symlink to read what it names (a reader cares what the conventions
|
|
330
|
-
* SAY); it never moves or writes through one. Never throws
|
|
330
|
+
* SAY); it never moves or writes through one. Never throws.
|
|
331
331
|
*/
|
|
332
332
|
export async function readBoundedHead(filePath, limit) {
|
|
333
333
|
let handle;
|
|
@@ -360,7 +360,7 @@ export async function readBoundedHead(filePath, limit) {
|
|
|
360
360
|
* select a provider the machine never did, and its counter is the one a user in
|
|
361
361
|
* that repository is capped on.
|
|
362
362
|
*
|
|
363
|
-
* Idempotent when a counter is absent; never throws
|
|
363
|
+
* Idempotent when a counter is absent; never throws. `fs.rm` with
|
|
364
364
|
* `force` treats an absent file — and an absent parent directory — as success.
|
|
365
365
|
*/
|
|
366
366
|
export async function rearmTrackerInference(devflowDir) {
|
|
@@ -385,7 +385,7 @@ export async function rearmTrackerInference(devflowDir) {
|
|
|
385
385
|
* bare presence marker, so the hook never has to open the manifest to learn which
|
|
386
386
|
* provider it is gating.
|
|
387
387
|
*
|
|
388
|
-
* Converges unconditionally in both directions
|
|
388
|
+
* Converges unconditionally in both directions: a provider
|
|
389
389
|
* flipped back to github removes the sentinel in the same call shape that wrote
|
|
390
390
|
* it, so there is no "enable wrote it, disable forgot it" asymmetry.
|
|
391
391
|
*/
|
package/dist/hud/colors.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ANSI color helpers — re-exported from src/core/ansi.ts.
|
|
3
3
|
*
|
|
4
|
-
* The canonical implementation lives in src/core/ansi.ts (agent-neutral home
|
|
5
|
-
*
|
|
4
|
+
* The canonical implementation lives in src/core/ansi.ts (agent-neutral home).
|
|
5
|
+
* This file is a re-export barrel so all existing HUD
|
|
6
6
|
* component call sites continue to resolve `../colors.js` without change.
|
|
7
7
|
*/
|
|
8
8
|
export { bold, dim, red, green, yellow, blue, magenta, cyan, gray, white, orange, brightRed, boldRed, bgGreen, bgYellow, bgRed, inverse, truncate, stripAnsi, } from '../core/ansi.js';
|
|
@@ -2,18 +2,58 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import { dim } from '../colors.js';
|
|
3
3
|
import { getDecisionsLedgerPath } from '../../core/project-paths.js';
|
|
4
4
|
import { getLedgerRoot } from '../../core/ledger-root.js';
|
|
5
|
+
import { isActiveDecisionsStatus } from '../../core/observations.js';
|
|
5
6
|
/** The HUD's per-git-command budget (src/hud/git.ts GIT_TIMEOUT). */
|
|
6
7
|
const LEDGER_ROOT_TIMEOUT_MS = 1000;
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* The largest ledger the statusline reads: 8 MiB, far above the roughly 0.5 MB
|
|
10
|
+
* real ledgers reach.
|
|
11
|
+
*
|
|
12
|
+
* D-HUD-LEDGER-BOUNDED: the statusline counts the ledger only when it is a regular
|
|
13
|
+
* file of at most LEDGER_MAX_BYTES, and never reads it if it is a symbolic link; a
|
|
14
|
+
* link, any other kind of file or a larger one shows no counts, as an absent
|
|
15
|
+
* ledger does. Reason: the statusline reads the ledger on every prompt, and a
|
|
16
|
+
* repository can commit it as a link to an endless source such as /dev/zero, or
|
|
17
|
+
* as a huge file, either of which would hang the statusline.
|
|
15
18
|
*/
|
|
16
|
-
const
|
|
19
|
+
export const LEDGER_MAX_BYTES = 8 * 1024 * 1024;
|
|
20
|
+
/**
|
|
21
|
+
* The ledger's text, or null when D-HUD-LEDGER-BOUNDED refuses it or it cannot be
|
|
22
|
+
* read. lstat refuses a link without following it, the open refuses one that took
|
|
23
|
+
* the ledger's place since (O_NOFOLLOW) and never blocks on a FIFO (O_NONBLOCK),
|
|
24
|
+
* and the read takes at most the size fstat checked.
|
|
25
|
+
*/
|
|
26
|
+
function readBoundedLedger(ledgerPath) {
|
|
27
|
+
let fd;
|
|
28
|
+
try {
|
|
29
|
+
if (!fs.lstatSync(ledgerPath).isFile())
|
|
30
|
+
return null;
|
|
31
|
+
fd = fs.openSync(ledgerPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const stat = fs.fstatSync(fd);
|
|
38
|
+
if (!stat.isFile() || stat.size > LEDGER_MAX_BYTES)
|
|
39
|
+
return null;
|
|
40
|
+
const buf = Buffer.alloc(stat.size);
|
|
41
|
+
let total = 0;
|
|
42
|
+
while (total < buf.length) {
|
|
43
|
+
const read = fs.readSync(fd, buf, total, buf.length - total, total);
|
|
44
|
+
if (read === 0)
|
|
45
|
+
break;
|
|
46
|
+
total += read;
|
|
47
|
+
}
|
|
48
|
+
return buf.toString('utf-8', 0, total);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
fs.closeSync(fd);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
17
57
|
function isLedgerCountRow(val) {
|
|
18
58
|
if (typeof val !== 'object' || val === null)
|
|
19
59
|
return false;
|
|
@@ -24,24 +64,16 @@ function isLedgerCountRow(val) {
|
|
|
24
64
|
return false;
|
|
25
65
|
return o.decisions_status === undefined || typeof o.decisions_status === 'string';
|
|
26
66
|
}
|
|
27
|
-
function isActive(row) {
|
|
28
|
-
if (!row.decisions_status)
|
|
29
|
-
return true;
|
|
30
|
-
return !INACTIVE_STATUSES.has(row.decisions_status);
|
|
31
|
-
}
|
|
32
67
|
/**
|
|
33
68
|
* Read .devflow/learning/decisions-ledger.jsonl and count active anchored
|
|
34
|
-
* rows by type. Returns null if the ledger is missing
|
|
35
|
-
* (graceful fallback). Exported for
|
|
69
|
+
* rows by type. Returns null if the ledger is missing, is refused by
|
|
70
|
+
* D-HUD-LEDGER-BOUNDED, or holds no valid rows (graceful fallback). Exported for
|
|
71
|
+
* use by the main HUD entry point.
|
|
36
72
|
*/
|
|
37
73
|
export function gatherLearningCounts(cwd) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
content = fs.readFileSync(getDecisionsLedgerPath(cwd), 'utf-8');
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
74
|
+
const content = readBoundedLedger(getDecisionsLedgerPath(cwd));
|
|
75
|
+
if (content === null)
|
|
43
76
|
return null;
|
|
44
|
-
}
|
|
45
77
|
const counts = { decisions: 0, pitfalls: 0 };
|
|
46
78
|
let parsedAny = false;
|
|
47
79
|
for (const rawLine of content.split('\n')) {
|
|
@@ -59,7 +91,7 @@ export function gatherLearningCounts(cwd) {
|
|
|
59
91
|
if (!isLedgerCountRow(parsed))
|
|
60
92
|
continue;
|
|
61
93
|
parsedAny = true;
|
|
62
|
-
if (!
|
|
94
|
+
if (!isActiveDecisionsStatus(parsed.decisions_status))
|
|
63
95
|
continue;
|
|
64
96
|
if (parsed.type === 'decision')
|
|
65
97
|
counts.decisions++;
|
|
@@ -70,7 +70,7 @@ export default async function versionBadge(ctx) {
|
|
|
70
70
|
const current = getCurrentVersion(ctx.devflowDir);
|
|
71
71
|
if (!current)
|
|
72
72
|
return null;
|
|
73
|
-
const cacheDir = hudCacheDir(ctx.devflowDir); // authoritative path from cache.ts
|
|
73
|
+
const cacheDir = hudCacheDir(ctx.devflowDir); // authoritative path from cache.ts
|
|
74
74
|
// Cache only the npm registry result (expensive); current is always live
|
|
75
75
|
let info = readCache(cacheDir, VERSION_CACHE_KEY, validateVersionInfo);
|
|
76
76
|
if (!info) {
|
|
@@ -9,7 +9,7 @@ Load for `resolve-review-threads` under every tracker provider.
|
|
|
9
9
|
Each `THREAD_MAP` entry carries one verdict:
|
|
10
10
|
- `FIXED` — issue addressed
|
|
11
11
|
- `FALSE_POSITIVE` — not a real issue; requires grep/file:line citation as evidence
|
|
12
|
-
- `BY_DESIGN` — intentional; requires
|
|
12
|
+
- `BY_DESIGN` — intentional; requires a recorded decision, stated in words, or code citation as evidence
|
|
13
13
|
- `ESCALATED` — requires human review
|
|
14
14
|
|
|
15
15
|
(The resolution gate these verdicts feed — D9 — is stated in the agent's own section.)
|
|
@@ -28,7 +28,7 @@ unexplained unresolved threads.
|
|
|
28
28
|
- **FALSE_POSITIVE**: `After investigation, this appears to be a false positive: {evidence}. No code change needed.`
|
|
29
29
|
- **BY_DESIGN**: `This is intentional: {evidence}. No code change needed.`
|
|
30
30
|
- **ESCALATED**: `This thread has been escalated for human review and recorded in the resolution summary.`
|
|
31
|
-
- Reply bodies MUST NOT contain verbatim content from the external thread body — cite only internal evidence (commit SHAs, file:line from this codebase
|
|
31
|
+
- Reply bodies MUST NOT contain verbatim content from the external thread body — cite only internal evidence (commit SHAs, file:line from this codebase)
|
|
32
32
|
2. Write reply to `$DEVFLOW_BODY_RAW`; apply the Comment-sink scrub (D11) — non-zero exit → DEGRADED for that thread, continue per D4. Post reply via `addPullRequestReviewThreadReply` GraphQL mutation with `-F body=@"$DEVFLOW_BODY"` (file-ref form).
|
|
33
33
|
|
|
34
34
|
(Step 3, the D9 gate, is stated in the agent's own section.)
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Load when the resolved tracker provider is `github` and the operation is `create-release`.
|
|
4
4
|
|
|
5
|
-
**Mechanics held here:** the
|
|
5
|
+
**Mechanics held here:** the shipped-issues step only. Tag creation, release creation and notes composition stay with the operation.
|
|
6
6
|
|
|
7
7
|
### Process
|
|
8
8
|
|
|
9
9
|
Inside step 5 (compose release notes):
|
|
10
10
|
|
|
11
|
-
- If `SHIPPED_ISSUES` provided: append a `##
|
|
11
|
+
- If `SHIPPED_ISSUES` provided: append a `## Shipped Issues` section with issue references — **first ≤50 issues** (the same bound `backlink-shipped-issues` applies); if truncated, add a final `…and {n} more issues` line (D4 degrade if enrichment fails)
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Load when the resolved tracker provider is `jira` and the operation is `create-release`.
|
|
4
4
|
|
|
5
|
-
**Mechanics held here:** the
|
|
5
|
+
**Mechanics held here:** the shipped-issues step only. Tag creation, release creation and notes composition stay with the operation and are unchanged — they are release-host mechanics, not tracker mechanics.
|
|
6
6
|
|
|
7
7
|
### Process
|
|
8
8
|
|
|
9
9
|
Inside step 5 (compose release notes):
|
|
10
10
|
|
|
11
|
-
- If `SHIPPED_ISSUES` is provided: append a `##
|
|
11
|
+
- If `SHIPPED_ISSUES` is provided: append a `## Shipped Issues` section rendering each entry through `## Reference Rendering` — **first ≤50 issues** (the same bound `backlink-shipped-issues` applies); if truncated, add a final `…and {n} more issues` line (D4 degrade if enrichment fails).
|
|
12
12
|
- Pre-flight the list against `^[A-Z][A-Z0-9_]{1,9}-[1-9][0-9]{0,8}$`, anchored at both ends, and **drop** every entry that fails, reporting each as `TRACEABILITY: DEGRADED (issue reference "{ref}" does not match jira reference grammar)` — a bare number among them goes with them, in silence: `ambiguous issue reference` answers a reference a caller named, not one line of a list nobody chose. Every entry dropped ⇒ `TRACEABILITY: DEGRADED (no parseable refs for provider {p})` and the section is omitted rather than rendered empty.
|
|
13
13
|
- `## Reference Rendering` absent, or its token discarded by the gate below ⇒ render the key itself on its own line, and record the discard under `### Substitutions`.
|
|
14
14
|
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Load when the resolved tracker provider is `linear` and the operation is `create-release`.
|
|
4
4
|
|
|
5
|
-
**Mechanics held here:** the
|
|
5
|
+
**Mechanics held here:** the shipped-issues step only. Tag creation, release creation and notes composition stay with the operation and are unchanged — they are release-host mechanics, not tracker mechanics.
|
|
6
6
|
|
|
7
7
|
### Process
|
|
8
8
|
|
|
9
9
|
Inside step 5 (compose release notes):
|
|
10
10
|
|
|
11
|
-
- If `SHIPPED_ISSUES` is provided: append a `##
|
|
11
|
+
- If `SHIPPED_ISSUES` is provided: append a `## Shipped Issues` section rendering each entry through `## Reference Rendering` — **first ≤50 issues** (the same bound `backlink-shipped-issues` applies); if truncated, add a final `…and {n} more issues` line (D4 degrade if enrichment fails).
|
|
12
12
|
- **ASCII-upper-normalise every entry first.** Pre-flight the list against **either** anchored form — the team-key form `^[A-Z][A-Z0-9]{0,9}-[1-9][0-9]{0,8}$` or the internal-id form `^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$`, anchored at both ends, never joined into one alternation, and **drop** every entry that fails, reporting each as `TRACEABILITY: DEGRADED (issue reference "{ref}" does not match linear reference grammar)` — a bare number among them goes with them, in silence: `ambiguous issue reference` answers a reference a caller named, not one line of a list nobody chose. Every entry dropped ⇒ `TRACEABILITY: DEGRADED (no parseable refs for provider {p})` and the section is omitted rather than rendered empty.
|
|
13
13
|
- `## Reference Rendering` absent, or its token discarded by the gate below ⇒ render the reference itself on its own line, and record the discard under `### Substitutions`.
|
|
14
14
|
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
* decided by the ids its caller passes (D-COMPLIANCE-REPO-LENS), never by which
|
|
15
15
|
* files are present.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* I/O orchestration in src/targets/; pure helpers in src/core/.
|
|
18
|
+
* Warn-not-throw for per-item failures.
|
|
19
|
+
* Temp-sibling+rename for skill dir rewrites.
|
|
20
|
+
* Both artifacts converge unconditionally (no || short-circuits).
|
|
21
21
|
*/
|
|
22
22
|
import { promises as fs } from 'fs';
|
|
23
23
|
import * as path from 'path';
|
|
@@ -53,7 +53,7 @@ async function pathExists(p) {
|
|
|
53
53
|
* Fragments always come from the canonical source (not user-overridable) — they carry
|
|
54
54
|
* registry-owned content (mapping cells, reference blurbs, checklist items, rule bullets).
|
|
55
55
|
*
|
|
56
|
-
*
|
|
56
|
+
* Parse errors and unreadable files are reported via warn; the framework is
|
|
57
57
|
* silently omitted from the result map (C5 in composeComplianceSkill handles the gap).
|
|
58
58
|
*/
|
|
59
59
|
async function loadComplianceFragments(canonicalSrc, frameworks, warn) {
|
|
@@ -92,8 +92,8 @@ async function loadComplianceFragments(canonicalSrc, frameworks, warn) {
|
|
|
92
92
|
* `fragments` is loaded once by convergeComplianceArtifacts and shared with the rule
|
|
93
93
|
* installer — the SKILL.md and the rule compose from the same parsed set.
|
|
94
94
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
95
|
+
* Build under a .tmp sibling, remove old target, rename.
|
|
96
|
+
* Unexpected I/O failures are reported via warn; never thrown.
|
|
97
97
|
*/
|
|
98
98
|
async function installSkillDir(claudeDir, devflowDir, stampFrameworks, fragments, warn) {
|
|
99
99
|
const canonicalSrc = path.join(skillsDir(), 'compliance');
|
|
@@ -108,7 +108,7 @@ async function installSkillDir(claudeDir, devflowDir, stampFrameworks, fragments
|
|
|
108
108
|
try {
|
|
109
109
|
// Clean up any orphaned tmp from a prior crashed run (best-effort).
|
|
110
110
|
await fs.rm(tmpTarget, { recursive: true, force: true });
|
|
111
|
-
// Build the new directory tree under the tmp sibling
|
|
111
|
+
// Build the new directory tree under the tmp sibling.
|
|
112
112
|
const refDst = path.join(tmpTarget, 'references');
|
|
113
113
|
await fs.mkdir(refDst, { recursive: true });
|
|
114
114
|
// SKILL.md: compose from template (shadow or canonical) + fragments.
|
|
@@ -121,7 +121,7 @@ async function installSkillDir(claudeDir, devflowDir, stampFrameworks, fragments
|
|
|
121
121
|
// Always-present reference files (detection.md, sources.md) from the canonical
|
|
122
122
|
// references/ directory — these are not framework-specific.
|
|
123
123
|
//
|
|
124
|
-
//
|
|
124
|
+
// Each copy is isolated. A skill dir missing one reference still works;
|
|
125
125
|
// aborting the whole install because one file is unreadable would take out
|
|
126
126
|
// SKILL.md too. Failures warn and the remaining refs still install.
|
|
127
127
|
const alwaysPresentSrc = path.join(canonicalSrc, 'references');
|
|
@@ -148,7 +148,7 @@ async function installSkillDir(claudeDir, devflowDir, stampFrameworks, fragments
|
|
|
148
148
|
warn(`compliance: reference "${fw}.md" not installed — ${String(err)}`);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
//
|
|
151
|
+
// Swap: remove old target, rename tmp into place (two calls, not atomic).
|
|
152
152
|
await fs.rm(target, { recursive: true, force: true });
|
|
153
153
|
await fs.rename(tmpTarget, target);
|
|
154
154
|
}
|
|
@@ -174,7 +174,7 @@ async function installSkillDir(claudeDir, devflowDir, stampFrameworks, fragments
|
|
|
174
174
|
* source (never the shadow — fragments are registry-owned) and shared with the skill
|
|
175
175
|
* installer.
|
|
176
176
|
*
|
|
177
|
-
*
|
|
177
|
+
* I/O failures are reported via warn; never thrown.
|
|
178
178
|
*/
|
|
179
179
|
async function installRuleFile(claudeDir, devflowDir, frameworks, fragments, warn) {
|
|
180
180
|
const ruleShadowFile = path.join(devflowDir, 'rules', 'compliance.md');
|
|
@@ -203,8 +203,10 @@ async function installRuleFile(claudeDir, devflowDir, frameworks, fragments, war
|
|
|
203
203
|
* enabled + !rulesEnabled → skill dir (every ref, machine stamp); remove stale rule
|
|
204
204
|
* !enabled → skill dir (every ref, neutral stamp); remove rule
|
|
205
205
|
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
206
|
+
* Both artifact operations execute unconditionally — no || short-circuits.
|
|
207
|
+
* Skill dir write builds under a temp sibling, then removes the target and renames
|
|
208
|
+
* the sibling into place, which narrows the ENOENT window to the gap between those
|
|
209
|
+
* two calls.
|
|
208
210
|
*
|
|
209
211
|
* D: the `warn` callback is injected (not console.warn) so callers control
|
|
210
212
|
* surfacing (init log lines, test spies, etc.) — per the dependency-injection
|
|
@@ -227,7 +229,7 @@ export async function convergeComplianceArtifacts(opts) {
|
|
|
227
229
|
const safeFrameworks = normalizeFrameworks(frameworks);
|
|
228
230
|
// I13: Track whether every artifact operation in this run completed without error.
|
|
229
231
|
// `converged` starts true and is set false by the tracking wrapper whenever any warn
|
|
230
|
-
// path is taken — including inside installSkillDir / installRuleFile (
|
|
232
|
+
// path is taken — including inside installSkillDir / installRuleFile (their per-item warn paths).
|
|
231
233
|
let converged = true;
|
|
232
234
|
const trackingWarn = (msg) => {
|
|
233
235
|
converged = false;
|
|
@@ -239,7 +241,7 @@ export async function convergeComplianceArtifacts(opts) {
|
|
|
239
241
|
// frameworks need one — a compliance-off machine stamps none.
|
|
240
242
|
const stampFrameworks = enabled ? safeFrameworks : [];
|
|
241
243
|
const fragments = await loadComplianceFragments(path.join(skillsDir(), 'compliance'), stampFrameworks, trackingWarn);
|
|
242
|
-
//
|
|
244
|
+
// The skill and the rule are independent operations. An error in
|
|
243
245
|
// installSkillDir is caught internally and reported via trackingWarn, so
|
|
244
246
|
// execution always continues to the rule step.
|
|
245
247
|
await installSkillDir(claudeDir, devflowDir, stampFrameworks, fragments, trackingWarn);
|
|
@@ -40,8 +40,8 @@ export function endsWithAny(suffixes) {
|
|
|
40
40
|
* `/scripts/hooks/session-start-memory.sh`), under any directory — so installs made
|
|
41
41
|
* under a custom or retired devflow directory are still recognised. It is never
|
|
42
42
|
* devflow's because it merely CONTAINS a marker word: a user's `~/bin/memory-worker`,
|
|
43
|
-
* `echo capture-turn` or `/opt/tools/run-hook preamble` is theirs
|
|
44
|
-
* remove only what devflow can prove it wrote). Removal goes through `removeHooks`,
|
|
43
|
+
* `echo capture-turn` or `/opt/tools/run-hook preamble` is theirs
|
|
44
|
+
* (remove only what devflow can prove it wrote). Removal goes through `removeHooks`,
|
|
45
45
|
* one hook at a time. Every hook module builds its predicates here;
|
|
46
46
|
* D-AMBIENT-EXACT-HOOK is the ambient instance of this rule.
|
|
47
47
|
*/
|