@deftai/directive-core 0.103.0 → 0.105.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/classify.js +682 -35
- package/dist/check/cached-orchestrator.d.ts +1 -1
- package/dist/check/cached-orchestrator.js +2 -2
- package/dist/check/gate-lists.d.ts +3 -2
- package/dist/check/gate-lists.js +5 -2
- package/dist/check/index.d.ts +1 -0
- package/dist/check/index.js +1 -0
- package/dist/check/named-cause.d.ts +1 -1
- package/dist/check/named-cause.js +33 -3
- package/dist/check/session-completed-ac.d.ts +39 -0
- package/dist/check/session-completed-ac.js +198 -0
- package/dist/codebase/default-extractor.d.ts +5 -0
- package/dist/codebase/default-extractor.js +7 -15
- package/dist/deposit/copy-tree.js +60 -0
- package/dist/deposit/python-free.js +26 -18
- package/dist/doctor/checks.d.ts +8 -0
- package/dist/doctor/checks.js +37 -25
- package/dist/doctor/doctor-state.d.ts +4 -0
- package/dist/doctor/doctor-state.js +7 -1
- package/dist/doctor/main.js +9 -14
- package/dist/doctor/types.d.ts +9 -0
- package/dist/fs/contained-write.d.ts +115 -1
- package/dist/fs/contained-write.js +242 -5
- package/dist/fs/mutation-ledger.d.ts +54 -0
- package/dist/fs/mutation-ledger.js +133 -0
- package/dist/fs/non-product-dirs.d.ts +79 -0
- package/dist/fs/non-product-dirs.js +96 -0
- package/dist/hooks/dispatcher.d.ts +3 -1
- package/dist/hooks/dispatcher.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.js +17 -15
- package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
- package/dist/init-deposit/core-guard-pin-content.js +393 -0
- package/dist/init-deposit/gitignore.js +9 -0
- package/dist/init-deposit/hygiene.d.ts +47 -11
- package/dist/init-deposit/hygiene.js +172 -37
- package/dist/init-deposit/legacy-detect.js +6 -2
- package/dist/init-deposit/refresh.d.ts +21 -1
- package/dist/init-deposit/refresh.js +106 -9
- package/dist/init-deposit/scaffold.js +28 -279
- package/dist/init-deposit/slash-deposit.js +13 -7
- package/dist/init-deposit/xbrief-projections.d.ts +8 -1
- package/dist/init-deposit/xbrief-projections.js +24 -11
- package/dist/intake/clause-derivation.d.ts +74 -0
- package/dist/intake/clause-derivation.js +409 -0
- package/dist/intake/index.d.ts +1 -0
- package/dist/intake/index.js +1 -0
- package/dist/intake/issue-ingest.d.ts +3 -1
- package/dist/intake/issue-ingest.js +36 -43
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
- package/dist/literal-acceptance/capture.d.ts +10 -0
- package/dist/literal-acceptance/capture.js +224 -75
- package/dist/literal-acceptance/evaluate.d.ts +29 -4
- package/dist/literal-acceptance/evaluate.js +70 -2
- package/dist/literal-acceptance/index.d.ts +3 -3
- package/dist/literal-acceptance/index.js +3 -3
- package/dist/literal-acceptance/safety.d.ts +41 -0
- package/dist/literal-acceptance/safety.js +126 -2
- package/dist/literal-acceptance/types.d.ts +8 -0
- package/dist/orphan-active/evaluate.d.ts +7 -1
- package/dist/orphan-active/evaluate.js +102 -23
- package/dist/platform/platform-capabilities.js +2 -3
- package/dist/policy/base-branch.d.ts +37 -0
- package/dist/policy/base-branch.js +114 -0
- package/dist/policy/branch-sync.d.ts +80 -0
- package/dist/policy/branch-sync.js +211 -0
- package/dist/policy/ceremony-dial-escalation.js +1 -0
- package/dist/policy/ceremony-dial.js +4 -14
- package/dist/policy/delivery-branch.d.ts +5 -3
- package/dist/policy/delivery-branch.js +7 -3
- package/dist/policy/file-size-thresholds.d.ts +24 -0
- package/dist/policy/file-size-thresholds.js +23 -0
- package/dist/policy/forge-outage-retry.d.ts +47 -0
- package/dist/policy/forge-outage-retry.js +157 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/index.js +66 -7
- package/dist/policy/project-invariants.d.ts +50 -0
- package/dist/policy/project-invariants.js +205 -0
- package/dist/policy/sync-default.d.ts +129 -0
- package/dist/policy/sync-default.js +452 -0
- package/dist/policy/sync-max-files.d.ts +93 -0
- package/dist/policy/sync-max-files.js +192 -0
- package/dist/pr-monitor/absent-required.d.ts +11 -0
- package/dist/pr-monitor/absent-required.js +61 -0
- package/dist/pr-monitor/constants.d.ts +7 -0
- package/dist/pr-monitor/constants.js +7 -0
- package/dist/pr-monitor/index.d.ts +2 -1
- package/dist/pr-monitor/index.js +2 -1
- package/dist/pr-monitor/main.js +4 -0
- package/dist/pr-monitor/monitor.js +24 -1
- package/dist/pr-wait-mergeable/classify.js +6 -0
- package/dist/preflight/evaluate.d.ts +9 -0
- package/dist/preflight/evaluate.js +49 -1
- package/dist/preflight/index.d.ts +5 -1
- package/dist/preflight/index.js +2 -0
- package/dist/preflight/intended-placement.d.ts +62 -0
- package/dist/preflight/intended-placement.js +275 -0
- package/dist/preflight/project-invariants-gate.d.ts +29 -0
- package/dist/preflight/project-invariants-gate.js +82 -0
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
- package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
- package/dist/product-first-done-gate/acceptance.d.ts +7 -4
- package/dist/product-first-done-gate/acceptance.js +79 -8
- package/dist/product-first-done-gate/empty-resolution.d.ts +5 -2
- package/dist/product-first-done-gate/empty-resolution.js +13 -2
- package/dist/product-first-done-gate/evaluate.d.ts +29 -0
- package/dist/product-first-done-gate/evaluate.js +307 -33
- package/dist/product-first-done-gate/index.d.ts +3 -1
- package/dist/product-first-done-gate/index.js +2 -1
- package/dist/release/build-dist.d.ts +101 -0
- package/dist/release/build-dist.js +428 -46
- package/dist/run-summary/emit.d.ts +50 -4
- package/dist/run-summary/emit.js +262 -26
- package/dist/run-summary/index.d.ts +3 -2
- package/dist/run-summary/index.js +2 -1
- package/dist/run-summary/session-id.d.ts +21 -0
- package/dist/run-summary/session-id.js +92 -0
- package/dist/run-summary/share.d.ts +1 -1
- package/dist/run-summary/share.js +2 -2
- package/dist/run-summary/types.d.ts +38 -6
- package/dist/run-summary/types.js +1 -0
- package/dist/scope/acceptance-activate-gate.d.ts +1 -1
- package/dist/scope/acceptance-activate-gate.js +4 -4
- package/dist/scope/acceptance-evidence.d.ts +27 -0
- package/dist/scope/acceptance-evidence.js +66 -0
- package/dist/scope/capacity-stamp.d.ts +2 -0
- package/dist/scope/capacity-stamp.js +4 -0
- package/dist/scope/delivery-evidence.d.ts +9 -3
- package/dist/scope/delivery-evidence.js +39 -27
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/parent-lineage.js +7 -6
- package/dist/scope/project-invariant-coverage.d.ts +54 -0
- package/dist/scope/project-invariant-coverage.js +322 -0
- package/dist/scope/transition.js +66 -4
- package/dist/scope-provenance/compare-intent.d.ts +27 -0
- package/dist/scope-provenance/compare-intent.js +233 -0
- package/dist/scope-provenance/digest.d.ts +18 -1
- package/dist/scope-provenance/digest.js +16 -11
- package/dist/scope-provenance/evaluate.d.ts +5 -1
- package/dist/scope-provenance/evaluate.js +64 -4
- package/dist/scope-provenance/extract-intent.d.ts +43 -0
- package/dist/scope-provenance/extract-intent.js +262 -0
- package/dist/scope-provenance/index.d.ts +7 -1
- package/dist/scope-provenance/index.js +7 -1
- package/dist/scope-provenance/intent-digest.d.ts +12 -0
- package/dist/scope-provenance/intent-digest.js +33 -0
- package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
- package/dist/scope-provenance/intent-evaluate.js +149 -0
- package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
- package/dist/scope-provenance/json-tokenizer.js +225 -0
- package/dist/scope-provenance/known-machine.d.ts +38 -0
- package/dist/scope-provenance/known-machine.js +157 -0
- package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
- package/dist/scope-provenance/mint-artifacts.js +425 -0
- package/dist/session/ac-pass-banking.d.ts +4 -36
- package/dist/session/ac-pass-banking.js +30 -77
- package/dist/session/ac-pass-reuse.d.ts +53 -0
- package/dist/session/ac-pass-reuse.js +95 -0
- package/dist/session/ceremony-dial-evidence.d.ts +42 -0
- package/dist/session/ceremony-dial-evidence.js +194 -0
- package/dist/session/git.js +19 -4
- package/dist/session/index.d.ts +5 -0
- package/dist/session/index.js +5 -0
- package/dist/session/occupancy-stress-worker.d.ts +7 -0
- package/dist/session/occupancy-stress-worker.js +36 -0
- package/dist/session/occupancy.d.ts +97 -0
- package/dist/session/occupancy.js +422 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
- package/dist/session/product-state-hash.d.ts +26 -0
- package/dist/session/product-state-hash.js +683 -0
- package/dist/session/session-ready.d.ts +2 -0
- package/dist/session/session-ready.js +52 -30
- package/dist/session/session-start.d.ts +7 -0
- package/dist/session/session-start.js +88 -5
- package/dist/session/verify-ac-session-cache.d.ts +42 -0
- package/dist/session/verify-ac-session-cache.js +126 -0
- package/dist/session/verify-session-ritual.d.ts +5 -0
- package/dist/session/verify-session-ritual.js +9 -0
- package/dist/slash/openclaw-deposit.js +13 -9
- package/dist/slice/constants.d.ts +2 -2
- package/dist/slice/constants.js +2 -2
- package/dist/slice/existing.js +21 -4
- package/dist/slice/lock.d.ts +16 -1
- package/dist/slice/lock.js +137 -11
- package/dist/slice/record.d.ts +8 -5
- package/dist/slice/record.js +21 -13
- package/dist/story-ready/evaluate.d.ts +2 -0
- package/dist/story-ready/evaluate.js +13 -0
- package/dist/swarm/complete-cohort.d.ts +3 -0
- package/dist/swarm/complete-cohort.js +59 -11
- package/dist/swarm/finalize-cohort.js +33 -2
- package/dist/swarm/launch.d.ts +22 -0
- package/dist/swarm/launch.js +155 -0
- package/dist/swarm/routing-set-cli.d.ts +11 -1
- package/dist/swarm/routing-set-cli.js +13 -3
- package/dist/swarm/routing-verify.d.ts +12 -0
- package/dist/swarm/routing-verify.js +59 -20
- package/dist/swarm/routing.d.ts +14 -0
- package/dist/swarm/routing.js +53 -3
- package/dist/telemetry-coverage/evaluate.d.ts +42 -0
- package/dist/telemetry-coverage/evaluate.js +148 -0
- package/dist/telemetry-coverage/fake-trial.d.ts +45 -0
- package/dist/telemetry-coverage/fake-trial.js +204 -0
- package/dist/telemetry-coverage/index.d.ts +5 -0
- package/dist/telemetry-coverage/index.js +5 -0
- package/dist/telemetry-coverage/kinds.d.ts +19 -0
- package/dist/telemetry-coverage/kinds.js +54 -0
- package/dist/telemetry-coverage/scan-callers.d.ts +25 -0
- package/dist/telemetry-coverage/scan-callers.js +168 -0
- package/dist/triage/actions/index.js +4 -1
- package/dist/ts-check-lane/index.d.ts +3 -0
- package/dist/ts-check-lane/index.js +2 -0
- package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
- package/dist/ts-check-lane/progress-reporter.js +38 -0
- package/dist/ts-check-lane/progress.d.ts +32 -0
- package/dist/ts-check-lane/progress.js +57 -0
- package/dist/ts-check-lane/run-lane.d.ts +3 -1
- package/dist/ts-check-lane/run-lane.js +8 -6
- package/dist/validate-content/validate-links.d.ts +8 -0
- package/dist/validate-content/validate-links.js +9 -15
- package/dist/value/feedback-file.js +12 -2
- package/dist/vbrief-build/index.d.ts +1 -0
- package/dist/vbrief-build/index.js +1 -0
- package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
- package/dist/vbrief-build/project-invariants-io.js +41 -0
- package/dist/vbrief-reconcile/graph.js +18 -1
- package/dist/vbrief-reconcile/index.d.ts +3 -1
- package/dist/vbrief-reconcile/index.js +2 -1
- package/dist/vbrief-reconcile/origin-freshness.d.ts +44 -0
- package/dist/vbrief-reconcile/origin-freshness.js +221 -0
- package/dist/vbrief-reconcile/types.d.ts +13 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
- package/dist/vbrief-reconcile/umbrellas.js +261 -26
- package/dist/vbrief-validate/index.d.ts +1 -1
- package/dist/vbrief-validate/index.js +1 -1
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +68 -0
- package/dist/verify-ac/evaluate.d.ts +7 -1
- package/dist/verify-ac/evaluate.js +34 -26
- package/dist/verify-ac/flag.d.ts +4 -0
- package/dist/verify-ac/flag.js +16 -0
- package/dist/verify-ac/index.d.ts +2 -2
- package/dist/verify-ac/index.js +2 -2
- package/dist/verify-source/contained-writes.js +2 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/verify-stubs.d.ts +15 -1
- package/dist/verify-source/verify-stubs.js +20 -16
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
- package/dist/xbrief-migrate/migrate-project.js +2 -5
- package/package.json +3 -3
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { extractReferencesFromVbrief, GITHUB_ISSUE_REF_TYPES, parseIssueNumber, } from "../intake/reconcile-issues.js";
|
|
2
|
+
import { call } from "../scm/call.js";
|
|
3
|
+
const ISSUE_URL_PATTERN = /https:\/\/github\.com\/([^/\s]+)\/([^/\s]+)\/issues\/(\d+)/g;
|
|
4
|
+
const ISSUE_API_URL_PATTERN = /https:\/\/api\.github\.com\/repos\/([^/\s]+)\/([^/\s]+)\/issues\/(\d+)/g;
|
|
5
|
+
export const ORIGIN_FRESHNESS_REMEDIATION = "Re-read the issue body and comments (#2143), then either refresh the brief or record intentional divergence and bump xBRIEFInfo.updated. Do not auto-write origin text onto the brief (#309 D12).";
|
|
6
|
+
export function briefUpdatedOf(payload) {
|
|
7
|
+
const info = payload.xBRIEFInfo;
|
|
8
|
+
if (info !== null && typeof info === "object" && !Array.isArray(info)) {
|
|
9
|
+
const updated = info.updated;
|
|
10
|
+
if (typeof updated === "string" && updated.trim().length > 0) {
|
|
11
|
+
return updated.trim();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const plan = payload.plan;
|
|
15
|
+
if (plan !== null && typeof plan === "object" && !Array.isArray(plan)) {
|
|
16
|
+
const updated = plan.updated;
|
|
17
|
+
if (typeof updated === "string" && updated.trim().length > 0) {
|
|
18
|
+
return updated.trim();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
export function extractGithubIssueOrigins(payload) {
|
|
24
|
+
const origins = [];
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
const add = (origin) => {
|
|
27
|
+
const key = `${origin.owner}/${origin.repo}#${origin.number}`;
|
|
28
|
+
if (seen.has(key)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
seen.add(key);
|
|
32
|
+
origins.push(origin);
|
|
33
|
+
};
|
|
34
|
+
for (const rec of extractReferencesFromVbrief(payload)) {
|
|
35
|
+
const type = String(rec.type ?? "");
|
|
36
|
+
if (!GITHUB_ISSUE_REF_TYPES.has(type)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const fromUri = originFromUri(rec);
|
|
40
|
+
let origin = fromUri !== null ? { ...fromUri, type } : null;
|
|
41
|
+
if (origin === null) {
|
|
42
|
+
const number = parseIssueNumber(rec);
|
|
43
|
+
if (number === null) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
origin = {
|
|
47
|
+
owner: "",
|
|
48
|
+
repo: "",
|
|
49
|
+
number,
|
|
50
|
+
uri: typeof rec.uri === "string" ? rec.uri : "",
|
|
51
|
+
type,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
add(origin);
|
|
55
|
+
}
|
|
56
|
+
for (const text of textualOriginFields(payload)) {
|
|
57
|
+
for (const fromText of originsFromText(text)) {
|
|
58
|
+
add({ ...fromText, type: "x-xbrief/github-issue" });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return origins;
|
|
62
|
+
}
|
|
63
|
+
export function extractGithubIssueOrigin(payload) {
|
|
64
|
+
return extractGithubIssueOrigins(payload)[0] ?? null;
|
|
65
|
+
}
|
|
66
|
+
function originFromUri(ref) {
|
|
67
|
+
for (const key of ["uri", "url"]) {
|
|
68
|
+
const value = ref[key];
|
|
69
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const parsed = originFromText(value);
|
|
73
|
+
if (parsed !== null) {
|
|
74
|
+
return parsed;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function originsFromText(text) {
|
|
80
|
+
const origins = [];
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
for (const pattern of [ISSUE_URL_PATTERN, ISSUE_API_URL_PATTERN]) {
|
|
83
|
+
for (const match of text.matchAll(pattern)) {
|
|
84
|
+
if (!match[1] || !match[2] || !match[3]) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const owner = match[1];
|
|
88
|
+
const repo = match[2];
|
|
89
|
+
const number = Number.parseInt(match[3], 10);
|
|
90
|
+
const key = `${owner}/${repo}#${number}`;
|
|
91
|
+
if (seen.has(key)) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
seen.add(key);
|
|
95
|
+
origins.push({
|
|
96
|
+
owner,
|
|
97
|
+
repo,
|
|
98
|
+
number,
|
|
99
|
+
uri: `https://github.com/${owner}/${repo}/issues/${number}`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return origins;
|
|
104
|
+
}
|
|
105
|
+
function originFromText(text) {
|
|
106
|
+
return originsFromText(text)[0] ?? null;
|
|
107
|
+
}
|
|
108
|
+
function textualOriginFields(payload) {
|
|
109
|
+
const texts = [];
|
|
110
|
+
const plan = payload.plan;
|
|
111
|
+
if (plan !== null && typeof plan === "object" && !Array.isArray(plan)) {
|
|
112
|
+
const narratives = plan.narratives;
|
|
113
|
+
if (narratives !== null && typeof narratives === "object" && !Array.isArray(narratives)) {
|
|
114
|
+
const origin = narratives.Origin;
|
|
115
|
+
if (typeof origin === "string" && origin.length > 0) {
|
|
116
|
+
texts.push(origin);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const key of ["xBRIEFInfo", "vBRIEFInfo"]) {
|
|
121
|
+
const info = payload[key];
|
|
122
|
+
if (info !== null && typeof info === "object" && !Array.isArray(info)) {
|
|
123
|
+
const description = info.description;
|
|
124
|
+
if (typeof description === "string" && description.length > 0) {
|
|
125
|
+
texts.push(description);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return texts;
|
|
130
|
+
}
|
|
131
|
+
export function compareOriginFreshness(briefUpdated, originUpdatedAt) {
|
|
132
|
+
if (originUpdatedAt === null || originUpdatedAt.trim().length === 0) {
|
|
133
|
+
return "uncomparable";
|
|
134
|
+
}
|
|
135
|
+
if (briefUpdated === null || briefUpdated.trim().length === 0) {
|
|
136
|
+
return "stale";
|
|
137
|
+
}
|
|
138
|
+
const briefMs = Date.parse(briefUpdated);
|
|
139
|
+
const originMs = Date.parse(originUpdatedAt);
|
|
140
|
+
if (Number.isNaN(briefMs) || Number.isNaN(originMs)) {
|
|
141
|
+
return "uncomparable";
|
|
142
|
+
}
|
|
143
|
+
return originMs > briefMs ? "stale" : "current";
|
|
144
|
+
}
|
|
145
|
+
export function formatOriginStaleMessage(origin, originUpdatedAt, briefUpdated) {
|
|
146
|
+
const slug = origin.owner.length > 0 ? `${origin.owner}/${origin.repo}` : "origin";
|
|
147
|
+
const briefStamp = briefUpdated ?? "(missing)";
|
|
148
|
+
return (`Origin GitHub issue #${origin.number} (${slug}) is newer than this xBRIEF ` +
|
|
149
|
+
`(origin updated_at=${originUpdatedAt}, brief updated=${briefStamp}). ` +
|
|
150
|
+
ORIGIN_FRESHNESS_REMEDIATION);
|
|
151
|
+
}
|
|
152
|
+
export function fetchGithubIssueUpdatedAt(origin, options = {}) {
|
|
153
|
+
if (origin.owner.length === 0 || origin.repo.length === 0) {
|
|
154
|
+
return { error: "origin reference is missing owner/repo in the GitHub issue URI" };
|
|
155
|
+
}
|
|
156
|
+
const scmCall = options.scmCall ?? call;
|
|
157
|
+
let result;
|
|
158
|
+
try {
|
|
159
|
+
result = scmCall("github-issue", "api", [`repos/${origin.owner}/${origin.repo}/issues/${origin.number}`], { timeout: 30, cwd: options.cwd });
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
163
|
+
return { error: `gh CLI not available (${message})` };
|
|
164
|
+
}
|
|
165
|
+
if (result.returncode !== 0) {
|
|
166
|
+
const stderr = result.stderr.trim();
|
|
167
|
+
return { error: stderr.length > 0 ? stderr : `gh api exited ${result.returncode}` };
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const payload = JSON.parse(result.stdout);
|
|
171
|
+
const updatedAt = payload.updated_at;
|
|
172
|
+
if (typeof updatedAt !== "string" || updatedAt.trim().length === 0) {
|
|
173
|
+
return { error: "origin issue payload lacks updated_at" };
|
|
174
|
+
}
|
|
175
|
+
return { updatedAt: updatedAt.trim() };
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return { error: "origin issue payload is not valid JSON" };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
export function evaluateOriginFreshness(payload, options = {}) {
|
|
182
|
+
if (options.skip === true) {
|
|
183
|
+
return { ok: true, kind: "no-origin" };
|
|
184
|
+
}
|
|
185
|
+
const origins = extractGithubIssueOrigins(payload);
|
|
186
|
+
if (origins.length === 0) {
|
|
187
|
+
return { ok: true, kind: "no-origin" };
|
|
188
|
+
}
|
|
189
|
+
const fetch = options.fetchOriginUpdatedAt ??
|
|
190
|
+
((next) => fetchGithubIssueUpdatedAt(next, { cwd: options.cwd }));
|
|
191
|
+
const briefUpdated = briefUpdatedOf(payload);
|
|
192
|
+
for (const origin of origins) {
|
|
193
|
+
const fetched = fetch(origin);
|
|
194
|
+
if ("error" in fetched) {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
kind: "uncomparable",
|
|
198
|
+
message: `Could not fetch origin GitHub issue #${origin.number} for freshness check: ${fetched.error}. ` +
|
|
199
|
+
ORIGIN_FRESHNESS_REMEDIATION,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const kind = compareOriginFreshness(briefUpdated, fetched.updatedAt);
|
|
203
|
+
if (kind === "stale") {
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
kind,
|
|
207
|
+
message: formatOriginStaleMessage(origin, fetched.updatedAt, briefUpdated),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (kind === "uncomparable") {
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
kind,
|
|
214
|
+
message: `Could not compare origin GitHub issue #${origin.number} updated_at=${fetched.updatedAt} ` +
|
|
215
|
+
`to brief updated=${briefUpdated ?? "(missing)"}. ${ORIGIN_FRESHNESS_REMEDIATION}`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return { ok: true, kind: "current" };
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=origin-freshness.js.map
|
|
@@ -54,6 +54,11 @@ export interface Candidate {
|
|
|
54
54
|
}
|
|
55
55
|
export interface ReconcileGraphOutcome {
|
|
56
56
|
promoted: string[];
|
|
57
|
+
/** Successful promote messages, including refused-stamp remediation (#3398). */
|
|
58
|
+
promotedNotices?: Array<{
|
|
59
|
+
story_id: string;
|
|
60
|
+
message: string;
|
|
61
|
+
}>;
|
|
57
62
|
deferredWip: string[];
|
|
58
63
|
waiting: Array<{
|
|
59
64
|
story_id: string;
|
|
@@ -110,6 +115,12 @@ export interface UmbrellaChange {
|
|
|
110
115
|
* `unchanged` when already correct, `skipped` when body APIs unavailable.
|
|
111
116
|
*/
|
|
112
117
|
readonly checklist_action?: "edited" | "unchanged" | "skipped";
|
|
118
|
+
/**
|
|
119
|
+
* slices.jsonl expected_close_signal close (#3428): `closed` when the
|
|
120
|
+
* umbrella was closed this run, `unchanged` when already closed under
|
|
121
|
+
* all-children-merged, `skipped` when the signal is not a close.
|
|
122
|
+
*/
|
|
123
|
+
readonly close_action?: "closed" | "unchanged" | "skipped";
|
|
113
124
|
}
|
|
114
125
|
export interface ReconcileUmbrellasOutcome {
|
|
115
126
|
changed: UmbrellaChange[];
|
|
@@ -138,5 +149,7 @@ export interface UmbrellaClient {
|
|
|
138
149
|
fetchIssueBody?(repo: string, issueNumber: number): string;
|
|
139
150
|
/** Optional: write reconciled checklist body (#1649). */
|
|
140
151
|
editIssueBody?(repo: string, issueNumber: number, body: string): void;
|
|
152
|
+
/** Optional: close an umbrella via REST (#3428). */
|
|
153
|
+
closeIssue?(repo: string, issueNumber: number): void;
|
|
141
154
|
}
|
|
142
155
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -63,8 +63,22 @@ export declare class ScmUmbrellaClient implements UmbrellaClient {
|
|
|
63
63
|
fetchIssueStates(repo: string, issueNumbers: readonly number[]): ReadonlyMap<number, ForgeIssueState>;
|
|
64
64
|
fetchIssueBody(repo: string, issueNumber: number): string;
|
|
65
65
|
editIssueBody(repo: string, issueNumber: number, body: string): void;
|
|
66
|
+
closeIssue(repo: string, issueNumber: number): void;
|
|
66
67
|
}
|
|
67
68
|
export declare function nowIso(date?: Date): string;
|
|
69
|
+
/** Children listed on a slices.jsonl row (#3428). Last write of a duplicate `n` wins. */
|
|
70
|
+
export declare function childrenFromSliceRecord(record: Record<string, unknown>): Child[];
|
|
71
|
+
/**
|
|
72
|
+
* Close only when the slice signal is all-children-merged, the umbrella is
|
|
73
|
+
* still open, and every child has forge state closed (#3428).
|
|
74
|
+
*/
|
|
75
|
+
export declare function shouldCloseOnAllChildrenMerged(options: {
|
|
76
|
+
readonly signal: unknown;
|
|
77
|
+
readonly children: readonly Child[];
|
|
78
|
+
readonly forgeStates: ReadonlyMap<number, ForgeIssueState> | null | undefined;
|
|
79
|
+
readonly umbrellaState: ForgeIssueState | null | undefined;
|
|
80
|
+
}): boolean;
|
|
81
|
+
export declare function formatCloseComment(signal: string, children: readonly Child[]): string;
|
|
68
82
|
export interface ReconcileUmbrellasOptions {
|
|
69
83
|
readonly repo?: string | null;
|
|
70
84
|
readonly dryRun?: boolean;
|
|
@@ -5,6 +5,8 @@ import { createIssueComment, editIssueBody, editIssueCommentBody, fetchIssueBody
|
|
|
5
5
|
import { fetchIssueStates, IssueState } from "../intake/reconcile-issues.js";
|
|
6
6
|
import { hasArtifactSuffix, resolveLifecycleRoot, stripArtifactSuffix } from "../layout/resolve.js";
|
|
7
7
|
import { call } from "../scm/call.js";
|
|
8
|
+
import { restCloseIssue } from "../scm/gh-rest.js";
|
|
9
|
+
import { readAll } from "../slice/record.js";
|
|
8
10
|
import { extractIssueRef, parseGithubIssueUri } from "../triage/reconcile/parse-uri.js";
|
|
9
11
|
import { isRepoMutationAllowed } from "./repo-guard.js";
|
|
10
12
|
export const OPEN_FOLDERS = ["proposed", "pending", "active"];
|
|
@@ -307,33 +309,43 @@ export function classifyPassType(prevTotal, total) {
|
|
|
307
309
|
function hasCurrentShape(body) {
|
|
308
310
|
return HEADER_RE.test(body);
|
|
309
311
|
}
|
|
312
|
+
function parseCommentPage(data) {
|
|
313
|
+
if (!Array.isArray(data))
|
|
314
|
+
return [];
|
|
315
|
+
const comments = [];
|
|
316
|
+
for (const entry of data) {
|
|
317
|
+
if (typeof entry === "object" &&
|
|
318
|
+
entry !== null &&
|
|
319
|
+
!Array.isArray(entry) &&
|
|
320
|
+
typeof entry.id === "number" &&
|
|
321
|
+
typeof entry.body === "string") {
|
|
322
|
+
const rec = entry;
|
|
323
|
+
comments.push({ id: rec.id, body: rec.body });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return comments;
|
|
327
|
+
}
|
|
310
328
|
export class ScmUmbrellaClient {
|
|
311
329
|
fetchComments(repo, issueNumber) {
|
|
312
|
-
const proc = call(SCM_SOURCE, "api", [
|
|
313
|
-
`repos/${repo}/issues/${issueNumber}/comments?per_page=100`,
|
|
314
|
-
]);
|
|
315
|
-
if (proc.returncode !== 0) {
|
|
316
|
-
throw new UmbrellaScmError(`list comments #${issueNumber} (${repo}) failed: ${(proc.stderr || "").trim()}`);
|
|
317
|
-
}
|
|
318
|
-
let data;
|
|
319
|
-
try {
|
|
320
|
-
data = JSON.parse(proc.stdout || "[]");
|
|
321
|
-
}
|
|
322
|
-
catch (exc) {
|
|
323
|
-
throw new UmbrellaScmError(`list comments #${issueNumber} (${repo}) returned non-JSON: ${String(exc)}`);
|
|
324
|
-
}
|
|
325
|
-
if (!Array.isArray(data))
|
|
326
|
-
return [];
|
|
327
330
|
const comments = [];
|
|
328
|
-
for (
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
331
|
+
for (let page = 1; page <= 50; page += 1) {
|
|
332
|
+
const proc = call(SCM_SOURCE, "api", [
|
|
333
|
+
`repos/${repo}/issues/${issueNumber}/comments?per_page=100&page=${page}`,
|
|
334
|
+
]);
|
|
335
|
+
if (proc.returncode !== 0) {
|
|
336
|
+
throw new UmbrellaScmError(`list comments #${issueNumber} (${repo}) failed: ${(proc.stderr || "").trim()}`);
|
|
337
|
+
}
|
|
338
|
+
let data;
|
|
339
|
+
try {
|
|
340
|
+
data = JSON.parse(proc.stdout || "[]");
|
|
341
|
+
}
|
|
342
|
+
catch (exc) {
|
|
343
|
+
throw new UmbrellaScmError(`list comments #${issueNumber} (${repo}) returned non-JSON: ${String(exc)}`);
|
|
336
344
|
}
|
|
345
|
+
const batch = parseCommentPage(data);
|
|
346
|
+
comments.push(...batch);
|
|
347
|
+
if (batch.length < 100)
|
|
348
|
+
break;
|
|
337
349
|
}
|
|
338
350
|
return comments;
|
|
339
351
|
}
|
|
@@ -394,6 +406,15 @@ export class ScmUmbrellaClient {
|
|
|
394
406
|
throw new UmbrellaScmError(`edit issue body #${issueNumber} (${repo}) failed: ${message}`);
|
|
395
407
|
}
|
|
396
408
|
}
|
|
409
|
+
closeIssue(repo, issueNumber) {
|
|
410
|
+
try {
|
|
411
|
+
restCloseIssue(repo, issueNumber, "completed");
|
|
412
|
+
}
|
|
413
|
+
catch (exc) {
|
|
414
|
+
const message = exc instanceof Error ? exc.message : String(exc);
|
|
415
|
+
throw new UmbrellaScmError(`close issue #${issueNumber} (${repo}) failed: ${message}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
397
418
|
}
|
|
398
419
|
function planShape(epicData, index, options = {}) {
|
|
399
420
|
const children = computeChildren(epicData, index, {
|
|
@@ -545,6 +566,147 @@ function reconcileOneEpic(epicData, index, options) {
|
|
|
545
566
|
export function nowIso(date = new Date()) {
|
|
546
567
|
return date.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
547
568
|
}
|
|
569
|
+
const ALL_CHILDREN_MERGED = "all-children-merged";
|
|
570
|
+
/** Children listed on a slices.jsonl row (#3428). Last write of a duplicate `n` wins. */
|
|
571
|
+
export function childrenFromSliceRecord(record) {
|
|
572
|
+
const raw = record.children;
|
|
573
|
+
if (!Array.isArray(raw))
|
|
574
|
+
return [];
|
|
575
|
+
const byNumber = new Map();
|
|
576
|
+
for (const entry of raw) {
|
|
577
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
578
|
+
continue;
|
|
579
|
+
const rec = entry;
|
|
580
|
+
const n = rec.n;
|
|
581
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1)
|
|
582
|
+
continue;
|
|
583
|
+
byNumber.set(n, {
|
|
584
|
+
story_id: `#${n}`,
|
|
585
|
+
title: `#${n}`,
|
|
586
|
+
kind: "story",
|
|
587
|
+
folder: UNKNOWN_FOLDER,
|
|
588
|
+
depends_on: [],
|
|
589
|
+
issue_number: n,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
return [...byNumber.values()];
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Close only when the slice signal is all-children-merged, the umbrella is
|
|
596
|
+
* still open, and every child has forge state closed (#3428).
|
|
597
|
+
*/
|
|
598
|
+
export function shouldCloseOnAllChildrenMerged(options) {
|
|
599
|
+
if (options.signal !== ALL_CHILDREN_MERGED)
|
|
600
|
+
return false;
|
|
601
|
+
if (options.umbrellaState !== "open")
|
|
602
|
+
return false;
|
|
603
|
+
if (options.children.length === 0)
|
|
604
|
+
return false;
|
|
605
|
+
if (options.forgeStates == null)
|
|
606
|
+
return false;
|
|
607
|
+
return options.children.every((c) => typeof c.issue_number === "number" && options.forgeStates?.get(c.issue_number) === "closed");
|
|
608
|
+
}
|
|
609
|
+
export function formatCloseComment(signal, children) {
|
|
610
|
+
const nums = children
|
|
611
|
+
.map((c) => c.issue_number)
|
|
612
|
+
.filter((n) => typeof n === "number")
|
|
613
|
+
.sort((a, b) => a - b)
|
|
614
|
+
.map((n) => `#${n}`);
|
|
615
|
+
return `expected_close_signal=${signal}; children: ${nums.join(", ")}`;
|
|
616
|
+
}
|
|
617
|
+
function loadLatestSliceRecords(vbriefDir) {
|
|
618
|
+
const logPath = join(vbriefDir, ".triage-cache", "slices.jsonl");
|
|
619
|
+
if (!existsSync(logPath))
|
|
620
|
+
return [];
|
|
621
|
+
const latest = new Map();
|
|
622
|
+
for (const rec of readAll({ path: logPath })) {
|
|
623
|
+
const n = rec.umbrella;
|
|
624
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1)
|
|
625
|
+
continue;
|
|
626
|
+
const [urlRepo] = parseGithubIssueUri(rec.umbrella_url);
|
|
627
|
+
latest.set(`${urlRepo ?? ""}:${n}`, rec);
|
|
628
|
+
}
|
|
629
|
+
return [...latest.values()];
|
|
630
|
+
}
|
|
631
|
+
function epicDataFromSlice(repo, number, children) {
|
|
632
|
+
const refs = [
|
|
633
|
+
{ type: "x-xbrief/github-issue", uri: `https://github.com/${repo}/issues/${number}` },
|
|
634
|
+
...children.map((c) => ({
|
|
635
|
+
type: "x-xbrief/github-issue",
|
|
636
|
+
uri: `https://github.com/${repo}/issues/${c.issue_number}`,
|
|
637
|
+
title: c.title,
|
|
638
|
+
})),
|
|
639
|
+
];
|
|
640
|
+
return {
|
|
641
|
+
plan: {
|
|
642
|
+
id: `slice-umbrella-${number}`,
|
|
643
|
+
metadata: { kind: "epic" },
|
|
644
|
+
references: refs,
|
|
645
|
+
},
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function maybeCloseSliceUmbrella(options) {
|
|
649
|
+
if (typeof options.client.closeIssue !== "function")
|
|
650
|
+
return "skipped";
|
|
651
|
+
if (typeof options.client.fetchIssueStates !== "function")
|
|
652
|
+
return "skipped";
|
|
653
|
+
const mutateGate = isRepoMutationAllowed(options.repo, options.projectRoot, {
|
|
654
|
+
allowCrossRepo: options.allowCrossRepo,
|
|
655
|
+
allowlist: options.repoAllowlist,
|
|
656
|
+
explicitRepo: options.explicitRepo ?? null,
|
|
657
|
+
});
|
|
658
|
+
if (!mutateGate.allowed) {
|
|
659
|
+
throw new Error(mutateGate.reason ?? `refusing cross-repo mutation on ${options.repo}`);
|
|
660
|
+
}
|
|
661
|
+
const childNumbers = options.children
|
|
662
|
+
.map((c) => c.issue_number)
|
|
663
|
+
.filter((n) => typeof n === "number");
|
|
664
|
+
const states = options.client.fetchIssueStates(options.repo, [...childNumbers, options.number]);
|
|
665
|
+
const umbrellaState = states.get(options.number) ?? null;
|
|
666
|
+
if (!shouldCloseOnAllChildrenMerged({
|
|
667
|
+
signal: options.signal,
|
|
668
|
+
children: options.children,
|
|
669
|
+
forgeStates: states,
|
|
670
|
+
umbrellaState,
|
|
671
|
+
})) {
|
|
672
|
+
return options.signal === ALL_CHILDREN_MERGED && umbrellaState === "closed"
|
|
673
|
+
? "unchanged"
|
|
674
|
+
: "skipped";
|
|
675
|
+
}
|
|
676
|
+
const closeBody = formatCloseComment(ALL_CHILDREN_MERGED, options.children);
|
|
677
|
+
if (!options.dryRun) {
|
|
678
|
+
const alreadyCommented = options.client
|
|
679
|
+
.fetchComments(options.repo, options.number)
|
|
680
|
+
.some((c) => c.body.trim() === closeBody);
|
|
681
|
+
if (!alreadyCommented) {
|
|
682
|
+
options.client.createComment(options.repo, options.number, closeBody);
|
|
683
|
+
}
|
|
684
|
+
options.client.closeIssue(options.repo, options.number);
|
|
685
|
+
}
|
|
686
|
+
return "closed";
|
|
687
|
+
}
|
|
688
|
+
function pushUmbrellaChange(outcome, change) {
|
|
689
|
+
if (change.action === "unchanged" && change.close_action !== "closed") {
|
|
690
|
+
outcome.unchanged.push(change);
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
outcome.changed.push(change);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function replaceCloseAction(outcome, repo, issueNumber, closeAction) {
|
|
697
|
+
const match = (c) => c.repo === repo && c.issue_number === issueNumber;
|
|
698
|
+
for (const list of [outcome.changed, outcome.unchanged]) {
|
|
699
|
+
const idx = list.findIndex(match);
|
|
700
|
+
if (idx < 0)
|
|
701
|
+
continue;
|
|
702
|
+
const prev = list[idx];
|
|
703
|
+
if (prev === undefined)
|
|
704
|
+
return;
|
|
705
|
+
list.splice(idx, 1);
|
|
706
|
+
pushUmbrellaChange(outcome, { ...prev, close_action: closeAction });
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
548
710
|
export function reconcileUmbrellas(projectRoot, options = {}) {
|
|
549
711
|
const root = resolve(projectRoot);
|
|
550
712
|
let vbriefDir;
|
|
@@ -616,7 +778,6 @@ export function reconcileUmbrellas(projectRoot, options = {}) {
|
|
|
616
778
|
const key = `${effectiveRepo}:${number}`;
|
|
617
779
|
if (seenIssues.has(key))
|
|
618
780
|
continue;
|
|
619
|
-
seenIssues.add(key);
|
|
620
781
|
try {
|
|
621
782
|
const change = reconcileOneEpic(data, index, {
|
|
622
783
|
storyId,
|
|
@@ -630,6 +791,7 @@ export function reconcileUmbrellas(projectRoot, options = {}) {
|
|
|
630
791
|
repoAllowlist: options.repoAllowlist,
|
|
631
792
|
explicitRepo: options.repo ?? null,
|
|
632
793
|
});
|
|
794
|
+
seenIssues.add(key);
|
|
633
795
|
if (change.action === "unchanged")
|
|
634
796
|
outcome.unchanged.push(change);
|
|
635
797
|
else
|
|
@@ -643,6 +805,71 @@ export function reconcileUmbrellas(projectRoot, options = {}) {
|
|
|
643
805
|
}
|
|
644
806
|
}
|
|
645
807
|
}
|
|
808
|
+
// slices.jsonl umbrellas have no epic xBRIEF (#3428). Last row per umbrella wins.
|
|
809
|
+
for (const record of loadLatestSliceRecords(vbriefDir)) {
|
|
810
|
+
const number = record.umbrella;
|
|
811
|
+
if (typeof number !== "number")
|
|
812
|
+
continue;
|
|
813
|
+
const [urlRepo] = parseGithubIssueUri(record.umbrella_url);
|
|
814
|
+
const effectiveRepo = urlRepo ?? options.repo ?? null;
|
|
815
|
+
if (effectiveRepo === null) {
|
|
816
|
+
outcome.skipped_no_ref.push(`slice-umbrella-${number}`);
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
const key = `${effectiveRepo}:${number}`;
|
|
820
|
+
const children = childrenFromSliceRecord(record);
|
|
821
|
+
const storyId = `slice-umbrella-${number}`;
|
|
822
|
+
try {
|
|
823
|
+
if (seenIssues.has(key)) {
|
|
824
|
+
const closeAction = maybeCloseSliceUmbrella({
|
|
825
|
+
signal: record.expected_close_signal,
|
|
826
|
+
children,
|
|
827
|
+
repo: effectiveRepo,
|
|
828
|
+
number,
|
|
829
|
+
client,
|
|
830
|
+
dryRun: options.dryRun ?? false,
|
|
831
|
+
projectRoot: root,
|
|
832
|
+
allowCrossRepo: options.allowCrossRepo ?? false,
|
|
833
|
+
repoAllowlist: options.repoAllowlist,
|
|
834
|
+
explicitRepo: options.repo ?? null,
|
|
835
|
+
});
|
|
836
|
+
replaceCloseAction(outcome, effectiveRepo, number, closeAction);
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
const change = reconcileOneEpic(epicDataFromSlice(effectiveRepo, number, children), index, {
|
|
840
|
+
storyId,
|
|
841
|
+
repo: effectiveRepo,
|
|
842
|
+
number,
|
|
843
|
+
client,
|
|
844
|
+
dryRun: options.dryRun ?? false,
|
|
845
|
+
now,
|
|
846
|
+
projectRoot: root,
|
|
847
|
+
allowCrossRepo: options.allowCrossRepo ?? false,
|
|
848
|
+
repoAllowlist: options.repoAllowlist,
|
|
849
|
+
explicitRepo: options.repo ?? null,
|
|
850
|
+
});
|
|
851
|
+
seenIssues.add(key);
|
|
852
|
+
const closeAction = maybeCloseSliceUmbrella({
|
|
853
|
+
signal: record.expected_close_signal,
|
|
854
|
+
children,
|
|
855
|
+
repo: effectiveRepo,
|
|
856
|
+
number,
|
|
857
|
+
client,
|
|
858
|
+
dryRun: options.dryRun ?? false,
|
|
859
|
+
projectRoot: root,
|
|
860
|
+
allowCrossRepo: options.allowCrossRepo ?? false,
|
|
861
|
+
repoAllowlist: options.repoAllowlist,
|
|
862
|
+
explicitRepo: options.repo ?? null,
|
|
863
|
+
});
|
|
864
|
+
pushUmbrellaChange(outcome, { ...change, close_action: closeAction });
|
|
865
|
+
}
|
|
866
|
+
catch (exc) {
|
|
867
|
+
outcome.errors.push({
|
|
868
|
+
story_id: storyId,
|
|
869
|
+
message: exc instanceof Error ? exc.message : String(exc),
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
}
|
|
646
873
|
return [outcome.errors.length > 0 ? 1 : 0, outcome];
|
|
647
874
|
}
|
|
648
875
|
function formatChecklistSuffix(action) {
|
|
@@ -650,6 +877,11 @@ function formatChecklistSuffix(action) {
|
|
|
650
877
|
return "";
|
|
651
878
|
return `; checklist=${action}`;
|
|
652
879
|
}
|
|
880
|
+
function formatCloseSuffix(action) {
|
|
881
|
+
if (action === undefined || action === "skipped")
|
|
882
|
+
return "";
|
|
883
|
+
return `; close=${action}`;
|
|
884
|
+
}
|
|
653
885
|
export function renderUmbrellasReport(outcome) {
|
|
654
886
|
const lines = ["vBRIEF reconcile umbrellas", ""];
|
|
655
887
|
const suffix = outcome.dry_run ? " (dry-run)" : "";
|
|
@@ -657,7 +889,8 @@ export function renderUmbrellasReport(outcome) {
|
|
|
657
889
|
if (outcome.changed.length > 0) {
|
|
658
890
|
for (const c of outcome.changed) {
|
|
659
891
|
lines.push(`- #${c.issue_number} (${c.repo}) [${c.story_id}]: ${c.action} -> pass-${c.pass_n}` +
|
|
660
|
-
formatChecklistSuffix(c.checklist_action)
|
|
892
|
+
formatChecklistSuffix(c.checklist_action) +
|
|
893
|
+
formatCloseSuffix(c.close_action));
|
|
661
894
|
}
|
|
662
895
|
}
|
|
663
896
|
else {
|
|
@@ -668,7 +901,8 @@ export function renderUmbrellasReport(outcome) {
|
|
|
668
901
|
if (outcome.unchanged.length > 0) {
|
|
669
902
|
for (const c of outcome.unchanged) {
|
|
670
903
|
lines.push(`- #${c.issue_number} (${c.repo}) [${c.story_id}]: pass-${c.pass_n}` +
|
|
671
|
-
formatChecklistSuffix(c.checklist_action)
|
|
904
|
+
formatChecklistSuffix(c.checklist_action) +
|
|
905
|
+
formatCloseSuffix(c.close_action));
|
|
672
906
|
}
|
|
673
907
|
}
|
|
674
908
|
else {
|
|
@@ -696,6 +930,7 @@ export function umbrellasOutcomeToJson(outcome) {
|
|
|
696
930
|
action: c.action,
|
|
697
931
|
pass_n: c.pass_n,
|
|
698
932
|
checklist_action: c.checklist_action ?? "skipped",
|
|
933
|
+
close_action: c.close_action ?? "skipped",
|
|
699
934
|
});
|
|
700
935
|
return {
|
|
701
936
|
changed: outcome.changed.map(toChange),
|
|
@@ -6,7 +6,7 @@ export { validateFolderStatus } from "./folder-status.js";
|
|
|
6
6
|
export type { ConformanceCliOptions, ValidateCliOptions } from "./main.js";
|
|
7
7
|
export { cmdVbriefValidate, runConformance, runValidate } from "./main.js";
|
|
8
8
|
export { validateOriginProvenance } from "./origin.js";
|
|
9
|
-
export { validateSessionRitualStalenessHoursOnPlan, validateTriageRankingLabelsOnPlan, validateWipCapOnPlan, } from "./plan-hooks.js";
|
|
9
|
+
export { validateForgeOutageRetryMinutesOnPlan, validateProjectInvariantsOnPlan, validateSessionRitualStalenessHoursOnPlan, validateTriageRankingLabelsOnPlan, validateWipCapOnPlan, } from "./plan-hooks.js";
|
|
10
10
|
export { validateProjectDefinition } from "./project-definition.js";
|
|
11
11
|
export type { ExtensionEntry, ExtensionRoundtripEvaluateResult, ExtensionRoundtripFinding, } from "./roundtrip.js";
|
|
12
12
|
export { collectExtensionEntries, EXTENSION_CONFORMANCE_FIXTURES_DIR, evaluateExtensionRoundtrip, findExtensionPreservationViolations, reEmitVbriefArtifact, renderExtensionRoundtripFinding, VbriefSchemaValidationError, } from "./roundtrip.js";
|
|
@@ -4,7 +4,7 @@ export { matchesFilenameConvention, validateFilename } from "./filename.js";
|
|
|
4
4
|
export { validateFolderStatus } from "./folder-status.js";
|
|
5
5
|
export { cmdVbriefValidate, runConformance, runValidate } from "./main.js";
|
|
6
6
|
export { validateOriginProvenance } from "./origin.js";
|
|
7
|
-
export { validateSessionRitualStalenessHoursOnPlan, validateTriageRankingLabelsOnPlan, validateWipCapOnPlan, } from "./plan-hooks.js";
|
|
7
|
+
export { validateForgeOutageRetryMinutesOnPlan, validateProjectInvariantsOnPlan, validateSessionRitualStalenessHoursOnPlan, validateTriageRankingLabelsOnPlan, validateWipCapOnPlan, } from "./plan-hooks.js";
|
|
8
8
|
export { validateProjectDefinition } from "./project-definition.js";
|
|
9
9
|
export { collectExtensionEntries, EXTENSION_CONFORMANCE_FIXTURES_DIR, evaluateExtensionRoundtrip, findExtensionPreservationViolations, reEmitVbriefArtifact, renderExtensionRoundtripFinding, VbriefSchemaValidationError, } from "./roundtrip.js";
|
|
10
10
|
export { normalizeNarrativeKey, validateVbriefSchema } from "./schema.js";
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
export declare function validateWipCapOnPlan(plan: unknown, filepath: string): string[];
|
|
3
3
|
/** vbrief_validate hook for ``sessionRitualStalenessHours`` (#1348). */
|
|
4
4
|
export declare function validateSessionRitualStalenessHoursOnPlan(plan: unknown, filepath: string): string[];
|
|
5
|
+
/** vbrief_validate hook for ``projectInvariants`` (#3425). */
|
|
6
|
+
export declare function validateProjectInvariantsOnPlan(plan: unknown, filepath: string): string[];
|
|
7
|
+
/** vbrief_validate hook for ``forgeOutageRetryMinutes`` (#3422). */
|
|
8
|
+
export declare function validateForgeOutageRetryMinutesOnPlan(plan: unknown, filepath: string): string[];
|
|
5
9
|
/** vbrief_validate hook: validate ``plan.policy.triageRankingLabels`` (#1128). */
|
|
6
10
|
export declare function validateTriageRankingLabelsOnPlan(plan: unknown, filepath: string): string[];
|
|
7
11
|
/** vbrief_validate hook: validate ``plan.policy.runtimeAuthority`` (#1394). */
|