@deftai/directive-core 0.105.0 → 0.106.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 +591 -34
- package/dist/check/named-cause.js +1 -1
- package/dist/forward-coverage/diff-coverage.d.ts +39 -0
- package/dist/forward-coverage/diff-coverage.js +112 -0
- package/dist/forward-coverage/diff-lines.d.ts +19 -0
- package/dist/forward-coverage/diff-lines.js +173 -0
- package/dist/forward-coverage/evaluate.d.ts +24 -9
- package/dist/forward-coverage/evaluate.js +125 -19
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +1 -1
- package/dist/intake/clause-derivation.d.ts +9 -0
- package/dist/intake/clause-derivation.js +30 -3
- package/dist/lifecycle-visible/evaluate.d.ts +98 -0
- package/dist/lifecycle-visible/evaluate.js +710 -0
- package/dist/lifecycle-visible/index.d.ts +2 -0
- package/dist/lifecycle-visible/index.js +2 -0
- package/dist/literal-acceptance/capture.js +3 -3
- package/dist/orchestration/judgment-policy.d.ts +13 -0
- package/dist/orchestration/judgment-policy.js +15 -0
- package/dist/orchestration/probe-session.js +13 -8
- package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
- package/dist/orchestration/verify-judgment-gates.js +43 -11
- package/dist/policy/ceremony-dial.js +7 -7
- package/dist/policy/host-hooks.d.ts +20 -0
- package/dist/policy/host-hooks.js +121 -2
- package/dist/policy/org-force-on-migration.js +2 -1
- package/dist/policy/plan-extensions.d.ts +1 -1
- package/dist/policy/plan-extensions.js +2 -0
- package/dist/policy/product-signal.js +6 -7
- package/dist/policy/require-human-merge.js +8 -6
- package/dist/policy/resolve.d.ts +7 -2
- package/dist/policy/resolve.js +25 -8
- package/dist/policy/value-feedback.js +7 -8
- package/dist/preflight-cache/evaluate.d.ts +12 -0
- package/dist/preflight-cache/evaluate.js +15 -1
- package/dist/preflight-cache/index.d.ts +1 -1
- package/dist/preflight-cache/index.js +1 -1
- package/dist/product-first-done-gate/evaluate.d.ts +4 -0
- package/dist/product-first-done-gate/evaluate.js +46 -5
- package/dist/release/closed-verb-gate.d.ts +12 -0
- package/dist/release/closed-verb-gate.js +51 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/pipeline.js +7 -0
- package/dist/release/types.d.ts +12 -0
- package/dist/render/constants.d.ts +2 -1
- package/dist/render/constants.js +2 -1
- package/dist/render/framework-commands.js +4 -0
- package/dist/render/spec-validate.js +23 -12
- package/dist/run-summary/types.d.ts +4 -0
- package/dist/scope/main.js +15 -5
- package/dist/scope/promote-from-issue.js +5 -5
- package/dist/scope-provenance/evaluate.js +3 -3
- package/dist/session/ac-pass-banking.d.ts +5 -0
- package/dist/session/ac-pass-banking.js +3 -2
- package/dist/session/process-cost.d.ts +32 -10
- package/dist/session/process-cost.js +189 -1
- package/dist/session/product-state-hash.js +41 -24
- package/dist/session/ritual-entrypoint.js +16 -1
- package/dist/session/session-start.d.ts +6 -0
- package/dist/session/session-start.js +32 -2
- package/dist/session/verify-session-ritual.d.ts +24 -1
- package/dist/session/verify-session-ritual.js +89 -6
- package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
- package/dist/swarm/finalize-cohort-cli.js +165 -35
- package/dist/swarm/finalize-cohort.d.ts +1 -0
- package/dist/swarm/finalize-cohort.js +14 -2
- package/dist/swarm/routing-set-cli.js +2 -0
- package/dist/swarm/routing-verify.d.ts +1 -1
- package/dist/swarm/routing-verify.js +2 -2
- package/dist/triage/bootstrap/index.js +6 -6
- package/dist/triage/help/registry-data.d.ts +1 -1
- package/dist/triage/help/registry-data.js +1 -1
- package/dist/triage/welcome/writers.d.ts +1 -1
- package/dist/triage/welcome/writers.js +10 -5
- package/dist/value/readback.d.ts +4 -1
- package/dist/value/readback.js +51 -12
- package/dist/vbrief-validate/schema.d.ts +4 -0
- package/dist/vbrief-validate/schema.js +1 -1
- package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
- package/dist/verify-env/agent-hook-readiness.js +12 -8
- package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
- package/dist/verify-env/agent-hooks-live-probe.js +26 -5
- package/dist/verify-env/agent-hooks.js +3 -4
- package/package.json +7 -3
package/dist/policy/resolve.js
CHANGED
|
@@ -13,6 +13,16 @@ export const ENV_BYPASS = "DEFT_ALLOW_DEFAULT_BRANCH_COMMIT";
|
|
|
13
13
|
export const LEGACY_NARRATIVE_KEY = "Allow direct commits to master";
|
|
14
14
|
/** Audit log relative path (#746). */
|
|
15
15
|
export const AUDIT_LOG_REL_PATH = "meta/policy-changes.log";
|
|
16
|
+
/** Operator line when a policy writer matches current state and does not append (#3528). */
|
|
17
|
+
export const POLICY_AUDIT_NOOP_STDOUT = " no-op: value already matched (ledger unchanged).";
|
|
18
|
+
const CHANGED_TOKEN_RE = /(?:^|\s)changed=(?:true|false)(?:\s|$)/;
|
|
19
|
+
/** Stamp a machine-readable `changed=true|false` token (#3528 / UPGRADING.md). */
|
|
20
|
+
export function stampChangedToken(entry, changed) {
|
|
21
|
+
if (CHANGED_TOKEN_RE.test(entry)) {
|
|
22
|
+
return entry;
|
|
23
|
+
}
|
|
24
|
+
return `${entry} changed=${changed ? "true" : "false"}`;
|
|
25
|
+
}
|
|
16
26
|
const TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
17
27
|
/**
|
|
18
28
|
* Resolve absolute path to PROJECT-DEFINITION. Layout-aware (#2109 part 2a):
|
|
@@ -176,10 +186,15 @@ export function resolvePolicy(projectRoot) {
|
|
|
176
186
|
function nowIso() {
|
|
177
187
|
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
178
188
|
}
|
|
179
|
-
/** Append a one-line audit entry to meta/policy-changes.log (#746).
|
|
180
|
-
|
|
189
|
+
/** Append a one-line audit entry to meta/policy-changes.log (#746).
|
|
190
|
+
* No-op (`changed=false`) does not create or modify the tracked file (#3528). */
|
|
191
|
+
export function appendAuditLog(projectRoot, entry, changed = true) {
|
|
181
192
|
const root = pathResolve(projectRoot);
|
|
182
193
|
const logPath = join(root, AUDIT_LOG_REL_PATH);
|
|
194
|
+
const stamped = stampChangedToken(entry, changed);
|
|
195
|
+
if (!changed) {
|
|
196
|
+
return logPath;
|
|
197
|
+
}
|
|
183
198
|
// #2980 wave D: product write sink routes through containedWrite.
|
|
184
199
|
if (!existsSync(logPath)) {
|
|
185
200
|
const header = "# meta/policy-changes.log -- audit trail for " +
|
|
@@ -194,7 +209,7 @@ export function appendAuditLog(projectRoot, entry) {
|
|
|
194
209
|
containedWrite({
|
|
195
210
|
root,
|
|
196
211
|
target: AUDIT_LOG_REL_PATH,
|
|
197
|
-
data: `${nowIso()} ${
|
|
212
|
+
data: `${nowIso()} ${stamped}\n`,
|
|
198
213
|
mode: "append",
|
|
199
214
|
});
|
|
200
215
|
return logPath;
|
|
@@ -224,7 +239,7 @@ export function setPolicy(projectRoot, options) {
|
|
|
224
239
|
}
|
|
225
240
|
}
|
|
226
241
|
const plan = data.plan;
|
|
227
|
-
migrateLegacyPolicyKey(plan);
|
|
242
|
+
const legacyKeyMigrated = migrateLegacyPolicyKey(plan);
|
|
228
243
|
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
229
244
|
if (typeof existingPolicy !== "object" ||
|
|
230
245
|
existingPolicy === null ||
|
|
@@ -248,8 +263,7 @@ export function setPolicy(projectRoot, options) {
|
|
|
248
263
|
delete narratives[LEGACY_NARRATIVE_KEY];
|
|
249
264
|
legacyDropped = true;
|
|
250
265
|
}
|
|
251
|
-
|
|
252
|
-
const changed = previous !== Boolean(allowDirectCommits) || legacyDropped;
|
|
266
|
+
const changed = previous !== Boolean(allowDirectCommits) || legacyDropped || legacyKeyMigrated;
|
|
253
267
|
const parts = [
|
|
254
268
|
`actor=${actor}`,
|
|
255
269
|
`allowDirectCommitsToMaster=${allowDirectCommits ? "true" : "false"}`,
|
|
@@ -261,8 +275,11 @@ export function setPolicy(projectRoot, options) {
|
|
|
261
275
|
if (note) {
|
|
262
276
|
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
263
277
|
}
|
|
264
|
-
const auditEntry = parts.join(" ");
|
|
265
|
-
|
|
278
|
+
const auditEntry = stampChangedToken(parts.join(" "), changed);
|
|
279
|
+
if (changed) {
|
|
280
|
+
atomicWriteProjectDefinition(path, data);
|
|
281
|
+
}
|
|
282
|
+
appendAuditLog(projectRoot, auditEntry, changed);
|
|
266
283
|
return { changed, auditEntry };
|
|
267
284
|
});
|
|
268
285
|
}
|
|
@@ -3,7 +3,7 @@ import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "..
|
|
|
3
3
|
import { valueFeedbackInstallForceOnSource } from "./org-force-on-migration.js";
|
|
4
4
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
5
5
|
import { policyColonInvocation } from "./policy-invocation.js";
|
|
6
|
-
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
6
|
+
import { appendAuditLog, loadProjectDefinition, POLICY_AUDIT_NOOP_STDOUT, projectDefinitionPath, } from "./resolve.js";
|
|
7
7
|
import { isTrustedOrgAutoEnable } from "./value-feedback-autoenable.js";
|
|
8
8
|
/** Canonical registered policy field name (matches other FIELD_* dotted paths). */
|
|
9
9
|
export const FIELD_VALUE_FEEDBACK = "plan.policy.valueFeedback";
|
|
@@ -236,7 +236,7 @@ export function enableValueFeedback(projectRoot, options) {
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
const plan = data.plan;
|
|
239
|
-
migrateLegacyPolicyKey(plan);
|
|
239
|
+
const legacyKeyMigrated = migrateLegacyPolicyKey(plan);
|
|
240
240
|
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
241
241
|
if (typeof existingPolicy !== "object" ||
|
|
242
242
|
existingPolicy === null ||
|
|
@@ -273,7 +273,8 @@ export function enableValueFeedback(projectRoot, options) {
|
|
|
273
273
|
const changedFlag = previousNormalized.enabled !== nextBlock.enabled ||
|
|
274
274
|
previousNormalized.emitEvents !== nextBlock.emitEvents ||
|
|
275
275
|
previousNormalized.sessionLine !== nextBlock.sessionLine ||
|
|
276
|
-
previousNormalized.upstreamPrompt !== nextBlock.upstreamPrompt
|
|
276
|
+
previousNormalized.upstreamPrompt !== nextBlock.upstreamPrompt ||
|
|
277
|
+
legacyKeyMigrated;
|
|
277
278
|
policyBlock.valueFeedback = nextBlock;
|
|
278
279
|
if (changedFlag) {
|
|
279
280
|
atomicWriteProjectDefinition(path, data);
|
|
@@ -291,15 +292,13 @@ export function enableValueFeedback(projectRoot, options) {
|
|
|
291
292
|
if (note) {
|
|
292
293
|
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
293
294
|
}
|
|
294
|
-
appendAuditLog(projectRoot, parts.join(" "));
|
|
295
|
+
appendAuditLog(projectRoot, parts.join(" "), changedFlag);
|
|
295
296
|
return { changed: changedFlag };
|
|
296
297
|
});
|
|
297
298
|
const resolved = resolveValueFeedback(projectRoot);
|
|
298
299
|
const lines = [
|
|
299
300
|
`\u2713 ${FIELD_VALUE_FEEDBACK}.enabled=true (value-feedback ON).`,
|
|
300
|
-
changed
|
|
301
|
-
? " audit: meta/policy-changes.log updated."
|
|
302
|
-
: " no-op: value already matched (audit entry still appended for trail).",
|
|
301
|
+
changed ? " audit: meta/policy-changes.log updated." : POLICY_AUDIT_NOOP_STDOUT,
|
|
303
302
|
formatValueFeedbackStatusLine(resolved),
|
|
304
303
|
];
|
|
305
304
|
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
|
|
@@ -354,7 +353,7 @@ export function clearValueFeedback(projectRoot, options = {}) {
|
|
|
354
353
|
if (note) {
|
|
355
354
|
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
356
355
|
}
|
|
357
|
-
appendAuditLog(projectRoot, parts.join(" "));
|
|
356
|
+
appendAuditLog(projectRoot, parts.join(" "), true);
|
|
358
357
|
return { changed: true };
|
|
359
358
|
});
|
|
360
359
|
const resolved = resolveValueFeedback(projectRoot);
|
|
@@ -32,6 +32,12 @@ export interface EvaluateOptions {
|
|
|
32
32
|
forIssue?: number | null;
|
|
33
33
|
allowStale?: boolean;
|
|
34
34
|
allowMissingBootstrap?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Skip the live GitHub drift probe only (#3507). Age staleness stays hard.
|
|
37
|
+
* Explicit flag — never inferred from a missing `--for-issue`.
|
|
38
|
+
* `--for-issue` (work selection) wins over this skip.
|
|
39
|
+
*/
|
|
40
|
+
skipDriftProbe?: boolean;
|
|
35
41
|
/** Injectable clock for tests. */
|
|
36
42
|
nowFn?: () => Date;
|
|
37
43
|
/** Injectable drift probe for tests. */
|
|
@@ -40,6 +46,12 @@ export interface EvaluateOptions {
|
|
|
40
46
|
autoPopulateEmpty?: boolean;
|
|
41
47
|
}
|
|
42
48
|
export declare function normaliseRepoUrl(url: string): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Drift skip is an explicit `--skip-drift-probe` request (#3507).
|
|
51
|
+
* Presence of `--for-issue` is work selection and re-arms the probe.
|
|
52
|
+
* Absence of `--for-issue` is not a skip signal.
|
|
53
|
+
*/
|
|
54
|
+
export declare function shouldSkipDriftProbe(options: EvaluateOptions): boolean;
|
|
43
55
|
/**
|
|
44
56
|
* Evaluate cache freshness for the given project root.
|
|
45
57
|
*
|
|
@@ -310,6 +310,16 @@ function runDriftProbe(resolvedRepo, cacheRoot, source, options) {
|
|
|
310
310
|
return null;
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Drift skip is an explicit `--skip-drift-probe` request (#3507).
|
|
315
|
+
* Presence of `--for-issue` is work selection and re-arms the probe.
|
|
316
|
+
* Absence of `--for-issue` is not a skip signal.
|
|
317
|
+
*/
|
|
318
|
+
export function shouldSkipDriftProbe(options) {
|
|
319
|
+
if (options.forIssue !== undefined && options.forIssue !== null)
|
|
320
|
+
return false;
|
|
321
|
+
return options.skipDriftProbe === true;
|
|
322
|
+
}
|
|
313
323
|
const REMEDIATION_NO_CANDIDATES = [
|
|
314
324
|
" Recovery: run `deft triage:bootstrap` to initialise the triage log.",
|
|
315
325
|
].join("\n");
|
|
@@ -471,7 +481,8 @@ function evaluateWithContext(projectRoot, options, ctx) {
|
|
|
471
481
|
].join("\n"),
|
|
472
482
|
};
|
|
473
483
|
}
|
|
474
|
-
|
|
484
|
+
const skipDrift = shouldSkipDriftProbe(options);
|
|
485
|
+
if (resolvedRepo !== null && !allowStale && !skipDrift) {
|
|
475
486
|
const drift = runDriftProbe(resolvedRepo, cacheRoot, source, options);
|
|
476
487
|
if (drift !== null &&
|
|
477
488
|
(drift.stateDriftNumbers.length > 0 || drift.contentDriftNumbers.length > 0)) {
|
|
@@ -506,6 +517,9 @@ function evaluateWithContext(projectRoot, options, ctx) {
|
|
|
506
517
|
if (options.forIssue !== undefined && options.forIssue !== null) {
|
|
507
518
|
msg += ` Issue #${options.forIssue} latest decision = accept; in subscription scope.`;
|
|
508
519
|
}
|
|
520
|
+
if (skipDrift) {
|
|
521
|
+
msg += " Drift probe skipped (no work selection).";
|
|
522
|
+
}
|
|
509
523
|
return { code: 0, message: msg };
|
|
510
524
|
}
|
|
511
525
|
function evaluateForIssue(repo, issueNumber, candidatesPath, cacheRoot, source, scopeRules, _projectRoot) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { EvaluateOptions, GateResult } from "./evaluate.js";
|
|
2
|
-
export { CACHE_DIR_NAME, CANDIDATES_RELPATH, DEFAULT_MAX_AGE_HOURS, DEFAULT_SOURCE, ENV_MAX_AGE_HOURS, ENV_RELEASE_PREFLIGHT, ENV_TRIAGE_REPO, evaluate, REQUIRED_DECISION, } from "./evaluate.js";
|
|
2
|
+
export { CACHE_DIR_NAME, CANDIDATES_RELPATH, DEFAULT_MAX_AGE_HOURS, DEFAULT_SOURCE, ENV_MAX_AGE_HOURS, ENV_RELEASE_PREFLIGHT, ENV_TRIAGE_REPO, evaluate, REQUIRED_DECISION, shouldSkipDriftProbe, } from "./evaluate.js";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { CACHE_DIR_NAME, CANDIDATES_RELPATH, DEFAULT_MAX_AGE_HOURS, DEFAULT_SOURCE, ENV_MAX_AGE_HOURS, ENV_RELEASE_PREFLIGHT, ENV_TRIAGE_REPO, evaluate, REQUIRED_DECISION, } from "./evaluate.js";
|
|
1
|
+
export { CACHE_DIR_NAME, CANDIDATES_RELPATH, DEFAULT_MAX_AGE_HOURS, DEFAULT_SOURCE, ENV_MAX_AGE_HOURS, ENV_RELEASE_PREFLIGHT, ENV_TRIAGE_REPO, evaluate, REQUIRED_DECISION, shouldSkipDriftProbe, } from "./evaluate.js";
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -23,6 +23,10 @@ export interface VerifyAcResult extends LiteralAcceptanceGateResult {
|
|
|
23
23
|
readonly clauseWalked?: boolean;
|
|
24
24
|
/** How the result was obtained (#3387). */
|
|
25
25
|
readonly servedFrom?: AcServedFrom;
|
|
26
|
+
/** Config-error cause when resolution is config (#3559). */
|
|
27
|
+
readonly cause?: string;
|
|
28
|
+
/** Reuse-gate miss cause when servedFrom is executed (#3558). */
|
|
29
|
+
readonly missReason?: string;
|
|
26
30
|
}
|
|
27
31
|
export interface EvaluateVerifyAcOptions extends EvaluateLiteralAcceptanceOptions {
|
|
28
32
|
/**
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, readFileSync } from "node:fs";
|
|
11
11
|
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
12
|
-
import { emitAcceptanceStampFromPlan } from "../intake/clause-derivation.js";
|
|
12
|
+
import { emitAcceptanceStampFromPlan, MISSING_AMBIGUITY_ATTESTATION_CAUSE, stampedAmbiguityAttestationError, } from "../intake/clause-derivation.js";
|
|
13
13
|
import { appendLiteralAcceptanceAdvisory, evaluateLiteralAcceptanceFromPlan, isNoopRefusalReason, runLiteralAcceptanceCommands, stripLiteralAcceptanceAdvisory, } from "../literal-acceptance/index.js";
|
|
14
14
|
import { ENV_RUN_SUMMARY_PATH, RunSummaryEmitter, } from "../run-summary/index.js";
|
|
15
15
|
import { maybeBankOnAcPass } from "../session/ac-pass-banking.js";
|
|
16
16
|
import { resolveAcReuse, resolveScopeIdForAcReuse, snapshotFromReuseFields, } from "../session/ac-pass-reuse.js";
|
|
17
|
+
import { gitHead } from "../session/git.js";
|
|
17
18
|
import { hashProductState } from "../session/product-state-hash.js";
|
|
18
19
|
import { resolveVerifyAcSessionId, writeVerifyAcSessionCache, } from "../session/verify-ac-session-cache.js";
|
|
19
20
|
import { formatClauseWalkMessage, walkAcceptanceClauses, } from "../verify-ac/clauses.js";
|
|
@@ -162,6 +163,24 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
|
|
|
162
163
|
}, optionsWithScope, plan);
|
|
163
164
|
}
|
|
164
165
|
const projectRoot = resolve(optionsWithScope.projectRoot ?? process.cwd());
|
|
166
|
+
const attestationError = stampedAmbiguityAttestationError(optionsWithScope.observedAcceptance !== undefined
|
|
167
|
+
? optionsWithScope.observedAcceptance
|
|
168
|
+
: plan.acceptance);
|
|
169
|
+
if (attestationError !== null) {
|
|
170
|
+
return applyOracle({
|
|
171
|
+
ok: false,
|
|
172
|
+
code: 2,
|
|
173
|
+
message: optionsWithScope.quiet === true ? "" : attestationError.message,
|
|
174
|
+
commands: [],
|
|
175
|
+
runs: [],
|
|
176
|
+
sourceRung: acceptance.source_rung,
|
|
177
|
+
noneStated: acceptance.none_stated,
|
|
178
|
+
acceptance,
|
|
179
|
+
resolution: "config",
|
|
180
|
+
resolvedCommandCount: 0,
|
|
181
|
+
cause: attestationError.cause ?? MISSING_AMBIGUITY_ATTESTATION_CAUSE,
|
|
182
|
+
}, optionsWithScope, plan);
|
|
183
|
+
}
|
|
165
184
|
const reused = tryReuseVerifyAc(plan, acceptance, optionsWithScope, projectRoot);
|
|
166
185
|
if (reused !== null) {
|
|
167
186
|
return applyOracle(reused, optionsWithScope, plan);
|
|
@@ -372,6 +391,8 @@ function emitAcceptanceOutcome(result, options, projectRoot) {
|
|
|
372
391
|
outcome: row.outcome,
|
|
373
392
|
})),
|
|
374
393
|
served_from: result.servedFrom ?? "executed",
|
|
394
|
+
...(result.cause !== undefined ? { cause: result.cause } : {}),
|
|
395
|
+
miss_reason: (result.servedFrom ?? "executed") === "executed" ? result.missReason : undefined,
|
|
375
396
|
});
|
|
376
397
|
}
|
|
377
398
|
catch {
|
|
@@ -524,10 +545,27 @@ function applyOracle(result, options, plan = {}) {
|
|
|
524
545
|
next = { ...next, resolution: "fail" };
|
|
525
546
|
}
|
|
526
547
|
}
|
|
548
|
+
const servedFrom = next.servedFrom ?? "executed";
|
|
549
|
+
let missReason = next.missReason;
|
|
550
|
+
if (servedFrom === "executed" && (missReason === undefined || missReason.length === 0)) {
|
|
551
|
+
const reuse = resolveAcReuse({
|
|
552
|
+
projectRoot,
|
|
553
|
+
plan,
|
|
554
|
+
scopeId: options.bankScopeId,
|
|
555
|
+
sessionId: options.sessionId,
|
|
556
|
+
env: options.env,
|
|
557
|
+
productPaths: options.productPaths,
|
|
558
|
+
allowCache: (options.reuseMode ?? "auto") === "auto",
|
|
559
|
+
allowBank: true,
|
|
560
|
+
});
|
|
561
|
+
if (reuse.kind === "miss")
|
|
562
|
+
missReason = reuse.reason;
|
|
563
|
+
}
|
|
527
564
|
const stamped = {
|
|
528
565
|
...next,
|
|
529
566
|
message: options.quiet === true ? next.message : labelVerdict(next),
|
|
530
|
-
servedFrom
|
|
567
|
+
servedFrom,
|
|
568
|
+
missReason: servedFrom === "executed" ? missReason : undefined,
|
|
531
569
|
};
|
|
532
570
|
persistVerifyAcSessionCache(stamped, options, projectRoot, plan);
|
|
533
571
|
if (options.skipAcceptanceEmit !== true) {
|
|
@@ -634,14 +672,14 @@ export function resolveOracleScopeKey(plan, xbriefPath, projectRoot) {
|
|
|
634
672
|
return rel;
|
|
635
673
|
}
|
|
636
674
|
/**
|
|
637
|
-
* After
|
|
638
|
-
* Soft/advisory
|
|
675
|
+
* After verified-pass, FINALIZE the banking checkpoint (#3285 / #3558).
|
|
676
|
+
* Soft/advisory empty-pass still skips. Ledger I/O failures fail closed.
|
|
639
677
|
*/
|
|
640
678
|
function maybeAttachAcPassBank(result, plan, xbriefPath, options) {
|
|
641
679
|
if (options.bankOnPass === false) {
|
|
642
680
|
return result;
|
|
643
681
|
}
|
|
644
|
-
if (!result.ok || result.
|
|
682
|
+
if (!result.ok || result.resolution !== "verified-pass") {
|
|
645
683
|
return result;
|
|
646
684
|
}
|
|
647
685
|
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
@@ -661,8 +699,11 @@ function maybeAttachAcPassBank(result, plan, xbriefPath, options) {
|
|
|
661
699
|
projectRoot,
|
|
662
700
|
scopeId,
|
|
663
701
|
executableRuns: result.runs.length,
|
|
702
|
+
verifiedPass: true,
|
|
664
703
|
quiet: options.quiet,
|
|
665
704
|
productStateHash: hashed.complete ? hashed.digest : null,
|
|
705
|
+
environ: options.env,
|
|
706
|
+
headSha: gitHead(projectRoot).head,
|
|
666
707
|
});
|
|
667
708
|
if (options.quiet || banked.notes.length === 0) {
|
|
668
709
|
return result;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ClosedVerbDecision } from "../authz/types.js";
|
|
2
|
+
import type { ReleaseConfig, ReleaseSeams } from "./types.js";
|
|
3
|
+
export declare const TAG_PUSH_CLOSED_VERB: "release-publish";
|
|
4
|
+
export declare function evaluateReleaseTagPushGate(version: string, projectRoot: string, seams: ReleaseSeams, repo: string): ClosedVerbDecision;
|
|
5
|
+
/**
|
|
6
|
+
* Fail closed before git tag + atomic tag push when skipTag is false.
|
|
7
|
+
* Dry-run and `--skip-tag` do not require a grant (no npm distribution).
|
|
8
|
+
* Returns EXIT_OK to continue, EXIT_VIOLATION to halt. Never spends the grant
|
|
9
|
+
* — draft-flip `release:publish` still owns markGrantUsed (#1095).
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertTagPushClosedVerb(config: ReleaseConfig, seams: ReleaseSeams): number;
|
|
12
|
+
//# sourceMappingURL=closed-verb-gate.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Closed-verb gate on the tag-push / npm-publish boundary (#3527).
|
|
3
|
+
*
|
|
4
|
+
* Reuses the draft-flip evaluator (`evaluateReleasePublishGate`) so the
|
|
5
|
+
* distributing step carries the same authz tier. Does not delete or weaken
|
|
6
|
+
* the later `release:publish` draft-flip check.
|
|
7
|
+
*/
|
|
8
|
+
import { closedVerbEnvBypassKey } from "../authz/closed-verb.js";
|
|
9
|
+
import { evaluateReleasePublishGate } from "../release-publish/pipeline.js";
|
|
10
|
+
import { EXIT_OK, EXIT_VIOLATION, TOTAL_STEPS } from "./constants.js";
|
|
11
|
+
function emitGate(label, status) {
|
|
12
|
+
process.stderr.write(`[10/${TOTAL_STEPS}] ${label}... ${status}\n`);
|
|
13
|
+
}
|
|
14
|
+
export const TAG_PUSH_CLOSED_VERB = "release-publish";
|
|
15
|
+
export function evaluateReleaseTagPushGate(version, projectRoot, seams, repo) {
|
|
16
|
+
return evaluateReleasePublishGate(version, projectRoot, {
|
|
17
|
+
grants: seams.closedVerbGrants,
|
|
18
|
+
env: seams.closedVerbEnv,
|
|
19
|
+
repo,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fail closed before git tag + atomic tag push when skipTag is false.
|
|
24
|
+
* Dry-run and `--skip-tag` do not require a grant (no npm distribution).
|
|
25
|
+
* Returns EXIT_OK to continue, EXIT_VIOLATION to halt. Never spends the grant
|
|
26
|
+
* — draft-flip `release:publish` still owns markGrantUsed (#1095).
|
|
27
|
+
*/
|
|
28
|
+
export function assertTagPushClosedVerb(config, seams) {
|
|
29
|
+
if (config.skipTag) {
|
|
30
|
+
return EXIT_OK;
|
|
31
|
+
}
|
|
32
|
+
const tag = `v${config.version}`;
|
|
33
|
+
const label = `Closed-verb gate ${TAG_PUSH_CLOSED_VERB} ${tag} (tag-push / npm-publish)`;
|
|
34
|
+
if (config.dryRun) {
|
|
35
|
+
emitGate(label, "DRYRUN (would require human-origin grant or DEFT_ALLOW_RELEASE_PUBLISH=1 before tag push)");
|
|
36
|
+
return EXIT_OK;
|
|
37
|
+
}
|
|
38
|
+
const gate = evaluateReleaseTagPushGate(config.version, config.projectRoot, seams, config.repo);
|
|
39
|
+
if (!gate.allowed) {
|
|
40
|
+
const bypassKey = gate.envBypassKey ??
|
|
41
|
+
closedVerbEnvBypassKey(TAG_PUSH_CLOSED_VERB, config.projectRoot) ??
|
|
42
|
+
"DEFT_ALLOW_RELEASE_PUBLISH";
|
|
43
|
+
emitGate(label, `FAIL (${gate.code}: ${gate.reason})`);
|
|
44
|
+
process.stderr.write(`[release] denied code=${gate.code} — set ${bypassKey}=1 or ` +
|
|
45
|
+
`mint \`deft authz:grant -- --template ${TAG_PUSH_CLOSED_VERB} --target ${config.version}\`\n`);
|
|
46
|
+
return EXIT_VIOLATION;
|
|
47
|
+
}
|
|
48
|
+
emitGate(label, `OK (${gate.code}${gate.humanApprovalRef !== null ? ` grant=${gate.humanApprovalRef}` : ""})`);
|
|
49
|
+
return EXIT_OK;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=closed-verb-gate.js.map
|
package/dist/release/index.d.ts
CHANGED
package/dist/release/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* v8 ignore file -- re-export barrel; covered via main.ts */
|
|
2
2
|
export * from "./auto-hatch.js";
|
|
3
3
|
export * from "./build-dist.js";
|
|
4
|
+
export * from "./closed-verb-gate.js";
|
|
4
5
|
export * from "./constants.js";
|
|
5
6
|
export * from "./coverage-debt-ledger.js";
|
|
6
7
|
export * from "./flags.js";
|
package/dist/release/pipeline.js
CHANGED
|
@@ -4,6 +4,7 @@ import { assertProjectionContained } from "../fs/projection-containment.js";
|
|
|
4
4
|
import { readCoverageTotalsFromReport } from "../vitest-runner/coverage-debt.js";
|
|
5
5
|
import { buildCoverageDebtIssueDraft, classifyStep5FailureWithFreshness, evaluateAutoHatch, formatAutoHatchBanner, parseExitCodeFromReason, reasonLooksLikeTimeout, } from "./auto-hatch.js";
|
|
6
6
|
import { prependUpgradeBanner, promoteChangelog, sectionForVersion } from "./changelog.js";
|
|
7
|
+
import { assertTagPushClosedVerb } from "./closed-verb-gate.js";
|
|
7
8
|
import { EXIT_CONFIG_ERROR, EXIT_OK, EXIT_VIOLATION, RELEASE_ARTIFACTS, RELEASE_CHECK_TIMEOUT_MINUTES, TOTAL_STEPS, VERIFY_DRAFT_INTERVAL_SECONDS, VERIFY_DRAFT_MAX_ATTEMPTS, } from "./constants.js";
|
|
8
9
|
import { createCoverageDebtIssue, probeOpenCoverageDebtLedger } from "./coverage-debt-ledger.js";
|
|
9
10
|
import { checkTagAvailable, createGithubRelease, readTextFile, verifyReleaseDraft } from "./gh.js";
|
|
@@ -373,6 +374,12 @@ export function runPipeline(config, seams = {}) {
|
|
|
373
374
|
return EXIT_VIOLATION;
|
|
374
375
|
}
|
|
375
376
|
}
|
|
377
|
+
// Step 10–11 boundary: closed-verb gate before tag + atomic push (#3527).
|
|
378
|
+
// npm publish fires on tag push; draft-flip `release:publish` stays (#1095).
|
|
379
|
+
const tagPushGate = assertTagPushClosedVerb(config, seams);
|
|
380
|
+
if (tagPushGate !== EXIT_OK) {
|
|
381
|
+
return tagPushGate;
|
|
382
|
+
}
|
|
376
383
|
// Step 10: git tag.
|
|
377
384
|
label = `Tag v${version}`;
|
|
378
385
|
if (config.skipTag) {
|
package/dist/release/types.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { EnvMap } from "../authz/closed-verb.js";
|
|
2
|
+
import type { HumanOriginGrant } from "../authz/types.js";
|
|
1
3
|
export interface ReleaseConfig {
|
|
2
4
|
readonly version: string;
|
|
3
5
|
readonly repo: string;
|
|
@@ -77,5 +79,15 @@ export interface ReleaseSeams {
|
|
|
77
79
|
readonly headSha?: (projectRoot: string) => string | null;
|
|
78
80
|
/** #3187 — CI detector; when true suite stamp is never trusted. */
|
|
79
81
|
readonly isCi?: () => boolean;
|
|
82
|
+
/**
|
|
83
|
+
* #3527 — closed-verb grants for the tag-push / npm-publish gate.
|
|
84
|
+
* When omitted, production loads active human-origin grants from disk.
|
|
85
|
+
*/
|
|
86
|
+
readonly closedVerbGrants?: readonly HumanOriginGrant[];
|
|
87
|
+
/**
|
|
88
|
+
* #3527 — env map for DEFT_ALLOW_RELEASE_PUBLISH. When omitted, process.env.
|
|
89
|
+
* Tests inject `{}` so a host env bypass cannot leak into fail-closed cases.
|
|
90
|
+
*/
|
|
91
|
+
readonly closedVerbEnv?: EnvMap;
|
|
80
92
|
}
|
|
81
93
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Canonical v0.6 status enum (matches `scripts/spec_validate.py`). */
|
|
2
2
|
export declare const VALID_STATUSES: Set<string>;
|
|
3
|
-
export
|
|
3
|
+
export { VALID_VBRIEF_VERSIONS } from "../vbrief-validate/constants.js";
|
|
4
|
+
export { resolveInfoBlock } from "../vbrief-validate/schema.js";
|
|
4
5
|
export declare const SPECIFICATION_NARRATIVE_KEY_ORDER: readonly ["Overview", "ProblemStatement", "Goals", "UserStories", "Requirements", "SuccessMetrics", "EdgeCases", "Architecture", "TechDecisions", "ImplementationPhases", "PreImplementationGates"];
|
|
5
6
|
/** Migration capture narrative; omitted from default compact SPECIFICATION.md (#1566). */
|
|
6
7
|
export declare const LEGACY_ARTIFACTS_NARRATIVE_KEY = "LegacyArtifacts";
|
package/dist/render/constants.js
CHANGED
|
@@ -10,7 +10,8 @@ export const VALID_STATUSES = new Set([
|
|
|
10
10
|
"failed",
|
|
11
11
|
"cancelled",
|
|
12
12
|
]);
|
|
13
|
-
export
|
|
13
|
+
export { VALID_VBRIEF_VERSIONS } from "../vbrief-validate/constants.js";
|
|
14
|
+
export { resolveInfoBlock } from "../vbrief-validate/schema.js";
|
|
14
15
|
export const SPECIFICATION_NARRATIVE_KEY_ORDER = [
|
|
15
16
|
"Overview",
|
|
16
17
|
"ProblemStatement",
|
|
@@ -85,6 +85,9 @@ export const COMMANDS = {
|
|
|
85
85
|
"verify:orphan-active": spec("verify:orphan-active", "verify_orphan_active:main", {
|
|
86
86
|
projectRootArg: "--project-root",
|
|
87
87
|
}),
|
|
88
|
+
"verify:lifecycle-visible": spec("verify:lifecycle-visible", "verify_lifecycle_visible:main", {
|
|
89
|
+
projectRootArg: "--project-root",
|
|
90
|
+
}),
|
|
88
91
|
"verify:completed-tracked": spec("verify:completed-tracked", "verify_completed_tracked:main", {
|
|
89
92
|
projectRootArg: "--project-root",
|
|
90
93
|
}),
|
|
@@ -302,6 +305,7 @@ const ENTRYPOINT_VERB = {
|
|
|
302
305
|
"preflight_cache:main": "preflight-cache",
|
|
303
306
|
"preflight_wip_cap:main": "verify-wip-cap",
|
|
304
307
|
"verify_orphan_active:main": "verify-orphan-active",
|
|
308
|
+
"verify_lifecycle_visible:main": "verify-lifecycle-visible",
|
|
305
309
|
"verify_completed_tracked:main": "verify-completed-tracked",
|
|
306
310
|
"pack_render:main": "pack-render",
|
|
307
311
|
"validate_strategy_output:main": "validate-strategy-output",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { VALID_INFO_ROOT_KEYS, VALID_VBRIEF_VERSIONS } from "../vbrief-validate/constants.js";
|
|
4
|
+
import { resolveInfoBlock } from "../vbrief-validate/schema.js";
|
|
5
|
+
import { VALID_STATUSES } from "./constants.js";
|
|
4
6
|
function validateNarratives(narratives, path, errors) {
|
|
5
7
|
if (typeof narratives !== "object" || narratives === null || Array.isArray(narratives)) {
|
|
6
8
|
errors.push(`${path} must be an object`);
|
|
@@ -46,19 +48,28 @@ function validatePlanItem(item, path, errors) {
|
|
|
46
48
|
}
|
|
47
49
|
function validateSchema(data) {
|
|
48
50
|
const errors = [];
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
const resolved = resolveInfoBlock(data);
|
|
52
|
+
if (resolved === null) {
|
|
53
|
+
let infoShapeError = false;
|
|
54
|
+
for (const key of VALID_INFO_ROOT_KEYS) {
|
|
55
|
+
if (!(key in data))
|
|
56
|
+
continue;
|
|
57
|
+
const info = data[key];
|
|
58
|
+
if (info === null || Array.isArray(info) || typeof info !== "object") {
|
|
59
|
+
errors.push(`'${key}' must be an object`);
|
|
60
|
+
infoShapeError = true;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!infoShapeError) {
|
|
65
|
+
errors.push("missing required top-level key 'vBRIEFInfo' or 'xBRIEFInfo'");
|
|
66
|
+
}
|
|
51
67
|
}
|
|
52
68
|
else {
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
else {
|
|
58
|
-
const version = info.version;
|
|
59
|
-
if (!VALID_VBRIEF_VERSIONS.has(String(version))) {
|
|
60
|
-
errors.push(`'vBRIEFInfo.version' must be '0.6' (canonical v0.6 schema, #533), got ${JSON.stringify(version)}. Run \`task migrate:vbrief\` to upgrade pre-existing v0.5 vBRIEFs in-place.`);
|
|
61
|
-
}
|
|
69
|
+
const version = resolved.info.version;
|
|
70
|
+
if (!VALID_VBRIEF_VERSIONS.has(String(version))) {
|
|
71
|
+
const allowed = [...VALID_VBRIEF_VERSIONS].map((v) => `'${v}'`).join(", ");
|
|
72
|
+
errors.push(`'${resolved.key}.version' must be one of ${allowed} (canonical v0.6/v0.8 schema, #2107), got ${JSON.stringify(version)}. Run \`task migrate:xbrief\` to upgrade pre-existing v0.5 vBRIEFs in-place.`);
|
|
62
73
|
}
|
|
63
74
|
}
|
|
64
75
|
if (!("plan" in data)) {
|
|
@@ -128,6 +128,10 @@ export interface AcceptanceRunSummaryPayload {
|
|
|
128
128
|
readonly clause_outcomes?: readonly AcceptanceClauseOutcomeRow[];
|
|
129
129
|
/** bank | cache | executed — how verify:ac obtained this result (#3387). */
|
|
130
130
|
readonly served_from?: "bank" | "cache" | "executed";
|
|
131
|
+
/** Config-error cause when outcome is config-error (#3559). */
|
|
132
|
+
readonly cause?: string;
|
|
133
|
+
/** Reuse-gate miss cause when served_from is executed (#3558). */
|
|
134
|
+
readonly miss_reason?: string;
|
|
131
135
|
}
|
|
132
136
|
/** Intake-time stamp: which rung locked, whether commands were stated, counts (#3323). */
|
|
133
137
|
export interface AcceptanceStampRunSummaryPayload {
|
package/dist/scope/main.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { maybeRunStalenessTickler } from "../staleness-tickler/run.js";
|
|
4
|
+
import { interceptHelp } from "../triage/help/index.js";
|
|
4
5
|
import { reconcileUmbrellas, renderUmbrellasReport } from "../vbrief-reconcile/umbrellas.js";
|
|
5
6
|
import { canonicalLogPath, readAll } from "./audit-log.js";
|
|
6
7
|
import { batchPromote } from "./batch-promote.js";
|
|
@@ -13,12 +14,11 @@ import { promoteFromIssue } from "./promote-from-issue.js";
|
|
|
13
14
|
import { promotePath } from "./promote-path.js";
|
|
14
15
|
import { runTransition } from "./transition.js";
|
|
15
16
|
import { findByDecisionId, isAlreadyUndone, REVERSIBLE_ACTIONS, undoBatch, undoOne, } from "./undo.js";
|
|
16
|
-
const LIFECYCLE_USAGE_STDERR = "usage:
|
|
17
|
-
" [--
|
|
17
|
+
const LIFECYCLE_USAGE_STDERR = "usage: deft scope:<action> [file] [--batch] [--from-issue N] [--repo OWNER/NAME]\n" +
|
|
18
|
+
" [--force] [--project-root PATH] [--strict] [--force-no-cache]\n" +
|
|
18
19
|
" [--path PATH]\n" +
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"scope_lifecycle.py: error: the following arguments are required: action, file\n" +
|
|
20
|
+
" actions: activate, block, cancel, complete, fail, promote, restore, unblock\n" +
|
|
21
|
+
"The verb already encodes the action (e.g. deft scope:promote <file>). Do not pass the action again.\n" +
|
|
22
22
|
"(promote --batch may omit file and promotes all proposed/ scopes; #3011)\n" +
|
|
23
23
|
"(promote --from-issue=N may omit file; #1136)\n";
|
|
24
24
|
function parseLifecycleArgv(argv) {
|
|
@@ -50,6 +50,8 @@ function parseLifecycleArgv(argv) {
|
|
|
50
50
|
if (arg === undefined) {
|
|
51
51
|
return { args: null, error: "usage" };
|
|
52
52
|
}
|
|
53
|
+
if (arg === "--")
|
|
54
|
+
continue;
|
|
53
55
|
if (arg === "--force") {
|
|
54
56
|
force = true;
|
|
55
57
|
}
|
|
@@ -236,6 +238,14 @@ function parseLifecycleArgv(argv) {
|
|
|
236
238
|
}
|
|
237
239
|
/** Main entry for scope_lifecycle.py parity. */
|
|
238
240
|
export function lifecycleMain(argv) {
|
|
241
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
242
|
+
const helpRc = interceptHelp("scope_lifecycle", argv);
|
|
243
|
+
if (helpRc !== null) {
|
|
244
|
+
return helpRc;
|
|
245
|
+
}
|
|
246
|
+
process.stdout.write(LIFECYCLE_USAGE_STDERR);
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
239
249
|
const parsed = parseLifecycleArgv(argv);
|
|
240
250
|
if (parsed.args === null) {
|
|
241
251
|
if (parsed.error === "usage") {
|
|
@@ -195,7 +195,7 @@ export function promoteFromIssue(options) {
|
|
|
195
195
|
return {
|
|
196
196
|
ok: false,
|
|
197
197
|
message: `No triage-cache decision for #${n} (${repoSafe}). ` +
|
|
198
|
-
`Accept first:
|
|
198
|
+
`Accept first: deft triage:accept --issue ${n} --repo ${repoSafe} ` +
|
|
199
199
|
`(or omit --strict to soft-warn and proceed; --force-no-cache skips the gate).`,
|
|
200
200
|
exitCode: 1,
|
|
201
201
|
warnings,
|
|
@@ -212,7 +212,7 @@ export function promoteFromIssue(options) {
|
|
|
212
212
|
ok: false,
|
|
213
213
|
message: `Refusing promote for #${n} (${repoSafe}): latest triage decision is '${sanitizeMsg(latest.decision)}' ` +
|
|
214
214
|
`(decision_id=${latest.decision_id}). ` +
|
|
215
|
-
`Accept first:
|
|
215
|
+
`Accept first: deft triage:accept --issue ${n} --repo ${repoSafe} ` +
|
|
216
216
|
`or override with --force-no-cache.`,
|
|
217
217
|
exitCode: 1,
|
|
218
218
|
warnings,
|
|
@@ -315,8 +315,8 @@ export function promoteFromIssue(options) {
|
|
|
315
315
|
return {
|
|
316
316
|
ok: false,
|
|
317
317
|
message: `No proposed/ scope artifact found for issue #${n} (${repoSafe}). ` +
|
|
318
|
-
`Ingest first (
|
|
319
|
-
`or pass an explicit path:
|
|
318
|
+
`Ingest first (deft triage:accept --issue ${n} --repo ${repoSafe}) ` +
|
|
319
|
+
`or pass an explicit path: deft scope:promote <path>.`,
|
|
320
320
|
exitCode: 1,
|
|
321
321
|
warnings,
|
|
322
322
|
repo,
|
|
@@ -330,7 +330,7 @@ export function promoteFromIssue(options) {
|
|
|
330
330
|
return {
|
|
331
331
|
ok: false,
|
|
332
332
|
message: `Multiple proposed/ artifacts match issue #${n}; refuse to guess.\n${list}\n` +
|
|
333
|
-
`Promote one path explicitly:
|
|
333
|
+
`Promote one path explicitly: deft scope:promote <path> ` +
|
|
334
334
|
`or re-run with --path <one-of-the-above>.`,
|
|
335
335
|
exitCode: 1,
|
|
336
336
|
warnings,
|