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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `resolve-evidence-policy.cjs`, never a second implementation of it.
|
|
4
4
|
*
|
|
5
5
|
* D-POLICY-CJS-SEAM: the resolver is plain CommonJS under src/assets/scripts/,
|
|
6
|
-
* outside every tsconfig
|
|
6
|
+
* outside every tsconfig, so the interfaces below are
|
|
7
7
|
* TRANSCRIBED from its JSDoc typedefs and are the only shape authority on this
|
|
8
8
|
* side — open those typedefs before changing anything here. The module is loaded
|
|
9
9
|
* with `require()` from `scriptsDir()`, which resolves under the package root both
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* `lib/project-config.cjs` (loadProjectConfigLib), so the CLI judges a config
|
|
21
21
|
* file's bytes exactly as the resolvers do.
|
|
22
22
|
*
|
|
23
|
-
* D-POLICY-NO-WRITE
|
|
23
|
+
* D-POLICY-NO-WRITE: `.devflow/project.json` is team-owned, and
|
|
24
24
|
* devflow never writes or replaces a shared file it cannot prove it wrote. This
|
|
25
25
|
* module therefore imports no fs API; the CLI only PRINTS the bytes a team may
|
|
26
26
|
* choose to commit (`evidencePolicySuggestion`, and the migration lines of
|
|
@@ -101,23 +101,24 @@ function surfaceMismatches(value, surface) {
|
|
|
101
101
|
* require() one package script and shape-check it against `surface`. Never
|
|
102
102
|
* throws: a missing file is `not-found`; a module that throws on load or lacks a
|
|
103
103
|
* surface key is `unusable`. The caller's type parameter is justified by the
|
|
104
|
-
* surface check, which `satisfies` ties to the interface's keys.
|
|
104
|
+
* surface check, which `satisfies` ties to the interface's keys. Exported for
|
|
105
|
+
* src/core/learning-store.ts, which loads the learning store the same way.
|
|
105
106
|
*/
|
|
106
|
-
function loadScript(
|
|
107
|
+
export function loadScript(scriptPath, surface) {
|
|
107
108
|
let loaded;
|
|
108
109
|
try {
|
|
109
|
-
loaded = createRequire(import.meta.url)(
|
|
110
|
+
loaded = createRequire(import.meta.url)(scriptPath);
|
|
110
111
|
}
|
|
111
112
|
catch (err) {
|
|
112
113
|
const code = err.code;
|
|
113
114
|
if (code === 'MODULE_NOT_FOUND')
|
|
114
|
-
return { ok: false, error: { kind: 'not-found', path:
|
|
115
|
+
return { ok: false, error: { kind: 'not-found', path: scriptPath } };
|
|
115
116
|
const detail = err instanceof Error ? err.message : String(err);
|
|
116
|
-
return { ok: false, error: { kind: 'unusable', path:
|
|
117
|
+
return { ok: false, error: { kind: 'unusable', path: scriptPath, detail } };
|
|
117
118
|
}
|
|
118
119
|
const mismatches = surfaceMismatches(loaded, surface);
|
|
119
120
|
if (mismatches.length > 0) {
|
|
120
|
-
return { ok: false, error: { kind: 'unusable', path:
|
|
121
|
+
return { ok: false, error: { kind: 'unusable', path: scriptPath, detail: `missing or mistyped: ${mismatches.join(', ')}` } };
|
|
121
122
|
}
|
|
122
123
|
return { ok: true, value: loaded };
|
|
123
124
|
}
|
|
@@ -167,10 +168,10 @@ export function formatEvidencePolicyUnavailable(error) {
|
|
|
167
168
|
/**
|
|
168
169
|
* The `compliance --status` line: the resolved policy for `opts.dir`, or the
|
|
169
170
|
* unavailable line when the loader failed — that line is the whole handling
|
|
170
|
-
* (
|
|
171
|
-
* manifest is never read twice. `resolve()` makes
|
|
172
|
-
* bounds every subprocess with a timeout, so an
|
|
173
|
-
* flagged result rather than a hang.
|
|
171
|
+
* (only a damaged package fails the load). The caller passes the compliance
|
|
172
|
+
* state it already read, so the manifest is never read twice. `resolve()` makes
|
|
173
|
+
* at most three `gh` calls and bounds every subprocess with a timeout, so an
|
|
174
|
+
* offline machine degrades to a flagged result rather than a hang.
|
|
174
175
|
*/
|
|
175
176
|
export function evidencePolicyStatusLine(loaded, opts) {
|
|
176
177
|
if (!loaded.ok)
|
|
@@ -198,7 +199,7 @@ function suggestedFrameworks(complianceState) {
|
|
|
198
199
|
* count); `null` otherwise. The bytes come
|
|
199
200
|
* from the settings resolver's `serializeProjectSuggestion`, which returns them
|
|
200
201
|
* only when they read back through the shared parser as exactly what was asked.
|
|
201
|
-
* Nothing is written (D-POLICY-NO-WRITE
|
|
202
|
+
* Nothing is written (D-POLICY-NO-WRITE).
|
|
202
203
|
*/
|
|
203
204
|
export function evidencePolicySuggestion(complianceState, policy, settings) {
|
|
204
205
|
if (policy.complianceDefault(complianceState) !== 'required')
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* in src/core/model-discovery.ts. The TUI picker and --set validation use the
|
|
10
10
|
* ExternalModelCatalog returned by those functions.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* Pure core-layer module, no Claude Code adapter concerns.
|
|
13
13
|
*
|
|
14
14
|
* NOTE: the internal routing runtime package name must NEVER appear in
|
|
15
15
|
* user-visible strings, CLI output, or error messages. User-facing vocabulary:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
|
+
import { firstSymbolicLink } from './linked-path.js';
|
|
3
4
|
import { getFeatureConfigPath } from './project-paths.js';
|
|
4
5
|
import { parseTrackerId } from './tracker.js';
|
|
5
6
|
import { loadProjectConfigLib } from './evidence-policy.js';
|
|
@@ -14,7 +15,7 @@ import { loadProjectConfigLib } from './evidence-policy.js';
|
|
|
14
15
|
* carrying it would leave a `learning: false` in the file that no longer does
|
|
15
16
|
* what it says. `decisions` is the pre-rename spelling of `learning`;
|
|
16
17
|
* `autoCommit` is inert. `features` is deliberately NOT here — it is a live key,
|
|
17
|
-
* carried like any other unmanaged key
|
|
18
|
+
* carried like any other unmanaged key.
|
|
18
19
|
*/
|
|
19
20
|
const RETIRED_CONFIG_KEYS = new Set([
|
|
20
21
|
'memory', 'learning', 'knowledge', 'decisions', 'autoCommit',
|
|
@@ -77,7 +78,7 @@ function coerceConfig(parsed) {
|
|
|
77
78
|
if (!isJsonObject(parsed))
|
|
78
79
|
return null;
|
|
79
80
|
const p = parsed;
|
|
80
|
-
// Coerce reviewPublication: any invalid or absent value → 'auto' (self-heal
|
|
81
|
+
// Coerce reviewPublication: any invalid or absent value → 'auto' (self-heal).
|
|
81
82
|
const rp = p.reviewPublication;
|
|
82
83
|
const reviewPublication = rp === 'auto' || rp === 'full' || rp === 'off' ? rp : 'auto';
|
|
83
84
|
// The per-repo tracker override is carried through VERBATIM — never coerced,
|
|
@@ -169,23 +170,70 @@ async function readConfigBody(projectRoot, lib = loadProjectConfigLib()) {
|
|
|
169
170
|
}
|
|
170
171
|
return classifyConfigBytes(read.bytes, lib.value);
|
|
171
172
|
}
|
|
173
|
+
/** A thrown value's message, or the value itself when it is not an Error. */
|
|
174
|
+
function messageOf(err) {
|
|
175
|
+
return err instanceof Error ? err.message : String(err);
|
|
176
|
+
}
|
|
172
177
|
/**
|
|
173
|
-
* Serialise a config body to a project's config file.
|
|
178
|
+
* Serialise a config body to a project's config file. Never throws.
|
|
174
179
|
* Creates the .devflow/ directory if missing.
|
|
175
180
|
* Uses an atomic temp+rename pattern to prevent partial reads under concurrent writes.
|
|
181
|
+
* The copy is created only where nothing stands ('wx'), so an entry a repository
|
|
182
|
+
* planted at its name — a symbolic link among them — is never written through
|
|
183
|
+
* (D-CLI-NO-SYMLINK) and, not being this run's, is left where it is; the write then
|
|
184
|
+
* fails and the Result says so. Once this run has created the copy, a write, close or
|
|
185
|
+
* rename that fails removes it again, so a failed write leaves nothing beside the
|
|
186
|
+
* config; the Result names the failure, and the copy too when it could not be removed.
|
|
176
187
|
*/
|
|
177
188
|
async function writeConfigBody(projectRoot, body) {
|
|
178
189
|
const configPath = getFeatureConfigPath(projectRoot);
|
|
179
|
-
await fs.mkdir(path.join(projectRoot, '.devflow'), { recursive: true });
|
|
180
190
|
const tmpPath = configPath + '.tmp.' + process.pid;
|
|
181
|
-
|
|
182
|
-
|
|
191
|
+
let text;
|
|
192
|
+
let copy;
|
|
193
|
+
try {
|
|
194
|
+
text = JSON.stringify(body, null, 2) + '\n';
|
|
195
|
+
await fs.mkdir(path.join(projectRoot, '.devflow'), { recursive: true });
|
|
196
|
+
copy = await fs.open(tmpPath, 'wx', 0o600);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
return { ok: false, detail: messageOf(err) };
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
try {
|
|
203
|
+
// The open handle is fs.writeFile's destination and the text its data: the same
|
|
204
|
+
// write as copy.writeFile(text), in the form a path-traversal scan reads right,
|
|
205
|
+
// since it takes a writeFile's first argument for a path.
|
|
206
|
+
await fs.writeFile(copy, text, 'utf-8');
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
await copy.close();
|
|
210
|
+
}
|
|
211
|
+
await fs.rename(tmpPath, configPath);
|
|
212
|
+
return { ok: true };
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
return { ok: false, detail: await removeCopy(tmpPath, messageOf(err)) };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Remove the copy a failed config write created, and say why the write failed:
|
|
220
|
+
* `detail`, followed by the removal's own failure when the copy stays. A copy that
|
|
221
|
+
* is already gone counts as removed (`force`).
|
|
222
|
+
*/
|
|
223
|
+
async function removeCopy(tmpPath, detail) {
|
|
224
|
+
try {
|
|
225
|
+
await fs.rm(tmpPath, { force: true });
|
|
226
|
+
return detail;
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
return `${detail}; its copy ${tmpPath} could not be removed: ${messageOf(err)}`;
|
|
230
|
+
}
|
|
183
231
|
}
|
|
184
232
|
/**
|
|
185
233
|
* Merge devflow's managed keys over the config body the file already holds.
|
|
186
234
|
* Pure — returns a new object and never mutates `existing`.
|
|
187
235
|
*
|
|
188
|
-
* D-CONFIG-PRESERVE-UNMANAGED
|
|
236
|
+
* D-CONFIG-PRESERVE-UNMANAGED: `.devflow/config.json` is a
|
|
189
237
|
* user-editable file that devflow only PARTLY owns. The managed keys come from
|
|
190
238
|
* `managed`; every other key comes from the file, verbatim and by key presence
|
|
191
239
|
* — the hand-written per-repo `tracker` override (whose invalid values must
|
|
@@ -227,21 +275,31 @@ export function mergeManagedConfig(existing, managed) {
|
|
|
227
275
|
* change. Acceptable because init is a single-threaded, user-initiated command
|
|
228
276
|
* and the window is milliseconds on a local filesystem; the file swap itself is
|
|
229
277
|
* atomic (temp + rename), so a reader never sees a partial file.
|
|
278
|
+
*
|
|
279
|
+
* D-CLI-NO-SYMLINK (firstSymbolicLink): a `.devflow` that is a symbolic link is
|
|
280
|
+
* left alone, and the Result says so; the file is neither read nor written there.
|
|
230
281
|
*/
|
|
231
282
|
export async function writeManagedConfig(projectRoot, managed, lib = loadProjectConfigLib()) {
|
|
232
283
|
const configPath = getFeatureConfigPath(projectRoot);
|
|
284
|
+
let linked;
|
|
285
|
+
try {
|
|
286
|
+
linked = await firstSymbolicLink([path.dirname(configPath)]);
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
return { ok: false, error: { kind: 'unreadable', path: configPath, detail: messageOf(err) } };
|
|
290
|
+
}
|
|
291
|
+
if (linked !== null) {
|
|
292
|
+
return { ok: false, error: { kind: 'unreadable', path: configPath, detail: `${linked} is a symbolic link, and devflow writes nothing through one` } };
|
|
293
|
+
}
|
|
233
294
|
const existing = await readConfigBody(projectRoot, lib);
|
|
234
295
|
if (existing.kind === 'malformed')
|
|
235
296
|
return { ok: false, error: { kind: 'malformed', path: configPath } };
|
|
236
297
|
if (existing.kind === 'unreadable') {
|
|
237
298
|
return { ok: false, error: { kind: 'unreadable', path: configPath, detail: existing.detail } };
|
|
238
299
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
catch (err) {
|
|
243
|
-
return { ok: false, error: { kind: 'write-failed', path: configPath, detail: err instanceof Error ? err.message : String(err) } };
|
|
244
|
-
}
|
|
300
|
+
const written = await writeConfigBody(projectRoot, mergeManagedConfig(objectOf(existing), managed));
|
|
301
|
+
if (!written.ok)
|
|
302
|
+
return { ok: false, error: { kind: 'write-failed', path: configPath, detail: written.detail } };
|
|
245
303
|
return { ok: true };
|
|
246
304
|
}
|
|
247
305
|
/**
|
|
@@ -6,7 +6,7 @@ function isJsonObject(value) {
|
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* The pre-rename key each machine feature was stored under, where one exists:
|
|
9
|
-
* `learning` was `decisions`
|
|
9
|
+
* `learning` was `decisions` and `knowledge` was `kb`. A manifest no
|
|
10
10
|
* command has rewritten since the rename can still hold only the legacy key.
|
|
11
11
|
* `memory` was never renamed.
|
|
12
12
|
*/
|
|
@@ -19,7 +19,7 @@ const LEGACY_KEYS = {
|
|
|
19
19
|
*
|
|
20
20
|
* Only an explicit boolean `false` switches a feature off. A missing key, a
|
|
21
21
|
* non-boolean value, or anything that is not a manifest-shaped object reads as
|
|
22
|
-
* ON — fail-open
|
|
22
|
+
* ON — fail-open, and the exact rule `queue_read_gates` applies in the
|
|
23
23
|
* shell hooks, so the CLI's status and the runtime never disagree about the
|
|
24
24
|
* same file.
|
|
25
25
|
*
|
|
@@ -37,7 +37,7 @@ const LEGACY_KEYS = {
|
|
|
37
37
|
* precedence exactly, so `devflow knowledge --status` reports a `kb: false`
|
|
38
38
|
* as disabled. queue_read_gates never reads knowledge, so there is no shell
|
|
39
39
|
* mirror. The knowledge write-back prose gate deliberately does not learn the
|
|
40
|
-
* legacy key (
|
|
40
|
+
* legacy key (no prompt text for a state only an un-upgraded install
|
|
41
41
|
* can hold); readManifest rewrites `kb` to `knowledge` on the next CLI run
|
|
42
42
|
* that loads the manifest, after which that gate reads the healed key.
|
|
43
43
|
*
|
package/dist/core/flags.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* D14: Typed registry — flags carry kind (boolean|enum|number|string), target
|
|
8
8
|
* (env|setting), and per-kind defaultValue. Neutral values delete their target
|
|
9
9
|
* key; active values write the appropriate payload. Number 0 is ACTIVE. Sink
|
|
10
|
-
* validation via coerceFlagValue (
|
|
10
|
+
* validation via coerceFlagValue (validate at the convergence
|
|
11
11
|
* point every caller reaches). applyFlags(settingsJson, FlagsRecord) is the
|
|
12
12
|
* sole API; init.ts works directly with FlagsRecord (no legacy string[] bridge).
|
|
13
13
|
*/
|
|
@@ -139,7 +139,7 @@ export const FLAG_REGISTRY = [
|
|
|
139
139
|
{
|
|
140
140
|
// Devflow fan-outs routinely exceed the upstream default of 20.
|
|
141
141
|
// Set to 40 by default so parallel Code/Review/Research waves don't
|
|
142
|
-
// silently queue. upstreamDefault recorded for display.
|
|
142
|
+
// silently queue. upstreamDefault recorded for display.
|
|
143
143
|
id: 'max-concurrent-subagents',
|
|
144
144
|
label: 'Max concurrent subagents',
|
|
145
145
|
description: 'Maximum number of subagents Claude Code will spawn concurrently',
|
|
@@ -150,7 +150,7 @@ export const FLAG_REGISTRY = [
|
|
|
150
150
|
recommended: true,
|
|
151
151
|
defaultValue: 40,
|
|
152
152
|
min: 1,
|
|
153
|
-
max: 100, // devflow sanity bound
|
|
153
|
+
max: 100, // devflow sanity bound
|
|
154
154
|
integer: true,
|
|
155
155
|
upstreamDefault: 20,
|
|
156
156
|
},
|
|
@@ -341,7 +341,7 @@ export const FLAG_REGISTRY = [
|
|
|
341
341
|
// ── Valued flags (number/enum/string) ────────────────────────────────────
|
|
342
342
|
{
|
|
343
343
|
// Domain: unset by default; set only when users want a non-default spawn depth.
|
|
344
|
-
// upstreamDefault: 3 (recorded for display).
|
|
344
|
+
// upstreamDefault: 3 (recorded for display). Sanity bound: max 10.
|
|
345
345
|
id: 'subagent-spawn-depth',
|
|
346
346
|
label: 'Max subagent spawn depth',
|
|
347
347
|
description: 'Maximum depth of nested subagent spawning',
|
|
@@ -352,7 +352,7 @@ export const FLAG_REGISTRY = [
|
|
|
352
352
|
recommended: false,
|
|
353
353
|
defaultValue: undefined,
|
|
354
354
|
min: 1,
|
|
355
|
-
max: 10, // devflow sanity bound
|
|
355
|
+
max: 10, // devflow sanity bound
|
|
356
356
|
integer: true,
|
|
357
357
|
upstreamDefault: 3,
|
|
358
358
|
},
|
|
@@ -384,7 +384,7 @@ export const FLAG_REGISTRY = [
|
|
|
384
384
|
},
|
|
385
385
|
{
|
|
386
386
|
// Upstream default: 30 min. 0 = disabled (still ACTIVE — written to env).
|
|
387
|
-
//
|
|
387
|
+
// Sanity bounds: max 1440 (24h). min 0 (0 = off, explicit value not neutral).
|
|
388
388
|
id: 'goal-checkin-minutes',
|
|
389
389
|
label: 'Goal check-in interval',
|
|
390
390
|
description: 'Interval in minutes for Claude to check in on task goals',
|
|
@@ -395,10 +395,34 @@ export const FLAG_REGISTRY = [
|
|
|
395
395
|
recommended: false,
|
|
396
396
|
defaultValue: undefined,
|
|
397
397
|
min: 0, // 0 = off (ACTIVE, not neutral — written as "0")
|
|
398
|
-
max: 1440, // devflow sanity bound: 24 hours
|
|
398
|
+
max: 1440, // devflow sanity bound: 24 hours
|
|
399
399
|
integer: true,
|
|
400
400
|
upstreamDefault: 30,
|
|
401
401
|
},
|
|
402
|
+
{
|
|
403
|
+
// D-FOREGROUND-RUN: every agent runs builds and tests in the foreground under an explicit
|
|
404
|
+
// Bash `timeout` whose ceiling is Claude Code's 600000 ms, or this variable when set. A
|
|
405
|
+
// suite that cannot be split under that ceiling is reported BLOCKED with the remedy
|
|
406
|
+
// `devflow flags --set bash-max-timeout-ms=<ms>`, so this flag is that remedy.
|
|
407
|
+
// The env name was confirmed against Claude Code 2.1.294 (docs/reference/claude-code-flags-probe.md):
|
|
408
|
+
// the binary reads BASH_MAX_TIMEOUT_MS, ignores a non-positive or NaN value, and takes the
|
|
409
|
+
// larger of it and the default timeout. Neutral by default (undefined → manifest null →
|
|
410
|
+
// key deleted). min 600000 = upstream default, so the flag only ever raises the ceiling;
|
|
411
|
+
// max 7200000 (2 h) is the devflow sanity bound.
|
|
412
|
+
id: 'bash-max-timeout-ms',
|
|
413
|
+
label: 'Bash max timeout',
|
|
414
|
+
description: 'Ceiling in milliseconds for a foreground Bash command timeout',
|
|
415
|
+
hint: 'Raises the Bash timeout ceiling past 600000 ms for long-running suites',
|
|
416
|
+
blurb: 'Bash timeout ceiling',
|
|
417
|
+
kind: 'number',
|
|
418
|
+
target: { type: 'env', key: 'BASH_MAX_TIMEOUT_MS' },
|
|
419
|
+
recommended: false,
|
|
420
|
+
defaultValue: undefined,
|
|
421
|
+
min: 600000, // upstream default ceiling: lower values would only shrink it
|
|
422
|
+
max: 7200000, // devflow sanity bound: 2 hours
|
|
423
|
+
integer: true,
|
|
424
|
+
upstreamDefault: 600000,
|
|
425
|
+
},
|
|
402
426
|
{
|
|
403
427
|
// Writes as { command: value } per Claude Code spellcheck setting shape.
|
|
404
428
|
id: 'spellcheck',
|
|
@@ -411,7 +435,7 @@ export const FLAG_REGISTRY = [
|
|
|
411
435
|
recommended: false,
|
|
412
436
|
defaultValue: undefined,
|
|
413
437
|
wrapKey: 'command',
|
|
414
|
-
maxLength: 256, // devflow sanity bound
|
|
438
|
+
maxLength: 256, // devflow sanity bound
|
|
415
439
|
},
|
|
416
440
|
{
|
|
417
441
|
// view-mode folded into the registry; neutralValue 'default' deletes the viewMode key.
|
|
@@ -474,7 +498,7 @@ export function isNeutral(flag, value) {
|
|
|
474
498
|
/**
|
|
475
499
|
* Map a record value to a TUI value.
|
|
476
500
|
*
|
|
477
|
-
* viewMode GLUE RULE (
|
|
501
|
+
* viewMode GLUE RULE (one shared definition): the mapping lives here,
|
|
478
502
|
* next to neutralValueOf — the definition it depends on — not across a module boundary.
|
|
479
503
|
* enum with neutralValue: neutralValue → null in TUI (null is the TUI representation
|
|
480
504
|
* of "use the default"; the key is deleted when persisted).
|
|
@@ -494,7 +518,7 @@ export function recordToTui(flag, v) {
|
|
|
494
518
|
/**
|
|
495
519
|
* Map a TUI value back to a record value.
|
|
496
520
|
*
|
|
497
|
-
* viewMode GLUE RULE (
|
|
521
|
+
* viewMode GLUE RULE (one shared definition): inverse of recordToTui,
|
|
498
522
|
* co-located with that function so the round-trip contract is auditable in one place.
|
|
499
523
|
* enum with neutralValue: null → neutralValue (e.g. 'default').
|
|
500
524
|
* All other values pass through unchanged.
|
|
@@ -509,7 +533,7 @@ export function tuiToRecord(flag, v) {
|
|
|
509
533
|
}
|
|
510
534
|
/**
|
|
511
535
|
* Validate and coerce `raw` to a safe value for `flag` at the sink.
|
|
512
|
-
* Returns null when the value is invalid (hostile-value defence
|
|
536
|
+
* Returns null when the value is invalid (hostile-value defence).
|
|
513
537
|
*
|
|
514
538
|
* Number invariants: finite, within [min, max], integer when required.
|
|
515
539
|
* String invariants: within maxLength, no control characters.
|
|
@@ -567,7 +591,7 @@ export function coerceFlagValue(flag, raw) {
|
|
|
567
591
|
* Parse a CLI text input to a FlagsRecordValue.
|
|
568
592
|
* 'unset' (literal) → null for any flag.
|
|
569
593
|
*
|
|
570
|
-
* Number branch uses strict decimal grammar (
|
|
594
|
+
* Number branch uses strict decimal grammar (invariant at the sink
|
|
571
595
|
* every caller reaches, not per-caller): rejects empty, padded, hex, exponent,
|
|
572
596
|
* and leading-zero forms. Equivalent to the TUI's strict parsing so both entry
|
|
573
597
|
* points share one grammar.
|
|
@@ -749,7 +773,7 @@ export function readViewMode(record) {
|
|
|
749
773
|
* Sanitize a FlagsRecord by coercing each known flag's value through
|
|
750
774
|
* coerceFlagValue.
|
|
751
775
|
*
|
|
752
|
-
* Known flag IDs (
|
|
776
|
+
* Known flag IDs (key-presence semantics):
|
|
753
777
|
* - explicit null input → kept as null (deliberately unset)
|
|
754
778
|
* - valid non-null input → kept as coerced value
|
|
755
779
|
* - invalid non-null input → KEY DROPPED (absent = adopt default on next init,
|
|
@@ -758,7 +782,7 @@ export function readViewMode(record) {
|
|
|
758
782
|
* Unknown flag IDs (forward-compat):
|
|
759
783
|
* - primitive values (boolean, number, string, null) → kept as-is
|
|
760
784
|
* - non-primitive values (objects, arrays) → DROPPED to avoid laundering
|
|
761
|
-
* untrusted shapes into FlagsRecordValue
|
|
785
|
+
* untrusted shapes into FlagsRecordValue
|
|
762
786
|
*
|
|
763
787
|
* D39: `__proto__`, `constructor`, `prototype` are always skipped.
|
|
764
788
|
*/
|
|
@@ -823,7 +847,7 @@ export function getDefaultFlagsRecord() {
|
|
|
823
847
|
* Migrate a legacy (string-array) enabled-flags manifest to a typed FlagsRecord.
|
|
824
848
|
* Called by manifest.ts self-healing when it encounters an old string-array manifest.
|
|
825
849
|
*
|
|
826
|
-
* Contract
|
|
850
|
+
* Contract:
|
|
827
851
|
* - knownIds defined → knownSet = knownIds ∪ enabledIds
|
|
828
852
|
* - knownIds undefined → knownSet = full current registry ∪ enabledIds
|
|
829
853
|
* (pre-knownFlags manifests: all flags known, so adopt-nothing is expressed
|
|
@@ -884,7 +908,7 @@ export function settingValueHoldsManagedShape(flag, value) {
|
|
|
884
908
|
return isDeepStrictEqual(value, guard);
|
|
885
909
|
}
|
|
886
910
|
/**
|
|
887
|
-
* D-ATTR-GUARD single-source predicate (consistency-01
|
|
911
|
+
* D-ATTR-GUARD single-source predicate (consistency-01).
|
|
888
912
|
*
|
|
889
913
|
* Returns true when the settings.json string contains a value at the flag's
|
|
890
914
|
* target key that equals the flag's managed shape (`settingDeleteGuard`).
|
|
@@ -926,7 +950,7 @@ export function settingHoldsManagedShape(settingsJson, flagId) {
|
|
|
926
950
|
*
|
|
927
951
|
* Delegates to `settingValueHoldsManagedShape` — the single equality oracle for
|
|
928
952
|
* managed-shape comparisons — so there is exactly one `isDeepStrictEqual` call
|
|
929
|
-
* across the entire apply/strip pipeline
|
|
953
|
+
* across the entire apply/strip pipeline.
|
|
930
954
|
*
|
|
931
955
|
* Single-source invariant: both the disable path (applyFlags neutral branch) and
|
|
932
956
|
* the uninstall path (stripFlags) collapse to this predicate.
|
|
@@ -980,7 +1004,7 @@ function buildPayload(flag, value) {
|
|
|
980
1004
|
* Apply a FlagsRecord to a settings JSON string.
|
|
981
1005
|
*
|
|
982
1006
|
* - Unknown flag IDs are skipped (forward-compatible with future flags).
|
|
983
|
-
* - `coerceFlagValue` is called at the sink before applying
|
|
1007
|
+
* - `coerceFlagValue` is called at the sink before applying.
|
|
984
1008
|
* - Neutral values delete their target key.
|
|
985
1009
|
* - Env payloads for number flags are stringified ('40', never 40).
|
|
986
1010
|
* - Setting payloads for string flags with wrapKey are shaped ({ command: v }).
|
|
@@ -988,7 +1012,7 @@ function buildPayload(flag, value) {
|
|
|
988
1012
|
* - `__proto__`, `constructor`, `prototype` keys are silently skipped.
|
|
989
1013
|
*/
|
|
990
1014
|
export function applyFlags(settingsJson, flags) {
|
|
991
|
-
// REL-M2 sink guard
|
|
1015
|
+
// REL-M2 sink guard: a non-plain-object root (null, array, scalar)
|
|
992
1016
|
// would cause a silent no-op or a confusing TypeError deep inside the loop.
|
|
993
1017
|
// Throw early with a clear message so every caller path is self-guarding.
|
|
994
1018
|
const root = JSON.parse(settingsJson);
|
|
@@ -1003,7 +1027,7 @@ export function applyFlags(settingsJson, flags) {
|
|
|
1003
1027
|
const flag = FLAG_REGISTRY_MAP.get(id);
|
|
1004
1028
|
if (!flag)
|
|
1005
1029
|
continue; // unknown id — skip for forward compat
|
|
1006
|
-
// Coerce at the sink (
|
|
1030
|
+
// Coerce at the sink (validate at the convergence point)
|
|
1007
1031
|
const safe = coerceFlagValue(flag, value);
|
|
1008
1032
|
if (isNeutral(flag, safe)) {
|
|
1009
1033
|
// Neutral → delete the target key
|
|
@@ -1045,7 +1069,7 @@ export function applyFlags(settingsJson, flags) {
|
|
|
1045
1069
|
* Cleans up empty env object. Strip-then-apply idempotence preserved (INV-1).
|
|
1046
1070
|
*/
|
|
1047
1071
|
export function stripFlags(settingsJson) {
|
|
1048
|
-
// REL-M2 sink guard
|
|
1072
|
+
// REL-M2 sink guard: mirror of applyFlags — throw early on a
|
|
1049
1073
|
// non-plain-object root so every caller path is self-guarding.
|
|
1050
1074
|
const root = JSON.parse(settingsJson);
|
|
1051
1075
|
if (root === null || typeof root !== 'object' || Array.isArray(root)) {
|
|
@@ -1122,7 +1146,7 @@ export function resolveFinalViewMode(current, selected, explicit) {
|
|
|
1122
1146
|
// ─── Fold-before-strip pipeline ───────────────────────────────────────────────
|
|
1123
1147
|
/**
|
|
1124
1148
|
* Fold-before-strip pipeline — the single authoritative entry point for all
|
|
1125
|
-
* settings.json mutation paths
|
|
1149
|
+
* settings.json mutation paths.
|
|
1126
1150
|
*
|
|
1127
1151
|
* Both `init.ts` and `persistFlagConfig` (flags.ts) MUST call this instead of
|
|
1128
1152
|
* invoking `stripFlags` + `applyFlags` directly; the invariant lives in the
|
|
@@ -1143,7 +1167,7 @@ export function resolveFinalViewMode(current, selected, explicit) {
|
|
|
1143
1167
|
* A flag is "claimed" when it is present and non-null in the claimed set.
|
|
1144
1168
|
* Claimed: record value wins (devflow previously set this value).
|
|
1145
1169
|
* Unclaimed: fold from settings — if the user has a value in settings.json,
|
|
1146
|
-
* adopt it into the record (
|
|
1170
|
+
* adopt it into the record (devflow takes ownership).
|
|
1147
1171
|
*
|
|
1148
1172
|
* Boolean flags: never folded — on/off is always record-driven.
|
|
1149
1173
|
*
|
|
@@ -1166,7 +1190,7 @@ export function resolveFinalViewMode(current, selected, explicit) {
|
|
|
1166
1190
|
*/
|
|
1167
1191
|
export function convergeFlagsIntoSettings(settingsJson, record, opts) {
|
|
1168
1192
|
// ── Step 1: fold view-mode (must read pre-strip) ──────────────────────────
|
|
1169
|
-
//
|
|
1193
|
+
// resolveExistingViewMode reads the viewMode key. stripFlags removes
|
|
1170
1194
|
// it as part of the view-mode registry entry. Reading after strip silently
|
|
1171
1195
|
// reverts an externally-set /focus.
|
|
1172
1196
|
const folded = {
|
|
@@ -1229,7 +1253,7 @@ export function convergeFlagsIntoSettings(settingsJson, record, opts) {
|
|
|
1229
1253
|
}
|
|
1230
1254
|
}
|
|
1231
1255
|
// ── Step 2b: adopt guarded boolean settings before the strip ─────────────
|
|
1232
|
-
// D-ATTR-ADOPT
|
|
1256
|
+
// D-ATTR-ADOPT: a delete guard is evidence about the VALUE,
|
|
1233
1257
|
// never about the record — a pre-existing on-disk key whose value matches the
|
|
1234
1258
|
// managed shape means devflow wrote it, so adopt it into the record now, before
|
|
1235
1259
|
// stripFlags can unconditionally remove it on the next line.
|
package/dist/core/fs-atomic.js
CHANGED
|
@@ -5,11 +5,10 @@ import { promises as fs } from 'fs';
|
|
|
5
5
|
* D34: Canonical atomic-write helper for the TypeScript CLI surface.
|
|
6
6
|
*
|
|
7
7
|
* Call sites: used by the CLI's exclusive-write call sites (migrations, init, post-install,
|
|
8
|
-
* uninstall, security, ambient, memory, HUD
|
|
9
|
-
* The CJS counterpart (`writeExclusive` in `src/assets/scripts/hooks/
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* retry logic here MUST be mirrored in both CJS files.
|
|
8
|
+
* uninstall, security, ambient, memory, HUD).
|
|
9
|
+
* The CJS counterpart (`writeExclusive` in `src/assets/scripts/hooks/lib/learning-store.cjs`)
|
|
10
|
+
* intentionally remains a separate implementation — same semantics, different module
|
|
11
|
+
* system. Any change to the retry logic here MUST be mirrored there.
|
|
13
12
|
*/
|
|
14
13
|
/**
|
|
15
14
|
* Atomically write `filePath` by writing to a sibling `.tmp` then renaming.
|
|
@@ -34,7 +33,7 @@ export async function writeFileAtomicExclusive(filePath, data) {
|
|
|
34
33
|
// PID-scope the tmp name so concurrent writers from different processes
|
|
35
34
|
// (e.g., two Claude Code sessions) never collide on the same .tmp path.
|
|
36
35
|
// mirrors proxy-log.ts rotation at src/core/proxy-log.ts which PID-scopes
|
|
37
|
-
// for the same reason.
|
|
36
|
+
// for the same reason.
|
|
38
37
|
const tmp = `${filePath}.tmp.${process.pid}`;
|
|
39
38
|
try {
|
|
40
39
|
await fs.writeFile(tmp, data, { encoding: 'utf-8', flag: 'wx' });
|
|
@@ -58,7 +57,7 @@ export async function writeFileAtomicExclusive(filePath, data) {
|
|
|
58
57
|
//
|
|
59
58
|
// Non-fatal path: if stat fails (ENOENT → fresh file, or any other I/O
|
|
60
59
|
// error), skip chmod and keep the umask default — the write must still
|
|
61
|
-
// complete correctly
|
|
60
|
+
// complete correctly.
|
|
62
61
|
try {
|
|
63
62
|
const { mode } = await fs.stat(filePath);
|
|
64
63
|
// mode includes file-type bits; mask to permission bits only for chmod.
|
|
@@ -1,92 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file learning-queue-cleanup.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `src/cli/commands/learning.ts
|
|
6
|
-
*
|
|
7
|
-
* - `drainLearningQueue` — used by `devflow learning --clear` / `--disable`
|
|
4
|
+
* `drainLearningQueue`, the learning queue drain shared by `devflow learning
|
|
5
|
+
* --clear` and `--disable` (src/cli/commands/learning.ts) and by the drain
|
|
6
|
+
* `devflow init` runs when learning is switched off (src/cli/commands/init.ts).
|
|
8
7
|
*/
|
|
9
|
-
import { promises as fs } from 'fs';
|
|
10
8
|
import * as path from 'path';
|
|
11
|
-
import { getLearningPendingTurnsPath, getLearningPendingTurnsProcessingPath, } from './project-paths.js';
|
|
12
|
-
|
|
13
|
-
// Legacy marker-pipeline sweep
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
/** Fixed-name stamps left by the retired dream marker pipeline. */
|
|
16
|
-
const LEGACY_FIXED_STAMPS = ['.decisions-runs-today', '.curation-last', '.processor-spawned-at'];
|
|
17
|
-
/** Per-session marker variants: (decisions|curation).*.{json,processing,retries,failed} */
|
|
18
|
-
function isLegacyPerSessionMarker(name) {
|
|
19
|
-
return ((name.startsWith('decisions.') || name.startsWith('curation.')) &&
|
|
20
|
-
(name.endsWith('.json') || name.endsWith('.processing') || name.endsWith('.retries') || name.endsWith('.failed')));
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Sweep legacy marker-pipeline files from a `.devflow/learning/` directory:
|
|
24
|
-
* the fixed-name stamps above, plus per-session `decisions.*`/`curation.*`
|
|
25
|
-
* markers. Never touches `learning.json` or the live
|
|
26
|
-
* `.pending-turns.jsonl`/`.pending-turns.processing` queue files.
|
|
27
|
-
*
|
|
28
|
-
* ENOENT-idempotent (missing learning dir or already-removed files are not
|
|
29
|
-
* errors). Non-ENOENT errors are rethrown — callers that need best-effort
|
|
30
|
-
* semantics (e.g. `--reset`, which must still finish releasing its lock)
|
|
31
|
-
* should wrap the call in their own try/catch.
|
|
32
|
-
*
|
|
33
|
-
* @returns number of files removed
|
|
34
|
-
*/
|
|
35
|
-
export async function sweepLegacyDreamMarkers(learningDir) {
|
|
36
|
-
let removed = 0;
|
|
37
|
-
for (const name of LEGACY_FIXED_STAMPS) {
|
|
38
|
-
try {
|
|
39
|
-
await fs.unlink(path.join(learningDir, name));
|
|
40
|
-
removed++;
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
const code = err.code;
|
|
44
|
-
if (code !== 'ENOENT')
|
|
45
|
-
throw err;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
try {
|
|
49
|
-
const entries = await fs.readdir(learningDir);
|
|
50
|
-
for (const entry of entries) {
|
|
51
|
-
if (isLegacyPerSessionMarker(entry)) {
|
|
52
|
-
try {
|
|
53
|
-
await fs.unlink(path.join(learningDir, entry));
|
|
54
|
-
removed++;
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
const code = err.code;
|
|
58
|
-
if (code !== 'ENOENT')
|
|
59
|
-
throw err;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
const code = err.code;
|
|
66
|
-
if (code !== 'ENOENT')
|
|
67
|
-
throw err;
|
|
68
|
-
}
|
|
69
|
-
return removed;
|
|
70
|
-
}
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
// Live queue drain
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
9
|
+
import { getLearningClaimOwnerPath, getLearningDir, getLearningPendingTurnsPath, getLearningPendingTurnsProcessingPath, } from './project-paths.js';
|
|
10
|
+
import { drainQueueFiles } from './queue-drain.js';
|
|
74
11
|
/**
|
|
75
|
-
* Drain the learning (decisions-detection) pending-turns queue
|
|
76
|
-
* don't process later — used by
|
|
77
|
-
*
|
|
78
|
-
* the desired outcome in both cases.
|
|
12
|
+
* Drain the learning (decisions-detection) pending-turns queue, its claimed
|
|
13
|
+
* batch and the claim's owner file so stale turns don't process later — used by
|
|
14
|
+
* both `--clear` and `--disable`. A mid-run Learning agent whose claimed batch
|
|
15
|
+
* vanishes aborts without changes, which is the desired outcome in both cases.
|
|
16
|
+
* Refused, deleting nothing, when `.devflow` or `.devflow/learning` under `gitRoot`
|
|
17
|
+
* is a symbolic link (D-CLI-NO-SYMLINK). ENOENT-tolerant; other errors propagate.
|
|
79
18
|
*/
|
|
80
19
|
export async function drainLearningQueue(gitRoot) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
fs.unlink(getLearningPendingTurnsProcessingPath(gitRoot)).catch((e) => {
|
|
87
|
-
if (e.code !== 'ENOENT')
|
|
88
|
-
throw e;
|
|
89
|
-
}),
|
|
20
|
+
const learningDir = getLearningDir(gitRoot);
|
|
21
|
+
return drainQueueFiles([path.dirname(learningDir), learningDir], [
|
|
22
|
+
getLearningPendingTurnsPath(gitRoot),
|
|
23
|
+
getLearningPendingTurnsProcessingPath(gitRoot),
|
|
24
|
+
getLearningClaimOwnerPath(gitRoot),
|
|
90
25
|
]);
|
|
91
26
|
}
|
|
92
27
|
//# sourceMappingURL=learning-queue-cleanup.js.map
|