@deftai/directive-core 0.106.0 → 0.107.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.
Files changed (100) hide show
  1. package/dist/check/gate-lists.js +2 -0
  2. package/dist/design-critique/exclusive-chip.d.ts +33 -0
  3. package/dist/design-critique/exclusive-chip.js +68 -0
  4. package/dist/design-critique/parent-audit.d.ts +59 -0
  5. package/dist/design-critique/parent-audit.js +121 -0
  6. package/dist/doctor/checks.d.ts +6 -0
  7. package/dist/doctor/checks.js +35 -0
  8. package/dist/hooks/dest-form.d.ts +46 -0
  9. package/dist/hooks/dest-form.js +575 -0
  10. package/dist/hooks/dispatcher.d.ts +17 -1
  11. package/dist/hooks/dispatcher.js +82 -11
  12. package/dist/hooks/index.d.ts +1 -0
  13. package/dist/hooks/index.js +1 -0
  14. package/dist/intake/github-auth-modes-cli.js +14 -2
  15. package/dist/intake/github-auth-modes.d.ts +54 -9
  16. package/dist/intake/github-auth-modes.js +326 -123
  17. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
  18. package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
  19. package/dist/lifecycle/completed-write-guard.d.ts +48 -0
  20. package/dist/lifecycle/completed-write-guard.js +373 -0
  21. package/dist/lifecycle/index.d.ts +1 -0
  22. package/dist/lifecycle/index.js +1 -0
  23. package/dist/literal-acceptance/capture.d.ts +16 -0
  24. package/dist/literal-acceptance/capture.js +75 -7
  25. package/dist/literal-acceptance/evaluate.js +25 -3
  26. package/dist/literal-acceptance/index.d.ts +2 -2
  27. package/dist/literal-acceptance/index.js +2 -2
  28. package/dist/literal-acceptance/run.js +9 -3
  29. package/dist/literal-acceptance/types.d.ts +6 -0
  30. package/dist/literal-acceptance/types.js +6 -0
  31. package/dist/policy/runtime-authority.d.ts +18 -0
  32. package/dist/policy/runtime-authority.js +11 -0
  33. package/dist/policy/write-fence.js +3 -0
  34. package/dist/preflight-cache/evaluate.js +9 -3
  35. package/dist/product-first-done-gate/evaluate.js +36 -5
  36. package/dist/scm/design-critique-chip.d.ts +37 -0
  37. package/dist/scm/design-critique-chip.js +171 -0
  38. package/dist/scm/index.d.ts +1 -0
  39. package/dist/scm/index.js +1 -0
  40. package/dist/scm/main.d.ts +3 -0
  41. package/dist/scm/main.js +15 -1
  42. package/dist/scm/readiness-cli.d.ts +2 -0
  43. package/dist/scm/readiness-cli.js +42 -1
  44. package/dist/scm/readiness.d.ts +2 -1
  45. package/dist/scm/readiness.js +7 -1
  46. package/dist/scope/decompose.js +2 -1
  47. package/dist/scope/delivery-evidence.d.ts +27 -1
  48. package/dist/scope/index.d.ts +1 -0
  49. package/dist/scope/index.js +1 -0
  50. package/dist/scope/lifecycle-write.d.ts +24 -0
  51. package/dist/scope/lifecycle-write.js +64 -0
  52. package/dist/scope/main.js +3 -1
  53. package/dist/scope/transition.js +90 -11
  54. package/dist/scope-provenance/evaluate.js +5 -1
  55. package/dist/session/git.d.ts +18 -0
  56. package/dist/session/git.js +81 -1
  57. package/dist/session/occupancy.d.ts +2 -1
  58. package/dist/session/occupancy.js +11 -4
  59. package/dist/session/session-start.d.ts +12 -0
  60. package/dist/session/session-start.js +17 -0
  61. package/dist/session/verify-session-ritual.d.ts +22 -3
  62. package/dist/session/verify-session-ritual.js +29 -4
  63. package/dist/swarm/launch.d.ts +69 -0
  64. package/dist/swarm/launch.js +191 -1
  65. package/dist/triage/actions/index.d.ts +4 -0
  66. package/dist/triage/actions/index.js +6 -0
  67. package/dist/triage/evaluate/evaluate.d.ts +11 -0
  68. package/dist/triage/evaluate/evaluate.js +169 -0
  69. package/dist/triage/evaluate/github.d.ts +16 -0
  70. package/dist/triage/evaluate/github.js +153 -0
  71. package/dist/triage/evaluate/index.d.ts +9 -0
  72. package/dist/triage/evaluate/index.js +8 -0
  73. package/dist/triage/evaluate/paths.d.ts +7 -0
  74. package/dist/triage/evaluate/paths.js +25 -0
  75. package/dist/triage/evaluate/sink.d.ts +5 -0
  76. package/dist/triage/evaluate/sink.js +52 -0
  77. package/dist/triage/evaluate/types.d.ts +93 -0
  78. package/dist/triage/evaluate/types.js +13 -0
  79. package/dist/triage/evaluate/validity.d.ts +8 -0
  80. package/dist/triage/evaluate/validity.js +87 -0
  81. package/dist/triage/evaluate/value.d.ts +12 -0
  82. package/dist/triage/evaluate/value.js +33 -0
  83. package/dist/triage/evaluate/wip-census.d.ts +5 -0
  84. package/dist/triage/evaluate/wip-census.js +39 -0
  85. package/dist/triage/evaluate/worktrees.d.ts +8 -0
  86. package/dist/triage/evaluate/worktrees.js +68 -0
  87. package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
  88. package/dist/triage/evaluate/xbrief-refs.js +60 -0
  89. package/dist/triage/help/registry-data.d.ts +21 -7
  90. package/dist/triage/help/registry-data.js +53 -5
  91. package/dist/triage/index.d.ts +1 -0
  92. package/dist/triage/index.js +1 -0
  93. package/dist/value/feedback-file.d.ts +17 -2
  94. package/dist/value/feedback-file.js +80 -6
  95. package/dist/vbrief-reconcile/labels.d.ts +1 -0
  96. package/dist/vbrief-reconcile/labels.js +30 -0
  97. package/dist/vbrief-validation/story-quality.d.ts +7 -0
  98. package/dist/vbrief-validation/story-quality.js +8 -1
  99. package/dist/xbrief-migrate/agents-header.js +69 -7
  100. package/package.json +3 -3
