@deftai/directive-core 0.71.0 → 0.71.1
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/dist/init-deposit/gitignore.js +10 -0
- package/dist/intake/candidates-log.d.ts +0 -1
- package/dist/intake/candidates-log.js +5 -2
- package/dist/intake/issue-ingest.d.ts +8 -0
- package/dist/intake/issue-ingest.js +19 -1
- package/dist/intake/reconcile-issues.js +9 -2
- package/dist/orchestration/subagent-monitor.js +2 -2
- package/dist/task-surface/index.js +4 -1
- package/dist/triage/actions/index.js +8 -32
- package/dist/triage/bootstrap/gitignore.d.ts +2 -0
- package/dist/triage/bootstrap/gitignore.js +10 -3
- package/dist/triage/cache-path.d.ts +2 -0
- package/dist/triage/cache-path.js +21 -4
- package/package.json +3 -3
|
@@ -31,6 +31,16 @@ export const CANONICAL_GITIGNORE_BASELINE = [
|
|
|
31
31
|
"vbrief/.triage-cache/scope-lifecycle.jsonl",
|
|
32
32
|
"vbrief/.triage-cache/decompositions/",
|
|
33
33
|
"vbrief/.triage-cache/doctor-state.json",
|
|
34
|
+
// Symmetric `xbrief/` layout entries (#2348). On the migrated `xbrief/` tree
|
|
35
|
+
// the engine writes operator-private triage-cache files to
|
|
36
|
+
// `xbrief/.triage-cache/`; without these the paths are trackable, violating
|
|
37
|
+
// the #1144 hybrid policy. Both layouts are emitted (harmless extra lines on
|
|
38
|
+
// the layout not in use) to match the both-layout `.eval/` treatment.
|
|
39
|
+
"xbrief/.triage-cache/candidates.jsonl",
|
|
40
|
+
"xbrief/.triage-cache/summary-history.jsonl",
|
|
41
|
+
"xbrief/.triage-cache/scope-lifecycle.jsonl",
|
|
42
|
+
"xbrief/.triage-cache/decompositions/",
|
|
43
|
+
"xbrief/.triage-cache/doctor-state.json",
|
|
34
44
|
"vbrief/*.lock",
|
|
35
45
|
".deft/core.bak-*/",
|
|
36
46
|
".deft/*.bak-*",
|
|
@@ -2,7 +2,10 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, unlinkSync, writeSync, } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { pyRepr } from "../scm/py-format.js";
|
|
5
|
-
|
|
5
|
+
import { resolveCandidatesLogPath } from "../triage/cache-path.js";
|
|
6
|
+
function defaultLogPath() {
|
|
7
|
+
return resolveCandidatesLogPath(process.cwd());
|
|
8
|
+
}
|
|
6
9
|
const VALID_DECISIONS = new Set([
|
|
7
10
|
"accept",
|
|
8
11
|
"reject",
|
|
@@ -105,7 +108,7 @@ function validateEntry(entry) {
|
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
function resolvePath(path) {
|
|
108
|
-
return path !== undefined && path !== null ? resolve(path) :
|
|
111
|
+
return path !== undefined && path !== null ? resolve(path) : defaultLogPath();
|
|
109
112
|
}
|
|
110
113
|
function acquireAppendLock(logPath) {
|
|
111
114
|
while (threadLocked) {
|
|
@@ -44,6 +44,14 @@ export interface FetchIssueOptions {
|
|
|
44
44
|
readonly cacheRoot?: string | null;
|
|
45
45
|
readonly scmCall?: ScmCallFn;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Strip the `# #<number>: <title>` header that `renderContent` prepends when it
|
|
49
|
+
* materializes an issue's `content.md` at cache-put (#2314). The header is the
|
|
50
|
+
* only shape difference between the cache-read body and the live/raw-body path;
|
|
51
|
+
* removing it makes an ingested xBRIEF's `Overview` identical whether the source
|
|
52
|
+
* was a cache hit or a live fetch. A missing/older-shape header is left intact.
|
|
53
|
+
*/
|
|
54
|
+
export declare function stripRenderedIssueHeader(content: string, number: number): string;
|
|
47
55
|
export declare function fetchFromCache(repo: string, number: number, options?: FetchIssueOptions): Record<string, unknown> | null;
|
|
48
56
|
export declare function fetchIssueComments(repo: string, number: number, options?: FetchIssueOptions): IssueComment[];
|
|
49
57
|
export declare function attachIssueCommentThread(issue: Record<string, unknown>, comments: readonly IssueComment[]): Record<string, unknown>;
|
|
@@ -386,6 +386,20 @@ export function targetFilename(number, title, artifactSuffix = LEGACY_ARTIFACT_S
|
|
|
386
386
|
const slug = slugify(title) || `issue-${number}`;
|
|
387
387
|
return `${TODAY}-${number}-${slug}${artifactSuffix}`;
|
|
388
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* Strip the `# #<number>: <title>` header that `renderContent` prepends when it
|
|
391
|
+
* materializes an issue's `content.md` at cache-put (#2314). The header is the
|
|
392
|
+
* only shape difference between the cache-read body and the live/raw-body path;
|
|
393
|
+
* removing it makes an ingested xBRIEF's `Overview` identical whether the source
|
|
394
|
+
* was a cache hit or a live fetch. A missing/older-shape header is left intact.
|
|
395
|
+
*/
|
|
396
|
+
export function stripRenderedIssueHeader(content, number) {
|
|
397
|
+
if (!Number.isFinite(number)) {
|
|
398
|
+
return content;
|
|
399
|
+
}
|
|
400
|
+
const header = new RegExp(`^# #${number}:[^\\n]*\\n\\n`);
|
|
401
|
+
return content.replace(header, "");
|
|
402
|
+
}
|
|
389
403
|
export function fetchFromCache(repo, number, options = {}) {
|
|
390
404
|
const key = `${repo}/${number}`;
|
|
391
405
|
try {
|
|
@@ -405,7 +419,11 @@ export function fetchFromCache(repo, number, options = {}) {
|
|
|
405
419
|
// buildIssueVbrief.
|
|
406
420
|
if (result.contentPath !== null) {
|
|
407
421
|
try {
|
|
408
|
-
|
|
422
|
+
// #2314: content.md is `renderContent`-prefixed with a `# #<n>: <title>`
|
|
423
|
+
// header at cache-put. Strip that header so the cache-read Overview
|
|
424
|
+
// matches the live/raw-body path (which has no header), keeping the
|
|
425
|
+
// durable xBRIEF identical for a cache hit vs a live fetch.
|
|
426
|
+
issue.body = stripRenderedIssueHeader(readFileSync(result.contentPath, "utf8"), Number(issue.number));
|
|
409
427
|
}
|
|
410
428
|
catch {
|
|
411
429
|
// fall back to the raw body (re-scanned downstream)
|
|
@@ -6,6 +6,7 @@ import { call } from "../scm/call.js";
|
|
|
6
6
|
import { updateDecomposedChildBackReferences } from "../scope/decomposed-refs.js";
|
|
7
7
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
8
8
|
import { resolveProjectRepo } from "../slice/project-context.js";
|
|
9
|
+
import { LEGACY_INFO_ROOT_KEY, MIGRATED_INFO_ROOT_KEY } from "../xbrief-migrate/constants.js";
|
|
9
10
|
export const LIFECYCLE_FOLDERS = [
|
|
10
11
|
"proposed",
|
|
11
12
|
"pending",
|
|
@@ -633,8 +634,14 @@ export function applyLifecycleFixes(vbriefDir, report, projectRoot = null) {
|
|
|
633
634
|
const terminalStatus = destFolder === "cancelled" ? "cancelled" : "completed";
|
|
634
635
|
plan.status = terminalStatus;
|
|
635
636
|
const stamp = utcNowIso();
|
|
636
|
-
|
|
637
|
-
|
|
637
|
+
// Stamp `updated` into whichever info envelope the file already uses (#2346).
|
|
638
|
+
// Canonical v0.8 briefs use `xBRIEFInfo`; stamping `vBRIEFInfo` unconditionally
|
|
639
|
+
// appended a stray, version-less `vBRIEFInfo` block that then failed
|
|
640
|
+
// `vbrief:validate` ('vBRIEFInfo.version' must be one of ..., got 'undefined').
|
|
641
|
+
// Legacy briefs (or files without either envelope) keep the `vBRIEFInfo` key.
|
|
642
|
+
const infoKey = MIGRATED_INFO_ROOT_KEY in data ? MIGRATED_INFO_ROOT_KEY : LEGACY_INFO_ROOT_KEY;
|
|
643
|
+
const info = (data[infoKey] ?? {});
|
|
644
|
+
data[infoKey] = info;
|
|
638
645
|
info.updated = stamp;
|
|
639
646
|
plan.updated = stamp;
|
|
640
647
|
propagateItemStatus(plan.items, terminalStatus, stamp);
|
|
@@ -379,8 +379,8 @@ export function cmdSubagentMonitor(argv, cwd = process.cwd()) {
|
|
|
379
379
|
process.stderr.write(`Error: ${args.error}\n`);
|
|
380
380
|
return EXIT_EXTERNAL_ERROR;
|
|
381
381
|
}
|
|
382
|
-
if (args.thresholdMinutes <= 0) {
|
|
383
|
-
process.stderr.write(`Error: --threshold-minutes must be positive, got ${args.thresholdMinutes}\n`);
|
|
382
|
+
if (Number.isNaN(args.thresholdMinutes) || args.thresholdMinutes <= 0) {
|
|
383
|
+
process.stderr.write(`Error: --threshold-minutes must be a positive number, got ${args.thresholdMinutes}\n`);
|
|
384
384
|
return EXIT_EXTERNAL_ERROR;
|
|
385
385
|
}
|
|
386
386
|
const scratchEntries = args.scratchDirs.length > 0
|
|
@@ -37,7 +37,10 @@ export function runChangelogCheck(projectRoot, io) {
|
|
|
37
37
|
io.writeOut("FAIL: CHANGELOG.md not found\n");
|
|
38
38
|
return 1;
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
// Normalize CRLF -> LF before matching (#2329). On Windows checkouts with
|
|
41
|
+
// core.autocrlf=true the working tree uses CRLF, and the `[ \t]*\n` header
|
|
42
|
+
// pattern does not consume the `\r`, so the section falsely reads as absent.
|
|
43
|
+
const text = readFileSync(path, "utf8").replace(/\r\n/g, "\n");
|
|
41
44
|
const match = UNRELEASED_RE.exec(text);
|
|
42
45
|
if (match === null) {
|
|
43
46
|
io.writeOut("FAIL: No [Unreleased] section found in CHANGELOG.md\n");
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import { join } from "node:path";
|
|
4
2
|
import { CacheNotFoundError } from "../../cache/errors.js";
|
|
5
3
|
import { cacheGet } from "../../cache/operations.js";
|
|
4
|
+
import { ingestSingleForAccept as ingestSingleForAcceptTs } from "../../intake/issue-ingest.js";
|
|
6
5
|
import { call } from "../../scm/call.js";
|
|
7
6
|
import { ScmStubError } from "../../scm/errors.js";
|
|
8
7
|
import { createCandidatesLog, findByIssue, resolveAuditLogPath, rollbackAuditEntry, } from "./candidates-log.js";
|
|
@@ -18,12 +17,6 @@ const TERMINAL_DECISIONS = new Set(["accept", "reject", "mark-duplicate"]);
|
|
|
18
17
|
const DEFAULT_ACTOR = "agent:triage";
|
|
19
18
|
const DEFAULT_NEEDS_AC_COMMENT = "This issue lacks acceptance criteria. Please add a Test/Acceptance " +
|
|
20
19
|
"narrative before this can be triaged. (deft #845)";
|
|
21
|
-
function resolveDeftRoot() {
|
|
22
|
-
if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
|
|
23
|
-
return resolve(process.env.DEFT_ROOT);
|
|
24
|
-
}
|
|
25
|
-
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "..", "..");
|
|
26
|
-
}
|
|
27
20
|
function defaultNowIso() {
|
|
28
21
|
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
29
22
|
}
|
|
@@ -62,39 +55,22 @@ function defaultScmRunner() {
|
|
|
62
55
|
},
|
|
63
56
|
};
|
|
64
57
|
}
|
|
65
|
-
function defaultIssueIngest(
|
|
58
|
+
function defaultIssueIngest() {
|
|
66
59
|
return {
|
|
67
60
|
ingestSingleForAccept(issueNumber, repo, options = {}) {
|
|
68
61
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"import issue_ingest",
|
|
74
|
-
"issue_ingest.ingest_single_for_accept(",
|
|
75
|
-
`${issueNumber},`,
|
|
76
|
-
`${JSON.stringify(repo)},`,
|
|
77
|
-
`project_root=Path(${JSON.stringify(projectRoot)}),`,
|
|
78
|
-
")",
|
|
79
|
-
].join("\n");
|
|
80
|
-
const result = spawnSync("uv", ["run", "python", "-c", script], {
|
|
81
|
-
cwd: deftRoot,
|
|
82
|
-
encoding: "utf8",
|
|
83
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
84
|
-
});
|
|
85
|
-
if (result.status !== 0) {
|
|
86
|
-
const stderr = (result.stderr ?? "").trim();
|
|
87
|
-
throw new Error(stderr || "issue:ingest delegation failed");
|
|
88
|
-
}
|
|
62
|
+
// Delegate to the native TS intake path (#2350). The legacy Python
|
|
63
|
+
// `scripts/issue_ingest.py` shell-out was orphaned when #1933 removed the
|
|
64
|
+
// Python surface, leaving `triage:accept` raising ModuleNotFoundError.
|
|
65
|
+
ingestSingleForAcceptTs(issueNumber, repo, { projectRoot });
|
|
89
66
|
},
|
|
90
67
|
};
|
|
91
68
|
}
|
|
92
69
|
/** Default dependency bundle for production CLI use. */
|
|
93
70
|
export function createDefaultDeps(projectRoot) {
|
|
94
|
-
const deftRoot = resolveDeftRoot();
|
|
95
71
|
const deps = {
|
|
96
72
|
candidatesLog: createCandidatesLog(projectRoot),
|
|
97
|
-
issueIngest: defaultIssueIngest(
|
|
73
|
+
issueIngest: defaultIssueIngest(),
|
|
98
74
|
scm: defaultScmRunner(),
|
|
99
75
|
nowIso: defaultNowIso,
|
|
100
76
|
stderr: (message) => process.stderr.write(`${message}\n`),
|
|
@@ -9,6 +9,8 @@ export declare function gitattributesTriageCacheGlob(projectRoot: string): strin
|
|
|
9
9
|
export declare const GITATTRIBUTES_EVAL_RULE = "vbrief/.triage-cache/*.jsonl merge=union";
|
|
10
10
|
export declare const FORBIDDEN_BLANKET_EVAL_LINES: readonly string[];
|
|
11
11
|
export declare const EVAL_README_BODY = "# `vbrief/.triage-cache/` -- triage working-set artefacts\n\nThis directory holds the append-only JSON-lines logs that the triage and\nslicing skills emit. The framework governs which files in here are tracked\nby git versus gitignored using a **hybrid policy** (#1144, child of #1119).\n\n## Tracking policy\n\n| File | Tracked? | Why |\n| --- | --- | --- |\n| `slices.jsonl` | Yes -- **committed** | Team-shared cohort records produced by slicing skills (D13 / #1132). New operators joining the team need to see prior cohort outputs to detect orphans and avoid re-slicing the same scope. |\n| `candidates.jsonl` | No -- **gitignored** | Operator-private triage decisions (#845 Story 2). Each operator's local accept / defer / reject stream is per-machine state; sharing it would conflate operators' timing + identity across the team. Re-derive on a fresh clone via `task triage:bootstrap`. |\n| `summary-history.jsonl` | No -- **gitignored** | Operator-private observability for `task triage:summary` output time-series. Not load-bearing for any decision. |\n| `scope-lifecycle.jsonl` | No -- **gitignored** | Operator-private scope-lifecycle audit decisions (D1 / #1121). Each demote (`task scope:demote`) appends one entry including a `demote_meta` block (`was_promoted`, `original_promotion_decision_id`, `days_in_pending`, `demote_reason`, `demoted_from`). Per-operator stream; sharing would conflate operators' demote timing across the team. Lightweight metrics over this log are tracked separately at #1180. |\n| `decompositions/` | No -- **gitignored** | Temporary story-decomposition proposal drafts. These JSON drafts are local scratch artifacts, not vBRIEFs; generated child story vBRIEFs are created by `task scope:decompose` in lifecycle folders, defaulting to `vbrief/pending/`. |\n| `doctor-state.json` | No -- **gitignored** | Per-machine `task doctor` throttle state (last exit code + timestamps) persisted to gate the 24h/4h re-probe window (#1308 / #1464). Local to each clone; never committed. |\n\nThe gitignore lines live in the repo-root `.gitignore` (`vbrief/.triage-cache/candidates.jsonl`,\n`vbrief/.triage-cache/summary-history.jsonl`, `vbrief/.triage-cache/scope-lifecycle.jsonl`,\n`vbrief/.triage-cache/decompositions/`, and `vbrief/.triage-cache/doctor-state.json`). All paths\nnot listed above remain committed by default.\n\n## Fresh-clone regeneration\n\nOn a fresh clone (or any machine that has never run triage), `candidates.jsonl`\nis absent. Regenerate it with:\n\n```\ntask triage:bootstrap\n```\n\nThe bootstrap path detects the missing file, runs the auto-classifier, and\nwrites a fresh `vbrief/.triage-cache/candidates.jsonl`. It does NOT touch the tracked\n`slices.jsonl`; cohort records remain a team-shared resource.\n\n## `merge=union` policy for `*.jsonl`\n\nThe repo-root `.gitattributes` declares:\n\n```\nvbrief/.triage-cache/*.jsonl merge=union\n```\n\nThe `union` merge driver concatenates both sides' appended lines on\nauto-merge, so two branches that each appended a different record to the\nsame JSON-lines file rebase cleanly without operator surgery. Two things\noperators should know:\n\n- **Concatenation, not set-union.** When two branches append DIFFERENT\n records to the file, the merge driver concatenates both sides' lines\n -- there is no smart deduplication of \"semantically similar\" records.\n (Identical line-for-line appends collapse because git's three-way\n merge sees them as the same change, but distinct records always\n survive verbatim, even if a downstream reader would consider them\n redundant.) The append-only writers in `scripts/candidates_log.py`\n mint a fresh `decision_id` per call, so genuinely duplicate records\n are not the expected case, but downstream readers MUST tolerate\n multiple records describing the same logical decision.\n- **Single-operator scope only.** This is the foundational rebase\n ergonomic for the single-operator case (operator A rebases their\n feature branch onto a master that grew while they were AFK).\n Multi-operator merge-conflict resolution is explicitly out of scope per\n #1119 R4 (tracked separately as M1-M4 in #1183).\n\n## See also\n\n- Current Shape comment on #1144 for the canonical decisions (the source\n of truth this README documents).\n- `.gitignore` -- selective gitignore entries for the operator-private\n files.\n- `.gitattributes` -- the `merge=union` rule.\n- `scripts/candidates_log.py` -- the writer for `candidates.jsonl`.\n";
|
|
12
|
+
/** Layout-aware triage-cache README body for the active lifecycle tree (#2344 / #2349). */
|
|
13
|
+
export declare function generateTriageCacheReadmeBody(projectRoot: string): string;
|
|
12
14
|
/** Strip an inline `# ...` comment from a gitignore line. */
|
|
13
15
|
export declare function stripGitignoreInlineComment(line: string): string;
|
|
14
16
|
/** Append `.deft-cache/` to `.gitignore` when absent. */
|
|
@@ -152,6 +152,12 @@ operators should know:
|
|
|
152
152
|
- \`.gitattributes\` -- the \`merge=union\` rule.
|
|
153
153
|
- \`scripts/candidates_log.py\` -- the writer for \`candidates.jsonl\`.
|
|
154
154
|
`;
|
|
155
|
+
/** Layout-aware triage-cache README body for the active lifecycle tree (#2344 / #2349). */
|
|
156
|
+
export function generateTriageCacheReadmeBody(projectRoot) {
|
|
157
|
+
const layout = resolveLifecycleLayout(projectRoot);
|
|
158
|
+
const triagePrefix = `${layout.artifactDir}/${TRIAGE_CACHE_DIR_NAME}`;
|
|
159
|
+
return EVAL_README_BODY.replaceAll("vbrief/.triage-cache", triagePrefix);
|
|
160
|
+
}
|
|
155
161
|
function stepOutcome(name, ok, message, details = {}, error = null) {
|
|
156
162
|
return { name, ok, message, error, details };
|
|
157
163
|
}
|
|
@@ -338,7 +344,8 @@ function ensureGitattributesMergeUnion(gitattributesPath, stepName, glob, ruleLi
|
|
|
338
344
|
gitattributes_created: true,
|
|
339
345
|
});
|
|
340
346
|
}
|
|
341
|
-
function ensureEvalReadme(
|
|
347
|
+
function ensureEvalReadme(options) {
|
|
348
|
+
const { projectRoot, readmePath, readmeRel, stepName } = options;
|
|
342
349
|
try {
|
|
343
350
|
readFileSync(readmePath, { encoding: "utf8" });
|
|
344
351
|
return stepOutcome(stepName, true, `${readmeRel} already present (no-op)`, {
|
|
@@ -351,7 +358,7 @@ function ensureEvalReadme(readmePath, readmeRel, stepName) {
|
|
|
351
358
|
}
|
|
352
359
|
try {
|
|
353
360
|
mkdirSync(dirname(readmePath), { recursive: true });
|
|
354
|
-
writeFileSync(readmePath,
|
|
361
|
+
writeFileSync(readmePath, generateTriageCacheReadmeBody(projectRoot), { encoding: "utf8" });
|
|
355
362
|
}
|
|
356
363
|
catch (exc) {
|
|
357
364
|
return stepOutcome(stepName, false, `could not create ${readmePath}`, { readme_created: false }, String(exc));
|
|
@@ -385,7 +392,7 @@ export function stepEnsureGitignoreEvalEntries(projectRoot) {
|
|
|
385
392
|
return stepOutcome(stepName, false, gaResult.message, details, gaResult.error ?? null);
|
|
386
393
|
}
|
|
387
394
|
Object.assign(details, gaResult.details);
|
|
388
|
-
const rdResult = ensureEvalReadme(readmePath, readmeRel, stepName);
|
|
395
|
+
const rdResult = ensureEvalReadme({ projectRoot, readmePath, readmeRel, stepName });
|
|
389
396
|
if (!rdResult.ok) {
|
|
390
397
|
Object.assign(details, rdResult.details);
|
|
391
398
|
return stepOutcome(stepName, false, rdResult.message, details, rdResult.error ?? null);
|
|
@@ -16,6 +16,8 @@ export interface TriageCacheMigrationResult {
|
|
|
16
16
|
readonly migratedFiles: readonly string[];
|
|
17
17
|
readonly skippedFiles: readonly string[];
|
|
18
18
|
readonly migratedDirs: readonly string[];
|
|
19
|
+
readonly regeneratedFiles: readonly string[];
|
|
20
|
+
readonly removedLegacyFiles: readonly string[];
|
|
19
21
|
}
|
|
20
22
|
/** Absolute path to the layout-aware `.triage-cache/` directory. */
|
|
21
23
|
export declare function resolveTriageCacheDir(projectRoot: string): string;
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Triage append-only logs and scratch dirs live under `.triage-cache/` so the
|
|
5
5
|
* `.eval/` namespace can be reclaimed for version-eval results (#1703 Tier 2).
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, mkdirSync, renameSync } from "node:fs";
|
|
7
|
+
import { existsSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join, relative } from "node:path";
|
|
9
9
|
import { resolveEvalDir, resolveLifecycleLayout, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
10
|
+
import { generateTriageCacheReadmeBody } from "./bootstrap/gitignore.js";
|
|
10
11
|
/** Directory name for the triage working-set cache (not version-eval results). */
|
|
11
12
|
export const TRIAGE_CACHE_DIR_NAME = ".triage-cache";
|
|
12
13
|
/** Legacy directory that previously held triage cache files before #1703. */
|
|
@@ -42,8 +43,10 @@ export function migrateLegacyTriageCacheFromEval(projectRoot) {
|
|
|
42
43
|
const migratedFiles = [];
|
|
43
44
|
const skippedFiles = [];
|
|
44
45
|
const migratedDirs = [];
|
|
46
|
+
const regeneratedFiles = [];
|
|
47
|
+
const removedLegacyFiles = [];
|
|
45
48
|
if (!existsSync(legacyDir)) {
|
|
46
|
-
return { migratedFiles, skippedFiles, migratedDirs };
|
|
49
|
+
return { migratedFiles, skippedFiles, migratedDirs, regeneratedFiles, removedLegacyFiles };
|
|
47
50
|
}
|
|
48
51
|
mkdirSync(targetDir, { recursive: true });
|
|
49
52
|
for (const name of TRIAGE_CACHE_FILE_NAMES) {
|
|
@@ -52,8 +55,21 @@ export function migrateLegacyTriageCacheFromEval(projectRoot) {
|
|
|
52
55
|
if (!existsSync(legacyPath)) {
|
|
53
56
|
continue;
|
|
54
57
|
}
|
|
58
|
+
if (name === "README.md") {
|
|
59
|
+
if (existsSync(targetPath)) {
|
|
60
|
+
unlinkSync(legacyPath);
|
|
61
|
+
removedLegacyFiles.push(name);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
writeFileSync(targetPath, generateTriageCacheReadmeBody(projectRoot), "utf8");
|
|
65
|
+
unlinkSync(legacyPath);
|
|
66
|
+
regeneratedFiles.push(name);
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
55
70
|
if (existsSync(targetPath)) {
|
|
56
|
-
|
|
71
|
+
unlinkSync(legacyPath);
|
|
72
|
+
removedLegacyFiles.push(name);
|
|
57
73
|
continue;
|
|
58
74
|
}
|
|
59
75
|
renameSync(legacyPath, targetPath);
|
|
@@ -72,7 +88,7 @@ export function migrateLegacyTriageCacheFromEval(projectRoot) {
|
|
|
72
88
|
renameSync(legacyPath, targetPath);
|
|
73
89
|
migratedDirs.push(name);
|
|
74
90
|
}
|
|
75
|
-
return { migratedFiles, skippedFiles, migratedDirs };
|
|
91
|
+
return { migratedFiles, skippedFiles, migratedDirs, regeneratedFiles, removedLegacyFiles };
|
|
76
92
|
}
|
|
77
93
|
/** Resolve a path under `.triage-cache/`, migrating legacy `.eval/` files first. */
|
|
78
94
|
export function resolveTriageCachePath(projectRoot, ...segments) {
|
|
@@ -91,6 +107,7 @@ export function resolveCandidatesLogPath(projectRoot) {
|
|
|
91
107
|
}
|
|
92
108
|
/** Ensure the triage cache directory exists (post-migration). */
|
|
93
109
|
export function ensureTriageCacheDir(projectRoot) {
|
|
110
|
+
migrateLegacyTriageCacheFromEval(projectRoot);
|
|
94
111
|
const dir = resolveTriageCacheDir(projectRoot);
|
|
95
112
|
mkdirSync(dir, { recursive: true });
|
|
96
113
|
return dir;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive-core",
|
|
3
|
-
"version": "0.71.
|
|
3
|
+
"version": "0.71.1",
|
|
4
4
|
"description": "TypeScript engine core for the Directive framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -281,8 +281,8 @@
|
|
|
281
281
|
"provenance": true
|
|
282
282
|
},
|
|
283
283
|
"dependencies": {
|
|
284
|
-
"@deftai/directive-content": "^0.71.
|
|
285
|
-
"@deftai/directive-types": "^0.71.
|
|
284
|
+
"@deftai/directive-content": "^0.71.1",
|
|
285
|
+
"@deftai/directive-types": "^0.71.1",
|
|
286
286
|
"archiver": "^8.0.0"
|
|
287
287
|
},
|
|
288
288
|
"scripts": {
|