@deftai/directive-core 0.99.0 → 0.101.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/authz/actions.d.ts +6 -0
- package/dist/authz/actions.js +3 -0
- package/dist/authz/classify.js +241 -30
- package/dist/authz/decompose-apply.d.ts +81 -0
- package/dist/authz/decompose-apply.js +377 -0
- package/dist/authz/index.d.ts +3 -1
- package/dist/authz/index.js +3 -1
- package/dist/authz/store.d.ts +53 -0
- package/dist/authz/store.js +219 -1
- package/dist/authz/types.d.ts +19 -2
- package/dist/authz/types.js +5 -1
- package/dist/check/cached-orchestrator.d.ts +26 -5
- package/dist/check/cached-orchestrator.js +228 -6
- package/dist/check/gate-lists.d.ts +10 -4
- package/dist/check/gate-lists.js +19 -5
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +2 -1
- package/dist/check/named-cause.d.ts +45 -0
- package/dist/check/named-cause.js +121 -0
- package/dist/check/orchestrator.js +6 -1
- package/dist/doctor/checks.d.ts +44 -0
- package/dist/doctor/checks.js +428 -2
- package/dist/doctor/help.d.ts +5 -0
- package/dist/doctor/help.js +31 -0
- package/dist/doctor/index.d.ts +2 -0
- package/dist/doctor/index.js +2 -0
- package/dist/doctor/main.d.ts +6 -7
- package/dist/doctor/main.js +130 -79
- package/dist/doctor/session-coda.d.ts +82 -0
- package/dist/doctor/session-coda.js +151 -0
- package/dist/doctor/types.d.ts +16 -0
- package/dist/doctor/which.d.ts +20 -1
- package/dist/doctor/which.js +67 -1
- package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
- package/dist/finish-loop/pr-finish-loop.js +116 -5
- package/dist/finish-loop/types.d.ts +1 -1
- package/dist/hooks/dispatcher.d.ts +9 -5
- package/dist/hooks/dispatcher.js +23 -15
- package/dist/hooks/readonly.d.ts +6 -3
- package/dist/hooks/readonly.js +44 -25
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.d.ts +20 -0
- package/dist/init-deposit/agent-hooks.js +51 -31
- package/dist/init-deposit/gitignore.js +2 -0
- package/dist/intake/issue-ingest.js +36 -0
- package/dist/lifecycle/completed-consistency.d.ts +60 -0
- package/dist/lifecycle/completed-consistency.js +443 -0
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +52 -0
- package/dist/literal-acceptance/capture.js +658 -0
- package/dist/literal-acceptance/evaluate.d.ts +47 -0
- package/dist/literal-acceptance/evaluate.js +193 -0
- package/dist/literal-acceptance/index.d.ts +12 -0
- package/dist/literal-acceptance/index.js +12 -0
- package/dist/literal-acceptance/run.d.ts +38 -0
- package/dist/literal-acceptance/run.js +216 -0
- package/dist/literal-acceptance/safety.d.ts +24 -0
- package/dist/literal-acceptance/safety.js +251 -0
- package/dist/literal-acceptance/types.d.ts +82 -0
- package/dist/literal-acceptance/types.js +26 -0
- package/dist/policy/ac-pass-banking.d.ts +58 -0
- package/dist/policy/ac-pass-banking.js +150 -0
- package/dist/policy/ceremony-dial.d.ts +96 -11
- package/dist/policy/ceremony-dial.js +250 -23
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +16 -1
- package/dist/policy/merge-approval-head.d.ts +129 -0
- package/dist/policy/merge-approval-head.js +461 -0
- package/dist/policy/require-human-merge.d.ts +5 -0
- package/dist/policy/require-human-merge.js +5 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
- package/dist/pr-merge-readiness/ci-gate.js +65 -5
- package/dist/pr-merge-readiness/compute.d.ts +13 -0
- package/dist/pr-merge-readiness/compute.js +99 -14
- package/dist/pr-merge-readiness/gh.d.ts +49 -0
- package/dist/pr-merge-readiness/gh.js +268 -0
- package/dist/pr-merge-readiness/index.d.ts +2 -2
- package/dist/pr-merge-readiness/index.js +2 -2
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
- package/dist/pr-wait-mergeable/cascade.js +115 -1
- package/dist/pr-wait-mergeable/main.d.ts +4 -0
- package/dist/pr-wait-mergeable/main.js +4 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
- package/dist/pr-wait-mergeable/wrappers.js +7 -1
- package/dist/preflight/evaluate.d.ts +15 -1
- package/dist/preflight/evaluate.js +33 -2
- package/dist/preflight-cache/evaluate.d.ts +3 -0
- package/dist/preflight-cache/evaluate.js +3 -0
- package/dist/product-first-done-gate/acceptance.d.ts +25 -0
- package/dist/product-first-done-gate/acceptance.js +247 -0
- package/dist/product-first-done-gate/check-mode.d.ts +45 -0
- package/dist/product-first-done-gate/check-mode.js +148 -0
- package/dist/product-first-done-gate/evaluate.d.ts +50 -0
- package/dist/product-first-done-gate/evaluate.js +255 -0
- package/dist/product-first-done-gate/index.d.ts +11 -0
- package/dist/product-first-done-gate/index.js +11 -0
- package/dist/product-first-done-gate/types.d.ts +64 -0
- package/dist/product-first-done-gate/types.js +61 -0
- package/dist/render/framework-commands.js +4 -0
- package/dist/run-summary/emit.d.ts +56 -0
- package/dist/run-summary/emit.js +171 -0
- package/dist/run-summary/index.d.ts +4 -0
- package/dist/run-summary/index.js +4 -0
- package/dist/run-summary/path.d.ts +25 -0
- package/dist/run-summary/path.js +78 -0
- package/dist/run-summary/types.d.ts +76 -0
- package/dist/run-summary/types.js +21 -0
- package/dist/scope/acceptance-evidence.d.ts +110 -0
- package/dist/scope/acceptance-evidence.js +422 -0
- package/dist/scope/coverage-map.d.ts +98 -0
- package/dist/scope/coverage-map.js +558 -0
- package/dist/scope/decompose.d.ts +5 -0
- package/dist/scope/decompose.js +134 -11
- package/dist/scope/index.d.ts +3 -0
- package/dist/scope/index.js +3 -0
- package/dist/scope/parent-lineage.d.ts +125 -0
- package/dist/scope/parent-lineage.js +626 -0
- package/dist/scope/transition.d.ts +8 -0
- package/dist/scope/transition.js +61 -2
- package/dist/session/ac-pass-banking.d.ts +221 -0
- package/dist/session/ac-pass-banking.js +761 -0
- package/dist/session/active-cli.d.ts +79 -0
- package/dist/session/active-cli.js +382 -0
- package/dist/session/deposit-sha.d.ts +49 -0
- package/dist/session/deposit-sha.js +121 -0
- package/dist/session/effort-budget.d.ts +136 -0
- package/dist/session/effort-budget.js +352 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +7 -0
- package/dist/session/orientation-compression.d.ts +127 -0
- package/dist/session/orientation-compression.js +425 -0
- package/dist/session/orientation-state.d.ts +29 -0
- package/dist/session/orientation-state.js +85 -0
- package/dist/session/session-start.d.ts +31 -0
- package/dist/session/session-start.js +228 -2
- package/dist/session/toolchain-preflight.d.ts +64 -0
- package/dist/session/toolchain-preflight.js +151 -0
- package/dist/session/verify-session-ritual.d.ts +14 -0
- package/dist/session/verify-session-ritual.js +33 -0
- package/dist/story-ready/evaluate.d.ts +11 -0
- package/dist/story-ready/evaluate.js +41 -3
- package/dist/triage/actions/candidates-log.d.ts +1 -2
- package/dist/triage/actions/candidates-log.js +37 -6
- package/dist/triage/bootstrap/gitignore.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +8 -2
- package/dist/triage/bootstrap/index.js +15 -3
- package/dist/triage/classify/index.d.ts +1 -0
- package/dist/triage/classify/index.js +2 -0
- package/dist/triage/classify/label-mirror.js +16 -0
- package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
- package/dist/triage/classify/mirror-discovery-tip.js +234 -0
- package/dist/triage/reconcile/reconcile.js +28 -6
- package/dist/triage/summary/index.js +12 -4
- package/dist/triage/welcome/default-mode.js +16 -0
- package/dist/ts-check-lane/run-lane.js +21 -4
- package/dist/vbrief-validate/conformance.js +7 -0
- package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
- package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
- package/dist/xbrief-migrate/migrate-project.js +92 -3
- package/package.json +15 -3
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-closed land check (#3264 / #1358 recurrence).
|
|
3
|
+
*
|
|
4
|
+
* Rule: if a GitHub issue is closed and Directive has a known lifecycle
|
|
5
|
+
* xBRIEF origin for it, the delivery branch tip must contain a *tracked*
|
|
6
|
+
* artifact under xbrief/completed/ or xbrief/cancelled/ that references
|
|
7
|
+
* that issue. Else fail with a single remediation path
|
|
8
|
+
* (`task swarm:finalize-cohort` or a lifecycle PR).
|
|
9
|
+
*
|
|
10
|
+
* Complements verify:orphan-active (#2321), which watches active/running
|
|
11
|
+
* residue — not completed-but-untracked laptop residue.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join, relative, resolve } from "node:path";
|
|
15
|
+
import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, resolveLifecycleRoot, } from "../layout/resolve.js";
|
|
16
|
+
import { collectGithubRefs } from "../orphan-active/refs.js";
|
|
17
|
+
import { resolveDeliveryBranch } from "../policy/delivery-branch.js";
|
|
18
|
+
import { defaultRunGh } from "../pr-protected-issues/gh.js";
|
|
19
|
+
import { defaultGitRunner } from "../session/git.js";
|
|
20
|
+
import { CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE } from "../triage/queue/constants.js";
|
|
21
|
+
import { resolveRepo } from "../triage/queue/repo.js";
|
|
22
|
+
/** Lifecycle folders scanned for local "known origin" xBRIEFs. */
|
|
23
|
+
export const LOCAL_ORIGIN_FOLDERS = [
|
|
24
|
+
"proposed",
|
|
25
|
+
"pending",
|
|
26
|
+
"active",
|
|
27
|
+
"completed",
|
|
28
|
+
"cancelled",
|
|
29
|
+
];
|
|
30
|
+
/** Non-terminal tip folders: closed issues still here need completed land. */
|
|
31
|
+
export const TIP_NONTERMINAL_FOLDERS = ["proposed", "pending", "active"];
|
|
32
|
+
/** Terminal tip folders that satisfy the land rule. */
|
|
33
|
+
export const TIP_TERMINAL_FOLDERS = ["completed", "cancelled"];
|
|
34
|
+
function readJson(path) {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
37
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
38
|
+
? parsed
|
|
39
|
+
: null;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function planOf(data) {
|
|
46
|
+
const plan = data?.plan;
|
|
47
|
+
return typeof plan === "object" && plan !== null && !Array.isArray(plan)
|
|
48
|
+
? plan
|
|
49
|
+
: null;
|
|
50
|
+
}
|
|
51
|
+
function issueKey(ref) {
|
|
52
|
+
return `${ref.repo}#${ref.number}`;
|
|
53
|
+
}
|
|
54
|
+
function formatIssue(ref) {
|
|
55
|
+
return `${ref.repo}#${ref.number}`;
|
|
56
|
+
}
|
|
57
|
+
function relPath(path, projectRoot) {
|
|
58
|
+
try {
|
|
59
|
+
return relative(resolve(projectRoot), resolve(path)).replace(/\\/g, "/");
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return path.replace(/\\/g, "/");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function readCachedIssueState(projectRoot, ref) {
|
|
66
|
+
const [owner, name] = ref.repo.split("/", 2);
|
|
67
|
+
if (!owner || !name) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const rawPath = join(projectRoot, CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE, owner, name, String(ref.number), "raw.json");
|
|
71
|
+
if (!existsSync(rawPath)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const raw = readJson(rawPath);
|
|
75
|
+
if (raw === null) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const state = typeof raw.state === "string" ? raw.state.toLowerCase() : "";
|
|
79
|
+
if (state === "closed" || state === "open") {
|
|
80
|
+
return state;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
function fetchIssueStateLive(ref, runGh) {
|
|
85
|
+
const path = `repos/${ref.repo}/issues/${ref.number}`;
|
|
86
|
+
const result = runGh(["gh", "api", path]);
|
|
87
|
+
if (result.returncode !== 0) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const payload = JSON.parse(result.stdout);
|
|
92
|
+
if (payload === null || typeof payload !== "object") {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const state = payload.state;
|
|
96
|
+
if (state === "closed" || state === "open") {
|
|
97
|
+
return state;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function defaultResolveIssueState(ref, projectRoot, runGh, skipGh) {
|
|
106
|
+
const cached = readCachedIssueState(projectRoot, ref);
|
|
107
|
+
// Closed cache is fail-closed evidence (safe to trust without live).
|
|
108
|
+
if (cached === "closed") {
|
|
109
|
+
return "closed";
|
|
110
|
+
}
|
|
111
|
+
if (skipGh) {
|
|
112
|
+
// Offline / fixture mode: honor cache open|null as-is.
|
|
113
|
+
return cached;
|
|
114
|
+
}
|
|
115
|
+
// Prefer live when network is allowed. Stale open cache must not suppress a
|
|
116
|
+
// later close when live succeeds (#3264 Greptile P1). When live fails, do
|
|
117
|
+
// NOT fall back to cached open — treat as unknown so we fail closed only on
|
|
118
|
+
// positive closed evidence (cached closed above, or live closed).
|
|
119
|
+
const live = fetchIssueStateLive(ref, runGh);
|
|
120
|
+
if (live !== null) {
|
|
121
|
+
return live;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
function collectIssuesFromPlan(plan, defaultRepo, originPath, out) {
|
|
126
|
+
const { issues } = collectGithubRefs(plan, defaultRepo);
|
|
127
|
+
for (const issue of issues) {
|
|
128
|
+
out.push({ issue, originPath });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function scanLocalOrigins(projectRoot, defaultRepo) {
|
|
132
|
+
const hits = [];
|
|
133
|
+
const roots = [];
|
|
134
|
+
try {
|
|
135
|
+
roots.push(resolveLifecycleRoot(projectRoot));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// no xbrief layout
|
|
139
|
+
}
|
|
140
|
+
// Read-accepted legacy vbrief/ root when present (same class as #3242 scans).
|
|
141
|
+
const legacyRoot = join(projectRoot, LEGACY_ARTIFACT_DIR);
|
|
142
|
+
if (existsSync(legacyRoot) && !roots.includes(legacyRoot)) {
|
|
143
|
+
roots.push(legacyRoot);
|
|
144
|
+
}
|
|
145
|
+
// Canonical path even when resolveLifecycleRoot fell back elsewhere.
|
|
146
|
+
const migrated = join(projectRoot, MIGRATED_ARTIFACT_DIR);
|
|
147
|
+
if (existsSync(migrated) && !roots.includes(migrated)) {
|
|
148
|
+
roots.push(migrated);
|
|
149
|
+
}
|
|
150
|
+
for (const root of roots) {
|
|
151
|
+
for (const folder of LOCAL_ORIGIN_FOLDERS) {
|
|
152
|
+
const dir = join(root, folder);
|
|
153
|
+
if (!existsSync(dir)) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
let entries;
|
|
157
|
+
try {
|
|
158
|
+
entries = readdirSync(dir);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
for (const name of entries.sort()) {
|
|
164
|
+
if (!hasArtifactSuffix(name)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const path = join(dir, name);
|
|
168
|
+
const plan = planOf(readJson(path));
|
|
169
|
+
if (plan === null) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
collectIssuesFromPlan(plan, defaultRepo, relPath(path, projectRoot), hits);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return hits;
|
|
177
|
+
}
|
|
178
|
+
function refExists(projectRoot, ref, runGit) {
|
|
179
|
+
const result = runGit(projectRoot, ["rev-parse", "--verify", "-q", ref]);
|
|
180
|
+
return result.code === 0;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Resolve the git tip to inspect for tracked completed/cancelled land.
|
|
184
|
+
* Prefers origin/<deliveryBranch> when present.
|
|
185
|
+
*/
|
|
186
|
+
export function resolveDeliveryTip(projectRoot, tipOverride, runGit) {
|
|
187
|
+
if (tipOverride !== null && tipOverride !== undefined && tipOverride.trim().length > 0) {
|
|
188
|
+
const tip = tipOverride.trim();
|
|
189
|
+
if (!refExists(projectRoot, tip, runGit)) {
|
|
190
|
+
return { tip: null, error: `delivery tip ref not found: ${tip}` };
|
|
191
|
+
}
|
|
192
|
+
return { tip, error: null };
|
|
193
|
+
}
|
|
194
|
+
const delivery = resolveDeliveryBranch(projectRoot, runGit);
|
|
195
|
+
const branch = delivery.branch;
|
|
196
|
+
for (const candidate of [`origin/${branch}`, branch]) {
|
|
197
|
+
if (refExists(projectRoot, candidate, runGit)) {
|
|
198
|
+
return { tip: candidate, error: null };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Last resort: HEAD of current checkout (still a real tip for fixtures).
|
|
202
|
+
if (refExists(projectRoot, "HEAD", runGit)) {
|
|
203
|
+
return { tip: "HEAD", error: null };
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
tip: null,
|
|
207
|
+
error: `could not resolve delivery tip for branch '${branch}' (no origin/${branch}, ${branch}, or HEAD)`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function listTreePaths(projectRoot, tip, prefixes, runGit) {
|
|
211
|
+
if (prefixes.length === 0) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
const result = runGit(projectRoot, ["ls-tree", "-r", "--name-only", tip, "--", ...prefixes]);
|
|
215
|
+
if (result.code !== 0) {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
return result.stdout
|
|
219
|
+
.split("\n")
|
|
220
|
+
.map((line) => line.trim().replace(/\\/g, "/"))
|
|
221
|
+
.filter((line) => line.length > 0 && hasArtifactSuffix(line));
|
|
222
|
+
}
|
|
223
|
+
function showBlob(projectRoot, tip, path, runGit) {
|
|
224
|
+
const result = runGit(projectRoot, ["show", `${tip}:${path}`]);
|
|
225
|
+
if (result.code !== 0) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return result.stdout;
|
|
229
|
+
}
|
|
230
|
+
function terminalPrefixes() {
|
|
231
|
+
const out = [];
|
|
232
|
+
for (const root of [MIGRATED_ARTIFACT_DIR, LEGACY_ARTIFACT_DIR]) {
|
|
233
|
+
for (const folder of TIP_TERMINAL_FOLDERS) {
|
|
234
|
+
out.push(`${root}/${folder}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
function nonterminalPrefixes() {
|
|
240
|
+
const out = [];
|
|
241
|
+
for (const root of [MIGRATED_ARTIFACT_DIR, LEGACY_ARTIFACT_DIR]) {
|
|
242
|
+
for (const folder of TIP_NONTERMINAL_FOLDERS) {
|
|
243
|
+
out.push(`${root}/${folder}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
function issuesFromBlobPaths(projectRoot, tip, paths, defaultRepo, runGit, originPrefix) {
|
|
249
|
+
const hits = [];
|
|
250
|
+
for (const path of paths) {
|
|
251
|
+
const body = showBlob(projectRoot, tip, path, runGit);
|
|
252
|
+
if (body === null) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
let parsed;
|
|
256
|
+
try {
|
|
257
|
+
parsed = JSON.parse(body);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const plan = planOf(parsed);
|
|
266
|
+
if (plan === null) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
collectIssuesFromPlan(plan, defaultRepo, `${originPrefix}:${path}`, hits);
|
|
270
|
+
}
|
|
271
|
+
return hits;
|
|
272
|
+
}
|
|
273
|
+
function mergeOrigins(hits) {
|
|
274
|
+
const map = new Map();
|
|
275
|
+
for (const hit of hits) {
|
|
276
|
+
const key = issueKey(hit.issue);
|
|
277
|
+
let entry = map.get(key);
|
|
278
|
+
if (entry === undefined) {
|
|
279
|
+
entry = { issue: hit.issue, origins: new Set() };
|
|
280
|
+
map.set(key, entry);
|
|
281
|
+
}
|
|
282
|
+
entry.origins.add(hit.originPath);
|
|
283
|
+
}
|
|
284
|
+
const out = new Map();
|
|
285
|
+
for (const [key, value] of map) {
|
|
286
|
+
out.set(key, {
|
|
287
|
+
issue: value.issue,
|
|
288
|
+
origins: [...value.origins].sort(),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
293
|
+
function formatRefusal(missing, projectRoot, tip) {
|
|
294
|
+
const lines = [
|
|
295
|
+
`verify:completed-tracked: ${missing.length} closed scoped issue${missing.length === 1 ? "" : "s"} lack a tracked xbrief/completed/ or xbrief/cancelled/ artifact on delivery tip ${tip} (project_root=${projectRoot}).`,
|
|
296
|
+
" Remediation: task swarm:finalize-cohort (or open a lifecycle PR that lands the completed/cancelled xBRIEFs).",
|
|
297
|
+
" Missing:",
|
|
298
|
+
];
|
|
299
|
+
for (const item of missing) {
|
|
300
|
+
const originSample = item.origins.slice(0, 3).join(", ");
|
|
301
|
+
const more = item.origins.length > 3 ? ` (+${item.origins.length - 3} more origin path(s))` : "";
|
|
302
|
+
lines.push(` - ${formatIssue(item.issue)} (origin: ${originSample}${more})`);
|
|
303
|
+
}
|
|
304
|
+
return lines.join("\n");
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Pure evaluator: closed scoped issues must have tracked completed/cancelled
|
|
308
|
+
* on the delivery tip (#3264).
|
|
309
|
+
*/
|
|
310
|
+
export function evaluateCompletedTracked(projectRoot, options = {}) {
|
|
311
|
+
const root = resolve(projectRoot);
|
|
312
|
+
const quiet = options.quiet ?? false;
|
|
313
|
+
const skipGh = options.skipGh ?? false;
|
|
314
|
+
const runGh = options.runGh ?? defaultRunGh;
|
|
315
|
+
const runGit = options.runGit ?? defaultGitRunner;
|
|
316
|
+
const defaultRepo = resolveRepo(options.repo, root);
|
|
317
|
+
const resolveState = options.resolveIssueState ??
|
|
318
|
+
((ref) => defaultResolveIssueState(ref, root, runGh, skipGh));
|
|
319
|
+
if (!existsSync(root)) {
|
|
320
|
+
return {
|
|
321
|
+
code: 2,
|
|
322
|
+
message: `verify:completed-tracked: project root does not exist: ${root}`,
|
|
323
|
+
stream: "stderr",
|
|
324
|
+
missing: [],
|
|
325
|
+
tip: null,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
// Not a git worktree → nothing to assert about tracked tip (greenfield /
|
|
329
|
+
// temp consumer fixtures). Soft-skip like orphan-active's empty-root path.
|
|
330
|
+
const gitProbe = runGit(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
331
|
+
if (gitProbe.code !== 0) {
|
|
332
|
+
if (quiet) {
|
|
333
|
+
return { code: 0, message: "", stream: "none", missing: [], tip: null };
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
code: 0,
|
|
337
|
+
message: `verify:completed-tracked: not a git worktree; skip tracked-land check (${root}).`,
|
|
338
|
+
stream: "stdout",
|
|
339
|
+
missing: [],
|
|
340
|
+
tip: null,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
const { tip, error: tipError } = resolveDeliveryTip(root, options.tip, runGit);
|
|
344
|
+
if (tip === null) {
|
|
345
|
+
return {
|
|
346
|
+
code: 2,
|
|
347
|
+
message: `verify:completed-tracked: ${tipError ?? "could not resolve delivery tip"}`,
|
|
348
|
+
stream: "stderr",
|
|
349
|
+
missing: [],
|
|
350
|
+
tip: null,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const localHits = scanLocalOrigins(root, defaultRepo);
|
|
354
|
+
const tipNonterminalPaths = listTreePaths(root, tip, nonterminalPrefixes(), runGit);
|
|
355
|
+
const tipNonterminalHits = issuesFromBlobPaths(root, tip, tipNonterminalPaths, defaultRepo, runGit, `tip:${tip}`);
|
|
356
|
+
const originMap = mergeOrigins([...localHits, ...tipNonterminalHits]);
|
|
357
|
+
if (originMap.size === 0) {
|
|
358
|
+
if (quiet) {
|
|
359
|
+
return { code: 0, message: "", stream: "none", missing: [], tip };
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
code: 0,
|
|
363
|
+
message: "verify:completed-tracked: no scoped lifecycle origins found; nothing to check.",
|
|
364
|
+
stream: "stdout",
|
|
365
|
+
missing: [],
|
|
366
|
+
tip,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
// Delivery tip only — the land invariant is post-merge tip truth (#3264 AC).
|
|
370
|
+
// Lifecycle PRs that commit completed/ on a feature branch are not expected
|
|
371
|
+
// to satisfy this gate until merge; run the verb with --tip HEAD when
|
|
372
|
+
// validating an in-flight land PR. The gate is a standalone verify verb
|
|
373
|
+
// (not wired into check:consumer) so product PRs are not deadlocked.
|
|
374
|
+
const tipTerminalPaths = listTreePaths(root, tip, terminalPrefixes(), runGit);
|
|
375
|
+
const tipTerminalHits = issuesFromBlobPaths(root, tip, tipTerminalPaths, defaultRepo, runGit, `tip:${tip}`);
|
|
376
|
+
const landedKeys = new Set(tipTerminalHits.map((h) => issueKey(h.issue)));
|
|
377
|
+
const missing = [];
|
|
378
|
+
for (const [key, entry] of originMap) {
|
|
379
|
+
if (landedKeys.has(key)) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
const state = resolveState(entry.issue);
|
|
383
|
+
if (state === "open") {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
// Closed: always fail. Unknown with live expected (!skipGh): also fail —
|
|
387
|
+
// cannot prove the issue is still open, so do not green-skip land debt
|
|
388
|
+
// (#3264 Greptile residual on live lookup failure).
|
|
389
|
+
if (state === "closed" || (state === null && !skipGh)) {
|
|
390
|
+
missing.push(entry);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
missing.sort((a, b) => issueKey(a.issue).localeCompare(issueKey(b.issue)));
|
|
394
|
+
if (missing.length > 0) {
|
|
395
|
+
return {
|
|
396
|
+
code: 1,
|
|
397
|
+
message: formatRefusal(missing, root, tip),
|
|
398
|
+
stream: "stderr",
|
|
399
|
+
missing,
|
|
400
|
+
tip,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (quiet) {
|
|
404
|
+
return { code: 0, message: "", stream: "none", missing: [], tip };
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
code: 0,
|
|
408
|
+
message: `verify:completed-tracked: all closed scoped issues have tracked completed/cancelled ` +
|
|
409
|
+
`on tip ${tip} (scoped origins checked: ${originMap.size}).`,
|
|
410
|
+
stream: "stdout",
|
|
411
|
+
missing: [],
|
|
412
|
+
tip,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
//# sourceMappingURL=completed-tracked-on-delivery.js.map
|
package/dist/lifecycle/index.js
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture exact stated acceptance commands from a task statement (#3267).
|
|
3
|
+
*
|
|
4
|
+
* Only extracts commands that appear literally in the text — never invents
|
|
5
|
+
* or paraphrases. Prefer fenced blocks and labeled lines (verify:/command:/run:).
|
|
6
|
+
*
|
|
7
|
+
* Safety-rejected shell-shaped lines are recorded on a rejected ledger so they
|
|
8
|
+
* do not vanish silently (#3267 residual). Dedup keys include cwd/expectedExitCode
|
|
9
|
+
* so distinct execution contexts are not collapsed.
|
|
10
|
+
*/
|
|
11
|
+
import type { LiteralAcceptanceCommand, RejectedLiteralCommand } from "./types.js";
|
|
12
|
+
/** Result of capture including the operator-visible rejected ledger. */
|
|
13
|
+
export interface CaptureLiteralAcceptanceResult {
|
|
14
|
+
readonly commands: readonly LiteralAcceptanceCommand[];
|
|
15
|
+
readonly rejected: readonly RejectedLiteralCommand[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Capture literal acceptance commands from free-form task statement text.
|
|
19
|
+
* Returns commands + rejected ledger — never invents commands.
|
|
20
|
+
*/
|
|
21
|
+
export declare function captureLiteralAcceptanceCommandsDetailed(taskStatement: string): CaptureLiteralAcceptanceResult;
|
|
22
|
+
/**
|
|
23
|
+
* Capture literal acceptance commands from free-form task statement text.
|
|
24
|
+
* Returns [] when none are stated — never invents commands.
|
|
25
|
+
*/
|
|
26
|
+
export declare function captureLiteralAcceptanceCommands(taskStatement: string): LiteralAcceptanceCommand[];
|
|
27
|
+
/**
|
|
28
|
+
* Read already-stored commands from plan.metadata / swarm.verify_commands / item.command.
|
|
29
|
+
* Preserves exact strings and execution context (cwd / expectedStdout / expectedExitCode).
|
|
30
|
+
*/
|
|
31
|
+
export declare function readStoredLiteralAcceptanceCommands(plan: Record<string, unknown> | null | undefined): LiteralAcceptanceCommand[];
|
|
32
|
+
/** Read stored commands plus any persisted rejected ledger. */
|
|
33
|
+
export declare function readStoredLiteralAcceptanceDetailed(plan: Record<string, unknown> | null | undefined): CaptureLiteralAcceptanceResult;
|
|
34
|
+
/**
|
|
35
|
+
* Attach captured commands onto plan.metadata without paraphrasing.
|
|
36
|
+
* Merges with existing literal_acceptance_commands.
|
|
37
|
+
* Only agent-executable sources (not task_statement) are mirrored into
|
|
38
|
+
* swarm.verify_commands — issue text must be promoted explicitly (#3267).
|
|
39
|
+
* Returns a shallow-cloned plan with updated metadata.
|
|
40
|
+
*/
|
|
41
|
+
export declare function attachLiteralAcceptanceCommands(plan: Record<string, unknown>, commands: readonly LiteralAcceptanceCommand[], rejected?: readonly RejectedLiteralCommand[]): Record<string, unknown>;
|
|
42
|
+
/**
|
|
43
|
+
* Capture from task statement text and attach onto a plan (intake helper).
|
|
44
|
+
*/
|
|
45
|
+
export declare function captureAndAttachLiteralAcceptance(plan: Record<string, unknown>, taskStatement: string): {
|
|
46
|
+
readonly plan: Record<string, unknown>;
|
|
47
|
+
readonly commands: readonly LiteralAcceptanceCommand[];
|
|
48
|
+
readonly rejected: readonly RejectedLiteralCommand[];
|
|
49
|
+
};
|
|
50
|
+
/** Format rejected ledger for CLI / complete-gate messages. */
|
|
51
|
+
export declare function formatRejectedLedger(rejected: readonly RejectedLiteralCommand[]): string;
|
|
52
|
+
//# sourceMappingURL=capture.d.ts.map
|