@@ -0,0 +1,153 @@
1
+ import { GhRestError, restIssueListPaginated, restIssueView, runGhApi, splitRepo, } from "../../scm/gh-rest.js";
2
+ const DUPLICATE_RE = /duplicate of\s+#(\d+)/iu;
3
+ const MENTION_RE = /(?:#|issues\/)(\d+)\b/gu;
4
+ function labelsOf(raw) {
5
+ const labels = raw.labels;
6
+ if (!Array.isArray(labels)) {
7
+ return [];
8
+ }
9
+ const out = [];
10
+ for (const item of labels) {
11
+ if (typeof item === "string") {
12
+ out.push(item);
13
+ continue;
14
+ }
15
+ if (item !== null && typeof item === "object" && "name" in item) {
16
+ const name = item.name;
17
+ if (typeof name === "string") {
18
+ out.push(name);
19
+ }
20
+ }
21
+ }
22
+ return out;
23
+ }
24
+ function duplicateOf(title, body, labels) {
25
+ const match = DUPLICATE_RE.exec(`${title}\n${body}`);
26
+ if (match?.[1] !== undefined) {
27
+ return Number.parseInt(match[1], 10);
28
+ }
29
+ if (labels.some((label) => label.toLowerCase().includes("duplicate"))) {
30
+ const hash = /#(\d+)/u.exec(title);
31
+ if (hash?.[1] !== undefined) {
32
+ return Number.parseInt(hash[1], 10);
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ export function snapshotFromIssuePayload(raw) {
38
+ const number = typeof raw.number === "number" ? raw.number : Number.parseInt(String(raw.number), 10);
39
+ const state = raw.state === "closed" ? "closed" : "open";
40
+ const title = typeof raw.title === "string" ? raw.title : "";
41
+ const body = typeof raw.body === "string" ? raw.body : "";
42
+ const labels = labelsOf(raw);
43
+ const htmlUrl = typeof raw.html_url === "string" ? raw.html_url : null;
44
+ return {
45
+ number,
46
+ state,
47
+ title,
48
+ body,
49
+ labels,
50
+ htmlUrl,
51
+ pullRequest: raw.pull_request !== undefined && raw.pull_request !== null,
52
+ duplicateOf: duplicateOf(title, body, labels),
53
+ };
54
+ }
55
+ function mentionsFrom(text) {
56
+ const found = new Set();
57
+ for (const match of text.matchAll(MENTION_RE)) {
58
+ if (match[1] !== undefined) {
59
+ found.add(Number.parseInt(match[1], 10));
60
+ }
61
+ }
62
+ return [...found];
63
+ }
64
+ export function snapshotFromPullPayload(raw) {
65
+ const number = typeof raw.number === "number" ? raw.number : Number.parseInt(String(raw.number), 10);
66
+ const title = typeof raw.title === "string" ? raw.title : "";
67
+ const body = typeof raw.body === "string" ? raw.body : "";
68
+ return {
69
+ number,
70
+ title,
71
+ body,
72
+ htmlUrl: typeof raw.html_url === "string" ? raw.html_url : null,
73
+ mentions: mentionsFrom(`${title}\n${body}`),
74
+ };
75
+ }
76
+ const OPEN_PULLS_PER_PAGE = 100;
77
+ const OPEN_PULLS_MAX_PAGES = 50;
78
+ function parsePullPage(stdout) {
79
+ const parsed = JSON.parse(stdout.trim() || "[]");
80
+ if (!Array.isArray(parsed)) {
81
+ return [];
82
+ }
83
+ return parsed
84
+ .filter((item) => item !== null && typeof item === "object")
85
+ .map((item) => snapshotFromPullPayload(item));
86
+ }
87
+ function fetchPullPage(runner, endpoint, page) {
88
+ const result = runner([
89
+ endpoint,
90
+ "--method",
91
+ "GET",
92
+ "--raw-field",
93
+ "state=open",
94
+ "--raw-field",
95
+ `per_page=${OPEN_PULLS_PER_PAGE}`,
96
+ "--raw-field",
97
+ `page=${page}`,
98
+ ]);
99
+ if (result.returncode !== 0) {
100
+ throw new Error(`GET ${endpoint} failed: ${result.stderr.trim() || "no stderr"}`);
101
+ }
102
+ return parsePullPage(result.stdout);
103
+ }
104
+ function listOpenPulls(repo, seams) {
105
+ const [owner, name] = splitRepo(repo);
106
+ const endpoint = `repos/${owner}/${name}/pulls`;
107
+ const runner = seams.runGhApiFn ?? runGhApi;
108
+ const out = [];
109
+ for (let page = 1; page <= OPEN_PULLS_MAX_PAGES; page += 1) {
110
+ const pageItems = fetchPullPage(runner, endpoint, page);
111
+ out.push(...pageItems);
112
+ if (pageItems.length < OPEN_PULLS_PER_PAGE) {
113
+ return out;
114
+ }
115
+ }
116
+ // Full last collected page: probe one more page to distinguish exactly-at-cap
117
+ // from a truncated census. Do not collect past the cap.
118
+ const overflow = fetchPullPage(runner, endpoint, OPEN_PULLS_MAX_PAGES + 1);
119
+ if (overflow.length === 0) {
120
+ return out;
121
+ }
122
+ throw new GhRestError({
123
+ stderr: `open-pull census truncated: last page ${OPEN_PULLS_MAX_PAGES} was full (${OPEN_PULLS_PER_PAGE} items) and page ${OPEN_PULLS_MAX_PAGES + 1} has more; later PRs are omitted`,
124
+ exitCode: 0,
125
+ endpoint,
126
+ payload: null,
127
+ hint: "census is incomplete; fail the evaluation rather than treating a truncated list as complete",
128
+ });
129
+ }
130
+ /** GET-only GitHub census. Parent-owned. Never POST/PATCH/PUT/DELETE. */
131
+ export function collectGithubCensus(repo, issues, seams = {}) {
132
+ const views = {};
133
+ for (const n of issues) {
134
+ const raw = restIssueView(repo, n, { runGhApiFn: seams.runGhApiFn });
135
+ views[n] = snapshotFromIssuePayload(raw);
136
+ }
137
+ const openIssues = restIssueListPaginated(repo, { state: "open", excludePulls: true, perPage: 100 }, { runGhApiFn: seams.runGhApiFn }).map((raw) => snapshotFromIssuePayload(raw));
138
+ const openPulls = listOpenPulls(repo, seams);
139
+ return { issues: views, openIssues, openPulls };
140
+ }
141
+ export function defaultGithubReader(seams = {}) {
142
+ return {
143
+ viewIssue: (repo, n) => snapshotFromIssuePayload(restIssueView(repo, n, { runGhApiFn: seams.runGhApiFn })),
144
+ listOpenIssues: (repo) => restIssueListPaginated(repo, { state: "open", excludePulls: true, perPage: 100 }, {
145
+ runGhApiFn: seams.runGhApiFn,
146
+ }).map((raw) => snapshotFromIssuePayload(raw)),
147
+ listOpenPulls: (repo) => listOpenPulls(repo, seams),
148
+ };
149
+ }
150
+ export function pullsMentioning(pulls, issue) {
151
+ return pulls.filter((pull) => pull.mentions.includes(issue) || pull.number === issue);
152
+ }
153
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1,9 @@
1
+ export { EvaluateError, evaluateIssues, renderEvaluateText } from "./evaluate.js";
2
+ export { evaluatorWorktreePath, sha12Of, sinkDir } from "./paths.js";
3
+ export type { EvaluateOptions, EvaluateResult, GithubReader, IssueEvalVerdict, ValidityState, ValueAdvice, WipCensus, } from "./types.js";
4
+ export { CRITIQUE_RECOMMEND_FIELD, DEFAULT_CONCURRENCY, RESERVED_CLEARANCE_RE } from "./types.js";
5
+ export { evaluateValidity } from "./validity.js";
6
+ export { buildValueAdvice, formatValueField, ReservedClearanceError } from "./value.js";
7
+ export { collectWipCensus } from "./wip-census.js";
8
+ export { addEvaluatorWorktree, removeEvaluatorWorktree } from "./worktrees.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,8 @@
1
+ export { EvaluateError, evaluateIssues, renderEvaluateText } from "./evaluate.js";
2
+ export { evaluatorWorktreePath, sha12Of, sinkDir } from "./paths.js";
3
+ export { CRITIQUE_RECOMMEND_FIELD, DEFAULT_CONCURRENCY, RESERVED_CLEARANCE_RE } from "./types.js";
4
+ export { evaluateValidity } from "./validity.js";
5
+ export { buildValueAdvice, formatValueField, ReservedClearanceError } from "./value.js";
6
+ export { collectWipCensus } from "./wip-census.js";
7
+ export { addEvaluatorWorktree, removeEvaluatorWorktree } from "./worktrees.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ export declare function sha12Of(fullSha: string): string;
2
+ export declare function issueEvalScratchRoot(projectRoot: string): string;
3
+ export declare function sinkDir(projectRoot: string, sha12: string, invocationId: string): string;
4
+ export declare function sinkVerdictPath(projectRoot: string, sha12: string, invocationId: string, issue: number): string;
5
+ export declare function sinkManifestPath(projectRoot: string, sha12: string, invocationId: string): string;
6
+ export declare function evaluatorWorktreePath(projectRoot: string, issue: number, invocationId: string): string;
7
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { join, resolve } from "node:path";
2
+ import { SHA12_LENGTH } from "./types.js";
3
+ export function sha12Of(fullSha) {
4
+ const trimmed = fullSha.trim().toLowerCase();
5
+ if (!/^[0-9a-f]{12,40}$/u.test(trimmed)) {
6
+ throw new Error(`origin/master SHA is not a git object id: ${fullSha.trim()}`);
7
+ }
8
+ return trimmed.slice(0, SHA12_LENGTH);
9
+ }
10
+ export function issueEvalScratchRoot(projectRoot) {
11
+ return join(resolve(projectRoot), ".deft-scratch", "issue-eval");
12
+ }
13
+ export function sinkDir(projectRoot, sha12, invocationId) {
14
+ return join(issueEvalScratchRoot(projectRoot), sha12, invocationId);
15
+ }
16
+ export function sinkVerdictPath(projectRoot, sha12, invocationId, issue) {
17
+ return join(sinkDir(projectRoot, sha12, invocationId), `issue-${issue}.json`);
18
+ }
19
+ export function sinkManifestPath(projectRoot, sha12, invocationId) {
20
+ return join(sinkDir(projectRoot, sha12, invocationId), "manifest.json");
21
+ }
22
+ export function evaluatorWorktreePath(projectRoot, issue, invocationId) {
23
+ return join(resolve(projectRoot), ".deft-scratch", "worktrees", `issue-eval-${issue}-${invocationId}`).replace(/\\/g, "/");
24
+ }
25
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1,5 @@
1
+ import type { EvaluateResult } from "./types.js";
2
+ export declare function writeInvocationSink(projectRoot: string, result: EvaluateResult): string;
3
+ export declare function gcStaleSha12Dirs(projectRoot: string, currentSha12: string): string[];
4
+ export declare function teardownSink(projectRoot: string, sha12: string, invocationId: string): void;
5
+ //# sourceMappingURL=sink.d.ts.map
@@ -0,0 +1,52 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { containedRemove, containedWrite } from "../../fs/contained-write.js";
4
+ import { issueEvalScratchRoot, sinkDir, sinkManifestPath, sinkVerdictPath } from "./paths.js";
5
+ import { assertNoReservedClearance } from "./value.js";
6
+ export function writeInvocationSink(projectRoot, result) {
7
+ const root = resolve(projectRoot);
8
+ const serialized = JSON.stringify(result, null, 2);
9
+ assertNoReservedClearance(serialized, "verdict sink");
10
+ containedWrite({
11
+ root,
12
+ target: sinkManifestPath(projectRoot, result.sha12, result.invocationId),
13
+ data: `${serialized}\n`,
14
+ mode: "replace",
15
+ });
16
+ for (const verdict of result.verdicts) {
17
+ const body = JSON.stringify(verdict, null, 2);
18
+ assertNoReservedClearance(body, `verdict issue-${verdict.issue}`);
19
+ containedWrite({
20
+ root,
21
+ target: sinkVerdictPath(projectRoot, result.sha12, result.invocationId, verdict.issue),
22
+ data: `${body}\n`,
23
+ mode: "replace",
24
+ });
25
+ }
26
+ return sinkDir(projectRoot, result.sha12, result.invocationId);
27
+ }
28
+ export function gcStaleSha12Dirs(projectRoot, currentSha12) {
29
+ const root = resolve(projectRoot);
30
+ const scratch = issueEvalScratchRoot(projectRoot);
31
+ if (!existsSync(scratch)) {
32
+ return [];
33
+ }
34
+ const removed = [];
35
+ for (const name of readdirSync(scratch)) {
36
+ if (name === currentSha12) {
37
+ continue;
38
+ }
39
+ const target = `${scratch.replace(/\\/g, "/")}/${name}`;
40
+ containedRemove({ root, target, recursive: true });
41
+ removed.push(name);
42
+ }
43
+ return removed;
44
+ }
45
+ export function teardownSink(projectRoot, sha12, invocationId) {
46
+ const dir = sinkDir(projectRoot, sha12, invocationId);
47
+ if (!existsSync(dir)) {
48
+ return;
49
+ }
50
+ containedRemove({ root: resolve(projectRoot), target: dir, recursive: true });
51
+ }
52
+ //# sourceMappingURL=sink.js.map
@@ -0,0 +1,93 @@
1
+ /** Stage A issue-eval types (#3648). */
2
+ export declare const DEFAULT_CONCURRENCY = 4;
3
+ export declare const ORIGIN_MASTER = "origin/master";
4
+ export declare const SHA12_LENGTH = 12;
5
+ export declare const CRITIQUE_RECOMMEND_FIELD = "critique-recommend";
6
+ export declare const RESERVED_CLEARANCE_RE: RegExp;
7
+ export declare const VALIDITY_STATES: readonly ["still-open", "partial", "likely-shipped", "needs-re-scope"];
8
+ export type ValidityState = (typeof VALIDITY_STATES)[number];
9
+ export interface ValidityVerdict {
10
+ readonly state: ValidityState;
11
+ readonly evidence: string;
12
+ readonly worktreePath: string;
13
+ readonly sessionStartReadOnly: true;
14
+ }
15
+ export interface WipHit {
16
+ readonly kind: "active-xbrief" | "pending-xbrief" | "plan-sequence";
17
+ readonly path: string;
18
+ readonly issue: number;
19
+ }
20
+ export interface WipCensus {
21
+ readonly active: readonly WipHit[];
22
+ readonly pending: readonly WipHit[];
23
+ readonly planSequence: readonly WipHit[];
24
+ }
25
+ export interface GithubIssueSnapshot {
26
+ readonly number: number;
27
+ readonly state: "open" | "closed";
28
+ readonly title: string;
29
+ readonly body: string;
30
+ readonly labels: readonly string[];
31
+ readonly htmlUrl: string | null;
32
+ readonly pullRequest: boolean;
33
+ readonly duplicateOf: number | null;
34
+ }
35
+ export interface GithubPullSnapshot {
36
+ readonly number: number;
37
+ readonly title: string;
38
+ readonly body: string;
39
+ readonly htmlUrl: string | null;
40
+ readonly mentions: readonly number[];
41
+ }
42
+ export interface GithubCensus {
43
+ readonly issues: Readonly<Record<number, GithubIssueSnapshot>>;
44
+ readonly openIssues: readonly GithubIssueSnapshot[];
45
+ readonly openPulls: readonly GithubPullSnapshot[];
46
+ }
47
+ export interface ValueAdvice {
48
+ readonly [CRITIQUE_RECOMMEND_FIELD]: boolean;
49
+ readonly reason: string;
50
+ }
51
+ export interface IssueEvalVerdict {
52
+ readonly issue: number;
53
+ readonly sha12: string;
54
+ readonly invocationId: string;
55
+ readonly validity: ValidityVerdict | null;
56
+ readonly wip: readonly WipHit[];
57
+ readonly github: GithubIssueSnapshot | null;
58
+ readonly openPulls: readonly GithubPullSnapshot[];
59
+ readonly duplicates: readonly number[];
60
+ readonly value: ValueAdvice;
61
+ readonly error: string | null;
62
+ }
63
+ export interface EvaluateResult {
64
+ readonly sha12: string;
65
+ readonly invocationId: string;
66
+ readonly originSha: string;
67
+ readonly sinkDir: string;
68
+ readonly concurrency: number;
69
+ readonly verdicts: readonly IssueEvalVerdict[];
70
+ }
71
+ export interface GitRunnerResult {
72
+ readonly returncode: number;
73
+ readonly stdout: string;
74
+ readonly stderr: string;
75
+ }
76
+ export type GitRunner = (args: readonly string[], cwd: string) => GitRunnerResult;
77
+ export type SessionStartFn = (worktreePath: string) => void;
78
+ export type GithubReader = {
79
+ readonly viewIssue: (repo: string, n: number) => GithubIssueSnapshot;
80
+ readonly listOpenIssues: (repo: string) => readonly GithubIssueSnapshot[];
81
+ readonly listOpenPulls: (repo: string) => readonly GithubPullSnapshot[];
82
+ };
83
+ export interface EvaluateOptions {
84
+ readonly projectRoot: string;
85
+ readonly repo: string;
86
+ readonly issues: readonly number[];
87
+ readonly concurrency?: number;
88
+ readonly invocationId?: string;
89
+ readonly git?: GitRunner;
90
+ readonly sessionStart?: SessionStartFn;
91
+ readonly github?: GithubReader;
92
+ }
93
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,13 @@
1
+ /** Stage A issue-eval types (#3648). */
2
+ export const DEFAULT_CONCURRENCY = 4;
3
+ export const ORIGIN_MASTER = "origin/master";
4
+ export const SHA12_LENGTH = 12;
5
+ export const CRITIQUE_RECOMMEND_FIELD = "critique-recommend";
6
+ export const RESERVED_CLEARANCE_RE = /design-critique:\s*(?:warranted|not warranted)(?:\s*\|\s*(?:warranted|not warranted))?\s*,\s*because/iu;
7
+ export const VALIDITY_STATES = [
8
+ "still-open",
9
+ "partial",
10
+ "likely-shipped",
11
+ "needs-re-scope",
12
+ ];
13
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,8 @@
1
+ import type { ValidityVerdict } from "./types.js";
2
+ /**
3
+ * Evaluator-owned. Reads only `worktreeRoot` (detached origin/master).
4
+ * Must not accept a WIP census argument.
5
+ */
6
+ export declare function evaluateValidity(worktreeRoot: string, issue: number): ValidityVerdict;
7
+ export declare function joinValidityWithGithub(validity: ValidityVerdict, githubState: "open" | "closed" | null): ValidityVerdict;
8
+ //# sourceMappingURL=validity.d.ts.map
@@ -0,0 +1,87 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { listXbriefHits } from "./xbrief-refs.js";
4
+ const ADR_DIR = join("docs", "decisions");
5
+ const CONTRACT_DIR = join("content", "contracts");
6
+ function filesMentionIssue(dir, issue) {
7
+ if (!existsSync(dir)) {
8
+ return [];
9
+ }
10
+ const needle = `#${issue}`;
11
+ const hits = [];
12
+ for (const name of readdirSync(dir)) {
13
+ if (!name.endsWith(".md")) {
14
+ continue;
15
+ }
16
+ const path = join(dir, name);
17
+ try {
18
+ const text = readFileSync(path, "utf8");
19
+ if (text.includes(needle) || text.includes(`/issues/${issue}`)) {
20
+ hits.push(path);
21
+ }
22
+ }
23
+ catch { }
24
+ }
25
+ return hits;
26
+ }
27
+ /**
28
+ * Evaluator-owned. Reads only `worktreeRoot` (detached origin/master).
29
+ * Must not accept a WIP census argument.
30
+ */
31
+ export function evaluateValidity(worktreeRoot, issue) {
32
+ const completed = listXbriefHits(worktreeRoot, "completed").filter((hit) => hit.issue === issue);
33
+ const pending = listXbriefHits(worktreeRoot, "pending").filter((hit) => hit.issue === issue);
34
+ const active = listXbriefHits(worktreeRoot, "active").filter((hit) => hit.issue === issue);
35
+ const proposed = listXbriefHits(worktreeRoot, "proposed").filter((hit) => hit.issue === issue);
36
+ const adrHits = filesMentionIssue(join(worktreeRoot, ADR_DIR), issue);
37
+ const contractHits = filesMentionIssue(join(worktreeRoot, CONTRACT_DIR), issue);
38
+ if (completed.length > 0) {
39
+ return {
40
+ state: "likely-shipped",
41
+ evidence: `completed xbrief on origin/master: ${completed[0]?.path}`,
42
+ worktreePath: worktreeRoot,
43
+ sessionStartReadOnly: true,
44
+ };
45
+ }
46
+ if (pending.length > 0 || active.length > 0 || proposed.length > 0) {
47
+ const hit = pending[0] ?? active[0] ?? proposed[0];
48
+ return {
49
+ state: "partial",
50
+ evidence: `committed lifecycle xbrief on origin/master: ${hit?.path}`,
51
+ worktreePath: worktreeRoot,
52
+ sessionStartReadOnly: true,
53
+ };
54
+ }
55
+ if (adrHits.length > 0 || contractHits.length > 0) {
56
+ return {
57
+ state: "partial",
58
+ evidence: `ADR/contract mention on origin/master: ${adrHits[0] ?? contractHits[0] ?? ""}`,
59
+ worktreePath: worktreeRoot,
60
+ sessionStartReadOnly: true,
61
+ };
62
+ }
63
+ return {
64
+ state: "still-open",
65
+ evidence: "no origin/master lifecycle coverage, ADR, or contract mention",
66
+ worktreePath: worktreeRoot,
67
+ sessionStartReadOnly: true,
68
+ };
69
+ }
70
+ export function joinValidityWithGithub(validity, githubState) {
71
+ if (githubState === "closed" && validity.state === "still-open") {
72
+ return {
73
+ ...validity,
74
+ state: "likely-shipped",
75
+ evidence: `${validity.evidence}; GitHub issue state=closed`,
76
+ };
77
+ }
78
+ if (githubState === "open" && validity.state === "likely-shipped") {
79
+ return {
80
+ ...validity,
81
+ state: "needs-re-scope",
82
+ evidence: `${validity.evidence}; GitHub issue still open`,
83
+ };
84
+ }
85
+ return validity;
86
+ }
87
+ //# sourceMappingURL=validity.js.map
@@ -0,0 +1,12 @@
1
+ import { type GithubIssueSnapshot, type ValueAdvice } from "./types.js";
2
+ export declare class ReservedClearanceError extends Error {
3
+ name: string;
4
+ }
5
+ export declare function assertNoReservedClearance(text: string, label: string): void;
6
+ /**
7
+ * Value MAY recommend critique via `critique-recommend:`.
8
+ * It MUST NOT stamp the reserved ADR-005 clearance grammar.
9
+ */
10
+ export declare function buildValueAdvice(issue: GithubIssueSnapshot | null): ValueAdvice;
11
+ export declare function formatValueField(advice: ValueAdvice): string;
12
+ //# sourceMappingURL=value.d.ts.map
@@ -0,0 +1,33 @@
1
+ import { CRITIQUE_RECOMMEND_FIELD, RESERVED_CLEARANCE_RE, } from "./types.js";
2
+ export class ReservedClearanceError extends Error {
3
+ name = "ReservedClearanceError";
4
+ }
5
+ export function assertNoReservedClearance(text, label) {
6
+ if (RESERVED_CLEARANCE_RE.test(text)) {
7
+ throw new ReservedClearanceError(`${label} must not emit the reserved clearance line ` +
8
+ "`design-critique: warranted | not warranted, because …`");
9
+ }
10
+ }
11
+ /**
12
+ * Value MAY recommend critique via `critique-recommend:`.
13
+ * It MUST NOT stamp the reserved ADR-005 clearance grammar.
14
+ */
15
+ export function buildValueAdvice(issue) {
16
+ const labels = issue?.labels ?? [];
17
+ const stamped = labels.some((label) => label === "design-critique:mechanism-shaped");
18
+ const reason = stamped
19
+ ? "GitHub label design-critique:mechanism-shaped is present; author still stamps clearance"
20
+ : "no mechanism-shaped stamp; critique not recommended from evaluation";
21
+ const advice = {
22
+ [CRITIQUE_RECOMMEND_FIELD]: stamped,
23
+ reason,
24
+ };
25
+ assertNoReservedClearance(JSON.stringify(advice), "value advice");
26
+ return advice;
27
+ }
28
+ export function formatValueField(advice) {
29
+ const line = `${CRITIQUE_RECOMMEND_FIELD}: ${advice[CRITIQUE_RECOMMEND_FIELD] ? "true" : "false"}`;
30
+ assertNoReservedClearance(`${line}\n${advice.reason}`, "value field");
31
+ return `${line}\n${advice.reason}`;
32
+ }
33
+ //# sourceMappingURL=value.js.map
@@ -0,0 +1,5 @@
1
+ import type { WipCensus, WipHit } from "./types.js";
2
+ /** Parent-owned. Evaluators must never receive this object. */
3
+ export declare function collectWipCensus(projectRoot: string, issues: readonly number[]): WipCensus;
4
+ export declare function wipHitsForIssue(census: WipCensus, issue: number): WipHit[];
5
+ //# sourceMappingURL=wip-census.d.ts.map
@@ -0,0 +1,39 @@
1
+ import { readPlanSequence } from "../../plan-sequence/store.js";
2
+ import { listXbriefHits } from "./xbrief-refs.js";
3
+ function hitsForIssues(hits, issues, kind) {
4
+ return hits
5
+ .filter((hit) => issues.has(hit.issue))
6
+ .map((hit) => ({ kind, path: hit.path, issue: hit.issue }));
7
+ }
8
+ /** Parent-owned. Evaluators must never receive this object. */
9
+ export function collectWipCensus(projectRoot, issues) {
10
+ const wanted = new Set(issues);
11
+ const activeHits = listXbriefHits(projectRoot, "active");
12
+ const pendingHits = listXbriefHits(projectRoot, "pending");
13
+ const planHits = [];
14
+ const sequence = readPlanSequence(projectRoot);
15
+ if (sequence !== null) {
16
+ for (const entry of sequence.entries) {
17
+ const fromField = entry.issue;
18
+ const fromId = Number.parseInt(entry.id.trim().replace(/^#/u, ""), 10);
19
+ const asNumber = typeof fromField === "number" ? fromField : fromId;
20
+ if (!Number.isFinite(asNumber) || !wanted.has(asNumber)) {
21
+ continue;
22
+ }
23
+ planHits.push({
24
+ kind: "plan-sequence",
25
+ path: ".deft/plan-sequence.json",
26
+ issue: asNumber,
27
+ });
28
+ }
29
+ }
30
+ return {
31
+ active: hitsForIssues(activeHits, wanted, "active-xbrief"),
32
+ pending: hitsForIssues(pendingHits, wanted, "pending-xbrief"),
33
+ planSequence: planHits,
34
+ };
35
+ }
36
+ export function wipHitsForIssue(census, issue) {
37
+ return [...census.active, ...census.pending, ...census.planSequence].filter((hit) => hit.issue === issue);
38
+ }
39
+ //# sourceMappingURL=wip-census.js.map
@@ -0,0 +1,8 @@
1
+ import type { GitRunner } from "./types.js";
2
+ export declare class EvaluatorWorktreeError extends Error {
3
+ name: string;
4
+ }
5
+ export declare function addEvaluatorWorktree(projectRoot: string, issue: number, invocationId: string, originSha: string, git?: GitRunner): string;
6
+ /** Verb-owned remove. Not a shared core git-worktree helper. */
7
+ export declare function removeEvaluatorWorktree(projectRoot: string, worktreePath: string, git?: GitRunner): void;
8
+ //# sourceMappingURL=worktrees.d.ts.map
@@ -0,0 +1,68 @@
1
+ import { existsSync, mkdirSync, rmSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { defaultGitRunner as swarmGitRunner, } from "../../swarm/worktrees.js";
4
+ import { evaluatorWorktreePath } from "./paths.js";
5
+ export class EvaluatorWorktreeError extends Error {
6
+ name = "EvaluatorWorktreeError";
7
+ }
8
+ function toSwarm(git) {
9
+ return (args, cwd) => git(args, cwd);
10
+ }
11
+ export function addEvaluatorWorktree(projectRoot, issue, invocationId, originSha, git = swarmGitRunner) {
12
+ const sha = originSha.trim();
13
+ if (sha.length === 0) {
14
+ throw new EvaluatorWorktreeError("originSha is required for a detached evaluator worktree");
15
+ }
16
+ const worktreePath = evaluatorWorktreePath(projectRoot, issue, invocationId);
17
+ mkdirSync(dirname(resolve(worktreePath)), { recursive: true });
18
+ const proc = toSwarm(git)(["worktree", "add", "--detach", worktreePath, sha], projectRoot);
19
+ if (proc.returncode !== 0) {
20
+ throw new EvaluatorWorktreeError(`git worktree add --detach failed for issue ${issue}: ${proc.stderr.trim() || "<no stderr>"}`);
21
+ }
22
+ return worktreePath;
23
+ }
24
+ function forceDeleteWorktreeDir(worktreePath) {
25
+ if (!existsSync(worktreePath)) {
26
+ return;
27
+ }
28
+ rmSync(worktreePath, { recursive: true, force: true });
29
+ }
30
+ function worktreeStillRegistered(git, projectRoot, worktreePath) {
31
+ const listed = git(["worktree", "list", "--porcelain"], projectRoot);
32
+ if (listed.returncode !== 0) {
33
+ return true;
34
+ }
35
+ const needle = resolve(worktreePath).replace(/\\/g, "/").toLowerCase();
36
+ for (const line of listed.stdout.split(/\r?\n/u)) {
37
+ if (!line.startsWith("worktree ")) {
38
+ continue;
39
+ }
40
+ const listedPath = line.slice("worktree ".length).replace(/\\/g, "/").toLowerCase();
41
+ if (listedPath === needle) {
42
+ return true;
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+ /** Verb-owned remove. Not a shared core git-worktree helper. */
48
+ export function removeEvaluatorWorktree(projectRoot, worktreePath, git = swarmGitRunner) {
49
+ const runner = toSwarm(git);
50
+ const proc = runner(["worktree", "remove", "--force", worktreePath], projectRoot);
51
+ if (proc.returncode === 0) {
52
+ return;
53
+ }
54
+ const firstError = proc.stderr.trim() || "<no stderr>";
55
+ forceDeleteWorktreeDir(worktreePath);
56
+ runner(["worktree", "prune"], projectRoot);
57
+ const retry = runner(["worktree", "remove", "--force", worktreePath], projectRoot);
58
+ if (retry.returncode === 0) {
59
+ return;
60
+ }
61
+ forceDeleteWorktreeDir(worktreePath);
62
+ runner(["worktree", "prune"], projectRoot);
63
+ if (!worktreeStillRegistered(runner, projectRoot, worktreePath) && !existsSync(worktreePath)) {
64
+ return;
65
+ }
66
+ throw new EvaluatorWorktreeError(`git worktree remove failed for ${worktreePath}: ${firstError}; fallback: ${retry.stderr.trim() || "<no stderr>"}`);
67
+ }
68
+ //# sourceMappingURL=worktrees.js.map
@@ -0,0 +1,8 @@
1
+ export interface XbriefHit {
2
+ readonly issue: number;
3
+ readonly path: string;
4
+ readonly folder: "active" | "pending" | "proposed" | "completed" | "cancelled";
5
+ }
6
+ export declare function issueNumbersFromXbriefJson(raw: unknown): number[];
7
+ export declare function listXbriefHits(root: string, folder: XbriefHit["folder"]): XbriefHit[];
8
+ //# sourceMappingURL=xbrief-refs.d.ts.map