@deftai/directive-core 0.79.4 → 0.81.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/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +139 -0
- package/dist/doctor/main.js +2 -1
- package/dist/hooks/dispatcher.d.ts +18 -3
- package/dist/hooks/dispatcher.js +173 -62
- package/dist/hooks/readonly.d.ts +7 -0
- package/dist/hooks/readonly.js +83 -0
- package/dist/hooks/tools.d.ts +7 -0
- package/dist/hooks/tools.js +16 -0
- package/dist/init-deposit/agent-hooks.d.ts +4 -1
- package/dist/init-deposit/agent-hooks.js +77 -21
- package/dist/policy/index.d.ts +3 -0
- package/dist/policy/index.js +45 -1
- package/dist/policy/product-signal.d.ts +45 -0
- package/dist/policy/product-signal.js +210 -0
- package/dist/policy/runtime-authority.d.ts +54 -0
- package/dist/policy/runtime-authority.js +180 -0
- package/dist/policy/staleness-tickler.d.ts +20 -0
- package/dist/policy/staleness-tickler.js +151 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +7 -1
- package/dist/pr-merge-readiness/ci-gate.js +38 -5
- package/dist/pr-merge-readiness/gh.d.ts +4 -0
- package/dist/pr-merge-readiness/gh.js +10 -3
- package/dist/pr-merge-readiness/index.d.ts +2 -1
- package/dist/pr-merge-readiness/index.js +2 -1
- package/dist/pr-merge-readiness/runner-capacity-stall.d.ts +35 -0
- package/dist/pr-merge-readiness/runner-capacity-stall.js +46 -0
- package/dist/pr-watch/constants.d.ts +5 -0
- package/dist/pr-watch/constants.js +6 -1
- package/dist/pr-watch/main.js +8 -0
- package/dist/pr-watch/probe.js +8 -0
- package/dist/pr-watch/types.d.ts +7 -0
- package/dist/pr-watch/watch.js +8 -1
- package/dist/product-signal/actor-name.d.ts +18 -0
- package/dist/product-signal/actor-name.js +94 -0
- package/dist/product-signal/consent.d.ts +30 -0
- package/dist/product-signal/consent.js +116 -0
- package/dist/product-signal/gates.d.ts +17 -0
- package/dist/product-signal/gates.js +66 -0
- package/dist/product-signal/github-private-sink-adapter.d.ts +28 -0
- package/dist/product-signal/github-private-sink-adapter.js +274 -0
- package/dist/product-signal/headless.d.ts +8 -0
- package/dist/product-signal/headless.js +32 -0
- package/dist/product-signal/install-context.d.ts +13 -0
- package/dist/product-signal/install-context.js +41 -0
- package/dist/product-signal/local-signal-summary.d.ts +7 -0
- package/dist/product-signal/local-signal-summary.js +173 -0
- package/dist/product-signal/payload.d.ts +90 -0
- package/dist/product-signal/payload.js +124 -0
- package/dist/product-signal/sink-bootstrap.d.ts +29 -0
- package/dist/product-signal/sink-bootstrap.js +108 -0
- package/dist/product-signal/submit-adapter.d.ts +14 -0
- package/dist/product-signal/submit-adapter.js +2 -0
- package/dist/product-signal/submit.d.ts +54 -0
- package/dist/product-signal/submit.js +290 -0
- package/dist/scm/gh-rest.d.ts +3 -1
- package/dist/scm/gh-rest.js +22 -0
- package/dist/scope/main.js +12 -0
- package/dist/session/release-availability.d.ts +21 -0
- package/dist/session/release-availability.js +109 -0
- package/dist/session/ritual-sentinel.d.ts +14 -0
- package/dist/session/ritual-sentinel.js +28 -0
- package/dist/session/session-start.d.ts +10 -0
- package/dist/session/session-start.js +18 -0
- package/dist/staleness-tickler/escalation.d.ts +14 -0
- package/dist/staleness-tickler/escalation.js +106 -0
- package/dist/staleness-tickler/idle.d.ts +18 -0
- package/dist/staleness-tickler/idle.js +47 -0
- package/dist/staleness-tickler/index.d.ts +8 -0
- package/dist/staleness-tickler/index.js +8 -0
- package/dist/staleness-tickler/probe-directive.d.ts +32 -0
- package/dist/staleness-tickler/probe-directive.js +106 -0
- package/dist/staleness-tickler/probe-xbrief.d.ts +12 -0
- package/dist/staleness-tickler/probe-xbrief.js +103 -0
- package/dist/staleness-tickler/run.d.ts +31 -0
- package/dist/staleness-tickler/run.js +227 -0
- package/dist/staleness-tickler/state.d.ts +10 -0
- package/dist/staleness-tickler/state.js +46 -0
- package/dist/staleness-tickler/types.d.ts +85 -0
- package/dist/staleness-tickler/types.js +5 -0
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +50 -0
- package/dist/verify-env/agent-hooks.js +4 -2
- package/package.json +3 -3
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { VBRIEF_VERSION } from "@deftai/directive-types";
|
|
3
|
+
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
4
|
+
import { readDeclaredArtifactVersion } from "../xbrief-migrate/transforms.js";
|
|
5
|
+
function defaultReadText(path) {
|
|
6
|
+
try {
|
|
7
|
+
return readFileSync(path, "utf8");
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function parseSchemaParts(version) {
|
|
14
|
+
const trimmed = version.trim();
|
|
15
|
+
const parts = trimmed.split(".");
|
|
16
|
+
if (parts.length < 2) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const major = Number(parts[0]);
|
|
20
|
+
const minor = Number(parts[1]);
|
|
21
|
+
if (!Number.isFinite(major) || !Number.isFinite(minor)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return { major, minor };
|
|
25
|
+
}
|
|
26
|
+
/** Classify declared xBRIEF schema distance vs the installed framework schema. */
|
|
27
|
+
export function classifyXbriefSchemaDistance(declaredVersion, targetVersion) {
|
|
28
|
+
if (declaredVersion === null) {
|
|
29
|
+
return "behind-major";
|
|
30
|
+
}
|
|
31
|
+
const declared = parseSchemaParts(declaredVersion);
|
|
32
|
+
const target = parseSchemaParts(targetVersion);
|
|
33
|
+
if (declared === null || target === null) {
|
|
34
|
+
return "behind-major";
|
|
35
|
+
}
|
|
36
|
+
if (declared.major < target.major) {
|
|
37
|
+
return "behind-major";
|
|
38
|
+
}
|
|
39
|
+
if (declared.major === target.major) {
|
|
40
|
+
const minorGap = target.minor - declared.minor;
|
|
41
|
+
if (minorGap >= 2) {
|
|
42
|
+
return "behind-major";
|
|
43
|
+
}
|
|
44
|
+
if (minorGap >= 1) {
|
|
45
|
+
return "behind-minor";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return "current";
|
|
49
|
+
}
|
|
50
|
+
/** Probe xBRIEF schema staleness from PROJECT-DEFINITION declared version. */
|
|
51
|
+
export function probeXbriefStaleness(projectRoot, options = {}) {
|
|
52
|
+
const targetVersion = options.targetVersion ?? VBRIEF_VERSION;
|
|
53
|
+
const readText = options.readText ?? defaultReadText;
|
|
54
|
+
const isFile = options.isFile ?? existsSync;
|
|
55
|
+
let definitionPath;
|
|
56
|
+
try {
|
|
57
|
+
definitionPath =
|
|
58
|
+
options.resolveDefinitionPath?.(projectRoot) ?? resolveProjectDefinitionPath(projectRoot);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return {
|
|
62
|
+
declaredVersion: null,
|
|
63
|
+
targetVersion,
|
|
64
|
+
distance: "behind-major",
|
|
65
|
+
stale: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (!isFile(definitionPath)) {
|
|
69
|
+
return {
|
|
70
|
+
declaredVersion: null,
|
|
71
|
+
targetVersion,
|
|
72
|
+
distance: "behind-major",
|
|
73
|
+
stale: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const text = readText(definitionPath);
|
|
77
|
+
if (text === null) {
|
|
78
|
+
return {
|
|
79
|
+
declaredVersion: null,
|
|
80
|
+
targetVersion,
|
|
81
|
+
distance: "behind-major",
|
|
82
|
+
stale: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
let declared = null;
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(text);
|
|
88
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
89
|
+
declared = readDeclaredArtifactVersion(parsed);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
declared = null;
|
|
94
|
+
}
|
|
95
|
+
const distance = classifyXbriefSchemaDistance(declared, targetVersion);
|
|
96
|
+
return {
|
|
97
|
+
declaredVersion: declared,
|
|
98
|
+
targetVersion,
|
|
99
|
+
distance,
|
|
100
|
+
stale: distance !== "current",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=probe-xbrief.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type IdleGateOptions } from "./idle.js";
|
|
2
|
+
import { type ProbeDirectiveOptions } from "./probe-directive.js";
|
|
3
|
+
import { type ProbeXbriefOptions } from "./probe-xbrief.js";
|
|
4
|
+
import type { StalenessProbeResult, StalenessTicklerRunResult } from "./types.js";
|
|
5
|
+
export declare const DIRECTIVE_UPGRADE_COMMAND = "npm i -g @deftai/directive@latest";
|
|
6
|
+
export declare const XBRIEF_MIGRATE_COMMAND = "deft migrate:xbrief";
|
|
7
|
+
export interface StalenessTicklerOptions {
|
|
8
|
+
readonly now?: Date;
|
|
9
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
10
|
+
readonly idle?: IdleGateOptions;
|
|
11
|
+
readonly probeDirective?: ProbeDirectiveOptions;
|
|
12
|
+
readonly probeXbrief?: ProbeXbriefOptions;
|
|
13
|
+
readonly askConsent?: (prompt: string) => "yes" | "no" | "snooze" | "remind";
|
|
14
|
+
readonly isInteractive?: boolean;
|
|
15
|
+
readonly executeUpgrade?: boolean;
|
|
16
|
+
readonly runUpgrade?: () => {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
readonly runMigrate?: (projectRoot: string) => {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
message: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare function probeStalenessDimensions(projectRoot: string, options?: StalenessTicklerOptions): StalenessProbeResult;
|
|
26
|
+
/**
|
|
27
|
+
* Maybe run the staleness tickler at a safe idle point (#2488 + #2489).
|
|
28
|
+
* Best-effort and non-fatal — callers should wrap in try/catch.
|
|
29
|
+
*/
|
|
30
|
+
export declare function maybeRunStalenessTickler(projectRoot: string, options?: StalenessTicklerOptions): StalenessTicklerRunResult;
|
|
31
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { loadProjectDefinition } from "../policy/resolve.js";
|
|
3
|
+
import { loadStalenessTicklerPolicy } from "../policy/staleness-tickler.js";
|
|
4
|
+
import { runXbriefMigration } from "../xbrief-migrate/migrate-project.js";
|
|
5
|
+
import { holdTierOnUnverified, isSnoozeActive, mergeHeldXbriefDistance, resolveTier, scoreDrift, shouldPromptDespiteSnooze, snoozeWindowMs, } from "./escalation.js";
|
|
6
|
+
import { isInteractiveSession, isSafeIdlePoint, shouldSkipTicklerEntirely, } from "./idle.js";
|
|
7
|
+
import { probeDirectiveStaleness } from "./probe-directive.js";
|
|
8
|
+
import { probeXbriefStaleness } from "./probe-xbrief.js";
|
|
9
|
+
import { loadStalenessTicklerState, saveStalenessTicklerState } from "./state.js";
|
|
10
|
+
export const DIRECTIVE_UPGRADE_COMMAND = "npm i -g @deftai/directive@latest";
|
|
11
|
+
export const XBRIEF_MIGRATE_COMMAND = "deft migrate:xbrief";
|
|
12
|
+
const TIER_TONE = {
|
|
13
|
+
quiet: "note",
|
|
14
|
+
notice: "recommend",
|
|
15
|
+
strong: "strongly recommend",
|
|
16
|
+
assert: "assert",
|
|
17
|
+
};
|
|
18
|
+
function defaultAskConsent(_prompt) {
|
|
19
|
+
return "snooze";
|
|
20
|
+
}
|
|
21
|
+
function defaultRunUpgrade() {
|
|
22
|
+
const result = spawnSync("npm", ["i", "-g", "@deftai/directive@latest"], {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
timeout: 120_000,
|
|
25
|
+
});
|
|
26
|
+
if (result.status === 0) {
|
|
27
|
+
return { ok: true, message: "Directive upgrade completed." };
|
|
28
|
+
}
|
|
29
|
+
const stderr = (result.stderr ?? "").trim();
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
message: stderr.length > 0 ? stderr : `npm exited with status ${result.status ?? "unknown"}`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function defaultRunMigrate(projectRoot) {
|
|
36
|
+
const io = { writeOut: () => { }, writeErr: () => { } };
|
|
37
|
+
const outcome = runXbriefMigration({ projectRoot }, io);
|
|
38
|
+
if (outcome.kind === "migrated") {
|
|
39
|
+
return {
|
|
40
|
+
ok: true,
|
|
41
|
+
message: `xBRIEF migration completed (${outcome.files} files; backup ${outcome.backupDir}).`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (outcome.kind === "converged" || outcome.kind === "noop") {
|
|
45
|
+
return { ok: true, message: outcome.message };
|
|
46
|
+
}
|
|
47
|
+
return { ok: false, message: outcome.message };
|
|
48
|
+
}
|
|
49
|
+
export function probeStalenessDimensions(projectRoot, options = {}) {
|
|
50
|
+
const directiveProbe = probeDirectiveStaleness(projectRoot, {
|
|
51
|
+
env: options.env,
|
|
52
|
+
...options.probeDirective,
|
|
53
|
+
});
|
|
54
|
+
const xbrief = probeXbriefStaleness(projectRoot, options.probeXbrief);
|
|
55
|
+
const directive = directiveProbe
|
|
56
|
+
? {
|
|
57
|
+
availability: directiveProbe.availability,
|
|
58
|
+
majorBehind: directiveProbe.majorBehind,
|
|
59
|
+
minorDistance: directiveProbe.minorDistance,
|
|
60
|
+
patchDistance: directiveProbe.patchDistance,
|
|
61
|
+
stale: directiveProbe.stale,
|
|
62
|
+
}
|
|
63
|
+
: {
|
|
64
|
+
availability: {
|
|
65
|
+
status: "unverified",
|
|
66
|
+
installedVersion: "0.0.0",
|
|
67
|
+
latestVersion: null,
|
|
68
|
+
resolver: "npm-view",
|
|
69
|
+
},
|
|
70
|
+
majorBehind: false,
|
|
71
|
+
minorDistance: 0,
|
|
72
|
+
patchDistance: 0,
|
|
73
|
+
stale: false,
|
|
74
|
+
};
|
|
75
|
+
const anyStale = directive.stale || xbrief.stale;
|
|
76
|
+
return {
|
|
77
|
+
directive,
|
|
78
|
+
xbrief,
|
|
79
|
+
anyStale,
|
|
80
|
+
directiveRegistryDisclosure: directiveProbe?.registryDisclosure,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function formatDimensionLines(probe) {
|
|
84
|
+
const lines = [];
|
|
85
|
+
if (probe.directive.stale && probe.directive.availability.status === "available") {
|
|
86
|
+
lines.push(`[deft staleness] Directive payload behind: installed v${probe.directive.availability.installedVersion}, ` +
|
|
87
|
+
`latest v${probe.directive.availability.latestVersion}. Run \`${DIRECTIVE_UPGRADE_COMMAND}\`.`);
|
|
88
|
+
}
|
|
89
|
+
if (probe.xbrief.stale) {
|
|
90
|
+
lines.push(`[deft staleness] xBRIEF schema ${probe.xbrief.distance}: declared ${probe.xbrief.declaredVersion ?? "unknown"}, ` +
|
|
91
|
+
`framework ${probe.xbrief.targetVersion}. Run \`${XBRIEF_MIGRATE_COMMAND}\`.`);
|
|
92
|
+
}
|
|
93
|
+
return lines;
|
|
94
|
+
}
|
|
95
|
+
function buildPromptLine(tier, probe) {
|
|
96
|
+
const tone = TIER_TONE[tier];
|
|
97
|
+
const actions = [];
|
|
98
|
+
if (probe.directive.stale) {
|
|
99
|
+
actions.push("upgrade Directive");
|
|
100
|
+
}
|
|
101
|
+
if (probe.xbrief.stale) {
|
|
102
|
+
actions.push("migrate xBRIEF");
|
|
103
|
+
}
|
|
104
|
+
const actionText = actions.join(" and ");
|
|
105
|
+
if (tier === "assert") {
|
|
106
|
+
return (`[deft staleness:${tone}] Version drift is compounding (${actionText}). ` +
|
|
107
|
+
"Plain snooze is unavailable — choose yes, opt-out via plan.policy.stalenessTickler.optOut, " +
|
|
108
|
+
"or remind after next release.");
|
|
109
|
+
}
|
|
110
|
+
return `[deft staleness:${tone}] ${actionText} now? [y/n/s]`;
|
|
111
|
+
}
|
|
112
|
+
function nextStateAfterPrompt(previous, tier, score, now, choice, policy, probe) {
|
|
113
|
+
const deferralCount = choice === "snooze" || choice === "no" || choice === "headless-advisory"
|
|
114
|
+
? (previous.deferralCount ?? 0) + 1
|
|
115
|
+
: (previous.deferralCount ?? 0);
|
|
116
|
+
const windowMs = choice === "remind" ? policy.snooze.quietMs : snoozeWindowMs(tier, deferralCount, policy);
|
|
117
|
+
const snoozedUntil = new Date(now.getTime() + windowMs).toISOString();
|
|
118
|
+
return {
|
|
119
|
+
firstDetectedAt: previous.firstDetectedAt ?? now.toISOString(),
|
|
120
|
+
lastTier: tier,
|
|
121
|
+
lastScore: score,
|
|
122
|
+
lastPromptAt: now.toISOString(),
|
|
123
|
+
deferralCount,
|
|
124
|
+
snoozedUntil,
|
|
125
|
+
heldDirectiveLatest: probe.directive.availability.status === "available" ||
|
|
126
|
+
probe.directive.availability.status === "current"
|
|
127
|
+
? probe.directive.availability.latestVersion
|
|
128
|
+
: (previous.heldDirectiveLatest ?? null),
|
|
129
|
+
heldXbriefDistance: mergeHeldXbriefDistance(probe.xbrief.distance, previous.heldXbriefDistance),
|
|
130
|
+
remindAfterNextRelease: choice === "remind" ? true : previous.remindAfterNextRelease,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Maybe run the staleness tickler at a safe idle point (#2488 + #2489).
|
|
135
|
+
* Best-effort and non-fatal — callers should wrap in try/catch.
|
|
136
|
+
*/
|
|
137
|
+
export function maybeRunStalenessTickler(projectRoot, options = {}) {
|
|
138
|
+
const env = options.env ?? process.env;
|
|
139
|
+
if (shouldSkipTicklerEntirely(env)) {
|
|
140
|
+
return { lines: [], prompted: false, skippedReason: "ritual-skip" };
|
|
141
|
+
}
|
|
142
|
+
const [data] = loadProjectDefinition(projectRoot);
|
|
143
|
+
const policy = loadStalenessTicklerPolicy(data);
|
|
144
|
+
const idle = isSafeIdlePoint(projectRoot, policy, { env, ...options.idle });
|
|
145
|
+
if (!idle.ok) {
|
|
146
|
+
return { lines: [], prompted: false, skippedReason: idle.reason };
|
|
147
|
+
}
|
|
148
|
+
const now = options.now ?? new Date();
|
|
149
|
+
const state = loadStalenessTicklerState(projectRoot);
|
|
150
|
+
const probe = probeStalenessDimensions(projectRoot, options);
|
|
151
|
+
if (!probe.anyStale) {
|
|
152
|
+
if (state.firstDetectedAt !== undefined ||
|
|
153
|
+
state.lastTier !== undefined ||
|
|
154
|
+
state.snoozedUntil !== undefined) {
|
|
155
|
+
saveStalenessTicklerState(projectRoot, {});
|
|
156
|
+
}
|
|
157
|
+
return { lines: [], prompted: false, skippedReason: "current" };
|
|
158
|
+
}
|
|
159
|
+
const directiveUnverified = probe.directive.availability.status === "unverified";
|
|
160
|
+
const xbriefDistance = directiveUnverified && state.heldXbriefDistance !== undefined
|
|
161
|
+
? mergeHeldXbriefDistance(probe.xbrief.distance, state.heldXbriefDistance)
|
|
162
|
+
: probe.xbrief.distance;
|
|
163
|
+
const ageMs = state.firstDetectedAt !== undefined
|
|
164
|
+
? Math.max(0, now.getTime() - Date.parse(state.firstDetectedAt))
|
|
165
|
+
: 0;
|
|
166
|
+
const inputs = {
|
|
167
|
+
directive: probe.directive,
|
|
168
|
+
xbrief: {
|
|
169
|
+
...probe.xbrief,
|
|
170
|
+
distance: xbriefDistance,
|
|
171
|
+
stale: xbriefDistance !== "current",
|
|
172
|
+
},
|
|
173
|
+
ageMs,
|
|
174
|
+
deferralCount: state.deferralCount ?? 0,
|
|
175
|
+
};
|
|
176
|
+
const computedScore = scoreDrift(inputs, policy);
|
|
177
|
+
const computedTier = resolveTier(inputs, policy);
|
|
178
|
+
const { score, tier } = holdTierOnUnverified(computedTier, computedScore, state, directiveUnverified);
|
|
179
|
+
if (isSnoozeActive(state, now) && !shouldPromptDespiteSnooze(tier, state)) {
|
|
180
|
+
return { lines: [], prompted: false, skippedReason: "snoozed" };
|
|
181
|
+
}
|
|
182
|
+
const lines = [];
|
|
183
|
+
if (probe.directiveRegistryDisclosure) {
|
|
184
|
+
lines.push(probe.directiveRegistryDisclosure);
|
|
185
|
+
}
|
|
186
|
+
lines.push(...formatDimensionLines(probe));
|
|
187
|
+
lines.push(buildPromptLine(tier, probe));
|
|
188
|
+
const interactive = options.isInteractive ?? isInteractiveSession(env);
|
|
189
|
+
let choice;
|
|
190
|
+
if (!interactive) {
|
|
191
|
+
lines.push("[deft staleness] Non-interactive session — recommendation recorded; " +
|
|
192
|
+
"decline with plan.policy.stalenessTickler.optOut or wait for snooze window.");
|
|
193
|
+
choice = "headless-advisory";
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const ask = options.askConsent ?? defaultAskConsent;
|
|
197
|
+
const response = ask(lines.join("\n"));
|
|
198
|
+
if (tier === "assert" && response === "snooze") {
|
|
199
|
+
choice = "remind";
|
|
200
|
+
lines.push("[deft staleness] Assert tier: treating snooze as remind-after-next-release.");
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
choice = response;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (choice === "yes") {
|
|
207
|
+
const runUpgrade = options.runUpgrade ?? defaultRunUpgrade;
|
|
208
|
+
const runMigrate = options.runMigrate ?? defaultRunMigrate;
|
|
209
|
+
if (probe.directive.stale) {
|
|
210
|
+
lines.push(`[deft staleness] Running \`${DIRECTIVE_UPGRADE_COMMAND}\`…`);
|
|
211
|
+
const upgrade = runUpgrade();
|
|
212
|
+
lines.push(upgrade.ok
|
|
213
|
+
? `[deft staleness] ${upgrade.message}`
|
|
214
|
+
: `[deft staleness] Upgrade failed (non-fatal): ${upgrade.message}`);
|
|
215
|
+
}
|
|
216
|
+
if (probe.xbrief.stale) {
|
|
217
|
+
lines.push(`[deft staleness] Running xBRIEF migration…`);
|
|
218
|
+
const migrate = runMigrate(projectRoot);
|
|
219
|
+
lines.push(migrate.ok
|
|
220
|
+
? `[deft staleness] ${migrate.message}`
|
|
221
|
+
: `[deft staleness] Migration failed (non-fatal): ${migrate.message}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
saveStalenessTicklerState(projectRoot, nextStateAfterPrompt(state, tier, score, now, choice, policy, probe));
|
|
225
|
+
return { lines, prompted: true };
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { StalenessTicklerState } from "./types.js";
|
|
2
|
+
export declare const STATE_RELATIVE_PATH: string;
|
|
3
|
+
export interface StateIo {
|
|
4
|
+
readonly readText?: (path: string) => string | null;
|
|
5
|
+
readonly writeText?: (path: string, content: string) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseStalenessTicklerState(text: string | null): StalenessTicklerState;
|
|
8
|
+
export declare function loadStalenessTicklerState(projectRoot: string, io?: StateIo): StalenessTicklerState;
|
|
9
|
+
export declare function saveStalenessTicklerState(projectRoot: string, state: StalenessTicklerState, io?: StateIo): void;
|
|
10
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
export const STATE_RELATIVE_PATH = join("xbrief", ".triage-cache", "staleness-tickler-state.json");
|
|
4
|
+
function defaultReadText(path) {
|
|
5
|
+
try {
|
|
6
|
+
return readFileSync(path, "utf8");
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function defaultWriteText(path, content) {
|
|
13
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
14
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
15
|
+
writeFileSync(temporary, content, "utf8");
|
|
16
|
+
renameSync(temporary, path);
|
|
17
|
+
}
|
|
18
|
+
export function parseStalenessTicklerState(text) {
|
|
19
|
+
if (text === null) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(text);
|
|
24
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// stale state must never block operator work
|
|
30
|
+
}
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
export function loadStalenessTicklerState(projectRoot, io = {}) {
|
|
34
|
+
const path = join(projectRoot, STATE_RELATIVE_PATH);
|
|
35
|
+
return parseStalenessTicklerState((io.readText ?? defaultReadText)(path));
|
|
36
|
+
}
|
|
37
|
+
export function saveStalenessTicklerState(projectRoot, state, io = {}) {
|
|
38
|
+
const path = join(projectRoot, STATE_RELATIVE_PATH);
|
|
39
|
+
try {
|
|
40
|
+
(io.writeText ?? defaultWriteText)(path, `${JSON.stringify(state, null, 2)}\n`);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// persistence is best-effort
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ReleaseAvailabilityResult } from "../doctor/release-availability.js";
|
|
2
|
+
/** Escalation tiers for the staleness tickler (#2489). */
|
|
3
|
+
export declare const STALENESS_TICKLER_TIERS: readonly ["quiet", "notice", "strong", "assert"];
|
|
4
|
+
export type StalenessTicklerTier = (typeof STALENESS_TICKLER_TIERS)[number];
|
|
5
|
+
/** xBRIEF schema distance relative to the installed framework schema. */
|
|
6
|
+
export declare const XBRIEF_SCHEMA_DISTANCES: readonly ["current", "behind-minor", "behind-major"];
|
|
7
|
+
export type XbriefSchemaDistance = (typeof XBRIEF_SCHEMA_DISTANCES)[number];
|
|
8
|
+
export interface ParsedSemverCore {
|
|
9
|
+
readonly major: number;
|
|
10
|
+
readonly minor: number;
|
|
11
|
+
readonly patch: number;
|
|
12
|
+
}
|
|
13
|
+
export interface DirectiveDrift {
|
|
14
|
+
readonly availability: ReleaseAvailabilityResult;
|
|
15
|
+
readonly majorBehind: boolean;
|
|
16
|
+
readonly minorDistance: number;
|
|
17
|
+
readonly patchDistance: number;
|
|
18
|
+
readonly stale: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface XbriefDrift {
|
|
21
|
+
readonly declaredVersion: string | null;
|
|
22
|
+
readonly targetVersion: string;
|
|
23
|
+
readonly distance: XbriefSchemaDistance;
|
|
24
|
+
readonly stale: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface DriftInputs {
|
|
27
|
+
readonly directive: DirectiveDrift;
|
|
28
|
+
readonly xbrief: XbriefDrift;
|
|
29
|
+
readonly ageMs: number;
|
|
30
|
+
readonly deferralCount: number;
|
|
31
|
+
}
|
|
32
|
+
export interface DriftScoreResult {
|
|
33
|
+
readonly score: number;
|
|
34
|
+
readonly tier: StalenessTicklerTier;
|
|
35
|
+
}
|
|
36
|
+
export interface StalenessTicklerState {
|
|
37
|
+
readonly firstDetectedAt?: string;
|
|
38
|
+
readonly lastTier?: StalenessTicklerTier;
|
|
39
|
+
readonly lastScore?: number;
|
|
40
|
+
readonly lastPromptAt?: string;
|
|
41
|
+
readonly deferralCount?: number;
|
|
42
|
+
readonly snoozedUntil?: string;
|
|
43
|
+
readonly heldDirectiveLatest?: string | null;
|
|
44
|
+
readonly heldXbriefDistance?: XbriefSchemaDistance;
|
|
45
|
+
readonly remindAfterNextRelease?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface StalenessTicklerWeights {
|
|
48
|
+
readonly directiveMajor: number;
|
|
49
|
+
readonly directiveMinor: number;
|
|
50
|
+
readonly directivePatch: number;
|
|
51
|
+
readonly schemaMajor: number;
|
|
52
|
+
readonly schemaMinor: number;
|
|
53
|
+
readonly agePerDay: number;
|
|
54
|
+
readonly deferral: number;
|
|
55
|
+
}
|
|
56
|
+
export interface StalenessTicklerTierThresholds {
|
|
57
|
+
readonly noticeMinorThreshold: number;
|
|
58
|
+
readonly strongAgeMs: number;
|
|
59
|
+
readonly assertDeferralCap: number;
|
|
60
|
+
}
|
|
61
|
+
export interface StalenessTicklerSnoozePolicy {
|
|
62
|
+
readonly quietMs: number;
|
|
63
|
+
readonly noticeMs: number;
|
|
64
|
+
readonly strongMs: number;
|
|
65
|
+
readonly maxWidenMultiplier: number;
|
|
66
|
+
}
|
|
67
|
+
export interface StalenessTicklerPolicy {
|
|
68
|
+
readonly enabled: boolean;
|
|
69
|
+
readonly optOut: boolean;
|
|
70
|
+
readonly weights: StalenessTicklerWeights;
|
|
71
|
+
readonly tiers: StalenessTicklerTierThresholds;
|
|
72
|
+
readonly snooze: StalenessTicklerSnoozePolicy;
|
|
73
|
+
}
|
|
74
|
+
export interface StalenessProbeResult {
|
|
75
|
+
readonly directive: DirectiveDrift;
|
|
76
|
+
readonly xbrief: XbriefDrift;
|
|
77
|
+
readonly anyStale: boolean;
|
|
78
|
+
readonly directiveRegistryDisclosure?: string;
|
|
79
|
+
}
|
|
80
|
+
export interface StalenessTicklerRunResult {
|
|
81
|
+
readonly lines: readonly string[];
|
|
82
|
+
readonly prompted: boolean;
|
|
83
|
+
readonly skippedReason?: string;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Escalation tiers for the staleness tickler (#2489). */
|
|
2
|
+
export const STALENESS_TICKLER_TIERS = ["quiet", "notice", "strong", "assert"];
|
|
3
|
+
/** xBRIEF schema distance relative to the installed framework schema. */
|
|
4
|
+
export const XBRIEF_SCHEMA_DISTANCES = ["current", "behind-minor", "behind-major"];
|
|
5
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -4,6 +4,10 @@ export declare function validateWipCapOnPlan(plan: unknown, filepath: string): s
|
|
|
4
4
|
export declare function validateSessionRitualStalenessHoursOnPlan(plan: unknown, filepath: string): string[];
|
|
5
5
|
/** vbrief_validate hook: validate ``plan.policy.triageRankingLabels`` (#1128). */
|
|
6
6
|
export declare function validateTriageRankingLabelsOnPlan(plan: unknown, filepath: string): string[];
|
|
7
|
+
/** vbrief_validate hook: validate ``plan.policy.runtimeAuthority`` (#1394). */
|
|
8
|
+
export declare function validateRuntimeAuthorityOnPlan(plan: unknown, filepath: string): string[];
|
|
9
|
+
/** vbrief_validate hook: validate ``plan.policy.stalenessTickler`` (#2489). */
|
|
10
|
+
export declare function validateStalenessTicklerOnPlan(plan: unknown, filepath: string): string[];
|
|
7
11
|
/** Run all PROJECT-DEFINITION policy hooks (mirrors lazy-import block in Python). */
|
|
8
12
|
export declare function runProjectDefinitionHooks(plan: unknown, filepath: string): string[];
|
|
9
13
|
//# sourceMappingURL=plan-hooks.d.ts.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { readPlanPolicy } from "../policy/plan-extensions.js";
|
|
2
|
+
import { validateRuntimeAuthority } from "../policy/runtime-authority.js";
|
|
3
|
+
import { validateStalenessTickler } from "../policy/staleness-tickler.js";
|
|
2
4
|
import { validateTriageAutoClassifyOnPlan, validateTriageHoldMarkersOnPlan, } from "../triage/classify/index.js";
|
|
3
5
|
import { validateRankingLabels } from "../triage/queue/ranking-labels.js";
|
|
4
6
|
import { pyStrRepr, pythonTypeName } from "../triage/scope/python-repr.js";
|
|
@@ -101,6 +103,42 @@ export function validateTriageRankingLabelsOnPlan(plan, filepath) {
|
|
|
101
103
|
const { errors } = validateRankingLabels(raw);
|
|
102
104
|
return errors.map((err) => `${filepath}: ${err} (#1128)`);
|
|
103
105
|
}
|
|
106
|
+
/** vbrief_validate hook: validate ``plan.policy.runtimeAuthority`` (#1394). */
|
|
107
|
+
export function validateRuntimeAuthorityOnPlan(plan, filepath) {
|
|
108
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const policy = readPlanPolicy(plan);
|
|
112
|
+
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
if (!("runtimeAuthority" in policy)) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
const out = [];
|
|
119
|
+
for (const err of validateRuntimeAuthority(policy.runtimeAuthority)) {
|
|
120
|
+
out.push(`${filepath}: ${err} (#1394)`);
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
/** vbrief_validate hook: validate ``plan.policy.stalenessTickler`` (#2489). */
|
|
125
|
+
export function validateStalenessTicklerOnPlan(plan, filepath) {
|
|
126
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
const policy = readPlanPolicy(plan);
|
|
130
|
+
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
if (!("stalenessTickler" in policy)) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
const out = [];
|
|
137
|
+
for (const err of validateStalenessTickler(policy.stalenessTickler)) {
|
|
138
|
+
out.push(`${filepath}: ${err} (#2489)`);
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
104
142
|
/** Run all PROJECT-DEFINITION policy hooks (mirrors lazy-import block in Python). */
|
|
105
143
|
export function runProjectDefinitionHooks(plan, filepath) {
|
|
106
144
|
const errors = [];
|
|
@@ -141,6 +179,18 @@ export function runProjectDefinitionHooks(plan, filepath) {
|
|
|
141
179
|
catch {
|
|
142
180
|
/* hook must not break validation */
|
|
143
181
|
}
|
|
182
|
+
try {
|
|
183
|
+
errors.push(...validateStalenessTicklerOnPlan(plan, filepath));
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
/* hook must not break validation */
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
errors.push(...validateRuntimeAuthorityOnPlan(plan, filepath));
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
/* hook must not break validation */
|
|
193
|
+
}
|
|
144
194
|
return errors;
|
|
145
195
|
}
|
|
146
196
|
//# sourceMappingURL=plan-hooks.js.map
|
|
@@ -37,8 +37,10 @@ export function evaluateAgentHooks(projectRoot) {
|
|
|
37
37
|
return {
|
|
38
38
|
code: 0,
|
|
39
39
|
message: "✓ deft agent hooks registered and structurally valid for Claude, Grok, Cursor, Codex " +
|
|
40
|
-
"(SessionStart + PreToolUse direct-write tools
|
|
41
|
-
"
|
|
40
|
+
"(SessionStart + PreToolUse direct-write and spawn/Task tools; compact re-arm deposited for Claude/Grok/Cursor; " +
|
|
41
|
+
"Codex has no native compact hook — re-run session ritual manually after compaction). " +
|
|
42
|
+
'Read-only explore: prefer Grok role `default_capability_mode = "read-only"`; hooks also honor ' +
|
|
43
|
+
"DEFT_HOOK_READ_ONLY=1 and explore subagent_type. Codex runtime trust is user-controlled and must be reviewed with `/hooks`; shell/MCP policy is deferred.",
|
|
42
44
|
stream: "stdout",
|
|
43
45
|
registrations,
|
|
44
46
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.81.0",
|
|
4
4
|
"description": "TypeScript engine core for the Directive framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -309,8 +309,8 @@
|
|
|
309
309
|
"provenance": true
|
|
310
310
|
},
|
|
311
311
|
"dependencies": {
|
|
312
|
-
"@deftai/directive-content": "^0.
|
|
313
|
-
"@deftai/directive-types": "^0.
|
|
312
|
+
"@deftai/directive-content": "^0.81.0",
|
|
313
|
+
"@deftai/directive-types": "^0.81.0",
|
|
314
314
|
"archiver": "^8.0.0"
|
|
315
315
|
},
|
|
316
316
|
"scripts": {
|