@deftai/directive-core 0.55.1 → 0.56.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/dist/cache/errors.d.ts +4 -1
- package/dist/cache/errors.js +8 -2
- package/dist/cache/fetch.d.ts +50 -0
- package/dist/cache/fetch.js +224 -3
- package/dist/cache/main.js +10 -5
- package/dist/cache/operations.js +6 -4
- package/dist/codebase/map-fresh.js +7 -1
- package/dist/deposit/copy-tree.d.ts +16 -0
- package/dist/deposit/copy-tree.js +58 -0
- package/dist/deposit/resolve-content.d.ts +28 -0
- package/dist/deposit/resolve-content.js +70 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +37 -0
- package/dist/doctor/constants.d.ts +2 -0
- package/dist/doctor/constants.js +8 -0
- package/dist/engine-version.d.ts +3 -0
- package/dist/engine-version.js +23 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/init-deposit/gitignore.d.ts +51 -0
- package/dist/init-deposit/gitignore.js +200 -0
- package/dist/init-deposit/index.d.ts +6 -0
- package/dist/init-deposit/index.js +6 -0
- package/dist/init-deposit/init-deposit.d.ts +44 -0
- package/dist/init-deposit/init-deposit.js +197 -0
- package/dist/init-deposit/legacy-detect.d.ts +61 -0
- package/dist/init-deposit/legacy-detect.js +223 -0
- package/dist/init-deposit/migrate.d.ts +88 -0
- package/dist/init-deposit/migrate.js +196 -0
- package/dist/init-deposit/refresh.d.ts +50 -0
- package/dist/init-deposit/refresh.js +292 -0
- package/dist/init-deposit/scaffold.d.ts +41 -0
- package/dist/init-deposit/scaffold.js +753 -0
- package/dist/legacy-bridge/bridge-drift.d.ts +70 -0
- package/dist/legacy-bridge/bridge-drift.js +171 -0
- package/dist/legacy-bridge/freeze-gate.d.ts +79 -0
- package/dist/legacy-bridge/freeze-gate.js +168 -0
- package/dist/legacy-bridge/index.d.ts +4 -0
- package/dist/legacy-bridge/index.js +4 -0
- package/dist/legacy-bridge/sot.d.ts +45 -0
- package/dist/legacy-bridge/sot.js +49 -0
- package/dist/preflight-cache/evaluate.d.ts +11 -0
- package/dist/preflight-cache/evaluate.js +63 -13
- package/dist/release-e2e/constants.d.ts +2 -0
- package/dist/release-e2e/constants.js +2 -0
- package/dist/release-e2e/flags.js +5 -1
- package/dist/release-e2e/legacy-bridge-leg.d.ts +134 -0
- package/dist/release-e2e/legacy-bridge-leg.js +460 -0
- package/dist/release-e2e/main.d.ts +2 -1
- package/dist/release-e2e/main.js +17 -1
- package/dist/release-e2e/npm-ops.d.ts +3 -1
- package/dist/release-e2e/npm-ops.js +5 -3
- package/dist/release-e2e/types.d.ts +9 -0
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -1
- package/dist/render/project-render.js +4 -3
- package/dist/render/roadmap-render.d.ts +4 -2
- package/dist/render/roadmap-render.js +14 -7
- package/dist/swarm/routing-verify.js +2 -2
- package/dist/swarm/subagent-backend.d.ts +16 -0
- package/dist/swarm/subagent-backend.js +14 -0
- package/dist/triage/actions/candidates-log.d.ts +12 -1
- package/dist/triage/actions/candidates-log.js +44 -7
- package/dist/triage/actions/index.d.ts +25 -1
- package/dist/triage/actions/index.js +94 -3
- package/dist/triage/summary/index.d.ts +1 -3
- package/dist/triage/summary/index.js +6 -57
- package/dist/triage/summary/reconcilable.js +4 -33
- package/dist/triage/welcome/default-mode.d.ts +1 -0
- package/dist/triage/welcome/default-mode.js +6 -0
- package/dist/vbrief-validate/project-definition.js +4 -69
- package/dist/vbrief-validate/registry-status.d.ts +20 -0
- package/dist/vbrief-validate/registry-status.js +85 -0
- package/package.json +11 -2
|
@@ -241,10 +241,13 @@ function renderScopeItem(vbriefData) {
|
|
|
241
241
|
parts.push(`\`[${status}]\``);
|
|
242
242
|
return [`- ${parts.join(" -- ")}`];
|
|
243
243
|
}
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
function resolveCompletedDir(pendingDir, completedDir) {
|
|
245
|
+
return completedDir ?? join(dirname(pendingDir), "completed");
|
|
246
|
+
}
|
|
247
|
+
/** Single render-to-buffer entry used by both write and --check (mirrors ``scripts/roadmap_render.generate_roadmap_content``). */
|
|
248
|
+
export function renderRoadmapToBuffer(pendingDir, completedDir) {
|
|
246
249
|
const vbriefs = loadVbriefs(pendingDir);
|
|
247
|
-
const resolvedCompleted =
|
|
250
|
+
const resolvedCompleted = resolveCompletedDir(pendingDir, completedDir);
|
|
248
251
|
const completedVbriefs = loadVbriefs(resolvedCompleted);
|
|
249
252
|
const lines = [ROADMAP_BANNER, "# Roadmap\n"];
|
|
250
253
|
if (vbriefs.length === 0 && completedVbriefs.length === 0) {
|
|
@@ -343,9 +346,13 @@ export function generateRoadmapContent(pendingDir, completedDir) {
|
|
|
343
346
|
}
|
|
344
347
|
return `${lines.join("\n")}\n`;
|
|
345
348
|
}
|
|
349
|
+
/** @deprecated Prefer ``renderRoadmapToBuffer`` — kept for existing imports and parity harnesses. */
|
|
350
|
+
export function generateRoadmapContent(pendingDir, completedDir) {
|
|
351
|
+
return renderRoadmapToBuffer(pendingDir, completedDir);
|
|
352
|
+
}
|
|
346
353
|
export function renderRoadmap(pendingDir, outPath, completedDir) {
|
|
347
354
|
try {
|
|
348
|
-
const content =
|
|
355
|
+
const content = renderRoadmapToBuffer(pendingDir, completedDir);
|
|
349
356
|
writeFileSync(outPath, content, "utf8");
|
|
350
357
|
return [true, `✓ Rendered ROADMAP.md to ${outPath}`];
|
|
351
358
|
}
|
|
@@ -353,11 +360,11 @@ export function renderRoadmap(pendingDir, outPath, completedDir) {
|
|
|
353
360
|
return [false, `✗ Failed to write ${outPath}: ${String(exc)}`];
|
|
354
361
|
}
|
|
355
362
|
}
|
|
356
|
-
export function checkDrift(pendingDir, roadmapPath) {
|
|
357
|
-
const expected =
|
|
363
|
+
export function checkDrift(pendingDir, roadmapPath, completedDir) {
|
|
364
|
+
const expected = renderRoadmapToBuffer(pendingDir, completedDir);
|
|
358
365
|
if (!existsSync(roadmapPath)) {
|
|
359
366
|
const hasPending = existsSync(pendingDir) && readdirSync(pendingDir).some((n) => n.endsWith(".vbrief.json"));
|
|
360
|
-
const inferredCompleted =
|
|
367
|
+
const inferredCompleted = resolveCompletedDir(pendingDir, completedDir);
|
|
361
368
|
const hasCompleted = existsSync(inferredCompleted) &&
|
|
362
369
|
readdirSync(inferredCompleted).some((n) => n.endsWith(".vbrief.json"));
|
|
363
370
|
if (!hasPending && !hasCompleted) {
|
|
@@ -73,7 +73,7 @@ export function verifyRouting(options) {
|
|
|
73
73
|
if (undecided.length === 0 && invalid.length === 0) {
|
|
74
74
|
return {
|
|
75
75
|
exitCode: EXIT_OK,
|
|
76
|
-
report: `[deft routing] provider '${provider}': all ${roles.length} gated role(s) decided.`,
|
|
76
|
+
report: `[deft routing] provider '${provider}': all ${roles.length} gated role(s) decided.\n[deft routing] NOTE: plan.policy.swarmSubagentBackend enum is deprecated (#1891); use 'task swarm:routing-set' / .deft/routing.local.json instead.`,
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
const parts = [];
|
|
@@ -85,7 +85,7 @@ export function verifyRouting(options) {
|
|
|
85
85
|
}
|
|
86
86
|
return {
|
|
87
87
|
exitCode: EXIT_OK,
|
|
88
|
-
report: `[deft routing] provider '${provider}' -- ${parts.join("; ")}. Decide before swarm dispatch: ${ROUTING_SET_CMD}
|
|
88
|
+
report: `[deft routing] provider '${provider}' -- ${parts.join("; ")}. Decide before swarm dispatch: ${ROUTING_SET_CMD}\n[deft routing] NOTE: plan.policy.swarmSubagentBackend enum is deprecated (#1891); use 'task swarm:routing-set' / .deft/routing.local.json instead.`,
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
if (undecided.length > 0) {
|
|
@@ -1,11 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated This module is superseded by per-role operator model routing
|
|
3
|
+
* (`.deft/routing.local.json`) introduced in #1739 / #1863.
|
|
4
|
+
*
|
|
5
|
+
* Use `task swarm:routing-set` and `task verify:routing` instead.
|
|
6
|
+
* See `packages/core/src/swarm/routing.ts` for the current implementation.
|
|
7
|
+
*
|
|
8
|
+
* The enum and associated helpers remain functional for consumers that have not
|
|
9
|
+
* yet migrated; they will be removed in a future major cleanup tracked by #1860.
|
|
10
|
+
*
|
|
11
|
+
* @see {@link https://github.com/deftai/directive/issues/1739} Superseding PR
|
|
12
|
+
* @see {@link https://github.com/deftai/directive/issues/1860} Hard deletion tracking
|
|
13
|
+
*/
|
|
1
14
|
import { LEAF_CODING_WORKER_ROLE } from "./constants.js";
|
|
15
|
+
/** @deprecated Superseded by `.deft/routing.local.json` routing (#1739). Use `task swarm:routing-set`. */
|
|
2
16
|
export declare const KNOWN_SUBAGENT_BACKEND_IDS: Set<string>;
|
|
17
|
+
/** @deprecated Superseded by `.deft/routing.local.json` routing (#1739). Use `task swarm:routing-set`. */
|
|
3
18
|
export interface SubagentBackendDescriptor {
|
|
4
19
|
readonly backend_id: string;
|
|
5
20
|
readonly display_name: string;
|
|
6
21
|
readonly roles: readonly string[];
|
|
7
22
|
readonly available: boolean;
|
|
8
23
|
}
|
|
24
|
+
/** @deprecated Superseded by `.deft/routing.local.json` routing (#1739). Use `task swarm:routing-set`. */
|
|
9
25
|
export interface SwarmSubagentBackendResult {
|
|
10
26
|
readonly backend_id: string | null;
|
|
11
27
|
readonly source: string;
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated This module is superseded by per-role operator model routing
|
|
3
|
+
* (`.deft/routing.local.json`) introduced in #1739 / #1863.
|
|
4
|
+
*
|
|
5
|
+
* Use `task swarm:routing-set` and `task verify:routing` instead.
|
|
6
|
+
* See `packages/core/src/swarm/routing.ts` for the current implementation.
|
|
7
|
+
*
|
|
8
|
+
* The enum and associated helpers remain functional for consumers that have not
|
|
9
|
+
* yet migrated; they will be removed in a future major cleanup tracked by #1860.
|
|
10
|
+
*
|
|
11
|
+
* @see {@link https://github.com/deftai/directive/issues/1739} Superseding PR
|
|
12
|
+
* @see {@link https://github.com/deftai/directive/issues/1860} Hard deletion tracking
|
|
13
|
+
*/
|
|
1
14
|
import { loadProjectDefinition } from "../policy/resolve.js";
|
|
2
15
|
import { LEAF_CODING_WORKER_ROLE, SUBAGENT_BACKEND_SET_CMD } from "./constants.js";
|
|
3
16
|
const TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
17
|
+
/** @deprecated Superseded by `.deft/routing.local.json` routing (#1739). Use `task swarm:routing-set`. */
|
|
4
18
|
export const KNOWN_SUBAGENT_BACKEND_IDS = new Set(["composer", "grok-build", "cursor-cloud"]);
|
|
5
19
|
const SUBAGENT_BACKEND_CATALOG = {
|
|
6
20
|
composer: {
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import type { CandidatesLog } from "./types.js";
|
|
1
|
+
import type { AuditEntry, CandidatesLog } from "./types.js";
|
|
2
2
|
export declare const AUDIT_LOG_REL_PATH = "vbrief/.eval/candidates.jsonl";
|
|
3
|
+
/** Read every well-formed audit-log row in insertion order (#1698 shared reader). */
|
|
4
|
+
export declare function readAuditLog(logPath: string, repo?: string | null): AuditEntry[];
|
|
5
|
+
/** Return every entry for ``(repo, issue_number)`` in insertion order. */
|
|
6
|
+
export declare function findByIssue(issueNumber: number, repo: string, logPath: string): AuditEntry[];
|
|
7
|
+
/**
|
|
8
|
+
* Canonical latest decision for ``(repo, issue_number)`` — no actor filter.
|
|
9
|
+
* Backfilled ``agent:bootstrap`` / ``agent:reconcile`` entries count equally.
|
|
10
|
+
*/
|
|
11
|
+
export declare function latestDecisionForIssue(issueNumber: number, repo: string, logPath: string): AuditEntry | null;
|
|
12
|
+
/** Collapse audit-log entries to ``{(repo, issue_number): decision}`` by timestamp. */
|
|
13
|
+
export declare function latestDecisions(entries: Iterable<AuditEntry | Record<string, unknown>>): Map<string, string>;
|
|
3
14
|
/** Filesystem-backed candidates audit log (mirrors ``scripts/candidates_log.py``). */
|
|
4
15
|
export declare function createCandidatesLog(defaultProjectRoot: string): CandidatesLog;
|
|
5
16
|
/** Remove the audit-log line whose ``decision_id`` matches (rollback path). */
|
|
@@ -113,7 +113,8 @@ function stableStringify(entry) {
|
|
|
113
113
|
}
|
|
114
114
|
return JSON.stringify(sorted);
|
|
115
115
|
}
|
|
116
|
-
|
|
116
|
+
/** Read every well-formed audit-log row in insertion order (#1698 shared reader). */
|
|
117
|
+
export function readAuditLog(logPath, repo = null) {
|
|
117
118
|
if (!existsSync(logPath)) {
|
|
118
119
|
return [];
|
|
119
120
|
}
|
|
@@ -132,7 +133,47 @@ function readAll(repo, logPath) {
|
|
|
132
133
|
continue;
|
|
133
134
|
out.push(row);
|
|
134
135
|
}
|
|
135
|
-
catch {
|
|
136
|
+
catch {
|
|
137
|
+
// tolerate malformed lines — mirrors candidates_log.read_all
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
/** Return every entry for ``(repo, issue_number)`` in insertion order. */
|
|
143
|
+
export function findByIssue(issueNumber, repo, logPath) {
|
|
144
|
+
return readAuditLog(logPath, repo).filter((row) => row.issue_number === issueNumber);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Canonical latest decision for ``(repo, issue_number)`` — no actor filter.
|
|
148
|
+
* Backfilled ``agent:bootstrap`` / ``agent:reconcile`` entries count equally.
|
|
149
|
+
*/
|
|
150
|
+
export function latestDecisionForIssue(issueNumber, repo, logPath) {
|
|
151
|
+
const rows = findByIssue(issueNumber, repo, logPath);
|
|
152
|
+
if (rows.length === 0)
|
|
153
|
+
return null;
|
|
154
|
+
rows.sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp)));
|
|
155
|
+
return rows.at(-1) ?? null;
|
|
156
|
+
}
|
|
157
|
+
/** Collapse audit-log entries to ``{(repo, issue_number): decision}`` by timestamp. */
|
|
158
|
+
export function latestDecisions(entries) {
|
|
159
|
+
const rows = [];
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
const repo = entry.repo;
|
|
162
|
+
const issueNumber = entry.issue_number;
|
|
163
|
+
const decision = entry.decision;
|
|
164
|
+
const timestamp = entry.timestamp;
|
|
165
|
+
if (typeof repo === "string" &&
|
|
166
|
+
typeof issueNumber === "number" &&
|
|
167
|
+
Number.isInteger(issueNumber) &&
|
|
168
|
+
typeof decision === "string" &&
|
|
169
|
+
typeof timestamp === "string") {
|
|
170
|
+
rows.push([timestamp, repo, issueNumber, decision]);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
rows.sort((a, b) => a[0].localeCompare(b[0]));
|
|
174
|
+
const out = new Map();
|
|
175
|
+
for (const [_ts, repo, n, decision] of rows) {
|
|
176
|
+
out.set(`${repo}\0${n}`, decision);
|
|
136
177
|
}
|
|
137
178
|
return out;
|
|
138
179
|
}
|
|
@@ -148,11 +189,7 @@ export function createCandidatesLog(defaultProjectRoot) {
|
|
|
148
189
|
},
|
|
149
190
|
latestDecision(issueNumber, repo, options) {
|
|
150
191
|
const logPath = resolveLogPath(defaultProjectRoot, options?.path);
|
|
151
|
-
|
|
152
|
-
if (rows.length === 0)
|
|
153
|
-
return null;
|
|
154
|
-
rows.sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp)));
|
|
155
|
-
return rows.at(-1) ?? null;
|
|
192
|
+
return latestDecisionForIssue(issueNumber, repo, logPath);
|
|
156
193
|
},
|
|
157
194
|
newDecisionId() {
|
|
158
195
|
return randomUUID();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AcceptOptions, AuditEntry, DeferOptions, RejectOptions, TriageActionsDeps } from "./types.js";
|
|
2
|
-
export { AUDIT_LOG_REL_PATH, createCandidatesLog, resolveAuditLogPath, rollbackAuditEntry, } from "./candidates-log.js";
|
|
2
|
+
export { AUDIT_LOG_REL_PATH, createCandidatesLog, findByIssue, latestDecisionForIssue, latestDecisions, readAuditLog, resolveAuditLogPath, rollbackAuditEntry, } from "./candidates-log.js";
|
|
3
3
|
export { CandidatesLogError, ResumeGrammarError, TriageError, UpstreamCloseError, } from "./errors.js";
|
|
4
4
|
export { parseResumeOn } from "./resume-on.js";
|
|
5
5
|
export type { AcceptOptions, AuditEntry, CandidatesLog, DeferOptions, IssueIngest, RejectOptions, ScmRunner, TriageActionsDeps, } from "./types.js";
|
|
@@ -8,12 +8,36 @@ export declare const REJECTED_LABEL_COLOR = "B60205";
|
|
|
8
8
|
export declare const REJECTED_LABEL_DESCRIPTION = "Issue rejected during deft triage";
|
|
9
9
|
/** Default dependency bundle for production CLI use. */
|
|
10
10
|
export declare function createDefaultDeps(projectRoot: string): TriageActionsDeps;
|
|
11
|
+
/** Return audit entries for ``issueNumber`` ordered ascending by timestamp. */
|
|
12
|
+
export declare function history(issueNumber: number, repo: string, _deps: TriageActionsDeps, options?: {
|
|
13
|
+
projectRoot?: string;
|
|
14
|
+
}): AuditEntry[];
|
|
11
15
|
/** Record an accept audit entry and delegate vBRIEF authoring to issue_ingest. */
|
|
12
16
|
export declare function accept(issueNumber: number, repo: string, deps: TriageActionsDeps, options?: AcceptOptions): string;
|
|
13
17
|
/** Close upstream, best-effort label, record reject audit entry. */
|
|
14
18
|
export declare function reject(issueNumber: number, repo: string, reason: string, deps: TriageActionsDeps, options?: RejectOptions): string;
|
|
15
19
|
/** Record a defer audit entry (#1123 -- structured reason + resume_on). */
|
|
16
20
|
export declare function deferAction(issueNumber: number, repo: string, reason: string | null | undefined, deps: TriageActionsDeps, options?: DeferOptions): string;
|
|
21
|
+
/** Record a needs-ac audit entry and best-effort post an AC-request comment upstream. */
|
|
22
|
+
export declare function needsAc(issueNumber: number, repo: string, deps: TriageActionsDeps, options?: {
|
|
23
|
+
actor?: string | null;
|
|
24
|
+
comment?: string | null;
|
|
25
|
+
projectRoot?: string;
|
|
26
|
+
}): string;
|
|
27
|
+
/** Validate duplicate target in cache and record a mark-duplicate audit entry. */
|
|
28
|
+
export declare function markDuplicate(issueNumber: number, repo: string, ofN: number, deps: TriageActionsDeps, options?: {
|
|
29
|
+
actor?: string | null;
|
|
30
|
+
projectRoot?: string;
|
|
31
|
+
}): string;
|
|
32
|
+
/** Return the latest decision for ``issueNumber`` in ``repo`` (null if none). */
|
|
33
|
+
export declare function status(issueNumber: number, repo: string, deps: TriageActionsDeps, options?: {
|
|
34
|
+
projectRoot?: string;
|
|
35
|
+
}): AuditEntry | null;
|
|
36
|
+
/** Record a reset audit entry referencing the prior decision_id (history is never deleted). */
|
|
37
|
+
export declare function reset(issueNumber: number, repo: string, deps: TriageActionsDeps, options?: {
|
|
38
|
+
actor?: string | null;
|
|
39
|
+
projectRoot?: string;
|
|
40
|
+
}): string;
|
|
17
41
|
/** Format a decision entry for CLI ``status`` / ``history`` output. */
|
|
18
42
|
export declare function formatDecision(entry: AuditEntry | null): string;
|
|
19
43
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { CacheNotFoundError } from "../../cache/errors.js";
|
|
5
|
+
import { cacheGet } from "../../cache/operations.js";
|
|
4
6
|
import { call } from "../../scm/call.js";
|
|
5
7
|
import { ScmStubError } from "../../scm/errors.js";
|
|
6
|
-
import { createCandidatesLog, resolveAuditLogPath, rollbackAuditEntry } from "./candidates-log.js";
|
|
8
|
+
import { createCandidatesLog, findByIssue, resolveAuditLogPath, rollbackAuditEntry, } from "./candidates-log.js";
|
|
7
9
|
import { TriageError, UpstreamCloseError } from "./errors.js";
|
|
8
10
|
import { parseResumeOn } from "./resume-on.js";
|
|
9
|
-
export { AUDIT_LOG_REL_PATH, createCandidatesLog, resolveAuditLogPath, rollbackAuditEntry, } from "./candidates-log.js";
|
|
11
|
+
export { AUDIT_LOG_REL_PATH, createCandidatesLog, findByIssue, latestDecisionForIssue, latestDecisions, readAuditLog, resolveAuditLogPath, rollbackAuditEntry, } from "./candidates-log.js";
|
|
10
12
|
export { CandidatesLogError, ResumeGrammarError, TriageError, UpstreamCloseError, } from "./errors.js";
|
|
11
13
|
export { parseResumeOn } from "./resume-on.js";
|
|
12
14
|
export const REJECTED_LABEL = "triage-rejected";
|
|
@@ -14,6 +16,8 @@ export const REJECTED_LABEL_COLOR = "B60205";
|
|
|
14
16
|
export const REJECTED_LABEL_DESCRIPTION = "Issue rejected during deft triage";
|
|
15
17
|
const TERMINAL_DECISIONS = new Set(["accept", "reject", "mark-duplicate"]);
|
|
16
18
|
const DEFAULT_ACTOR = "agent:triage";
|
|
19
|
+
const DEFAULT_NEEDS_AC_COMMENT = "This issue lacks acceptance criteria. Please add a Test/Acceptance " +
|
|
20
|
+
"narrative before this can be triaged. (deft #845)";
|
|
17
21
|
function resolveDeftRoot() {
|
|
18
22
|
if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
|
|
19
23
|
return resolve(process.env.DEFT_ROOT);
|
|
@@ -88,13 +92,24 @@ function defaultIssueIngest(deftRoot) {
|
|
|
88
92
|
/** Default dependency bundle for production CLI use. */
|
|
89
93
|
export function createDefaultDeps(projectRoot) {
|
|
90
94
|
const deftRoot = resolveDeftRoot();
|
|
91
|
-
|
|
95
|
+
const deps = {
|
|
92
96
|
candidatesLog: createCandidatesLog(projectRoot),
|
|
93
97
|
issueIngest: defaultIssueIngest(deftRoot),
|
|
94
98
|
scm: defaultScmRunner(),
|
|
95
99
|
nowIso: defaultNowIso,
|
|
96
100
|
stderr: (message) => process.stderr.write(`${message}\n`),
|
|
97
101
|
};
|
|
102
|
+
if (process.env.DEFT_TRIAGE_ACTIONS_PARITY === "1") {
|
|
103
|
+
return {
|
|
104
|
+
...deps,
|
|
105
|
+
scm: {
|
|
106
|
+
call() {
|
|
107
|
+
throw new UpstreamCloseError("gh disabled for parity");
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return deps;
|
|
98
113
|
}
|
|
99
114
|
function buildEntry(deps, decision, issueNumber, repo, actor, extras = {}) {
|
|
100
115
|
const entry = {
|
|
@@ -111,6 +126,16 @@ function buildEntry(deps, decision, issueNumber, repo, actor, extras = {}) {
|
|
|
111
126
|
function logPathFor(projectRoot) {
|
|
112
127
|
return resolveAuditLogPath(projectRoot);
|
|
113
128
|
}
|
|
129
|
+
function cacheRootFor(projectRoot) {
|
|
130
|
+
return join(projectRoot, ".deft-cache");
|
|
131
|
+
}
|
|
132
|
+
/** Return audit entries for ``issueNumber`` ordered ascending by timestamp. */
|
|
133
|
+
export function history(issueNumber, repo, _deps, options = {}) {
|
|
134
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
135
|
+
const entries = findByIssue(issueNumber, repo, logPathFor(projectRoot));
|
|
136
|
+
entries.sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp)));
|
|
137
|
+
return entries;
|
|
138
|
+
}
|
|
114
139
|
function isIdempotentRepeat(deps, issueNumber, repo, decision, projectRoot, linkedTo) {
|
|
115
140
|
if (!TERMINAL_DECISIONS.has(decision))
|
|
116
141
|
return null;
|
|
@@ -277,6 +302,72 @@ export function deferAction(issueNumber, repo, reason, deps, options = {}) {
|
|
|
277
302
|
const logPath = logPathFor(projectRoot);
|
|
278
303
|
return deps.candidatesLog.append(entry, { path: logPath });
|
|
279
304
|
}
|
|
305
|
+
/** Record a needs-ac audit entry and best-effort post an AC-request comment upstream. */
|
|
306
|
+
export function needsAc(issueNumber, repo, deps, options = {}) {
|
|
307
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
308
|
+
const body = options.comment ?? DEFAULT_NEEDS_AC_COMMENT;
|
|
309
|
+
const actor = resolveActor(options.actor);
|
|
310
|
+
const entry = buildEntry(deps, "needs-ac", issueNumber, repo, actor, { reason: body });
|
|
311
|
+
const logPath = logPathFor(projectRoot);
|
|
312
|
+
const decisionId = deps.candidatesLog.append(entry, { path: logPath });
|
|
313
|
+
const writeErr = deps.stderr ?? ((message) => process.stderr.write(`${message}\n`));
|
|
314
|
+
try {
|
|
315
|
+
runGh(deps, ["issue", "comment", String(issueNumber), "--repo", repo, "--body", body]);
|
|
316
|
+
}
|
|
317
|
+
catch (exc) {
|
|
318
|
+
writeErr(`triage_actions: needs-ac comment not posted for #${issueNumber} (${repo}): ${exc instanceof Error ? exc.message : String(exc)}`);
|
|
319
|
+
}
|
|
320
|
+
return decisionId;
|
|
321
|
+
}
|
|
322
|
+
/** Validate duplicate target in cache and record a mark-duplicate audit entry. */
|
|
323
|
+
export function markDuplicate(issueNumber, repo, ofN, deps, options = {}) {
|
|
324
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
325
|
+
if (ofN === issueNumber) {
|
|
326
|
+
throw new TriageError(`mark-duplicate target #${ofN} cannot equal source #${issueNumber}`);
|
|
327
|
+
}
|
|
328
|
+
const key = `${repo}/${ofN}`;
|
|
329
|
+
try {
|
|
330
|
+
cacheGet("github-issue", key, { allowStale: true, cacheRoot: cacheRootFor(projectRoot) });
|
|
331
|
+
}
|
|
332
|
+
catch (exc) {
|
|
333
|
+
if (exc instanceof CacheNotFoundError) {
|
|
334
|
+
throw new TriageError(`mark-duplicate target #${ofN} not found in cache for ${repo}: ${exc.message}`);
|
|
335
|
+
}
|
|
336
|
+
throw exc;
|
|
337
|
+
}
|
|
338
|
+
const prior = isIdempotentRepeat(deps, issueNumber, repo, "mark-duplicate", projectRoot, ofN);
|
|
339
|
+
if (prior !== null) {
|
|
340
|
+
return prior.decision_id;
|
|
341
|
+
}
|
|
342
|
+
const actor = resolveActor(options.actor);
|
|
343
|
+
const entry = buildEntry(deps, "mark-duplicate", issueNumber, repo, actor, { linked_to: ofN });
|
|
344
|
+
const logPath = logPathFor(projectRoot);
|
|
345
|
+
return deps.candidatesLog.append(entry, { path: logPath });
|
|
346
|
+
}
|
|
347
|
+
/** Return the latest decision for ``issueNumber`` in ``repo`` (null if none). */
|
|
348
|
+
export function status(issueNumber, repo, deps, options = {}) {
|
|
349
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
350
|
+
return deps.candidatesLog.latestDecision(issueNumber, repo, {
|
|
351
|
+
path: logPathFor(projectRoot),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/** Record a reset audit entry referencing the prior decision_id (history is never deleted). */
|
|
355
|
+
export function reset(issueNumber, repo, deps, options = {}) {
|
|
356
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
357
|
+
const logPath = logPathFor(projectRoot);
|
|
358
|
+
const prior = deps.candidatesLog.latestDecision(issueNumber, repo, { path: logPath });
|
|
359
|
+
if (prior === null) {
|
|
360
|
+
throw new TriageError(`cannot reset #${issueNumber}: no prior decision recorded for ${repo}`);
|
|
361
|
+
}
|
|
362
|
+
if (prior.decision === "reset") {
|
|
363
|
+
return prior.decision_id;
|
|
364
|
+
}
|
|
365
|
+
const actor = resolveActor(options.actor);
|
|
366
|
+
const entry = buildEntry(deps, "reset", issueNumber, repo, actor, {
|
|
367
|
+
prior_decision_id: prior.decision_id,
|
|
368
|
+
});
|
|
369
|
+
return deps.candidatesLog.append(entry, { path: logPath });
|
|
370
|
+
}
|
|
280
371
|
/** Format a decision entry for CLI ``status`` / ``history`` output. */
|
|
281
372
|
export function formatDecision(entry) {
|
|
282
373
|
if (entry === null) {
|
|
@@ -5,6 +5,7 @@ export { DEFAULT_WIP_CAP, PROJECT_DEFINITION_REL_PATH };
|
|
|
5
5
|
export declare const CACHE_DIR_NAME = ".deft-cache";
|
|
6
6
|
export declare const CACHE_SOURCE = "github-issue";
|
|
7
7
|
export declare const CANDIDATES_LOG_REL_PATH = "vbrief/.eval/candidates.jsonl";
|
|
8
|
+
export { latestDecisions, readAuditLog } from "../actions/candidates-log.js";
|
|
8
9
|
export declare const SUMMARY_HISTORY_REL_PATH = "vbrief/.eval/summary-history.jsonl";
|
|
9
10
|
export declare const SUMMARY_HISTORY_SCHEMA = "deft.triage.summary.v1";
|
|
10
11
|
export declare const EMPTY_CACHE_LINE = "[triage] cache empty -- run task triage:bootstrap";
|
|
@@ -38,9 +39,6 @@ export declare function utcIso(date?: Date): string;
|
|
|
38
39
|
export declare function isPosIntDirName(name: string): boolean;
|
|
39
40
|
/** Walk `<cache_root>/github-issue/<owner>/<repo>/<N>/` cache entries. */
|
|
40
41
|
export declare function iterCachedIssues(cacheRoot: string): Array<[string, number]>;
|
|
41
|
-
export declare function readAuditLog(logPath: string): Record<string, unknown>[];
|
|
42
|
-
/** Collapse audit-log entries to `{(repo, issue_number): decision}`. */
|
|
43
|
-
export declare function latestDecisions(entries: Iterable<Record<string, unknown>>): Map<string, string>;
|
|
44
42
|
/** Count filesystem-truth in-flight vBRIEFs (#1270). */
|
|
45
43
|
export declare function countFilesystemInFlight(projectRoot: string): number;
|
|
46
44
|
/** True iff `plan.policy.triageScope` is a non-empty list of dict rules. */
|
|
@@ -2,6 +2,7 @@ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from
|
|
|
2
2
|
import { join, resolve as pathResolve } from "node:path";
|
|
3
3
|
import { loadProjectDefinition, PROJECT_DEFINITION_REL_PATH } from "../../policy/resolve.js";
|
|
4
4
|
import { countVbriefWip, DEFAULT_WIP_CAP, resolveWipCap } from "../../policy/wip.js";
|
|
5
|
+
import { AUDIT_LOG_REL_PATH, latestDecisions, readAuditLog } from "../actions/candidates-log.js";
|
|
5
6
|
import { countReconcilable } from "./reconcilable.js";
|
|
6
7
|
import { computeScopeDriftTotal } from "./scope-drift.js";
|
|
7
8
|
// ---------------------------------------------------------------------------
|
|
@@ -11,7 +12,8 @@ export const MAX_LINE_CHARS = 120;
|
|
|
11
12
|
export { DEFAULT_WIP_CAP, PROJECT_DEFINITION_REL_PATH };
|
|
12
13
|
export const CACHE_DIR_NAME = ".deft-cache";
|
|
13
14
|
export const CACHE_SOURCE = "github-issue";
|
|
14
|
-
export const CANDIDATES_LOG_REL_PATH =
|
|
15
|
+
export const CANDIDATES_LOG_REL_PATH = AUDIT_LOG_REL_PATH;
|
|
16
|
+
export { latestDecisions, readAuditLog } from "../actions/candidates-log.js";
|
|
15
17
|
export const SUMMARY_HISTORY_REL_PATH = "vbrief/.eval/summary-history.jsonl";
|
|
16
18
|
export const SUMMARY_HISTORY_SCHEMA = "deft.triage.summary.v1";
|
|
17
19
|
export const EMPTY_CACHE_LINE = "[triage] cache empty -- run task triage:bootstrap";
|
|
@@ -83,62 +85,6 @@ export function iterCachedIssues(cacheRoot) {
|
|
|
83
85
|
}
|
|
84
86
|
return out;
|
|
85
87
|
}
|
|
86
|
-
export function readAuditLog(logPath) {
|
|
87
|
-
if (!existsSync(logPath)) {
|
|
88
|
-
return [];
|
|
89
|
-
}
|
|
90
|
-
let text;
|
|
91
|
-
try {
|
|
92
|
-
text = readFileSync(logPath, { encoding: "utf8" });
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
97
|
-
const out = [];
|
|
98
|
-
for (const raw of text.split("\n")) {
|
|
99
|
-
const stripped = raw.trim();
|
|
100
|
-
if (stripped.length === 0) {
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
const obj = JSON.parse(stripped);
|
|
105
|
-
if (typeof obj === "object" && obj !== null && !Array.isArray(obj)) {
|
|
106
|
-
out.push(obj);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
// tolerate malformed lines
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return out;
|
|
114
|
-
}
|
|
115
|
-
/** Collapse audit-log entries to `{(repo, issue_number): decision}`. */
|
|
116
|
-
export function latestDecisions(entries) {
|
|
117
|
-
const rows = [];
|
|
118
|
-
for (const entry of entries) {
|
|
119
|
-
const repo = entry.repo;
|
|
120
|
-
const issueNumber = entry.issue_number;
|
|
121
|
-
const decision = entry.decision;
|
|
122
|
-
const timestamp = entry.timestamp;
|
|
123
|
-
if (typeof repo !== "string" ||
|
|
124
|
-
typeof issueNumber !== "number" ||
|
|
125
|
-
!Number.isInteger(issueNumber) ||
|
|
126
|
-
typeof decision !== "string" ||
|
|
127
|
-
typeof timestamp !== "string") {
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
rows.push([timestamp, repo, issueNumber, decision]);
|
|
131
|
-
}
|
|
132
|
-
rows.sort((a, b) => a[0].localeCompare(b[0]));
|
|
133
|
-
const out = new Map();
|
|
134
|
-
for (const [_ts, repo, n, decision] of rows) {
|
|
135
|
-
out.set(`${repo}\0${n}`, decision);
|
|
136
|
-
}
|
|
137
|
-
return out;
|
|
138
|
-
}
|
|
139
|
-
function decisionKey(repo, issueNumber) {
|
|
140
|
-
return `${repo}\0${issueNumber}`;
|
|
141
|
-
}
|
|
142
88
|
// ---------------------------------------------------------------------------
|
|
143
89
|
// vBRIEF counters
|
|
144
90
|
// ---------------------------------------------------------------------------
|
|
@@ -206,6 +152,9 @@ export function isTriageScopeExplicitlyConfigured(projectRoot) {
|
|
|
206
152
|
export function resolveWipCapInt(projectRoot) {
|
|
207
153
|
return resolveWipCap(projectRoot).cap;
|
|
208
154
|
}
|
|
155
|
+
function decisionKey(repo, issueNumber) {
|
|
156
|
+
return `${repo}\0${issueNumber}`;
|
|
157
|
+
}
|
|
209
158
|
// ---------------------------------------------------------------------------
|
|
210
159
|
// compute / format / persist
|
|
211
160
|
// ---------------------------------------------------------------------------
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { AUDIT_LOG_REL_PATH, readAuditLog } from "../actions/candidates-log.js";
|
|
3
4
|
/** Lifecycle folders scanned for reconcilable vBRIEFs (#1468). */
|
|
4
5
|
export const BACKFILL_FOLDERS = ["proposed", "pending", "active"];
|
|
5
|
-
export const CANDIDATES_LOG_REL_PATH =
|
|
6
|
+
export const CANDIDATES_LOG_REL_PATH = AUDIT_LOG_REL_PATH;
|
|
6
7
|
function parseGithubIssueUri(uri) {
|
|
7
8
|
if (typeof uri !== "string" || uri.length === 0) {
|
|
8
9
|
return [null, null];
|
|
@@ -47,38 +48,8 @@ function extractIssueRef(data) {
|
|
|
47
48
|
}
|
|
48
49
|
function existingAuditRefs(auditPath) {
|
|
49
50
|
const seen = new Set();
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
let text;
|
|
54
|
-
try {
|
|
55
|
-
text = readFileSync(auditPath, { encoding: "utf8" });
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return seen;
|
|
59
|
-
}
|
|
60
|
-
for (const raw of text.split("\n")) {
|
|
61
|
-
const stripped = raw.trim();
|
|
62
|
-
if (stripped.length === 0) {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
const entry = JSON.parse(stripped);
|
|
67
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
const obj = entry;
|
|
71
|
-
const repo = obj.repo;
|
|
72
|
-
const issueNumber = obj.issue_number;
|
|
73
|
-
if (typeof repo === "string" &&
|
|
74
|
-
typeof issueNumber === "number" &&
|
|
75
|
-
Number.isInteger(issueNumber)) {
|
|
76
|
-
seen.add(`${repo}\0${issueNumber}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
// tolerate malformed lines
|
|
81
|
-
}
|
|
51
|
+
for (const entry of readAuditLog(auditPath)) {
|
|
52
|
+
seen.add(auditKey(entry.repo, entry.issue_number));
|
|
82
53
|
}
|
|
83
54
|
return seen;
|
|
84
55
|
}
|
|
@@ -15,6 +15,7 @@ export interface DefaultModeOptions {
|
|
|
15
15
|
readonly output?: (line: string) => void;
|
|
16
16
|
readonly writeHistory?: boolean;
|
|
17
17
|
readonly taskPrefix?: string | null;
|
|
18
|
+
readonly selfHealFn?: (projectRoot: string) => void;
|
|
18
19
|
}
|
|
19
20
|
/** Non-interactive default mode (#1309). */
|
|
20
21
|
export declare function runDefaultMode(projectRoot: string, options?: DefaultModeOptions): WelcomeOutcome;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
+
import { maybeSelfHealCache } from "../../cache/fetch.js";
|
|
2
3
|
import { FIRST_TIME_NUDGE, INCOMPLETE_NUDGE_TEMPLATE } from "./constants.js";
|
|
3
4
|
import { classifyOnboarding, detectPriorState } from "./prior-state.js";
|
|
4
5
|
import { emitOneliner } from "./summary.js";
|
|
@@ -29,6 +30,11 @@ export function runDefaultMode(projectRoot, options = {}) {
|
|
|
29
30
|
exitCode: 0,
|
|
30
31
|
bootstrapAction: null,
|
|
31
32
|
};
|
|
33
|
+
const heal = options.selfHealFn ??
|
|
34
|
+
((root) => {
|
|
35
|
+
maybeSelfHealCache(resolve(root));
|
|
36
|
+
});
|
|
37
|
+
heal(projectRoot);
|
|
32
38
|
emitOneliner(projectRoot, {
|
|
33
39
|
writeHistory: options.writeHistory !== false,
|
|
34
40
|
output: out,
|