@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
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { type CheckIgnoreMatch, displayIgnoreSource, evaluateLifecycleVisible, formatLifecycleVisibleSessionLines, indexFlagKind, isSelectiveLifecyclePath, LIFECYCLE_ROOT_PREFIXES, LIFECYCLE_STAGE_DIRS, type LifecycleHideFinding, type LifecycleHideKind, type LifecycleVisibleOptions, type LifecycleVisibleResult, type LsFilesVerboseRecord, lifecycleRootRelPaths, parseCheckIgnoreVerboseLine, parseLsFilesVerboseRecord, } from "./evaluate.js";
|
|
2
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { displayIgnoreSource, evaluateLifecycleVisible, formatLifecycleVisibleSessionLines, indexFlagKind, isSelectiveLifecyclePath, LIFECYCLE_ROOT_PREFIXES, LIFECYCLE_STAGE_DIRS, lifecycleRootRelPaths, parseCheckIgnoreVerboseLine, parseLsFilesVerboseRecord, } from "./evaluate.js";
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -167,12 +167,12 @@ function matchLabeledCommand(line) {
|
|
|
167
167
|
return null;
|
|
168
168
|
return line.slice(i).trimEnd();
|
|
169
169
|
}
|
|
170
|
-
/** `$ cmd`
|
|
170
|
+
/** `$ cmd` prompt lines. Markdown blockquote `>` is not a prompt (#3572). */
|
|
171
171
|
function matchPromptCommand(line) {
|
|
172
172
|
let i = skipWs(line, 0);
|
|
173
173
|
if (i >= line.length)
|
|
174
174
|
return null;
|
|
175
|
-
if (line[i] !== "$"
|
|
175
|
+
if (line[i] !== "$")
|
|
176
176
|
return null;
|
|
177
177
|
i = skipWs(line, i + 1);
|
|
178
178
|
if (i >= line.length)
|
|
@@ -259,7 +259,7 @@ function flushFenceBody(body, fenceLang, fenceStartLine, requireRegion, regionAc
|
|
|
259
259
|
return;
|
|
260
260
|
if (!isShellFenceLang(fenceLang))
|
|
261
261
|
return;
|
|
262
|
-
// Transcript lines are skipped.
|
|
262
|
+
// Transcript lines are skipped. `$` prompts inside a transcript fence are
|
|
263
263
|
// suggested-fixes (biome migrate), not stated AC (#3511). Comments do not
|
|
264
264
|
// mark a fence as transcript, so a genuine command next to `# Error:` still
|
|
265
265
|
// captures.
|
|
@@ -2,6 +2,19 @@ export declare const GATE_CLASSES: Set<string>;
|
|
|
2
2
|
export declare const GATE_TIERS: Set<string>;
|
|
3
3
|
export declare const GATE_MATCH_PREDICATES: Set<string>;
|
|
4
4
|
export declare const GATE_MATCH_STATES: Set<string>;
|
|
5
|
+
/** ADR-005 / #3434 Story 1 -- author-stamped marker vocabulary. */
|
|
6
|
+
export declare const DESIGN_CRITIQUE_GATE_ID = "design-critique";
|
|
7
|
+
/** List-visible mirror; this is what judgmentGates predicates match. */
|
|
8
|
+
export declare const DESIGN_CRITIQUE_MARKER_LABEL = "design-critique:mechanism-shaped";
|
|
9
|
+
/** Authoritative body-text field in the #1423 triage write-back comment. */
|
|
10
|
+
export declare const DESIGN_CRITIQUE_MARKER_FIELD = "mechanism-shaped: true";
|
|
11
|
+
/**
|
|
12
|
+
* Clearance line shape (ADR-005). Presence and shape only -- a non-empty
|
|
13
|
+
* because-clause is required; its semantic content is never evaluated.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DESIGN_CRITIQUE_CLEARANCE_SHAPE: RegExp;
|
|
16
|
+
/** True when `line` has clearance presence+shape. Never scores the reason text. */
|
|
17
|
+
export declare function designCritiqueClearanceShapeOk(line: string): boolean;
|
|
5
18
|
export interface JudgmentGate {
|
|
6
19
|
readonly gate_id: string;
|
|
7
20
|
readonly gate_class: string;
|
|
@@ -7,6 +7,21 @@ export const GATE_CLASSES = new Set(["mechanical", "declared"]);
|
|
|
7
7
|
export const GATE_TIERS = new Set(["auto", "review", "block"]);
|
|
8
8
|
export const GATE_MATCH_PREDICATES = new Set(["labels", "body-text", "paths", "state", "age-days"]);
|
|
9
9
|
export const GATE_MATCH_STATES = new Set(["open", "closed"]);
|
|
10
|
+
/** ADR-005 / #3434 Story 1 -- author-stamped marker vocabulary. */
|
|
11
|
+
export const DESIGN_CRITIQUE_GATE_ID = "design-critique";
|
|
12
|
+
/** List-visible mirror; this is what judgmentGates predicates match. */
|
|
13
|
+
export const DESIGN_CRITIQUE_MARKER_LABEL = "design-critique:mechanism-shaped";
|
|
14
|
+
/** Authoritative body-text field in the #1423 triage write-back comment. */
|
|
15
|
+
export const DESIGN_CRITIQUE_MARKER_FIELD = "mechanism-shaped: true";
|
|
16
|
+
/**
|
|
17
|
+
* Clearance line shape (ADR-005). Presence and shape only -- a non-empty
|
|
18
|
+
* because-clause is required; its semantic content is never evaluated.
|
|
19
|
+
*/
|
|
20
|
+
export const DESIGN_CRITIQUE_CLEARANCE_SHAPE = /^design-critique:\s*(warranted|not warranted),\s*because\s+\S/i;
|
|
21
|
+
/** True when `line` has clearance presence+shape. Never scores the reason text. */
|
|
22
|
+
export function designCritiqueClearanceShapeOk(line) {
|
|
23
|
+
return DESIGN_CRITIQUE_CLEARANCE_SHAPE.test(line.trim());
|
|
24
|
+
}
|
|
10
25
|
function validateStrList(value, prefix, key) {
|
|
11
26
|
if (!Array.isArray(value) || value.length === 0) {
|
|
12
27
|
return [`${prefix}.${key} must be a non-empty list of strings`];
|
|
@@ -12,6 +12,11 @@ export const SESSION_RELPATH = [".deft", "probe-session.json"];
|
|
|
12
12
|
export const STATE_INTERROGATE = "interrogate";
|
|
13
13
|
export const STATE_COMPLETE = "complete";
|
|
14
14
|
export const VALID_DECISION_STATUSES = new Set(["locked", "deferred", "risk-accepted"]);
|
|
15
|
+
/** Live CLI teach-string (#3556). The Python helper was deleted. */
|
|
16
|
+
const PROBE_SESSION_START = "`deft probe-session start --target <scope>`";
|
|
17
|
+
const PROBE_SESSION_RECORD = "`deft probe-session record ...`";
|
|
18
|
+
const PROBE_SESSION_COMPLETE = "`deft probe-session complete`";
|
|
19
|
+
const NO_SESSION_START = `No active probe session. Start one with ${PROBE_SESSION_START}.`;
|
|
15
20
|
export class ProbeHandoffBlockedError extends Error {
|
|
16
21
|
session;
|
|
17
22
|
constructor(message, session = null) {
|
|
@@ -249,7 +254,7 @@ export function startSession(projectRoot, options) {
|
|
|
249
254
|
export function recordDecision(projectRoot, options) {
|
|
250
255
|
const session = readSession(projectRoot);
|
|
251
256
|
if (session === null) {
|
|
252
|
-
throw new ProbeHandoffBlockedError(
|
|
257
|
+
throw new ProbeHandoffBlockedError(NO_SESSION_START);
|
|
253
258
|
}
|
|
254
259
|
if (session.state !== STATE_INTERROGATE) {
|
|
255
260
|
throw new ProbeHandoffBlockedError("Probe session is already complete; decisions cannot be appended.", session);
|
|
@@ -275,7 +280,7 @@ export function recordDecision(projectRoot, options) {
|
|
|
275
280
|
export function setCurrentBranch(projectRoot, branch) {
|
|
276
281
|
const session = readSession(projectRoot);
|
|
277
282
|
if (session === null) {
|
|
278
|
-
throw new ProbeHandoffBlockedError(
|
|
283
|
+
throw new ProbeHandoffBlockedError(NO_SESSION_START);
|
|
279
284
|
}
|
|
280
285
|
if (session.state !== STATE_INTERROGATE) {
|
|
281
286
|
throw new ProbeHandoffBlockedError("Probe session is already complete; current branch cannot change.", session);
|
|
@@ -287,7 +292,7 @@ export function setCurrentBranch(projectRoot, branch) {
|
|
|
287
292
|
export function markComplete(projectRoot, now) {
|
|
288
293
|
const session = readSession(projectRoot);
|
|
289
294
|
if (session === null) {
|
|
290
|
-
throw new ProbeHandoffBlockedError(
|
|
295
|
+
throw new ProbeHandoffBlockedError(NO_SESSION_START);
|
|
291
296
|
}
|
|
292
297
|
if (session.state === STATE_COMPLETE) {
|
|
293
298
|
return session;
|
|
@@ -307,7 +312,7 @@ function pythonRepr(value) {
|
|
|
307
312
|
function blockedMessage(session, action) {
|
|
308
313
|
if (session === null) {
|
|
309
314
|
return (`Probe handoff blocked for ${action}: no active probe session. ` +
|
|
310
|
-
|
|
315
|
+
`Start interrogation with ${PROBE_SESSION_START} ` +
|
|
311
316
|
"and finish with `... complete` only after transition criteria are met.");
|
|
312
317
|
}
|
|
313
318
|
return (`Probe handoff blocked for ${action}: session state is ` +
|
|
@@ -315,9 +320,9 @@ function blockedMessage(session, action) {
|
|
|
315
320
|
`currentBranch=${pythonRepr(session.current_branch)}, ` +
|
|
316
321
|
`resolvedDecisions=${session.resolved_decisions.length}). ` +
|
|
317
322
|
"Continue interrogation until transition criteria are met, record decisions " +
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
"or updating completedStrategies.probe in plan.
|
|
323
|
+
`with ${PROBE_SESSION_RECORD}, then run ` +
|
|
324
|
+
`${PROBE_SESSION_COMPLETE} before writing artifacts ` +
|
|
325
|
+
"or updating completedStrategies.probe in plan.xbrief.json.");
|
|
321
326
|
}
|
|
322
327
|
export function requireHandoffAllowed(projectRoot, action) {
|
|
323
328
|
const session = readSession(projectRoot);
|
|
@@ -330,7 +335,7 @@ export function guardProbeArtifact(projectRoot, artifactPath) {
|
|
|
330
335
|
return requireHandoffAllowed(projectRoot, `probe artifact write (${artifactPath})`);
|
|
331
336
|
}
|
|
332
337
|
export function guardPlanProbeRegistration(projectRoot) {
|
|
333
|
-
return requireHandoffAllowed(projectRoot, "completedStrategies.probe registration in plan.
|
|
338
|
+
return requireHandoffAllowed(projectRoot, "completedStrategies.probe registration in plan.xbrief.json");
|
|
334
339
|
}
|
|
335
340
|
export function sessionSummary(session) {
|
|
336
341
|
return {
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Risk-tiered judgment-gate engine (#1419 Slice 3). Port of scripts/verify_judgment_gates.py.
|
|
3
3
|
*/
|
|
4
4
|
import { type JudgmentGatesPolicy } from "./judgment-policy.js";
|
|
5
|
+
/** Thrown when a design-critique clearance reason fails presence/shape (never content). */
|
|
6
|
+
export declare class JudgmentClearanceShapeError extends Error {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
5
9
|
/** Display/back-compat constant; resolution flows through resolveAuditDir (#2109). */
|
|
6
10
|
export declare const AUDIT_DIR_REL = "vbrief/.audit";
|
|
7
11
|
export declare const CLEARANCE_LOG_NAME = "judgment-gate-clearances.jsonl";
|
|
@@ -7,8 +7,15 @@ import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, statSyn
|
|
|
7
7
|
import { join, resolve } from "node:path";
|
|
8
8
|
import { assertProjectionContained, assertWriteTargetSafe, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
9
9
|
import { resolveAuditDir } from "../layout/resolve.js";
|
|
10
|
-
import { resolveJudgmentGates } from "./judgment-policy.js";
|
|
10
|
+
import { DESIGN_CRITIQUE_GATE_ID, designCritiqueClearanceShapeOk, resolveJudgmentGates, } from "./judgment-policy.js";
|
|
11
11
|
import { matchAny } from "./pathspec.js";
|
|
12
|
+
/** Thrown when a design-critique clearance reason fails presence/shape (never content). */
|
|
13
|
+
export class JudgmentClearanceShapeError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "JudgmentClearanceShapeError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
12
19
|
/** Display/back-compat constant; resolution flows through resolveAuditDir (#2109). */
|
|
13
20
|
export const AUDIT_DIR_REL = "vbrief/.audit";
|
|
14
21
|
export const CLEARANCE_LOG_NAME = "judgment-gate-clearances.jsonl";
|
|
@@ -163,6 +170,12 @@ export function readClearances(projectRoot, logPath) {
|
|
|
163
170
|
return out;
|
|
164
171
|
}
|
|
165
172
|
export function recordClearance(projectRoot, options) {
|
|
173
|
+
if (options.gate_id === DESIGN_CRITIQUE_GATE_ID &&
|
|
174
|
+
!designCritiqueClearanceShapeOk(options.reason ?? "")) {
|
|
175
|
+
throw new JudgmentClearanceShapeError("design-critique clearance reason must match shape " +
|
|
176
|
+
"'design-critique: warranted | not warranted, because ...' " +
|
|
177
|
+
"(presence and shape only; the because-clause is not scored)");
|
|
178
|
+
}
|
|
166
179
|
const path = options.log_path ?? clearanceLogPath(projectRoot);
|
|
167
180
|
const auditDir = resolveAuditDir(projectRoot);
|
|
168
181
|
assertProjectionContained(projectRoot, auditDir);
|
|
@@ -202,6 +215,15 @@ export function fingerprintScope(evidence) {
|
|
|
202
215
|
const payload = JSON.stringify(sorted);
|
|
203
216
|
return createHash("sha256").update(payload, "utf8").digest("hex");
|
|
204
217
|
}
|
|
218
|
+
function acceptClearanceShape(gateId, clearance) {
|
|
219
|
+
if (clearance === null) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
if (gateId !== DESIGN_CRITIQUE_GATE_ID) {
|
|
223
|
+
return clearance;
|
|
224
|
+
}
|
|
225
|
+
return designCritiqueClearanceShapeOk(String(clearance.reason ?? "")) ? clearance : null;
|
|
226
|
+
}
|
|
205
227
|
function lookupClearance(clearances, gateId, scope) {
|
|
206
228
|
let valid = null;
|
|
207
229
|
let stale = null;
|
|
@@ -383,7 +405,8 @@ export function buildReport(projectRoot, candidate, options = {}) {
|
|
|
383
405
|
continue;
|
|
384
406
|
}
|
|
385
407
|
const scope = fingerprintScope(evidence);
|
|
386
|
-
const [
|
|
408
|
+
const [validRaw, stale] = lookupClearance(records, String(gate.id), scope);
|
|
409
|
+
const valid = acceptClearanceShape(String(gate.id), validRaw);
|
|
387
410
|
outcomes.push({
|
|
388
411
|
gate_id: String(gate.id),
|
|
389
412
|
gate_class: String(gate.class),
|
|
@@ -605,15 +628,24 @@ function clearMain(argv) {
|
|
|
605
628
|
if (args.updatedAt !== null)
|
|
606
629
|
evidence["age-days"] = args.updatedAt;
|
|
607
630
|
const scope = fingerprintScope(evidence);
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
631
|
+
try {
|
|
632
|
+
const entry = recordClearance(projectRoot, {
|
|
633
|
+
gate_id: args.gateId,
|
|
634
|
+
cleared_scope: scope,
|
|
635
|
+
reviewers: args.reviewers,
|
|
636
|
+
actor: args.actor,
|
|
637
|
+
reason: args.reason,
|
|
638
|
+
});
|
|
639
|
+
process.stdout.write(`recorded clearance ${String(entry.clearance_id)} for gate ${JSON.stringify(args.gateId)} (cleared_scope=${scope.slice(0, 12)}...)\n`);
|
|
640
|
+
return 0;
|
|
641
|
+
}
|
|
642
|
+
catch (err) {
|
|
643
|
+
if (err instanceof JudgmentClearanceShapeError) {
|
|
644
|
+
process.stderr.write(`${err.message}\n`);
|
|
645
|
+
return 1;
|
|
646
|
+
}
|
|
647
|
+
throw err;
|
|
648
|
+
}
|
|
617
649
|
}
|
|
618
650
|
function parseEvalArgs(argv) {
|
|
619
651
|
const parsed = {
|
|
@@ -23,7 +23,7 @@ import { RunSummaryEmitter } from "../run-summary/emit.js";
|
|
|
23
23
|
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
24
24
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
25
25
|
import { policyColonInvocation } from "./policy-invocation.js";
|
|
26
|
-
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
26
|
+
import { appendAuditLog, loadProjectDefinition, POLICY_AUDIT_NOOP_STDOUT, projectDefinitionPath, } from "./resolve.js";
|
|
27
27
|
/** Canonical dotted policy field name. */
|
|
28
28
|
export const FIELD_CEREMONY_DIAL = "plan.policy.ceremonyDial";
|
|
29
29
|
/** Short alias for `policy:show --field=ceremonyDial`. */
|
|
@@ -527,7 +527,7 @@ export function setCeremonyDial(projectRoot, options) {
|
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
529
|
const plan = data.plan;
|
|
530
|
-
migrateLegacyPolicyKey(plan);
|
|
530
|
+
const legacyKeyMigrated = migrateLegacyPolicyKey(plan);
|
|
531
531
|
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
532
532
|
if (typeof existingPolicy !== "object" ||
|
|
533
533
|
existingPolicy === null ||
|
|
@@ -564,7 +564,9 @@ export function setCeremonyDial(projectRoot, options) {
|
|
|
564
564
|
override: nextOverride,
|
|
565
565
|
};
|
|
566
566
|
const prevParsed = parseConfig(previous).config;
|
|
567
|
-
const changedFlag = prevParsed.enabled !== nextBlock.enabled ||
|
|
567
|
+
const changedFlag = prevParsed.enabled !== nextBlock.enabled ||
|
|
568
|
+
prevParsed.override !== nextBlock.override ||
|
|
569
|
+
legacyKeyMigrated;
|
|
568
570
|
policyBlock.ceremonyDial = nextBlock;
|
|
569
571
|
if (changedFlag) {
|
|
570
572
|
atomicWriteProjectDefinition(path, data);
|
|
@@ -580,15 +582,13 @@ export function setCeremonyDial(projectRoot, options) {
|
|
|
580
582
|
if (note) {
|
|
581
583
|
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
582
584
|
}
|
|
583
|
-
appendAuditLog(projectRoot, parts.join(" "));
|
|
585
|
+
appendAuditLog(projectRoot, parts.join(" "), changedFlag);
|
|
584
586
|
return { changed: changedFlag };
|
|
585
587
|
});
|
|
586
588
|
const resolved = resolveCeremonyDial(projectRoot);
|
|
587
589
|
const lines = [
|
|
588
590
|
`\u2713 ${FIELD_CEREMONY_DIAL} updated.`,
|
|
589
|
-
changed
|
|
590
|
-
? " audit: meta/policy-changes.log updated."
|
|
591
|
-
: " no-op: value already matched (audit entry still appended for trail).",
|
|
591
|
+
changed ? " audit: meta/policy-changes.log updated." : POLICY_AUDIT_NOOP_STDOUT,
|
|
592
592
|
formatCeremonyDialStatusLine(resolved),
|
|
593
593
|
];
|
|
594
594
|
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { HookHost } from "../hooks/dispatcher.js";
|
|
2
|
+
export type { HookHost };
|
|
2
3
|
export declare const FIELD_HOST_HOOKS = "plan.policy.hostHooks";
|
|
3
4
|
export declare const FIELD_HOST_HOOKS_CLI_ALIAS = "hostHooks";
|
|
5
|
+
export declare const DISABLE_HOST_HOOKS_SUBCOMMAND = "disable-host-hooks";
|
|
6
|
+
/** Confirm-gated disable verb for unused-host recovery copy (#3571). CLI form: no go-task `--`. */
|
|
7
|
+
export declare function disableHostHooksInvocation(trailing?: string): string;
|
|
8
|
+
export declare const HOST_HOOKS_DISABLE_CAPABILITY_COST_DISCLOSURE: string;
|
|
9
|
+
export declare const UNUSED_HOST_HOOKS_RECOVERY: string;
|
|
4
10
|
/** Per-host Directive hook deposit toggles (#2752). */
|
|
5
11
|
export type HostHooksPolicy = Record<HookHost, boolean>;
|
|
6
12
|
export declare const DEFAULT_HOST_HOOKS_POLICY: HostHooksPolicy;
|
|
@@ -18,4 +24,18 @@ export declare function isHostHookDepositEnabled(host: HookHost, policy?: HostHo
|
|
|
18
24
|
export declare function inspectHostHooks(data: Record<string, unknown> | null): HostHooksPolicyField;
|
|
19
25
|
/** Resolve host hook deposit policy from PROJECT-DEFINITION on disk. */
|
|
20
26
|
export declare function loadHostHooksPolicyFromProject(projectRoot: string): HostHooksPolicy;
|
|
27
|
+
export declare function parseHookHost(value: string | undefined): HookHost | null;
|
|
28
|
+
export interface DisableHostHooksOptions {
|
|
29
|
+
readonly host: HookHost;
|
|
30
|
+
readonly confirm: boolean;
|
|
31
|
+
readonly actor?: string;
|
|
32
|
+
readonly note?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface DisableHostHooksResult {
|
|
35
|
+
readonly exitCode: 0 | 1 | 2;
|
|
36
|
+
readonly stdout: string;
|
|
37
|
+
readonly changed: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Persist `hostHooks.<host>=false` after capability-cost disclosure (#3571). */
|
|
40
|
+
export declare function disableHostHooks(projectRoot: string, options: DisableHostHooksOptions): DisableHostHooksResult;
|
|
21
41
|
//# sourceMappingURL=host-hooks.d.ts.map
|
|
@@ -1,8 +1,36 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { HOOK_HOSTS } from "../hooks/dispatcher.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
3
|
+
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
4
|
+
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
5
|
+
import { policyColonInvocation } from "./policy-invocation.js";
|
|
6
|
+
import { appendAuditLog, loadProjectDefinition, POLICY_AUDIT_NOOP_STDOUT, projectDefinitionPath, } from "./resolve.js";
|
|
4
7
|
export const FIELD_HOST_HOOKS = "plan.policy.hostHooks";
|
|
5
8
|
export const FIELD_HOST_HOOKS_CLI_ALIAS = "hostHooks";
|
|
9
|
+
export const DISABLE_HOST_HOOKS_SUBCOMMAND = "disable-host-hooks";
|
|
10
|
+
/** Confirm-gated disable verb for unused-host recovery copy (#3571). CLI form: no go-task `--`. */
|
|
11
|
+
export function disableHostHooksInvocation(trailing = " --host <host> --confirm") {
|
|
12
|
+
return policyColonInvocation(DISABLE_HOST_HOOKS_SUBCOMMAND, trailing);
|
|
13
|
+
}
|
|
14
|
+
export const HOST_HOOKS_DISABLE_CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost disclosure -- disabling hostHooks for a host removes " +
|
|
15
|
+
"deft-hook pre-execution guardrails for anyone who later opens this repo in that host. " +
|
|
16
|
+
"The result is tracked.\n" +
|
|
17
|
+
" \u2022 Opted-out hosts skip Directive hook deposit; leftover-free files write {}.\n" +
|
|
18
|
+
" \u2022 This is not a timeout or live-probe fix. Retry the gated ritual when load is the cause.\n" +
|
|
19
|
+
" \u2022 Inspect: `" +
|
|
20
|
+
policyColonInvocation("show", " --field=hostHooks") +
|
|
21
|
+
"`.\n" +
|
|
22
|
+
" \u2022 Apply: `" +
|
|
23
|
+
disableHostHooksInvocation() +
|
|
24
|
+
"`.\n" +
|
|
25
|
+
" \u2022 Reversible: set the host back to true and run `deft update`.\n" +
|
|
26
|
+
" \u2022 Hand-edit of plan.policy.hostHooks plus `deft update` still strips " +
|
|
27
|
+
"(human high-trust bypass).\n" +
|
|
28
|
+
" \u2022 Changes are recorded to meta/policy-changes.log for auditability.";
|
|
29
|
+
export const UNUSED_HOST_HOOKS_RECOVERY = "If an affected host is unused, inspect `" +
|
|
30
|
+
policyColonInvocation("show", " --field=hostHooks") +
|
|
31
|
+
"`, then run `" +
|
|
32
|
+
disableHostHooksInvocation() +
|
|
33
|
+
"`. That removes deft-hook pre-execution guardrails for that host and the result is tracked.";
|
|
6
34
|
export const DEFAULT_HOST_HOOKS_POLICY = {
|
|
7
35
|
claude: true,
|
|
8
36
|
cursor: true,
|
|
@@ -93,4 +121,95 @@ export function loadHostHooksPolicyFromProject(projectRoot) {
|
|
|
93
121
|
}
|
|
94
122
|
return resolveHostHooksPolicy(policyBlock.hostHooks);
|
|
95
123
|
}
|
|
124
|
+
export function parseHookHost(value) {
|
|
125
|
+
if (value === undefined)
|
|
126
|
+
return null;
|
|
127
|
+
return HOOK_HOSTS.includes(value) ? value : null;
|
|
128
|
+
}
|
|
129
|
+
/** Persist `hostHooks.<host>=false` after capability-cost disclosure (#3571). */
|
|
130
|
+
export function disableHostHooks(projectRoot, options) {
|
|
131
|
+
if (!HOOK_HOSTS.includes(options.host)) {
|
|
132
|
+
return {
|
|
133
|
+
exitCode: 2,
|
|
134
|
+
stdout: `\u274c ${FIELD_HOST_HOOKS}.${String(options.host)} is not a deposited host (${HOOK_HOSTS.join(", ")}).\n`,
|
|
135
|
+
changed: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (!options.confirm) {
|
|
139
|
+
return {
|
|
140
|
+
exitCode: 1,
|
|
141
|
+
stdout: `${HOST_HOOKS_DISABLE_CAPABILITY_COST_DISCLOSURE}\n\n` +
|
|
142
|
+
`Re-run with --confirm to apply: ${disableHostHooksInvocation(` --host ${options.host} --confirm`)}\n`,
|
|
143
|
+
changed: false,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const path = projectDefinitionPath(projectRoot);
|
|
147
|
+
try {
|
|
148
|
+
const { changed } = projectDefinitionMutationLock(projectRoot, () => {
|
|
149
|
+
const parsed = JSON.parse(readFileSync(path, { encoding: "utf8" }));
|
|
150
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
151
|
+
throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
|
|
152
|
+
}
|
|
153
|
+
const data = parsed;
|
|
154
|
+
if (typeof data.plan !== "object" || data.plan === null || Array.isArray(data.plan)) {
|
|
155
|
+
if (data.plan === undefined) {
|
|
156
|
+
data.plan = {};
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
throw new Error("PROJECT-DEFINITION 'plan' is not an object");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const plan = data.plan;
|
|
163
|
+
const legacyKeyMigrated = migrateLegacyPolicyKey(plan);
|
|
164
|
+
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
165
|
+
if (typeof existingPolicy !== "object" ||
|
|
166
|
+
existingPolicy === null ||
|
|
167
|
+
Array.isArray(existingPolicy)) {
|
|
168
|
+
if (existingPolicy === undefined) {
|
|
169
|
+
plan[PLAN_POLICY_KEY] = {};
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
throw new Error("plan.policy is not an object");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const policyBlock = plan[PLAN_POLICY_KEY];
|
|
176
|
+
const previous = resolveHostHooksPolicy(policyBlock.hostHooks);
|
|
177
|
+
const next = { ...previous, [options.host]: false };
|
|
178
|
+
const changedFlag = previous[options.host] !== false || legacyKeyMigrated;
|
|
179
|
+
policyBlock.hostHooks = next;
|
|
180
|
+
if (changedFlag) {
|
|
181
|
+
atomicWriteProjectDefinition(path, data);
|
|
182
|
+
}
|
|
183
|
+
const actor = options.actor ?? disableHostHooksInvocation();
|
|
184
|
+
const note = options.note ?? "";
|
|
185
|
+
const parts = [
|
|
186
|
+
`actor=${actor}`,
|
|
187
|
+
`${FIELD_HOST_HOOKS}.${options.host}=false`,
|
|
188
|
+
`previous=${JSON.stringify(previous)}`,
|
|
189
|
+
];
|
|
190
|
+
if (note) {
|
|
191
|
+
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
192
|
+
}
|
|
193
|
+
appendAuditLog(projectRoot, parts.join(" "), changedFlag);
|
|
194
|
+
return { changed: changedFlag };
|
|
195
|
+
});
|
|
196
|
+
const lines = [
|
|
197
|
+
`\u2713 ${FIELD_HOST_HOOKS}.${options.host}=false (deft-hook guardrails removed for ${options.host}; result is tracked).`,
|
|
198
|
+
changed ? " audit: meta/policy-changes.log updated." : POLICY_AUDIT_NOOP_STDOUT,
|
|
199
|
+
" Run `deft update` to strip Directive-managed hook entries for that host.",
|
|
200
|
+
];
|
|
201
|
+
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
205
|
+
if (message.includes("ENOENT") || message.includes("PROJECT-DEFINITION not found")) {
|
|
206
|
+
return {
|
|
207
|
+
exitCode: 2,
|
|
208
|
+
stdout: `\u274c PROJECT-DEFINITION not found at ${path}\n`,
|
|
209
|
+
changed: false,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return { exitCode: 2, stdout: `\u274c Config error: ${message}\n`, changed: false };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
96
215
|
//# sourceMappingURL=host-hooks.js.map
|
|
@@ -421,6 +421,7 @@ function applyForceOn(projectRoot, options, intent) {
|
|
|
421
421
|
? existing.previousProductSignal
|
|
422
422
|
: ORG_FORCE_ON_NO_BASELINE;
|
|
423
423
|
const actor = options.actor ?? "directive-update";
|
|
424
|
+
const changed = valueFeedbackChanged || productSignalChanged;
|
|
424
425
|
appendAuditLog(projectRoot, [
|
|
425
426
|
`actor=${actor}`,
|
|
426
427
|
"org-force-on-v2822",
|
|
@@ -428,7 +429,7 @@ function applyForceOn(projectRoot, options, intent) {
|
|
|
428
429
|
`productSignal=${needsPs ? "forced-on" : "unchanged"}`,
|
|
429
430
|
`previousValueFeedback=${JSON.stringify(normalizePolicySnapshot(markerPreviousVf))}`,
|
|
430
431
|
`previousProductSignal=${JSON.stringify(normalizePolicySnapshot(markerPreviousPs))}`,
|
|
431
|
-
].join(" "));
|
|
432
|
+
].join(" "), changed);
|
|
432
433
|
writeMarker(projectRoot, options, {
|
|
433
434
|
// OR with existing so partial re-apply does not clear install-force-on source.
|
|
434
435
|
valueFeedback: needsVf || (existing?.valueFeedback ?? false),
|
|
@@ -41,7 +41,7 @@ export declare function readPlanOnboarding(plan: unknown): unknown;
|
|
|
41
41
|
* policy key is present. Used by writers so a mutation never strands a legacy
|
|
42
42
|
* bare block alongside the namespaced one.
|
|
43
43
|
*/
|
|
44
|
-
export declare function migrateLegacyPolicyKey(plan: Record<string, unknown>):
|
|
44
|
+
export declare function migrateLegacyPolicyKey(plan: Record<string, unknown>): boolean;
|
|
45
45
|
/** The namespaced/legacy key pairs a bare block can silently shadow (#2301). */
|
|
46
46
|
export declare const SHADOWABLE_PLAN_EXTENSIONS: ReadonlyArray<{
|
|
47
47
|
readonly namespacedKey: string;
|
|
@@ -68,7 +68,9 @@ export function migrateLegacyPolicyKey(plan) {
|
|
|
68
68
|
if (plan[PLAN_POLICY_KEY] === undefined && plan[LEGACY_PLAN_POLICY_KEY] !== undefined) {
|
|
69
69
|
plan[PLAN_POLICY_KEY] = plan[LEGACY_PLAN_POLICY_KEY];
|
|
70
70
|
delete plan[LEGACY_PLAN_POLICY_KEY];
|
|
71
|
+
return true;
|
|
71
72
|
}
|
|
73
|
+
return false;
|
|
72
74
|
}
|
|
73
75
|
/** The namespaced/legacy key pairs a bare block can silently shadow (#2301). */
|
|
74
76
|
export const SHADOWABLE_PLAN_EXTENSIONS = [
|
|
@@ -3,7 +3,7 @@ import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "..
|
|
|
3
3
|
import { productSignalInstallForceOnSource } 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
|
/** Canonical registered policy field name (#2693). */
|
|
8
8
|
export const FIELD_PRODUCT_SIGNAL = "plan.policy.productSignal";
|
|
9
9
|
/** Short alias for `policy:show --field=productSignal`. */
|
|
@@ -157,7 +157,7 @@ export function enableProductSignal(projectRoot, options) {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
const plan = data.plan;
|
|
160
|
-
migrateLegacyPolicyKey(plan);
|
|
160
|
+
const legacyKeyMigrated = migrateLegacyPolicyKey(plan);
|
|
161
161
|
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
162
162
|
if (typeof existingPolicy !== "object" ||
|
|
163
163
|
existingPolicy === null ||
|
|
@@ -181,7 +181,8 @@ export function enableProductSignal(projectRoot, options) {
|
|
|
181
181
|
const nextBlock = { enabled: true, sinkRepo };
|
|
182
182
|
const previousNormalized = resolveProductSignalFromTypedBlock(previous);
|
|
183
183
|
const changedFlag = previousNormalized.enabled !== nextBlock.enabled ||
|
|
184
|
-
previousNormalized.sinkRepo !== nextBlock.sinkRepo
|
|
184
|
+
previousNormalized.sinkRepo !== nextBlock.sinkRepo ||
|
|
185
|
+
legacyKeyMigrated;
|
|
185
186
|
policyBlock.productSignal = nextBlock;
|
|
186
187
|
if (changedFlag) {
|
|
187
188
|
atomicWriteProjectDefinition(path, data);
|
|
@@ -197,15 +198,13 @@ export function enableProductSignal(projectRoot, options) {
|
|
|
197
198
|
if (note) {
|
|
198
199
|
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
199
200
|
}
|
|
200
|
-
appendAuditLog(projectRoot, parts.join(" "));
|
|
201
|
+
appendAuditLog(projectRoot, parts.join(" "), changedFlag);
|
|
201
202
|
return { changed: changedFlag };
|
|
202
203
|
});
|
|
203
204
|
const resolved = resolveProductSignal(projectRoot);
|
|
204
205
|
const lines = [
|
|
205
206
|
`\u2713 ${FIELD_PRODUCT_SIGNAL}.enabled=true (product-signal ON).`,
|
|
206
|
-
changed
|
|
207
|
-
? " audit: meta/policy-changes.log updated."
|
|
208
|
-
: " no-op: value already matched (audit entry still appended for trail).",
|
|
207
|
+
changed ? " audit: meta/policy-changes.log updated." : POLICY_AUDIT_NOOP_STDOUT,
|
|
209
208
|
formatProductSignalStatusLine(resolved),
|
|
210
209
|
];
|
|
211
210
|
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
|
|
@@ -18,7 +18,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
18
18
|
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
19
19
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
20
20
|
import { policyColonInvocation } from "./policy-invocation.js";
|
|
21
|
-
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
21
|
+
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath, stampChangedToken, } from "./resolve.js";
|
|
22
22
|
export const FIELD_REQUIRE_HUMAN_MERGE = "plan.policy.requireHumanMerge";
|
|
23
23
|
export const FIELD_REQUIRE_HUMAN_MERGE_CLI_ALIAS = "requireHumanMerge";
|
|
24
24
|
export const FIELD_AUTO_DEPLOY_ON_MERGE = "plan.policy.autoDeployOnMerge";
|
|
@@ -285,7 +285,7 @@ export function setRequireHumanMerge(projectRoot, options) {
|
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
const plan = data.plan;
|
|
288
|
-
migrateLegacyPolicyKey(plan);
|
|
288
|
+
const legacyKeyMigrated = migrateLegacyPolicyKey(plan);
|
|
289
289
|
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
290
290
|
if (typeof existingPolicy !== "object" ||
|
|
291
291
|
existingPolicy === null ||
|
|
@@ -309,8 +309,7 @@ export function setRequireHumanMerge(projectRoot, options) {
|
|
|
309
309
|
delete narratives[LEGACY_REQUIRE_HUMAN_MERGE_KEY];
|
|
310
310
|
legacyDropped = true;
|
|
311
311
|
}
|
|
312
|
-
|
|
313
|
-
const changed = previous !== Boolean(requireHumanMerge) || legacyDropped;
|
|
312
|
+
const changed = previous !== Boolean(requireHumanMerge) || legacyDropped || legacyKeyMigrated;
|
|
314
313
|
const parts = [
|
|
315
314
|
`actor=${actor}`,
|
|
316
315
|
`requireHumanMerge=${requireHumanMerge ? "true" : "false"}`,
|
|
@@ -321,8 +320,11 @@ export function setRequireHumanMerge(projectRoot, options) {
|
|
|
321
320
|
if (note) {
|
|
322
321
|
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
323
322
|
}
|
|
324
|
-
const auditEntry = parts.join(" ");
|
|
325
|
-
|
|
323
|
+
const auditEntry = stampChangedToken(parts.join(" "), changed);
|
|
324
|
+
if (changed) {
|
|
325
|
+
atomicWriteProjectDefinition(path, data);
|
|
326
|
+
}
|
|
327
|
+
appendAuditLog(projectRoot, auditEntry, changed);
|
|
326
328
|
return { changed, auditEntry };
|
|
327
329
|
});
|
|
328
330
|
}
|
package/dist/policy/resolve.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export declare const ENV_BYPASS = "DEFT_ALLOW_DEFAULT_BRANCH_COMMIT";
|
|
|
6
6
|
export declare const LEGACY_NARRATIVE_KEY = "Allow direct commits to master";
|
|
7
7
|
/** Audit log relative path (#746). */
|
|
8
8
|
export declare const AUDIT_LOG_REL_PATH = "meta/policy-changes.log";
|
|
9
|
+
/** Operator line when a policy writer matches current state and does not append (#3528). */
|
|
10
|
+
export declare const POLICY_AUDIT_NOOP_STDOUT = " no-op: value already matched (ledger unchanged).";
|
|
11
|
+
/** Stamp a machine-readable `changed=true|false` token (#3528 / UPGRADING.md). */
|
|
12
|
+
export declare function stampChangedToken(entry: string, changed: boolean): string;
|
|
9
13
|
export type PolicySource = "typed" | "legacy-narrative" | "env-bypass" | "default-fail-closed";
|
|
10
14
|
export interface PolicyResult {
|
|
11
15
|
readonly allowDirectCommits: boolean;
|
|
@@ -28,8 +32,9 @@ export declare function coerceLegacyNarrative(value: unknown): {
|
|
|
28
32
|
export declare function loadProjectDefinition(projectRoot: string): [Record<string, unknown> | null, string | null];
|
|
29
33
|
/** Resolve the effective branch-commit policy (#746 / #747). */
|
|
30
34
|
export declare function resolvePolicy(projectRoot: string): PolicyResult;
|
|
31
|
-
/** Append a one-line audit entry to meta/policy-changes.log (#746).
|
|
32
|
-
|
|
35
|
+
/** Append a one-line audit entry to meta/policy-changes.log (#746).
|
|
36
|
+
* No-op (`changed=false`) does not create or modify the tracked file (#3528). */
|
|
37
|
+
export declare function appendAuditLog(projectRoot: string, entry: string, changed?: boolean): string;
|
|
33
38
|
/** Write the typed policy flag back to PROJECT-DEFINITION (#746). */
|
|
34
39
|
export declare function setPolicy(projectRoot: string, options: {
|
|
35
40
|
allowDirectCommits: boolean;
|