@deftai/directive-core 0.66.2 → 0.68.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/agents-md-advisory/evaluate.d.ts +44 -0
- package/dist/agents-md-advisory/evaluate.js +115 -0
- package/dist/agents-md-advisory/index.d.ts +2 -0
- package/dist/agents-md-advisory/index.js +2 -0
- package/dist/agents-md-budget/evaluate.d.ts +38 -0
- package/dist/agents-md-budget/evaluate.js +152 -0
- package/dist/agents-md-budget/index.d.ts +2 -0
- package/dist/agents-md-budget/index.js +2 -0
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +1 -1
- package/dist/check/orchestrator.d.ts +14 -4
- package/dist/check/orchestrator.js +30 -6
- package/dist/codebase/map.d.ts +1 -0
- package/dist/codebase/map.js +5 -2
- package/dist/doctor/checks.js +11 -11
- package/dist/doctor/constants.d.ts +7 -2
- package/dist/doctor/constants.js +17 -1
- package/dist/doctor/flags.js +5 -1
- package/dist/doctor/main.js +99 -8
- package/dist/doctor/payload-staleness.js +15 -6
- package/dist/doctor/types.d.ts +7 -0
- package/dist/forward-coverage/evaluate.d.ts +55 -0
- package/dist/forward-coverage/evaluate.js +254 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/constants.d.ts +14 -0
- package/dist/init-deposit/constants.js +10 -0
- package/dist/init-deposit/hygiene.d.ts +50 -0
- package/dist/init-deposit/hygiene.js +176 -0
- package/dist/init-deposit/init-deposit.d.ts +1 -0
- package/dist/init-deposit/init-deposit.js +7 -1
- package/dist/init-deposit/refresh.d.ts +2 -0
- package/dist/init-deposit/refresh.js +48 -30
- package/dist/init-deposit/scaffold.d.ts +3 -4
- package/dist/init-deposit/scaffold.js +3 -24
- package/dist/intake/github-auth-modes.d.ts +1 -0
- package/dist/intake/github-auth-modes.js +1 -0
- package/dist/intake/issue-ingest.d.ts +6 -2
- package/dist/intake/issue-ingest.js +65 -9
- package/dist/integration-e2e/helpers.d.ts +0 -1
- package/dist/integration-e2e/helpers.js +3 -7
- package/dist/layout/lifecycle-ref.d.ts +15 -0
- package/dist/layout/lifecycle-ref.js +55 -0
- package/dist/platform/agents-consumer-header.d.ts +13 -0
- package/dist/platform/agents-consumer-header.js +57 -0
- package/dist/platform/agents-md.d.ts +28 -0
- package/dist/platform/agents-md.js +38 -1
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/policy/agents-md-advisory.d.ts +52 -0
- package/dist/policy/agents-md-advisory.js +63 -0
- package/dist/policy/agents-md-budget.d.ts +24 -0
- package/dist/policy/agents-md-budget.js +89 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +1 -0
- package/dist/policy/resolve.js +56 -46
- package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
- package/dist/pr-merge-readiness/ci-gate.js +79 -0
- package/dist/pr-merge-readiness/compute.d.ts +15 -1
- package/dist/pr-merge-readiness/compute.js +129 -9
- package/dist/pr-merge-readiness/gh.d.ts +8 -0
- package/dist/pr-merge-readiness/gh.js +30 -5
- package/dist/pr-merge-readiness/index.d.ts +5 -1
- package/dist/pr-merge-readiness/index.js +3 -0
- package/dist/pr-merge-readiness/main.d.ts +3 -0
- package/dist/pr-merge-readiness/main.js +73 -6
- package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
- package/dist/pr-merge-readiness/mergeability.js +105 -0
- package/dist/pr-merge-readiness/output.js +20 -0
- package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
- package/dist/pr-merge-readiness/slizard-gate.js +119 -0
- package/dist/pr-monitor/index.d.ts +1 -1
- package/dist/pr-monitor/index.js +1 -1
- package/dist/pr-monitor/monitor.d.ts +8 -2
- package/dist/pr-monitor/monitor.js +35 -6
- package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
- package/dist/pr-wait-mergeable/wrappers.js +13 -5
- package/dist/pr-watch/constants.d.ts +33 -0
- package/dist/pr-watch/constants.js +33 -0
- package/dist/pr-watch/index.d.ts +6 -0
- package/dist/pr-watch/index.js +6 -0
- package/dist/pr-watch/main.d.ts +21 -0
- package/dist/pr-watch/main.js +206 -0
- package/dist/pr-watch/probe.d.ts +13 -0
- package/dist/pr-watch/probe.js +100 -0
- package/dist/pr-watch/types.d.ts +47 -0
- package/dist/pr-watch/types.js +2 -0
- package/dist/pr-watch/watch.d.ts +12 -0
- package/dist/pr-watch/watch.js +122 -0
- package/dist/preflight-cache/evaluate.d.ts +1 -0
- package/dist/preflight-cache/evaluate.js +17 -10
- package/dist/render/project-render.d.ts +16 -2
- package/dist/render/project-render.js +155 -97
- package/dist/scope/capacity-stamp.d.ts +24 -2
- package/dist/scope/capacity-stamp.js +80 -28
- package/dist/scope/decomposed-refs.js +2 -2
- package/dist/scope/project-definition-sync.js +3 -3
- package/dist/scope/vbrief-ref.d.ts +2 -1
- package/dist/scope/vbrief-ref.js +3 -2
- package/dist/spec-authority/constants.d.ts +6 -0
- package/dist/spec-authority/constants.js +11 -0
- package/dist/spec-authority/resolver.js +3 -3
- package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
- package/dist/swarm/finalize-cohort-cli.js +100 -0
- package/dist/swarm/finalize-cohort.d.ts +39 -0
- package/dist/swarm/finalize-cohort.js +589 -0
- package/dist/swarm/index.d.ts +2 -0
- package/dist/swarm/index.js +2 -0
- package/dist/swarm/launch.d.ts +13 -0
- package/dist/swarm/launch.js +19 -7
- package/dist/swarm/routing-verify.d.ts +1 -1
- package/dist/swarm/routing-verify.js +11 -10
- package/dist/swarm/routing.d.ts +9 -0
- package/dist/swarm/routing.js +46 -0
- package/dist/swarm/verify-review-clean-cli.js +25 -1
- package/dist/swarm/verify-review-clean.d.ts +9 -1
- package/dist/swarm/verify-review-clean.js +76 -3
- package/dist/triage/queue/index.d.ts +1 -0
- package/dist/triage/queue/index.js +1 -0
- package/dist/triage/queue/project.d.ts +6 -1
- package/dist/triage/queue/project.js +9 -4
- package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
- package/dist/triage/queue/reconcile-live-state.js +54 -0
- package/dist/triage/scope/mutations-core.js +91 -81
- package/dist/triage/scope/resolve.js +4 -3
- package/dist/triage/scope-drift/add-ignore.js +33 -32
- package/dist/triage/scope-drift/scope-rules.js +3 -3
- package/dist/triage/subscribe/index.js +51 -45
- package/dist/triage/welcome/writers.js +79 -64
- package/dist/vbrief-build/project-definition-io.js +8 -3
- package/dist/vbrief-validate/paths.js +8 -4
- package/dist/vbrief-validate/precutover.d.ts +1 -1
- package/dist/vbrief-validate/precutover.js +12 -10
- package/dist/verify-source/biome-config.d.ts +41 -0
- package/dist/verify-source/biome-config.js +97 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/xbrief-migrate/agents-header.d.ts +69 -0
- package/dist/xbrief-migrate/agents-header.js +179 -0
- package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
- package/dist/xbrief-migrate/fs-helpers.js +11 -0
- package/dist/xbrief-migrate/index.d.ts +1 -0
- package/dist/xbrief-migrate/index.js +1 -0
- package/dist/xbrief-migrate/migrate-project.js +29 -10
- package/package.json +15 -7
- package/dist/install-upgrade/index.d.ts +0 -13
- package/dist/install-upgrade/index.js +0 -251
|
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { resolveEvalPath, resolveProjectDefinitionPath } from "../../layout/resolve.js";
|
|
6
6
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY } from "../../policy/plan-extensions.js";
|
|
7
|
+
import { projectDefinitionMutationLock } from "../../vbrief-build/project-definition-io.js";
|
|
7
8
|
import { SUBSCRIPTION_HISTORY_SCHEMA } from "./constants.js";
|
|
8
9
|
import { pyStrRepr } from "./python-repr.js";
|
|
9
10
|
import { utcIso } from "./time.js";
|
|
@@ -128,93 +129,102 @@ export function subscribe(projectRoot, options) {
|
|
|
128
129
|
if (chosen.length !== 1) {
|
|
129
130
|
throw new Error(`subscribe() requires exactly one of --label / --milestone / --issue; got ${JSON.stringify(chosen)}`);
|
|
130
131
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
132
|
+
// Serialise the read-modify-write + subscription-history append under the
|
|
133
|
+
// shared PROJECT-DEFINITION mutation lock so concurrent mutators cannot lose
|
|
134
|
+
// an update or emit out-of-order audit rows (#1260).
|
|
135
|
+
return projectDefinitionMutationLock(projectRoot, () => {
|
|
136
|
+
const [data, path] = loadProjectDefinitionForMutation(projectRoot);
|
|
137
|
+
const plan = data.plan;
|
|
138
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
139
|
+
throw new Error(`PROJECT-DEFINITION at ${path} has a non-object 'plan' key`);
|
|
140
|
+
}
|
|
141
|
+
const planRec = plan;
|
|
142
|
+
migrateLegacyPolicyKey(planRec);
|
|
143
|
+
if (planRec[PLAN_POLICY_KEY] === undefined)
|
|
144
|
+
planRec[PLAN_POLICY_KEY] = {};
|
|
145
|
+
const policy = planRec[PLAN_POLICY_KEY];
|
|
146
|
+
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
|
|
147
|
+
throw new Error(`PROJECT-DEFINITION at ${path} has a non-object 'plan.policy' key`);
|
|
148
|
+
}
|
|
149
|
+
const policyRec = policy;
|
|
150
|
+
if (policyRec.triageScope === undefined)
|
|
151
|
+
policyRec.triageScope = [];
|
|
152
|
+
const rules = policyRec.triageScope;
|
|
153
|
+
if (!Array.isArray(rules)) {
|
|
154
|
+
throw new Error(`PROJECT-DEFINITION at ${path} has a non-list 'plan.policy.triageScope'`);
|
|
155
|
+
}
|
|
156
|
+
const before = snapshotRules(rules);
|
|
157
|
+
let changed;
|
|
158
|
+
let message;
|
|
159
|
+
if (options.label !== undefined) {
|
|
160
|
+
[changed, message] = applySubscribeLabel(rules, options.label);
|
|
161
|
+
}
|
|
162
|
+
else if (options.milestone !== undefined) {
|
|
163
|
+
[changed, message] = applySubscribeMilestone(rules, options.milestone);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
throw new Error("subscribe() requires exactly one of label or milestone");
|
|
167
|
+
}
|
|
168
|
+
if (!changed)
|
|
169
|
+
return [false, message];
|
|
170
|
+
atomicWriteProjectDefinition(path, data);
|
|
171
|
+
recordSubscriptionChange(projectRoot, {
|
|
172
|
+
op: "subscribe",
|
|
173
|
+
label: options.label ?? null,
|
|
174
|
+
milestone: options.milestone ?? null,
|
|
175
|
+
before,
|
|
176
|
+
after: snapshotRules(rules),
|
|
177
|
+
actor: options.actor,
|
|
178
|
+
});
|
|
179
|
+
return [true, message];
|
|
173
180
|
});
|
|
174
|
-
return [true, message];
|
|
175
181
|
}
|
|
176
182
|
export function addIgnore(projectRoot, label) {
|
|
177
183
|
if (!label.trim())
|
|
178
184
|
throw new Error(`label must be a non-empty string; got ${JSON.stringify(label)}`);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (planRec[PLAN_POLICY_KEY] === undefined)
|
|
187
|
-
planRec[PLAN_POLICY_KEY] = {};
|
|
188
|
-
const policy = planRec[PLAN_POLICY_KEY];
|
|
189
|
-
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
|
|
190
|
-
throw new Error(`PROJECT-DEFINITION at ${path} has a non-object 'plan.policy' key`);
|
|
191
|
-
}
|
|
192
|
-
const policyRec = policy;
|
|
193
|
-
if (policyRec.triageScopeIgnores === undefined)
|
|
194
|
-
policyRec.triageScopeIgnores = [];
|
|
195
|
-
const raw = policyRec.triageScopeIgnores;
|
|
196
|
-
if (!Array.isArray(raw)) {
|
|
197
|
-
throw new Error(`PROJECT-DEFINITION at ${path} has a non-list 'plan.policy.triageScopeIgnores'`);
|
|
198
|
-
}
|
|
199
|
-
const before = snapshotRules(raw);
|
|
200
|
-
for (const entry of raw) {
|
|
201
|
-
if (typeof entry === "object" &&
|
|
202
|
-
entry !== null &&
|
|
203
|
-
!Array.isArray(entry) &&
|
|
204
|
-
entry.label === label) {
|
|
205
|
-
return [false, `already-ignored (label=${label})`];
|
|
185
|
+
// Serialise the read-modify-write + subscription-history append under the
|
|
186
|
+
// shared PROJECT-DEFINITION mutation lock (#1260).
|
|
187
|
+
return projectDefinitionMutationLock(projectRoot, () => {
|
|
188
|
+
const [data, path] = loadProjectDefinitionForMutation(projectRoot);
|
|
189
|
+
const plan = data.plan;
|
|
190
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
191
|
+
throw new Error(`PROJECT-DEFINITION at ${path} has a non-object 'plan' key`);
|
|
206
192
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
193
|
+
const planRec = plan;
|
|
194
|
+
migrateLegacyPolicyKey(planRec);
|
|
195
|
+
if (planRec[PLAN_POLICY_KEY] === undefined)
|
|
196
|
+
planRec[PLAN_POLICY_KEY] = {};
|
|
197
|
+
const policy = planRec[PLAN_POLICY_KEY];
|
|
198
|
+
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
|
|
199
|
+
throw new Error(`PROJECT-DEFINITION at ${path} has a non-object 'plan.policy' key`);
|
|
200
|
+
}
|
|
201
|
+
const policyRec = policy;
|
|
202
|
+
if (policyRec.triageScopeIgnores === undefined)
|
|
203
|
+
policyRec.triageScopeIgnores = [];
|
|
204
|
+
const raw = policyRec.triageScopeIgnores;
|
|
205
|
+
if (!Array.isArray(raw)) {
|
|
206
|
+
throw new Error(`PROJECT-DEFINITION at ${path} has a non-list 'plan.policy.triageScopeIgnores'`);
|
|
207
|
+
}
|
|
208
|
+
const before = snapshotRules(raw);
|
|
209
|
+
for (const entry of raw) {
|
|
210
|
+
if (typeof entry === "object" &&
|
|
211
|
+
entry !== null &&
|
|
212
|
+
!Array.isArray(entry) &&
|
|
213
|
+
entry.label === label) {
|
|
214
|
+
return [false, `already-ignored (label=${label})`];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
raw.push({ label });
|
|
218
|
+
atomicWriteProjectDefinition(path, data);
|
|
219
|
+
const after = snapshotRules(raw);
|
|
220
|
+
recordSubscriptionChange(projectRoot, {
|
|
221
|
+
op: "ignore-label",
|
|
222
|
+
label,
|
|
223
|
+
before,
|
|
224
|
+
after,
|
|
225
|
+
actor: null,
|
|
226
|
+
});
|
|
227
|
+
return [true, `added ignore (label=${label})`];
|
|
217
228
|
});
|
|
218
|
-
return [true, `added ignore (label=${label})`];
|
|
219
229
|
}
|
|
220
230
|
//# sourceMappingURL=mutations-core.js.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { resolveProjectDefinitionPath } from "../../layout/resolve.js";
|
|
3
4
|
import { readPlanPolicy } from "../../policy/plan-extensions.js";
|
|
4
|
-
import { DEFAULT_TRIAGE_SCOPE
|
|
5
|
+
import { DEFAULT_TRIAGE_SCOPE } from "./constants.js";
|
|
5
6
|
export function projectDefinitionPath(projectRoot) {
|
|
6
|
-
return
|
|
7
|
+
return resolveProjectDefinitionPath(resolve(projectRoot));
|
|
7
8
|
}
|
|
8
9
|
export function loadProjectDefinition(projectRoot) {
|
|
9
10
|
const path = projectDefinitionPath(projectRoot);
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { resolveProjectDefinitionPath } from "../../layout/resolve.js";
|
|
4
4
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY } from "../../policy/plan-extensions.js";
|
|
5
|
-
|
|
6
|
-
function projectDefinitionPath(projectRoot) {
|
|
7
|
-
return join(resolve(projectRoot), PROJECT_DEFINITION_REL_PATH);
|
|
8
|
-
}
|
|
5
|
+
import { projectDefinitionMutationLock } from "../../vbrief-build/project-definition-io.js";
|
|
9
6
|
function loadForMutation(projectRoot) {
|
|
10
|
-
const path =
|
|
7
|
+
const path = resolveProjectDefinitionPath(resolve(projectRoot));
|
|
11
8
|
if (!existsSync(path)) {
|
|
12
9
|
throw new Error(`PROJECT-DEFINITION not found at ${path}; run task triage:welcome / task triage:bootstrap to scaffold one first.`);
|
|
13
10
|
}
|
|
@@ -49,32 +46,36 @@ export function addIgnore(projectRoot, options) {
|
|
|
49
46
|
if (typeof value !== "string" || !value.trim()) {
|
|
50
47
|
throw new Error(`${key} must be a non-empty string; got ${JSON.stringify(value)}`);
|
|
51
48
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
planRec[PLAN_POLICY_KEY]
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
49
|
+
// Serialise the read-modify-write under the shared PROJECT-DEFINITION
|
|
50
|
+
// mutation lock so concurrent mutators cannot lose an update (#1260).
|
|
51
|
+
return projectDefinitionMutationLock(projectRoot, () => {
|
|
52
|
+
const [data, path] = loadForMutation(projectRoot);
|
|
53
|
+
const plan = data.plan;
|
|
54
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
55
|
+
throw new Error(`PROJECT-DEFINITION at ${path} has a non-object 'plan' key`);
|
|
56
|
+
}
|
|
57
|
+
const planRec = plan;
|
|
58
|
+
migrateLegacyPolicyKey(planRec);
|
|
59
|
+
let policy = planRec[PLAN_POLICY_KEY];
|
|
60
|
+
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
|
|
61
|
+
policy = {};
|
|
62
|
+
planRec[PLAN_POLICY_KEY] = policy;
|
|
63
|
+
}
|
|
64
|
+
const policyRec = policy;
|
|
65
|
+
const raw = Array.isArray(policyRec.triageScopeIgnores)
|
|
66
|
+
? policyRec.triageScopeIgnores
|
|
67
|
+
: [];
|
|
68
|
+
policyRec.triageScopeIgnores = raw;
|
|
69
|
+
for (const entry of raw) {
|
|
70
|
+
if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
|
|
71
|
+
if (entry[key] === value) {
|
|
72
|
+
return { changed: false, message: `already-ignored (${key}=${value})` };
|
|
73
|
+
}
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
raw.push({ [key]: value });
|
|
77
|
+
atomicWrite(path, data);
|
|
78
|
+
return { changed: true, message: `added ignore (${key}=${value})` };
|
|
79
|
+
});
|
|
79
80
|
}
|
|
80
81
|
//# sourceMappingURL=add-ignore.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { resolveProjectDefinitionPath } from "../../layout/resolve.js";
|
|
3
4
|
import { readPlanPolicy } from "../../policy/plan-extensions.js";
|
|
4
|
-
const PROJECT_DEFINITION_REL_PATH = "vbrief/PROJECT-DEFINITION.vbrief.json";
|
|
5
5
|
const DEFAULT_TRIAGE_SCOPE = [{ rule: "all-open" }];
|
|
6
6
|
function loadProjectDefinition(projectRoot) {
|
|
7
|
-
const path =
|
|
7
|
+
const path = resolveProjectDefinitionPath(resolve(projectRoot));
|
|
8
8
|
if (!existsSync(path))
|
|
9
9
|
return null;
|
|
10
10
|
try {
|
|
@@ -3,6 +3,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlink
|
|
|
3
3
|
import { basename, join } from "node:path";
|
|
4
4
|
import { resolveEvalPath, resolveProjectDefinitionPath } from "../../layout/resolve.js";
|
|
5
5
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY } from "../../policy/plan-extensions.js";
|
|
6
|
+
import { projectDefinitionMutationLock } from "../../vbrief-build/project-definition-io.js";
|
|
6
7
|
export const SUBSCRIPTION_HISTORY_REL_PATH = "vbrief/.eval/subscription-history.jsonl";
|
|
7
8
|
export const SUBSCRIPTION_HISTORY_SCHEMA = "deft.triage.subscription-change.v1";
|
|
8
9
|
export const PROJECT_DEFINITION_REL_PATH = "vbrief/PROJECT-DEFINITION.vbrief.json";
|
|
@@ -246,57 +247,62 @@ function mutate(projectRoot, options) {
|
|
|
246
247
|
if (chosen.length !== 1) {
|
|
247
248
|
throw new Error(`${options.op}() requires exactly one of --label / --milestone / --issue; got ${JSON.stringify(chosen)}`);
|
|
248
249
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (typeof existingPolicy !== "object" ||
|
|
257
|
-
existingPolicy === null ||
|
|
258
|
-
Array.isArray(existingPolicy)) {
|
|
259
|
-
if (existingPolicy === undefined) {
|
|
260
|
-
plan[PLAN_POLICY_KEY] = {};
|
|
250
|
+
// Serialise the read-modify-write + subscription-history append under the
|
|
251
|
+
// shared PROJECT-DEFINITION mutation lock so concurrent mutators cannot lose
|
|
252
|
+
// an update or emit out-of-order audit rows (#1260).
|
|
253
|
+
return projectDefinitionMutationLock(projectRoot, () => {
|
|
254
|
+
const [data, path] = loadProjectDefinitionForMutation(projectRoot);
|
|
255
|
+
if (typeof data.plan !== "object" || data.plan === null || Array.isArray(data.plan)) {
|
|
256
|
+
throw new Error(`PROJECT-DEFINITION at ${path} has a non-object 'plan' key`);
|
|
261
257
|
}
|
|
262
|
-
|
|
263
|
-
|
|
258
|
+
const plan = data.plan;
|
|
259
|
+
migrateLegacyPolicyKey(plan);
|
|
260
|
+
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
261
|
+
if (typeof existingPolicy !== "object" ||
|
|
262
|
+
existingPolicy === null ||
|
|
263
|
+
Array.isArray(existingPolicy)) {
|
|
264
|
+
if (existingPolicy === undefined) {
|
|
265
|
+
plan[PLAN_POLICY_KEY] = {};
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
throw new Error(`PROJECT-DEFINITION at ${path} has a non-object 'plan.policy' key`);
|
|
269
|
+
}
|
|
264
270
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
271
|
+
const policy = plan[PLAN_POLICY_KEY];
|
|
272
|
+
if (!Array.isArray(policy.triageScope)) {
|
|
273
|
+
if (policy.triageScope === undefined) {
|
|
274
|
+
policy.triageScope = [];
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
throw new Error(`PROJECT-DEFINITION at ${path} has a non-list 'plan.policy.triageScope'`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const rules = policy.triageScope;
|
|
281
|
+
const before = snapshotRules(rules);
|
|
282
|
+
let changed;
|
|
283
|
+
let message;
|
|
284
|
+
if (options.op === "subscribe") {
|
|
285
|
+
[changed, message] = applySubscribe(rules, options.label, options.milestone, options.issue, options.issueNote ?? "added via task triage:subscribe");
|
|
270
286
|
}
|
|
271
287
|
else {
|
|
272
|
-
|
|
288
|
+
[changed, message] = applyUnsubscribe(rules, options.label, options.milestone, options.issue);
|
|
273
289
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const after = snapshotRules(rules);
|
|
290
|
-
recordSubscriptionChange(projectRoot, {
|
|
291
|
-
op: options.op,
|
|
292
|
-
label: options.label,
|
|
293
|
-
milestone: options.milestone,
|
|
294
|
-
issue: options.issue,
|
|
295
|
-
before,
|
|
296
|
-
after,
|
|
297
|
-
actor: options.actor,
|
|
290
|
+
if (!changed) {
|
|
291
|
+
return [false, message];
|
|
292
|
+
}
|
|
293
|
+
atomicWriteProjectDefinition(path, data);
|
|
294
|
+
const after = snapshotRules(rules);
|
|
295
|
+
recordSubscriptionChange(projectRoot, {
|
|
296
|
+
op: options.op,
|
|
297
|
+
label: options.label,
|
|
298
|
+
milestone: options.milestone,
|
|
299
|
+
issue: options.issue,
|
|
300
|
+
before,
|
|
301
|
+
after,
|
|
302
|
+
actor: options.actor,
|
|
303
|
+
});
|
|
304
|
+
return [true, message];
|
|
298
305
|
});
|
|
299
|
-
return [true, message];
|
|
300
306
|
}
|
|
301
307
|
export function subscribe(projectRoot, options = {}) {
|
|
302
308
|
return mutate(projectRoot, { op: "subscribe", ...options });
|
|
@@ -2,6 +2,7 @@ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renam
|
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { hasArtifactSuffix, resolveLifecycleFolder, resolveProjectDefinitionPath, } from "../../layout/resolve.js";
|
|
4
4
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY } from "../../policy/plan-extensions.js";
|
|
5
|
+
import { projectDefinitionMutationLock } from "../../vbrief-build/project-definition-io.js";
|
|
5
6
|
import { AUDIT_LOG_REL_PATH, DEFAULT_RELIEF_AGE_DAYS, DEFAULT_WIP_CAP, SUBSCRIPTION_PRESETS, WELCOME_AUDIT_TAG, } from "./constants.js";
|
|
6
7
|
function projectDefinitionPath(projectRoot) {
|
|
7
8
|
return resolveProjectDefinitionPath(projectRoot);
|
|
@@ -27,77 +28,91 @@ function atomicWrite(path, data) {
|
|
|
27
28
|
renameSync(tmp, path);
|
|
28
29
|
}
|
|
29
30
|
export function writeTriageScope(projectRoot, rules, options = { presetLabel: "custom" }) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
31
|
+
// Serialise read-modify-write + audit append under the shared lock (#1260).
|
|
32
|
+
return projectDefinitionMutationLock(projectRoot, () => {
|
|
33
|
+
const path = projectDefinitionPath(projectRoot);
|
|
34
|
+
if (!existsSync(path))
|
|
35
|
+
throw new Error(`PROJECT-DEFINITION not found at ${path}`);
|
|
36
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
37
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
38
|
+
throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
|
|
39
|
+
}
|
|
40
|
+
const data = parsed;
|
|
41
|
+
const plan = data.plan;
|
|
42
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
43
|
+
throw new Error("PROJECT-DEFINITION 'plan' is not an object");
|
|
44
|
+
}
|
|
45
|
+
const planRec = plan;
|
|
46
|
+
migrateLegacyPolicyKey(planRec);
|
|
47
|
+
let policy = planRec[PLAN_POLICY_KEY];
|
|
48
|
+
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
|
|
49
|
+
policy = {};
|
|
50
|
+
planRec[PLAN_POLICY_KEY] = policy;
|
|
51
|
+
}
|
|
52
|
+
const policyRec = policy;
|
|
53
|
+
const previous = policyRec.triageScope;
|
|
54
|
+
policyRec.triageScope = rules;
|
|
55
|
+
atomicWrite(path, data);
|
|
56
|
+
const changed = JSON.stringify(previous) !== JSON.stringify(rules);
|
|
57
|
+
const actor = options.actor ?? WELCOME_AUDIT_TAG;
|
|
58
|
+
const auditEntry = [
|
|
59
|
+
`actor=${actor}`,
|
|
60
|
+
"field=plan.policy.triageScope",
|
|
61
|
+
`preset=${options.presetLabel}`,
|
|
62
|
+
`rule_count=${rules.length}`,
|
|
63
|
+
`changed=${changed ? "true" : "false"}`,
|
|
64
|
+
].join(" ");
|
|
65
|
+
appendAuditEntry(projectRoot, auditEntry);
|
|
66
|
+
return [changed, auditEntry];
|
|
67
|
+
});
|
|
60
68
|
}
|
|
61
69
|
export function writeWipCap(projectRoot, wipCap, options = {}) {
|
|
62
70
|
if (!Number.isInteger(wipCap) || wipCap < 1) {
|
|
63
71
|
throw new Error(`wipCap must be a positive int, got ${JSON.stringify(wipCap)}`);
|
|
64
72
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
73
|
+
// Serialise read-modify-write + audit append under the shared lock (#1260).
|
|
74
|
+
return projectDefinitionMutationLock(projectRoot, () => {
|
|
75
|
+
const path = projectDefinitionPath(projectRoot);
|
|
76
|
+
if (!existsSync(path))
|
|
77
|
+
throw new Error(`PROJECT-DEFINITION not found at ${path}`);
|
|
78
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
79
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
80
|
+
throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
|
|
81
|
+
}
|
|
82
|
+
const data = parsed;
|
|
83
|
+
const plan = data.plan;
|
|
84
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
85
|
+
throw new Error("PROJECT-DEFINITION 'plan' is not an object");
|
|
86
|
+
}
|
|
87
|
+
const planRec = plan;
|
|
88
|
+
migrateLegacyPolicyKey(planRec);
|
|
89
|
+
let policy = planRec[PLAN_POLICY_KEY];
|
|
90
|
+
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
|
|
91
|
+
policy = {};
|
|
92
|
+
planRec[PLAN_POLICY_KEY] = policy;
|
|
93
|
+
}
|
|
94
|
+
const policyRec = policy;
|
|
95
|
+
const previous = policyRec.wipCap;
|
|
96
|
+
const actor = options.actor ?? WELCOME_AUDIT_TAG;
|
|
97
|
+
if (previous === undefined && wipCap === DEFAULT_WIP_CAP) {
|
|
98
|
+
return [false, ""];
|
|
99
|
+
}
|
|
100
|
+
if (previous !== undefined && wipCap === DEFAULT_WIP_CAP) {
|
|
101
|
+
delete policyRec.wipCap;
|
|
102
|
+
atomicWrite(path, data);
|
|
103
|
+
const auditEntry = `actor=${actor} field=plan.policy.wipCap action=cleared-to-default value=${wipCap} ` +
|
|
104
|
+
`previous=${JSON.stringify(previous)} changed=true`;
|
|
105
|
+
appendAuditEntry(projectRoot, auditEntry);
|
|
106
|
+
return [true, auditEntry];
|
|
107
|
+
}
|
|
108
|
+
policyRec.wipCap = wipCap;
|
|
88
109
|
atomicWrite(path, data);
|
|
89
|
-
const
|
|
90
|
-
|
|
110
|
+
const changed = previous !== wipCap;
|
|
111
|
+
const auditEntry = `actor=${actor} field=plan.policy.wipCap value=${wipCap} previous=${JSON.stringify(previous)} ` +
|
|
112
|
+
`changed=${changed ? "true" : "false"}`;
|
|
91
113
|
appendAuditEntry(projectRoot, auditEntry);
|
|
92
|
-
return [
|
|
93
|
-
}
|
|
94
|
-
policyRec.wipCap = wipCap;
|
|
95
|
-
atomicWrite(path, data);
|
|
96
|
-
const changed = previous !== wipCap;
|
|
97
|
-
const auditEntry = `actor=${actor} field=plan.policy.wipCap value=${wipCap} previous=${JSON.stringify(previous)} ` +
|
|
98
|
-
`changed=${changed ? "true" : "false"}`;
|
|
99
|
-
appendAuditEntry(projectRoot, auditEntry);
|
|
100
|
-
return [changed, auditEntry];
|
|
114
|
+
return [changed, auditEntry];
|
|
115
|
+
});
|
|
101
116
|
}
|
|
102
117
|
function daysInPending(path, now) {
|
|
103
118
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, writeSync, } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
4
5
|
import { PROJECT_DEFINITION_REL_PATH } from "./constants.js";
|
|
5
6
|
import { pythonJsonPretty } from "./json.js";
|
|
6
7
|
import { ProjectDefinitionIOError } from "./types.js";
|
|
@@ -18,8 +19,12 @@ function defaultSleep(ms) {
|
|
|
18
19
|
export function projectDefinitionMutationLock(projectRoot, fn, deps = {}) {
|
|
19
20
|
const sleepMs = deps.sleepMs ?? defaultSleep;
|
|
20
21
|
const now = deps.now ?? Date.now;
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
// Derive the sidecar lock path from the layout-aware resolved PROJECT-DEFINITION
|
|
23
|
+
// path (xbrief/ when migrated, else vbrief/) so the lock lives next to the real
|
|
24
|
+
// artifact and every mutator sharing a project root contends on the same lock,
|
|
25
|
+
// instead of the constant vbrief/ path which would strand a stray lock (#1260).
|
|
26
|
+
const path = resolveProjectDefinitionPath(resolve(projectRoot));
|
|
27
|
+
const lockPath = `${path}.lock`;
|
|
23
28
|
mkdirSync(dirname(lockPath), { recursive: true });
|
|
24
29
|
if (mutationThreadLock.held) {
|
|
25
30
|
throw new Error("project definition mutation lock is not reentrant");
|