@deftai/directive-core 0.65.0 → 0.66.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/architecture/sor-preflight.js +6 -4
- package/dist/capacity/backfill.js +5 -3
- package/dist/capacity/show.js +5 -3
- package/dist/category-b-namespace/index.d.ts +39 -0
- package/dist/category-b-namespace/index.js +120 -0
- package/dist/codebase/default-extractor.js +2 -1
- package/dist/codebase/map.js +2 -1
- package/dist/codebase/provider.js +4 -2
- package/dist/content-contracts/standards/_helpers.js +1 -1
- package/dist/doctor/checks.js +2 -1
- package/dist/doctor/doctor-state.js +2 -2
- package/dist/encoding/patterns.js +19 -0
- package/dist/encoding/scan.js +3 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/refresh.js +2 -1
- package/dist/init-deposit/scaffold.js +2 -1
- package/dist/install-upgrade/index.d.ts +2 -0
- package/dist/install-upgrade/index.js +31 -2
- package/dist/intake/issue-emit.d.ts +1 -1
- package/dist/intake/issue-emit.js +3 -2
- package/dist/intake/issue-ingest.d.ts +24 -0
- package/dist/intake/issue-ingest.js +113 -16
- package/dist/intake/reconcile-issues.js +11 -4
- package/dist/layout/index.d.ts +2 -0
- package/dist/layout/index.js +2 -0
- package/dist/layout/resolve.d.ts +87 -0
- package/dist/layout/resolve.js +159 -0
- package/dist/lifecycle/lifecycle-hygiene.d.ts +1 -1
- package/dist/lifecycle/lifecycle-hygiene.js +8 -5
- package/dist/orchestration/judgment-policy.js +4 -6
- package/dist/orchestration/verify-judgment-gates.d.ts +1 -0
- package/dist/orchestration/verify-judgment-gates.js +4 -2
- package/dist/policy/autonomy.js +4 -6
- package/dist/policy/capacity.js +4 -6
- package/dist/policy/decisions.d.ts +1 -0
- package/dist/policy/decisions.js +4 -2
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +3 -1
- package/dist/policy/plan-extensions.d.ts +35 -0
- package/dist/policy/plan-extensions.js +57 -0
- package/dist/policy/resolve.d.ts +6 -2
- package/dist/policy/resolve.js +18 -8
- package/dist/policy/wip.js +6 -4
- package/dist/preflight-cache/evaluate.js +8 -5
- package/dist/release/native-steps.js +4 -3
- package/dist/render/framework-commands.js +2 -1
- package/dist/render/prd-render.js +2 -2
- package/dist/render/project-render.js +23 -5
- package/dist/render/roadmap-render.js +36 -8
- package/dist/render/scope-outlook.js +3 -2
- package/dist/scope/audit-log.js +3 -2
- package/dist/scope/capacity-stamp.js +2 -1
- package/dist/scope/decompose.js +6 -4
- package/dist/scope/decomposed-refs.js +2 -1
- package/dist/scope/demote.js +5 -4
- package/dist/scope/project-context.js +2 -1
- package/dist/scope/project-definition-sync.js +4 -3
- package/dist/scope/transition.js +6 -2
- package/dist/scope/undo.js +2 -1
- package/dist/scope/vbrief-ref.js +5 -3
- package/dist/session/resume-conditions.js +2 -1
- package/dist/session/ritual-sentinel.js +2 -1
- package/dist/session/staleness.js +2 -1
- package/dist/slice/record.js +4 -4
- package/dist/spec-authority/resolver.js +5 -5
- package/dist/swarm/complete-cohort.js +5 -4
- package/dist/swarm/launch.js +6 -7
- package/dist/swarm/readiness.js +22 -20
- package/dist/swarm/subagent-backend.js +2 -5
- package/dist/swarm/verify-review-clean.js +2 -1
- package/dist/triage/actions/candidates-log.d.ts +3 -0
- package/dist/triage/actions/candidates-log.js +8 -2
- package/dist/triage/bootstrap/gitignore.js +22 -13
- package/dist/triage/bootstrap/index.js +7 -4
- package/dist/triage/bulk/index.js +2 -1
- package/dist/triage/classify/index.js +13 -11
- package/dist/triage/queue/audit.js +3 -3
- package/dist/triage/queue/cache.js +3 -2
- package/dist/triage/queue/ranking-labels.js +2 -1
- package/dist/triage/queue/scope-walk.js +8 -6
- package/dist/triage/reconcile/audit.js +2 -1
- package/dist/triage/reconcile/parse-uri.js +2 -1
- package/dist/triage/reconcile/reconcile.js +8 -7
- package/dist/triage/refresh/extract.js +4 -2
- package/dist/triage/refresh/refresh.js +3 -2
- package/dist/triage/scope/mutations-core.js +13 -9
- package/dist/triage/scope/renderers.js +5 -3
- package/dist/triage/scope/resolve.js +6 -5
- package/dist/triage/scope/validate.js +3 -6
- package/dist/triage/scope-drift/add-ignore.js +4 -2
- package/dist/triage/scope-drift/scope-rules.js +3 -2
- package/dist/triage/subscribe/index.js +13 -7
- package/dist/triage/summary/index.js +7 -5
- package/dist/triage/summary/reconcilable.js +6 -4
- package/dist/triage/summary/scope-drift.js +2 -1
- package/dist/triage/welcome/prior-state.js +9 -9
- package/dist/triage/welcome/summary.js +9 -8
- package/dist/triage/welcome/writers.js +12 -10
- package/dist/validate-content/capacity-policy.js +5 -4
- package/dist/validate-content/capacity-show.js +5 -3
- package/dist/validate-content/filename.d.ts +1 -1
- package/dist/validate-content/filename.js +5 -4
- package/dist/validate-content/validate-strategy-output.js +14 -5
- package/dist/vbrief-build/build.js +1 -1
- package/dist/vbrief-build/constants.js +7 -0
- package/dist/vbrief-build/speckit.js +3 -2
- package/dist/vbrief-reconcile/graph.js +3 -2
- package/dist/vbrief-reconcile/labels.js +4 -3
- package/dist/vbrief-reconcile/reconciliation.js +5 -1
- package/dist/vbrief-reconcile/swarm-deps.js +6 -5
- package/dist/vbrief-reconcile/umbrellas.d.ts +1 -1
- package/dist/vbrief-reconcile/umbrellas.js +9 -7
- package/dist/vbrief-validate/conformance.d.ts +1 -2
- package/dist/vbrief-validate/conformance.js +29 -14
- package/dist/vbrief-validate/constants.d.ts +18 -1
- package/dist/vbrief-validate/constants.js +32 -3
- package/dist/vbrief-validate/epic-links.js +3 -2
- package/dist/vbrief-validate/filename.js +5 -3
- package/dist/vbrief-validate/index.d.ts +3 -1
- package/dist/vbrief-validate/index.js +2 -1
- package/dist/vbrief-validate/main.js +17 -6
- package/dist/vbrief-validate/origin.js +1 -1
- package/dist/vbrief-validate/paths.js +2 -1
- package/dist/vbrief-validate/plan-hooks.js +4 -3
- package/dist/vbrief-validate/registry-status.d.ts +1 -0
- package/dist/vbrief-validate/registry-status.js +3 -1
- package/dist/vbrief-validate/roundtrip.d.ts +36 -0
- package/dist/vbrief-validate/roundtrip.js +168 -0
- package/dist/vbrief-validate/schema.d.ts +1 -1
- package/dist/vbrief-validate/schema.js +62 -19
- package/dist/vbrief-validate/validate-all.js +12 -4
- package/dist/verify-source/code-structure-validate.js +8 -4
- package/dist/verify-source/contract-drift.d.ts +7 -3
- package/dist/verify-source/contract-drift.js +40 -22
- package/dist/xbrief-migrate/constants.d.ts +13 -0
- package/dist/xbrief-migrate/constants.js +14 -0
- package/dist/xbrief-migrate/detect.d.ts +11 -0
- package/dist/xbrief-migrate/detect.js +81 -0
- package/dist/xbrief-migrate/drift-gate.d.ts +56 -0
- package/dist/xbrief-migrate/drift-gate.js +205 -0
- package/dist/xbrief-migrate/index.d.ts +7 -0
- package/dist/xbrief-migrate/index.js +7 -0
- package/dist/xbrief-migrate/migrate-project.d.ts +33 -0
- package/dist/xbrief-migrate/migrate-project.js +189 -0
- package/dist/xbrief-migrate/signpost.d.ts +5 -0
- package/dist/xbrief-migrate/signpost.js +19 -0
- package/dist/xbrief-migrate/transforms.d.ts +46 -0
- package/dist/xbrief-migrate/transforms.js +182 -0
- package/dist/xbrief-migrate/version.d.ts +8 -0
- package/dist/xbrief-migrate/version.js +25 -0
- package/package.json +11 -3
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { type ScmCallFn } from "./reconcile-issues.js";
|
|
2
2
|
export declare const INGEST_STATUSES: readonly ["proposed", "pending", "active"];
|
|
3
3
|
export type IngestStatus = (typeof INGEST_STATUSES)[number];
|
|
4
|
+
/** GitHub issue comment thread entry (REST `repos/.../issues/N/comments`). */
|
|
5
|
+
export interface IssueComment {
|
|
6
|
+
readonly id?: number;
|
|
7
|
+
readonly body?: string;
|
|
8
|
+
readonly user?: {
|
|
9
|
+
readonly login?: string;
|
|
10
|
+
};
|
|
11
|
+
readonly created_at?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Enriched on issues after `fetchIssue` when the comment thread is non-empty (#2143). */
|
|
14
|
+
export declare const ISSUE_COMMENT_THREAD_KEY: "issueCommentThread";
|
|
4
15
|
export declare function bodyControlCharacterLabels(body: string): string[];
|
|
5
16
|
export declare function warnBodyControlCharacters(number: number, body: string): void;
|
|
6
17
|
export declare function extractPlanItems(body: string): Record<string, string>[];
|
|
@@ -8,6 +19,9 @@ export declare function extractAcSectionItems(text: string): Record<string, stri
|
|
|
8
19
|
export declare function extractCrossRefs(body: string, repoUrl: string, exclude?: ReadonlySet<number>): Record<string, string>[];
|
|
9
20
|
export declare function provenanceIssueNumber(data: Record<string, unknown>): number | null;
|
|
10
21
|
export declare function scanProvenanceRefs(vbriefDir: string): Map<number, string[]>;
|
|
22
|
+
export declare function composeOverviewWithComments(body: string, comments: readonly IssueComment[]): string;
|
|
23
|
+
export declare function issueCommentThread(issue: Record<string, unknown>): IssueComment[];
|
|
24
|
+
export declare function issueCommentsAlreadyFetched(issue: Record<string, unknown>): boolean;
|
|
11
25
|
export declare function buildIssueVbrief(issue: Record<string, unknown>, status: IngestStatus, repoUrl: string): [Record<string, unknown>, string];
|
|
12
26
|
export declare function targetFilename(number: number, title: string): string;
|
|
13
27
|
export interface FetchIssueOptions {
|
|
@@ -16,6 +30,10 @@ export interface FetchIssueOptions {
|
|
|
16
30
|
readonly scmCall?: ScmCallFn;
|
|
17
31
|
}
|
|
18
32
|
export declare function fetchFromCache(repo: string, number: number, options?: FetchIssueOptions): Record<string, unknown> | null;
|
|
33
|
+
export declare function fetchIssueComments(repo: string, number: number, options?: FetchIssueOptions): IssueComment[];
|
|
34
|
+
export declare function attachIssueCommentThread(issue: Record<string, unknown>, comments: readonly IssueComment[]): Record<string, unknown>;
|
|
35
|
+
export declare function repoSlugFromUrl(repoUrl: string): string | null;
|
|
36
|
+
export declare function enrichIssueWithComments(issue: Record<string, unknown>, repoUrl: string, options?: FetchIssueOptions): Record<string, unknown>;
|
|
19
37
|
export declare function fetchSingleIssue(repo: string, number: number, options?: FetchIssueOptions): Record<string, unknown> | null;
|
|
20
38
|
export declare function fetchIssue(repo: string, number: number, options?: FetchIssueOptions): Record<string, unknown> | null;
|
|
21
39
|
export type IngestResult = "created" | "dryrun" | "duplicate";
|
|
@@ -25,6 +43,9 @@ export declare function ingestOne(issue: Record<string, unknown>, options: {
|
|
|
25
43
|
repoUrl: string;
|
|
26
44
|
dryRun?: boolean;
|
|
27
45
|
existingRefs?: Map<number, string[]>;
|
|
46
|
+
scmCall?: ScmCallFn;
|
|
47
|
+
cwd?: string | null;
|
|
48
|
+
cacheRoot?: string | null;
|
|
28
49
|
}): [IngestResult, string | null, string];
|
|
29
50
|
export declare function ingestBulk(issues: Record<string, unknown>[], options: {
|
|
30
51
|
vbriefDir: string;
|
|
@@ -32,6 +53,9 @@ export declare function ingestBulk(issues: Record<string, unknown>[], options: {
|
|
|
32
53
|
repoUrl: string;
|
|
33
54
|
label?: string | null;
|
|
34
55
|
dryRun?: boolean;
|
|
56
|
+
scmCall?: ScmCallFn;
|
|
57
|
+
cwd?: string | null;
|
|
58
|
+
cacheRoot?: string | null;
|
|
35
59
|
}): Record<string, string[] | number>;
|
|
36
60
|
export declare function resolveRepoUrl(repo: string): string;
|
|
37
61
|
export interface IssueIngestCliArgs {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { cacheGet } from "../cache/operations.js";
|
|
4
|
+
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
4
5
|
import { call } from "../scm/call.js";
|
|
5
6
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
6
7
|
import { resolveProjectRepo } from "../slice/project-context.js";
|
|
@@ -9,6 +10,8 @@ import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
|
|
|
9
10
|
import { findAcHeading, parseCheckboxItems, parseListItems, sliceAcSection, stripCodeBlocks, stripFencedCodeBlocks, } from "./markdown-scanners.js";
|
|
10
11
|
import { detectRepo, extractReferencesFromVbrief, fetchOpenIssues, GITHUB_ISSUE_REF_TYPES, LIFECYCLE_FOLDERS, parseIssueNumber, } from "./reconcile-issues.js";
|
|
11
12
|
export const INGEST_STATUSES = ["proposed", "pending", "active"];
|
|
13
|
+
/** Enriched on issues after `fetchIssue` when the comment thread is non-empty (#2143). */
|
|
14
|
+
export const ISSUE_COMMENT_THREAD_KEY = "issueCommentThread";
|
|
12
15
|
const STATUS_MAP = {
|
|
13
16
|
proposed: ["proposed", "proposed"],
|
|
14
17
|
pending: ["pending", "pending"],
|
|
@@ -23,9 +26,9 @@ const CONTROL_CHAR_LABELS = {
|
|
|
23
26
|
const ORIGIN_URL_PATTERN = /https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/issues\/(\d+)/;
|
|
24
27
|
const ORIGIN_BARE_PATTERN = /issue\s*#(\d+)/i;
|
|
25
28
|
const CROSS_REF_PATTERNS = [
|
|
26
|
-
["x-
|
|
27
|
-
["x-
|
|
28
|
-
["x-
|
|
29
|
+
["x-xbrief/closes", /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)\b/i],
|
|
30
|
+
["x-xbrief/blocks", /\bblocked[\s-]+by\s+#(\d+)\b/i],
|
|
31
|
+
["x-xbrief/refs", /\b(?:refs?|references?|see\s+also|related(?:\s+to)?)\s+#(\d+)\b/i],
|
|
29
32
|
];
|
|
30
33
|
function hasNonIndentationPrefix(text, index) {
|
|
31
34
|
const lineStart = text.lastIndexOf("\n", index) + 1;
|
|
@@ -148,7 +151,7 @@ export function scanProvenanceRefs(vbriefDir) {
|
|
|
148
151
|
continue;
|
|
149
152
|
}
|
|
150
153
|
const files = readdirSync(folderPath)
|
|
151
|
-
.filter((f) => f
|
|
154
|
+
.filter((f) => hasArtifactSuffix(f))
|
|
152
155
|
.sort();
|
|
153
156
|
for (const filename of files) {
|
|
154
157
|
let data;
|
|
@@ -191,6 +194,42 @@ export function scanProvenanceRefs(vbriefDir) {
|
|
|
191
194
|
}
|
|
192
195
|
return issueToVbriefs;
|
|
193
196
|
}
|
|
197
|
+
export function composeOverviewWithComments(body, comments) {
|
|
198
|
+
if (comments.length === 0) {
|
|
199
|
+
return body;
|
|
200
|
+
}
|
|
201
|
+
const parts = [];
|
|
202
|
+
if (body.length > 0) {
|
|
203
|
+
parts.push(body);
|
|
204
|
+
parts.push("");
|
|
205
|
+
parts.push("---");
|
|
206
|
+
parts.push("");
|
|
207
|
+
}
|
|
208
|
+
parts.push("## Issue comment thread");
|
|
209
|
+
parts.push("");
|
|
210
|
+
parts.push("_The issue body is the original write-up; maintainer comments below may supersede it. Read the full thread before building a dispatch envelope (#2143)._");
|
|
211
|
+
parts.push("");
|
|
212
|
+
for (const comment of comments) {
|
|
213
|
+
const author = comment.user?.login ?? "unknown";
|
|
214
|
+
const when = comment.created_at ?? "";
|
|
215
|
+
const header = when.length > 0 ? `### Comment by @${author} (${when})` : `### Comment by @${author}`;
|
|
216
|
+
parts.push(header);
|
|
217
|
+
parts.push("");
|
|
218
|
+
parts.push(typeof comment.body === "string" ? comment.body : "");
|
|
219
|
+
parts.push("");
|
|
220
|
+
}
|
|
221
|
+
return parts.join("\n").trimEnd();
|
|
222
|
+
}
|
|
223
|
+
export function issueCommentThread(issue) {
|
|
224
|
+
const raw = issue[ISSUE_COMMENT_THREAD_KEY];
|
|
225
|
+
if (!Array.isArray(raw)) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
return raw.filter((entry) => entry !== null && typeof entry === "object");
|
|
229
|
+
}
|
|
230
|
+
export function issueCommentsAlreadyFetched(issue) {
|
|
231
|
+
return Object.hasOwn(issue, ISSUE_COMMENT_THREAD_KEY);
|
|
232
|
+
}
|
|
194
233
|
export function buildIssueVbrief(issue, status, repoUrl) {
|
|
195
234
|
const number = Number(issue.number);
|
|
196
235
|
const title = (typeof issue.title === "string" && issue.title.length > 0
|
|
@@ -200,6 +239,8 @@ export function buildIssueVbrief(issue, status, repoUrl) {
|
|
|
200
239
|
(repoUrl.length > 0 ? `${repoUrl}/issues/${number}` : "");
|
|
201
240
|
const bodyRaw = issue.body;
|
|
202
241
|
const bodyStr = typeof bodyRaw === "string" && bodyRaw.length > 0 ? bodyRaw : "";
|
|
242
|
+
const commentThread = issueCommentThread(issue);
|
|
243
|
+
const overviewSource = commentThread.length > 0 ? composeOverviewWithComments(bodyStr, commentThread) : bodyStr;
|
|
203
244
|
const labelsRaw = issue.labels;
|
|
204
245
|
const labelNames = [];
|
|
205
246
|
if (Array.isArray(labelsRaw)) {
|
|
@@ -220,9 +261,9 @@ export function buildIssueVbrief(issue, status, repoUrl) {
|
|
|
220
261
|
Description: title,
|
|
221
262
|
Origin: url.length > 0 ? `Ingested from ${url}` : `Ingested from issue #${number}`,
|
|
222
263
|
};
|
|
223
|
-
if (
|
|
224
|
-
warnBodyControlCharacters(number,
|
|
225
|
-
narratives.Overview =
|
|
264
|
+
if (overviewSource.length > 0) {
|
|
265
|
+
warnBodyControlCharacters(number, overviewSource);
|
|
266
|
+
narratives.Overview = overviewSource;
|
|
226
267
|
}
|
|
227
268
|
if (labelNames.length > 0) {
|
|
228
269
|
narratives.Labels = labelNames.join(", ");
|
|
@@ -241,12 +282,12 @@ export function buildIssueVbrief(issue, status, repoUrl) {
|
|
|
241
282
|
const references = [
|
|
242
283
|
{
|
|
243
284
|
uri: url,
|
|
244
|
-
type: "x-
|
|
285
|
+
type: "x-xbrief/github-issue",
|
|
245
286
|
title: `Issue #${number}: ${title}`,
|
|
246
287
|
},
|
|
247
288
|
];
|
|
248
|
-
if (
|
|
249
|
-
references.push(...extractCrossRefs(
|
|
289
|
+
if (overviewSource.length > 0 && repoUrl.length > 0) {
|
|
290
|
+
references.push(...extractCrossRefs(overviewSource, repoUrl, new Set([number])));
|
|
250
291
|
}
|
|
251
292
|
plan.references = references;
|
|
252
293
|
}
|
|
@@ -283,6 +324,47 @@ export function fetchFromCache(repo, number, options = {}) {
|
|
|
283
324
|
return null;
|
|
284
325
|
}
|
|
285
326
|
}
|
|
327
|
+
export function fetchIssueComments(repo, number, options = {}) {
|
|
328
|
+
const scmCall = options.scmCall ?? call;
|
|
329
|
+
let result;
|
|
330
|
+
try {
|
|
331
|
+
result = scmCall("github-issue", "api", [`repos/${repo}/issues/${number}/comments`], {
|
|
332
|
+
timeout: 30,
|
|
333
|
+
cwd: options.cwd ?? undefined,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
return [];
|
|
338
|
+
}
|
|
339
|
+
if (result.returncode !== 0) {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
const parsed = JSON.parse(result.stdout);
|
|
344
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
export function attachIssueCommentThread(issue, comments) {
|
|
351
|
+
return { ...issue, [ISSUE_COMMENT_THREAD_KEY]: [...comments] };
|
|
352
|
+
}
|
|
353
|
+
export function repoSlugFromUrl(repoUrl) {
|
|
354
|
+
const match = /github\.com\/([^/\s]+\/[^/\s]+)/.exec(repoUrl);
|
|
355
|
+
return match?.[1] ?? null;
|
|
356
|
+
}
|
|
357
|
+
export function enrichIssueWithComments(issue, repoUrl, options = {}) {
|
|
358
|
+
if (issueCommentsAlreadyFetched(issue)) {
|
|
359
|
+
return issue;
|
|
360
|
+
}
|
|
361
|
+
const repo = repoSlugFromUrl(repoUrl);
|
|
362
|
+
const number = Number(issue.number);
|
|
363
|
+
if (repo === null || !Number.isFinite(number)) {
|
|
364
|
+
return issue;
|
|
365
|
+
}
|
|
366
|
+
return attachIssueCommentThread(issue, fetchIssueComments(repo, number, options));
|
|
367
|
+
}
|
|
286
368
|
export function fetchSingleIssue(repo, number, options = {}) {
|
|
287
369
|
const scmCall = options.scmCall ?? call;
|
|
288
370
|
let result;
|
|
@@ -315,9 +397,15 @@ export function fetchSingleIssue(repo, number, options = {}) {
|
|
|
315
397
|
export function fetchIssue(repo, number, options = {}) {
|
|
316
398
|
const live = fetchSingleIssue(repo, number, options);
|
|
317
399
|
if (live !== null) {
|
|
318
|
-
|
|
400
|
+
const comments = fetchIssueComments(repo, number, options);
|
|
401
|
+
return attachIssueCommentThread(live, comments);
|
|
402
|
+
}
|
|
403
|
+
const cached = fetchFromCache(repo, number, options);
|
|
404
|
+
if (cached === null) {
|
|
405
|
+
return null;
|
|
319
406
|
}
|
|
320
|
-
|
|
407
|
+
const comments = fetchIssueComments(repo, number, options);
|
|
408
|
+
return attachIssueCommentThread(cached, comments);
|
|
321
409
|
}
|
|
322
410
|
export function ingestOne(issue, options) {
|
|
323
411
|
const number = Number(issue.number);
|
|
@@ -330,7 +418,12 @@ export function ingestOne(issue, options) {
|
|
|
330
418
|
`#${number} already ingested at ${existing}`,
|
|
331
419
|
];
|
|
332
420
|
}
|
|
333
|
-
const
|
|
421
|
+
const enriched = enrichIssueWithComments(issue, options.repoUrl, {
|
|
422
|
+
scmCall: options.scmCall,
|
|
423
|
+
cwd: options.cwd,
|
|
424
|
+
cacheRoot: options.cacheRoot,
|
|
425
|
+
});
|
|
426
|
+
const [vbrief, folder] = buildIssueVbrief(enriched, options.status, options.repoUrl);
|
|
334
427
|
const filename = targetFilename(number, String(issue.title ?? ""));
|
|
335
428
|
const target = join(options.vbriefDir, folder, filename);
|
|
336
429
|
if (options.dryRun) {
|
|
@@ -402,9 +495,11 @@ export function issueIngestMain(args) {
|
|
|
402
495
|
process.stderr.write("Error: Use either a single issue number OR --all, not both\n");
|
|
403
496
|
return 2;
|
|
404
497
|
}
|
|
405
|
-
const vbriefDir = resolve(args.vbriefDir ?? "./vbrief");
|
|
406
|
-
mkdirSync(vbriefDir, { recursive: true });
|
|
407
498
|
const projectRoot = resolveProjectRoot(args.projectRoot ?? undefined);
|
|
499
|
+
const vbriefDir = args.vbriefDir
|
|
500
|
+
? resolve(args.vbriefDir)
|
|
501
|
+
: resolveLifecycleRoot(args.projectRoot ? resolve(args.projectRoot) : resolve("."));
|
|
502
|
+
mkdirSync(vbriefDir, { recursive: true });
|
|
408
503
|
let repo = resolveProjectRepo(args.repo ?? undefined, projectRoot);
|
|
409
504
|
if (repo === null) {
|
|
410
505
|
repo = detectRepo();
|
|
@@ -426,6 +521,7 @@ export function issueIngestMain(args) {
|
|
|
426
521
|
repoUrl,
|
|
427
522
|
label: args.label,
|
|
428
523
|
dryRun: args.dryRun,
|
|
524
|
+
cwd: projectRoot,
|
|
429
525
|
});
|
|
430
526
|
const created = summary.created;
|
|
431
527
|
const duplicate = summary.duplicate;
|
|
@@ -451,13 +547,14 @@ export function issueIngestMain(args) {
|
|
|
451
547
|
status,
|
|
452
548
|
repoUrl,
|
|
453
549
|
dryRun: args.dryRun,
|
|
550
|
+
cwd: projectRoot,
|
|
454
551
|
});
|
|
455
552
|
process.stdout.write(`${msg}\n`);
|
|
456
553
|
return result === "duplicate" ? 1 : 0;
|
|
457
554
|
}
|
|
458
555
|
export function ingestSingleForAccept(n, repo, options = {}) {
|
|
459
556
|
const root = resolve(options.projectRoot ?? process.cwd());
|
|
460
|
-
const vbriefDir =
|
|
557
|
+
const vbriefDir = resolveLifecycleRoot(root);
|
|
461
558
|
mkdirSync(vbriefDir, { recursive: true });
|
|
462
559
|
const repoUrl = resolveRepoUrl(repo);
|
|
463
560
|
const issue = fetchIssue(repo, n, {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
+
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
4
5
|
import { call } from "../scm/call.js";
|
|
5
6
|
import { updateDecomposedChildBackReferences } from "../scope/decomposed-refs.js";
|
|
6
7
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
@@ -13,7 +14,11 @@ export const LIFECYCLE_FOLDERS = [
|
|
|
13
14
|
"cancelled",
|
|
14
15
|
];
|
|
15
16
|
export const TERMINAL_LIFECYCLE_FOLDERS = new Set(["completed", "cancelled"]);
|
|
16
|
-
export const GITHUB_ISSUE_REF_TYPES = new Set([
|
|
17
|
+
export const GITHUB_ISSUE_REF_TYPES = new Set([
|
|
18
|
+
"github-issue",
|
|
19
|
+
"x-vbrief/github-issue",
|
|
20
|
+
"x-xbrief/github-issue",
|
|
21
|
+
]);
|
|
17
22
|
export const CANCELLED_STATE_REASONS = new Set(["NOT_PLANNED", "DUPLICATE"]);
|
|
18
23
|
const ISSUE_URL_PATTERN = /https:\/\/github\.com\/([^/\s]+)\/([^/\s]+)\/issues\/(\d+)/;
|
|
19
24
|
const ISSUE_ID_PATTERN = /^#(\d+)$/;
|
|
@@ -106,7 +111,7 @@ export function scanVbriefDir(vbriefDir) {
|
|
|
106
111
|
continue;
|
|
107
112
|
}
|
|
108
113
|
const files = readdirSync(folderPath)
|
|
109
|
-
.filter((f) => f
|
|
114
|
+
.filter((f) => hasArtifactSuffix(f))
|
|
110
115
|
.sort();
|
|
111
116
|
for (const filename of files) {
|
|
112
117
|
let data;
|
|
@@ -407,7 +412,7 @@ export function scanLifecycleAnchors(vbriefDir) {
|
|
|
407
412
|
continue;
|
|
408
413
|
}
|
|
409
414
|
const files = readdirSync(folderPath)
|
|
410
|
-
.filter((f) => f
|
|
415
|
+
.filter((f) => hasArtifactSuffix(f))
|
|
411
416
|
.sort();
|
|
412
417
|
for (const filename of files) {
|
|
413
418
|
let data;
|
|
@@ -667,7 +672,9 @@ export function detectRepo() {
|
|
|
667
672
|
return null;
|
|
668
673
|
}
|
|
669
674
|
export function reconcileMain(args) {
|
|
670
|
-
const vbriefDir =
|
|
675
|
+
const vbriefDir = args.vbriefDir
|
|
676
|
+
? resolve(args.vbriefDir)
|
|
677
|
+
: resolveLifecycleRoot(args.projectRoot ? resolve(args.projectRoot) : resolve("."));
|
|
671
678
|
try {
|
|
672
679
|
if (!statSync(vbriefDir).isDirectory()) {
|
|
673
680
|
process.stderr.write(`Error: vbrief directory not found: ${vbriefDir}\n`);
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { ARTIFACT_SUFFIXES, hasArtifactSuffix, isLifecycleArtifactPath, LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, LIFECYCLE_DIR_NAMES, type LifecycleLayout, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, projectDefinitionFilename, projectDefinitionRelPath, resolveAuditDir, resolveAuditPath, resolveEvalDir, resolveEvalPath, resolveLifecycleFolder, resolveLifecycleLayout, resolveLifecycleRoot, resolveProjectDefinitionPath, resolveSpecArtifactPath, specArtifactRelPath, stripArtifactSuffix, } from "./resolve.js";
|
|
2
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { ARTIFACT_SUFFIXES, hasArtifactSuffix, isLifecycleArtifactPath, LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, LIFECYCLE_DIR_NAMES, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, projectDefinitionFilename, projectDefinitionRelPath, resolveAuditDir, resolveAuditPath, resolveEvalDir, resolveEvalPath, resolveLifecycleFolder, resolveLifecycleLayout, resolveLifecycleRoot, resolveProjectDefinitionPath, resolveSpecArtifactPath, specArtifactRelPath, stripArtifactSuffix, } from "./resolve.js";
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY } from "../xbrief-migrate/constants.js";
|
|
2
|
+
export { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, };
|
|
3
|
+
/**
|
|
4
|
+
* Lifecycle directory names recognized by the layout-aware resolver, in
|
|
5
|
+
* preference order (migrated `xbrief` first, legacy `vbrief` second).
|
|
6
|
+
*/
|
|
7
|
+
export declare const LIFECYCLE_DIR_NAMES: readonly ["xbrief", "vbrief"];
|
|
8
|
+
/**
|
|
9
|
+
* Artifact filename suffixes recognized by the layout-aware resolver, in
|
|
10
|
+
* preference order (`.xbrief.json` first, `.vbrief.json` second).
|
|
11
|
+
*/
|
|
12
|
+
export declare const ARTIFACT_SUFFIXES: readonly [".xbrief.json", ".vbrief.json"];
|
|
13
|
+
/** The on-disk lifecycle layout an engine call site should resolve against (#2109). */
|
|
14
|
+
export interface LifecycleLayout {
|
|
15
|
+
/** The lifecycle directory name -- `xbrief` when migrated, else `vbrief`. */
|
|
16
|
+
readonly artifactDir: typeof MIGRATED_ARTIFACT_DIR | typeof LEGACY_ARTIFACT_DIR;
|
|
17
|
+
/** The artifact filename suffix matching `artifactDir`. */
|
|
18
|
+
readonly artifactSuffix: typeof MIGRATED_ARTIFACT_SUFFIX | typeof LEGACY_ARTIFACT_SUFFIX;
|
|
19
|
+
/** The `*BRIEFInfo` root key matching `artifactDir`. */
|
|
20
|
+
readonly infoRootKey: typeof MIGRATED_INFO_ROOT_KEY | typeof LEGACY_INFO_ROOT_KEY;
|
|
21
|
+
/** Absolute path to the resolved lifecycle root (`<projectRoot>/<artifactDir>`). */
|
|
22
|
+
readonly root: string;
|
|
23
|
+
/** True when the resolved layout is the migrated `xbrief` layout. */
|
|
24
|
+
readonly migrated: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** True when `name` ends with any recognized artifact suffix (.xbrief.json or .vbrief.json). */
|
|
27
|
+
export declare function hasArtifactSuffix(name: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Strip whichever recognized artifact suffix `name` ends with. Returns `name`
|
|
30
|
+
* unchanged when it carries no recognized suffix.
|
|
31
|
+
*/
|
|
32
|
+
export declare function stripArtifactSuffix(name: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* True when a POSIX-style path is a lifecycle artifact under either layout
|
|
35
|
+
* root (`xbrief/` or `vbrief/`) carrying either artifact suffix.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isLifecycleArtifactPath(posix: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the active lifecycle layout for `projectRoot` (#2109 part 1).
|
|
40
|
+
*
|
|
41
|
+
* Prefers the migrated `xbrief/` layout only when BOTH the `xbrief/` directory
|
|
42
|
+
* exists AND it contains at least one `.xbrief.json` artifact; otherwise falls
|
|
43
|
+
* back to the legacy `vbrief/` layout. With only `vbrief/` present (today's
|
|
44
|
+
* repo) the result is the legacy layout, so existing behavior is unchanged.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveLifecycleLayout(projectRoot: string): LifecycleLayout;
|
|
47
|
+
/** Convenience accessor for the absolute resolved lifecycle root directory. */
|
|
48
|
+
export declare function resolveLifecycleRoot(projectRoot: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* Absolute path to a lifecycle folder under the resolved layout root
|
|
51
|
+
* (e.g. `<root>/<xbrief|vbrief>/active`). Layout-aware (#2109 part 2a).
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveLifecycleFolder(projectRoot: string, folder: string): string;
|
|
54
|
+
/** Absolute path to the resolved lifecycle `.eval` cache directory (#2109 part 2a). */
|
|
55
|
+
export declare function resolveEvalDir(projectRoot: string): string;
|
|
56
|
+
/** Absolute path to a file or subpath under the resolved `.eval` cache directory. */
|
|
57
|
+
export declare function resolveEvalPath(projectRoot: string, ...segments: string[]): string;
|
|
58
|
+
/** Absolute path to the resolved lifecycle `.audit` directory (#2109 part 2a). */
|
|
59
|
+
export declare function resolveAuditDir(projectRoot: string): string;
|
|
60
|
+
/** Absolute path to a file or subpath under the resolved `.audit` directory. */
|
|
61
|
+
export declare function resolveAuditPath(projectRoot: string, ...segments: string[]): string;
|
|
62
|
+
/**
|
|
63
|
+
* The PROJECT-DEFINITION artifact filename for the resolved layout
|
|
64
|
+
* (`PROJECT-DEFINITION.xbrief.json` when migrated, else `.vbrief.json`).
|
|
65
|
+
*/
|
|
66
|
+
export declare function projectDefinitionFilename(projectRoot: string): string;
|
|
67
|
+
/** Absolute path to the resolved PROJECT-DEFINITION artifact (#2109 part 2a). */
|
|
68
|
+
export declare function resolveProjectDefinitionPath(projectRoot: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* POSIX-style display path to the resolved PROJECT-DEFINITION artifact relative
|
|
71
|
+
* to the project root (e.g. `vbrief/PROJECT-DEFINITION.vbrief.json`).
|
|
72
|
+
*/
|
|
73
|
+
export declare function projectDefinitionRelPath(projectRoot: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* Absolute path to the resolved specification artifact (#2132).
|
|
76
|
+
*
|
|
77
|
+
* Prefers `xbrief/specification.xbrief.json` on a migrated tree; falls back to
|
|
78
|
+
* `vbrief/specification.vbrief.json` on an unmigrated tree. Consistent with the
|
|
79
|
+
* layout resolver used by the #2109 Part-2a call-site sweep.
|
|
80
|
+
*/
|
|
81
|
+
export declare function resolveSpecArtifactPath(projectRoot: string): string;
|
|
82
|
+
/**
|
|
83
|
+
* POSIX-style display path to the resolved specification artifact relative to
|
|
84
|
+
* the project root (e.g. `xbrief/specification.xbrief.json`).
|
|
85
|
+
*/
|
|
86
|
+
export declare function specArtifactRelPath(projectRoot: string): string;
|
|
87
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, } from "../xbrief-migrate/constants.js";
|
|
4
|
+
export { LEGACY_ARTIFACT_DIR, LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, };
|
|
5
|
+
/**
|
|
6
|
+
* Lifecycle directory names recognized by the layout-aware resolver, in
|
|
7
|
+
* preference order (migrated `xbrief` first, legacy `vbrief` second).
|
|
8
|
+
*/
|
|
9
|
+
export const LIFECYCLE_DIR_NAMES = [MIGRATED_ARTIFACT_DIR, LEGACY_ARTIFACT_DIR];
|
|
10
|
+
/**
|
|
11
|
+
* Artifact filename suffixes recognized by the layout-aware resolver, in
|
|
12
|
+
* preference order (`.xbrief.json` first, `.vbrief.json` second).
|
|
13
|
+
*/
|
|
14
|
+
export const ARTIFACT_SUFFIXES = [MIGRATED_ARTIFACT_SUFFIX, LEGACY_ARTIFACT_SUFFIX];
|
|
15
|
+
function isDirectory(path) {
|
|
16
|
+
try {
|
|
17
|
+
return statSync(path).isDirectory();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** True when `name` ends with any recognized artifact suffix (.xbrief.json or .vbrief.json). */
|
|
24
|
+
export function hasArtifactSuffix(name) {
|
|
25
|
+
return ARTIFACT_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Strip whichever recognized artifact suffix `name` ends with. Returns `name`
|
|
29
|
+
* unchanged when it carries no recognized suffix.
|
|
30
|
+
*/
|
|
31
|
+
export function stripArtifactSuffix(name) {
|
|
32
|
+
for (const suffix of ARTIFACT_SUFFIXES) {
|
|
33
|
+
if (name.endsWith(suffix)) {
|
|
34
|
+
return name.slice(0, -suffix.length);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return name;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* True when a POSIX-style path is a lifecycle artifact under either layout
|
|
41
|
+
* root (`xbrief/` or `vbrief/`) carrying either artifact suffix.
|
|
42
|
+
*/
|
|
43
|
+
export function isLifecycleArtifactPath(posix) {
|
|
44
|
+
const underLifecycleDir = LIFECYCLE_DIR_NAMES.some((dir) => posix.startsWith(`${dir}/`));
|
|
45
|
+
return underLifecycleDir && hasArtifactSuffix(posix);
|
|
46
|
+
}
|
|
47
|
+
/** Walk `root` looking for any `.xbrief.json` file (bounded iterative scan). */
|
|
48
|
+
function containsMigratedArtifact(root) {
|
|
49
|
+
const stack = [root];
|
|
50
|
+
while (stack.length > 0) {
|
|
51
|
+
const dir = stack.pop();
|
|
52
|
+
if (dir === undefined || !isDirectory(dir)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
stack.push(join(dir, entry.name));
|
|
58
|
+
}
|
|
59
|
+
else if (entry.isFile() && entry.name.endsWith(MIGRATED_ARTIFACT_SUFFIX)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the active lifecycle layout for `projectRoot` (#2109 part 1).
|
|
68
|
+
*
|
|
69
|
+
* Prefers the migrated `xbrief/` layout only when BOTH the `xbrief/` directory
|
|
70
|
+
* exists AND it contains at least one `.xbrief.json` artifact; otherwise falls
|
|
71
|
+
* back to the legacy `vbrief/` layout. With only `vbrief/` present (today's
|
|
72
|
+
* repo) the result is the legacy layout, so existing behavior is unchanged.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveLifecycleLayout(projectRoot) {
|
|
75
|
+
const migratedRoot = join(projectRoot, MIGRATED_ARTIFACT_DIR);
|
|
76
|
+
if (isDirectory(migratedRoot) && containsMigratedArtifact(migratedRoot)) {
|
|
77
|
+
return {
|
|
78
|
+
artifactDir: MIGRATED_ARTIFACT_DIR,
|
|
79
|
+
artifactSuffix: MIGRATED_ARTIFACT_SUFFIX,
|
|
80
|
+
infoRootKey: MIGRATED_INFO_ROOT_KEY,
|
|
81
|
+
root: migratedRoot,
|
|
82
|
+
migrated: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
artifactDir: LEGACY_ARTIFACT_DIR,
|
|
87
|
+
artifactSuffix: LEGACY_ARTIFACT_SUFFIX,
|
|
88
|
+
infoRootKey: LEGACY_INFO_ROOT_KEY,
|
|
89
|
+
root: join(projectRoot, LEGACY_ARTIFACT_DIR),
|
|
90
|
+
migrated: false,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Convenience accessor for the absolute resolved lifecycle root directory. */
|
|
94
|
+
export function resolveLifecycleRoot(projectRoot) {
|
|
95
|
+
return resolveLifecycleLayout(projectRoot).root;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Absolute path to a lifecycle folder under the resolved layout root
|
|
99
|
+
* (e.g. `<root>/<xbrief|vbrief>/active`). Layout-aware (#2109 part 2a).
|
|
100
|
+
*/
|
|
101
|
+
export function resolveLifecycleFolder(projectRoot, folder) {
|
|
102
|
+
return join(resolveLifecycleRoot(projectRoot), folder);
|
|
103
|
+
}
|
|
104
|
+
/** Absolute path to the resolved lifecycle `.eval` cache directory (#2109 part 2a). */
|
|
105
|
+
export function resolveEvalDir(projectRoot) {
|
|
106
|
+
return join(resolveLifecycleRoot(projectRoot), ".eval");
|
|
107
|
+
}
|
|
108
|
+
/** Absolute path to a file or subpath under the resolved `.eval` cache directory. */
|
|
109
|
+
export function resolveEvalPath(projectRoot, ...segments) {
|
|
110
|
+
return join(resolveEvalDir(projectRoot), ...segments);
|
|
111
|
+
}
|
|
112
|
+
/** Absolute path to the resolved lifecycle `.audit` directory (#2109 part 2a). */
|
|
113
|
+
export function resolveAuditDir(projectRoot) {
|
|
114
|
+
return join(resolveLifecycleRoot(projectRoot), ".audit");
|
|
115
|
+
}
|
|
116
|
+
/** Absolute path to a file or subpath under the resolved `.audit` directory. */
|
|
117
|
+
export function resolveAuditPath(projectRoot, ...segments) {
|
|
118
|
+
return join(resolveAuditDir(projectRoot), ...segments);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The PROJECT-DEFINITION artifact filename for the resolved layout
|
|
122
|
+
* (`PROJECT-DEFINITION.xbrief.json` when migrated, else `.vbrief.json`).
|
|
123
|
+
*/
|
|
124
|
+
export function projectDefinitionFilename(projectRoot) {
|
|
125
|
+
return `PROJECT-DEFINITION${resolveLifecycleLayout(projectRoot).artifactSuffix}`;
|
|
126
|
+
}
|
|
127
|
+
/** Absolute path to the resolved PROJECT-DEFINITION artifact (#2109 part 2a). */
|
|
128
|
+
export function resolveProjectDefinitionPath(projectRoot) {
|
|
129
|
+
const layout = resolveLifecycleLayout(projectRoot);
|
|
130
|
+
return join(layout.root, `PROJECT-DEFINITION${layout.artifactSuffix}`);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* POSIX-style display path to the resolved PROJECT-DEFINITION artifact relative
|
|
134
|
+
* to the project root (e.g. `vbrief/PROJECT-DEFINITION.vbrief.json`).
|
|
135
|
+
*/
|
|
136
|
+
export function projectDefinitionRelPath(projectRoot) {
|
|
137
|
+
const layout = resolveLifecycleLayout(projectRoot);
|
|
138
|
+
return `${layout.artifactDir}/PROJECT-DEFINITION${layout.artifactSuffix}`;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Absolute path to the resolved specification artifact (#2132).
|
|
142
|
+
*
|
|
143
|
+
* Prefers `xbrief/specification.xbrief.json` on a migrated tree; falls back to
|
|
144
|
+
* `vbrief/specification.vbrief.json` on an unmigrated tree. Consistent with the
|
|
145
|
+
* layout resolver used by the #2109 Part-2a call-site sweep.
|
|
146
|
+
*/
|
|
147
|
+
export function resolveSpecArtifactPath(projectRoot) {
|
|
148
|
+
const layout = resolveLifecycleLayout(projectRoot);
|
|
149
|
+
return join(layout.root, `specification${layout.artifactSuffix}`);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* POSIX-style display path to the resolved specification artifact relative to
|
|
153
|
+
* the project root (e.g. `xbrief/specification.xbrief.json`).
|
|
154
|
+
*/
|
|
155
|
+
export function specArtifactRelPath(projectRoot) {
|
|
156
|
+
const layout = resolveLifecycleLayout(projectRoot);
|
|
157
|
+
return `${layout.artifactDir}/specification${layout.artifactSuffix}`;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -9,7 +9,7 @@ export declare const LIFECYCLE_FOLDERS: readonly ["proposed", "pending", "active
|
|
|
9
9
|
/** plan.status values that make an epic terminal. */
|
|
10
10
|
export declare const TERMINAL_STATUSES: Set<string>;
|
|
11
11
|
/** Child reference type that marks an epic as decomposed. */
|
|
12
|
-
export declare const CHILD_REF_TYPE = "x-
|
|
12
|
+
export declare const CHILD_REF_TYPE = "x-xbrief/plan";
|
|
13
13
|
/** Durable tech-debt acceptance ledger relative path segments. */
|
|
14
14
|
export declare const TECH_DEBT_LEDGER_RELPATH: readonly ["vbrief", ".audit", "epic-tech-debt-accepted.jsonl"];
|
|
15
15
|
/** Session-start nudge tiers (rate-of-harm ranking). */
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, } from "node:fs";
|
|
2
2
|
import { basename, join, resolve } from "node:path";
|
|
3
|
+
import { referenceTypeMatches } from "@deftai/directive-types";
|
|
3
4
|
import { computeReport } from "../capacity/show.js";
|
|
5
|
+
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
4
6
|
import { resolveCapacityAllocation } from "../policy/capacity.js";
|
|
7
|
+
import { readPlanPolicy } from "../policy/plan-extensions.js";
|
|
5
8
|
import { loadProjectDefinition } from "../policy/resolve.js";
|
|
6
9
|
/** Default dormancy (days) past which a partially-completed epic is stranded. */
|
|
7
10
|
export const EPIC_STRANDED_DAYS_DEFAULT = 30;
|
|
@@ -20,7 +23,7 @@ export const LIFECYCLE_FOLDERS = [
|
|
|
20
23
|
/** plan.status values that make an epic terminal. */
|
|
21
24
|
export const TERMINAL_STATUSES = new Set(["completed", "cancelled", "failed"]);
|
|
22
25
|
/** Child reference type that marks an epic as decomposed. */
|
|
23
|
-
export const CHILD_REF_TYPE = "x-
|
|
26
|
+
export const CHILD_REF_TYPE = "x-xbrief/plan";
|
|
24
27
|
/** Durable tech-debt acceptance ledger relative path segments. */
|
|
25
28
|
export const TECH_DEBT_LEDGER_RELPATH = [
|
|
26
29
|
"vbrief",
|
|
@@ -48,7 +51,7 @@ export function resolveEpicThresholds(projectRoot) {
|
|
|
48
51
|
if (data !== null && typeof data === "object") {
|
|
49
52
|
const plan = data.plan;
|
|
50
53
|
if (typeof plan === "object" && plan !== null && !Array.isArray(plan)) {
|
|
51
|
-
const pol = plan
|
|
54
|
+
const pol = readPlanPolicy(plan);
|
|
52
55
|
if (typeof pol === "object" && pol !== null && !Array.isArray(pol)) {
|
|
53
56
|
const cap = pol.capacityAllocation;
|
|
54
57
|
if (typeof cap === "object" && cap !== null && !Array.isArray(cap)) {
|
|
@@ -125,7 +128,7 @@ function childRefNames(plan) {
|
|
|
125
128
|
continue;
|
|
126
129
|
}
|
|
127
130
|
const rec = ref;
|
|
128
|
-
if (rec.type
|
|
131
|
+
if (!referenceTypeMatches(String(rec.type ?? ""), "plan")) {
|
|
129
132
|
continue;
|
|
130
133
|
}
|
|
131
134
|
const uri = rec.uri;
|
|
@@ -137,14 +140,14 @@ function childRefNames(plan) {
|
|
|
137
140
|
}
|
|
138
141
|
function iterVbriefs(projectRoot) {
|
|
139
142
|
const out = [];
|
|
140
|
-
const vroot =
|
|
143
|
+
const vroot = resolveLifecycleRoot(projectRoot);
|
|
141
144
|
for (const folder of LIFECYCLE_FOLDERS) {
|
|
142
145
|
const fdir = join(vroot, folder);
|
|
143
146
|
if (!existsSync(fdir)) {
|
|
144
147
|
continue;
|
|
145
148
|
}
|
|
146
149
|
const children = readdirSync(fdir)
|
|
147
|
-
.filter((name) => name
|
|
150
|
+
.filter((name) => hasArtifactSuffix(name))
|
|
148
151
|
.sort();
|
|
149
152
|
for (const name of children) {
|
|
150
153
|
const child = join(fdir, name);
|