@deftai/directive-core 0.96.0 → 0.98.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 +291 -0
- package/dist/cache/archive.js +10 -4
- package/dist/check/cached-orchestrator.d.ts +5 -0
- package/dist/check/cached-orchestrator.js +18 -1
- package/dist/check/gate-lists.d.ts +20 -0
- package/dist/check/gate-lists.js +54 -9
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +1 -1
- package/dist/check/orchestrator.d.ts +4 -0
- package/dist/check/orchestrator.js +4 -0
- package/dist/consumer-check-contract/evaluate.d.ts +124 -0
- package/dist/consumer-check-contract/evaluate.js +699 -0
- package/dist/consumer-check-contract/index.d.ts +5 -0
- package/dist/consumer-check-contract/index.js +5 -0
- package/dist/delivery-attempt/disk-begin.d.ts +51 -0
- package/dist/delivery-attempt/disk-begin.js +68 -0
- package/dist/delivery-attempt/evaluate.d.ts +26 -0
- package/dist/delivery-attempt/evaluate.js +443 -0
- package/dist/delivery-attempt/fingerprint.d.ts +32 -0
- package/dist/delivery-attempt/fingerprint.js +100 -0
- package/dist/delivery-attempt/handoff.d.ts +25 -0
- package/dist/delivery-attempt/handoff.js +102 -0
- package/dist/delivery-attempt/index.d.ts +17 -0
- package/dist/delivery-attempt/index.js +17 -0
- package/dist/delivery-attempt/ledger.d.ts +169 -0
- package/dist/delivery-attempt/ledger.js +758 -0
- package/dist/delivery-attempt/material-delta.d.ts +38 -0
- package/dist/delivery-attempt/material-delta.js +126 -0
- package/dist/delivery-attempt/types.d.ts +210 -0
- package/dist/delivery-attempt/types.js +77 -0
- package/dist/doctor/checks.d.ts +7 -0
- package/dist/doctor/checks.js +83 -0
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +12 -0
- package/dist/doctor/openclaw-soft-rebind.d.ts +26 -0
- package/dist/doctor/openclaw-soft-rebind.js +164 -0
- package/dist/hooks/dispatcher.d.ts +7 -1
- package/dist/hooks/dispatcher.js +117 -13
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/init-deposit/gitignore.js +7 -0
- package/dist/init-deposit/hygiene.d.ts +70 -1
- package/dist/init-deposit/hygiene.js +582 -8
- package/dist/init-deposit/init-deposit.js +5 -0
- package/dist/init-deposit/refresh.js +3 -0
- package/dist/init-deposit/scaffold.js +277 -4
- package/dist/init-deposit/skill-discovery-deposit.js +15 -0
- package/dist/policy/check-resume.d.ts +72 -0
- package/dist/policy/check-resume.js +253 -0
- package/dist/policy/coverage-check-resume-presets.d.ts +46 -0
- package/dist/policy/coverage-check-resume-presets.js +228 -0
- package/dist/policy/coverage-debt.d.ts +76 -0
- package/dist/policy/coverage-debt.js +262 -0
- package/dist/policy/index.d.ts +3 -0
- package/dist/policy/index.js +50 -21
- package/dist/pr-merge-readiness/ci-gate.d.ts +29 -1
- package/dist/pr-merge-readiness/ci-gate.js +191 -24
- package/dist/pr-merge-readiness/compute.js +10 -1
- package/dist/pr-merge-readiness/index.d.ts +2 -1
- package/dist/pr-merge-readiness/index.js +2 -1
- package/dist/pr-merge-readiness/output.js +14 -0
- package/dist/pr-merge-readiness/platform-status.d.ts +29 -0
- package/dist/pr-merge-readiness/platform-status.js +49 -0
- package/dist/pr-watch/constants.d.ts +10 -0
- package/dist/pr-watch/constants.js +12 -1
- package/dist/pr-watch/main.js +16 -1
- package/dist/pr-watch/probe.js +13 -1
- package/dist/pr-watch/types.d.ts +3 -2
- package/dist/pr-watch/watch.js +14 -7
- package/dist/release/auto-hatch.d.ts +114 -0
- package/dist/release/auto-hatch.js +301 -0
- package/dist/release/coverage-debt-ledger.d.ts +22 -0
- package/dist/release/coverage-debt-ledger.js +157 -0
- package/dist/release/index.d.ts +3 -0
- package/dist/release/index.js +3 -0
- package/dist/release/pipeline.js +164 -12
- package/dist/release/suite-stamp.d.ts +44 -0
- package/dist/release/suite-stamp.js +133 -0
- package/dist/release/types.d.ts +19 -0
- package/dist/scope-provenance/digest.d.ts +67 -0
- package/dist/scope-provenance/digest.js +188 -0
- package/dist/scope-provenance/evaluate.d.ts +82 -0
- package/dist/scope-provenance/evaluate.js +528 -0
- package/dist/scope-provenance/index.d.ts +6 -0
- package/dist/scope-provenance/index.js +6 -0
- package/dist/session/compact-ritual.d.ts +96 -0
- package/dist/session/compact-ritual.js +237 -0
- package/dist/session/compact-ritual.spec.d.ts +2 -0
- package/dist/session/compact-ritual.spec.js +21 -0
- package/dist/session/coverage-check-resume-nudge.d.ts +34 -0
- package/dist/session/coverage-check-resume-nudge.js +66 -0
- package/dist/session/index.d.ts +3 -0
- package/dist/session/index.js +3 -0
- package/dist/session/openclaw-soft-rebind-deposit.d.ts +46 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +165 -0
- package/dist/session/session-start.js +21 -0
- package/dist/test-boundary/evaluate.d.ts +54 -0
- package/dist/test-boundary/evaluate.js +368 -0
- package/dist/test-boundary/index.d.ts +6 -0
- package/dist/test-boundary/index.js +6 -0
- package/dist/test-boundary/policy.d.ts +52 -0
- package/dist/test-boundary/policy.js +182 -0
- package/dist/triage/bootstrap/gitignore.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +15 -1
- package/dist/triage/classify/label-mirror.d.ts +31 -1
- package/dist/triage/classify/label-mirror.js +78 -6
- package/dist/triage/help/registry-data.d.ts +6 -6
- package/dist/triage/help/registry-data.js +12 -3
- package/dist/vbrief-activate/activate.js +22 -6
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +54 -0
- package/dist/xbrief/styles.js +33 -17
- package/package.json +7 -3
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor check + --fix wire for OpenClaw soft AGENTS re-bind skill (#3171).
|
|
3
|
+
*
|
|
4
|
+
* Complements always-pins (#3001/#3008) and L2 product commands (#3064). This
|
|
5
|
+
* skill is the required durable soft post-amnesia surface for OpenClaw — not
|
|
6
|
+
* claimed via file-host PreCompact hooks alone.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { formatOpenClawSoftRebindSkillMarkdown, isManagedOpenClawSoftRebindSkill, OPENCLAW_SOFT_REBIND_SKILL_ID, } from "../session/compact-ritual.js";
|
|
12
|
+
import { assessOpenClawSoftRebindSkill, depositOpenClawSoftRebindSkill, } from "../session/openclaw-soft-rebind-deposit.js";
|
|
13
|
+
import { detectOpenClaw, listInScopeSkillsDirs } from "./openclaw-skills.js";
|
|
14
|
+
/** Stable doctor check id for JSON findings. */
|
|
15
|
+
export const OPENCLAW_SOFT_REBIND_CHECK = "openclaw-soft-agents-rebind";
|
|
16
|
+
const DOC_OPENCLAW_HOST = "docs/openclaw-agent-host.md";
|
|
17
|
+
const REMEDIATION_FIX = "deft doctor --fix";
|
|
18
|
+
function defaultIsDir(path) {
|
|
19
|
+
try {
|
|
20
|
+
return statSync(path).isDirectory();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function assessGaps(skillsDirs) {
|
|
27
|
+
const expected = formatOpenClawSoftRebindSkillMarkdown();
|
|
28
|
+
const missing = [];
|
|
29
|
+
const stale = [];
|
|
30
|
+
let present = 0;
|
|
31
|
+
for (const skillsDir of skillsDirs) {
|
|
32
|
+
const path = join(skillsDir, OPENCLAW_SOFT_REBIND_SKILL_ID, "SKILL.md");
|
|
33
|
+
if (!existsSync(path)) {
|
|
34
|
+
missing.push(`${skillsDir}/${OPENCLAW_SOFT_REBIND_SKILL_ID}`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
let raw;
|
|
38
|
+
try {
|
|
39
|
+
raw = readFileSync(path, "utf8");
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
missing.push(`${skillsDir}/${OPENCLAW_SOFT_REBIND_SKILL_ID}`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// Unmanaged content at the required slug is incomplete health, not pass
|
|
46
|
+
// (Greptile P1 #3171). Doctor --fix overwrites via deposit.
|
|
47
|
+
if (!isManagedOpenClawSoftRebindSkill(raw) || raw !== expected) {
|
|
48
|
+
stale.push(`${skillsDir}/${OPENCLAW_SOFT_REBIND_SKILL_ID}`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
present += 1;
|
|
52
|
+
}
|
|
53
|
+
return { missing, stale, present };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Doctor check: OpenClaw soft AGENTS re-bind skill present and current.
|
|
57
|
+
* No-ops when OpenClaw is not detected. Fix mode deposits managed skill.
|
|
58
|
+
*/
|
|
59
|
+
export function runOpenClawSoftRebindCheck(sink, addFinding, options) {
|
|
60
|
+
const seams = options.seams ?? {};
|
|
61
|
+
const env = seams.openclawEnv ?? process.env;
|
|
62
|
+
const homeDirFn = seams.openclawHomeDir ?? (() => homedir());
|
|
63
|
+
const isDir = seams.isDir ?? defaultIsDir;
|
|
64
|
+
const detect = detectOpenClaw(env, { homeDir: homeDirFn(), isDir });
|
|
65
|
+
if (!detect.detected) {
|
|
66
|
+
sink.info(`${OPENCLAW_SOFT_REBIND_CHECK}: skip -- OpenClaw not detected`);
|
|
67
|
+
addFinding({
|
|
68
|
+
severity: "skip",
|
|
69
|
+
message: "OpenClaw not detected",
|
|
70
|
+
check: OPENCLAW_SOFT_REBIND_CHECK,
|
|
71
|
+
status: "skip",
|
|
72
|
+
reason: "openclaw-not-detected",
|
|
73
|
+
});
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const skillsDirs = listInScopeSkillsDirs(detect.stateDir, options.allAgents, {
|
|
77
|
+
isDir,
|
|
78
|
+
});
|
|
79
|
+
const gaps = assessGaps(skillsDirs);
|
|
80
|
+
if (gaps.missing.length === 0 && gaps.stale.length === 0) {
|
|
81
|
+
const scope = options.allAgents ? "main + workspace-* seats" : "main workspace";
|
|
82
|
+
const message = `${OPENCLAW_SOFT_REBIND_CHECK}: soft AGENTS re-bind skill present ` +
|
|
83
|
+
`(${OPENCLAW_SOFT_REBIND_SKILL_ID}) in ${scope}`;
|
|
84
|
+
sink.success(message);
|
|
85
|
+
addFinding({
|
|
86
|
+
severity: "skip",
|
|
87
|
+
message,
|
|
88
|
+
check: OPENCLAW_SOFT_REBIND_CHECK,
|
|
89
|
+
status: "present",
|
|
90
|
+
skill: OPENCLAW_SOFT_REBIND_SKILL_ID,
|
|
91
|
+
skills_dirs: skillsDirs,
|
|
92
|
+
});
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
if (options.fixMode) {
|
|
96
|
+
const depositResult = depositOpenClawSoftRebindSkill({
|
|
97
|
+
env,
|
|
98
|
+
homeDir: homeDirFn(),
|
|
99
|
+
allAgents: options.allAgents,
|
|
100
|
+
isDir,
|
|
101
|
+
skillsDirs,
|
|
102
|
+
});
|
|
103
|
+
const after = assessGaps(skillsDirs);
|
|
104
|
+
if (after.missing.length === 0 && after.stale.length === 0) {
|
|
105
|
+
const message = `${OPENCLAW_SOFT_REBIND_CHECK}: deposited soft AGENTS re-bind skill; ` +
|
|
106
|
+
`restart OpenClaw gateway or start a new session (${DOC_OPENCLAW_HOST})`;
|
|
107
|
+
sink.success(message);
|
|
108
|
+
addFinding({
|
|
109
|
+
severity: "skip",
|
|
110
|
+
message,
|
|
111
|
+
check: OPENCLAW_SOFT_REBIND_CHECK,
|
|
112
|
+
status: "fixed",
|
|
113
|
+
written: depositResult.writtenPaths,
|
|
114
|
+
});
|
|
115
|
+
return depositResult;
|
|
116
|
+
}
|
|
117
|
+
// Partial fix: report remaining gaps from post-deposit assessment (SLizard P2).
|
|
118
|
+
const partsAfter = [];
|
|
119
|
+
if (after.missing.length > 0) {
|
|
120
|
+
partsAfter.push(`missing=${after.missing.join(", ")}`);
|
|
121
|
+
}
|
|
122
|
+
if (after.stale.length > 0) {
|
|
123
|
+
partsAfter.push(`stale=${after.stale.join(", ")}`);
|
|
124
|
+
}
|
|
125
|
+
const messageAfter = `${OPENCLAW_SOFT_REBIND_CHECK}: OpenClaw soft AGENTS re-bind skill incomplete after fix ` +
|
|
126
|
+
`(${partsAfter.join("; ")}). Remediation: ${REMEDIATION_FIX} — see ${DOC_OPENCLAW_HOST}`;
|
|
127
|
+
sink.warn(messageAfter);
|
|
128
|
+
addFinding({
|
|
129
|
+
severity: "warning",
|
|
130
|
+
message: messageAfter,
|
|
131
|
+
check: OPENCLAW_SOFT_REBIND_CHECK,
|
|
132
|
+
status: "incomplete",
|
|
133
|
+
missing: after.missing,
|
|
134
|
+
stale: after.stale,
|
|
135
|
+
written: depositResult.writtenPaths,
|
|
136
|
+
suggestion: REMEDIATION_FIX,
|
|
137
|
+
docs: [DOC_OPENCLAW_HOST],
|
|
138
|
+
});
|
|
139
|
+
return depositResult;
|
|
140
|
+
}
|
|
141
|
+
const parts = [];
|
|
142
|
+
if (gaps.missing.length > 0) {
|
|
143
|
+
parts.push(`missing=${gaps.missing.join(", ")}`);
|
|
144
|
+
}
|
|
145
|
+
if (gaps.stale.length > 0) {
|
|
146
|
+
parts.push(`stale=${gaps.stale.join(", ")}`);
|
|
147
|
+
}
|
|
148
|
+
const message = `${OPENCLAW_SOFT_REBIND_CHECK}: OpenClaw soft AGENTS re-bind skill incomplete ` +
|
|
149
|
+
`(${parts.join("; ")}). Remediation: ${REMEDIATION_FIX} — see ${DOC_OPENCLAW_HOST}`;
|
|
150
|
+
sink.warn(message);
|
|
151
|
+
addFinding({
|
|
152
|
+
severity: "warning",
|
|
153
|
+
message,
|
|
154
|
+
check: OPENCLAW_SOFT_REBIND_CHECK,
|
|
155
|
+
status: "incomplete",
|
|
156
|
+
missing: gaps.missing,
|
|
157
|
+
stale: gaps.stale,
|
|
158
|
+
suggestion: REMEDIATION_FIX,
|
|
159
|
+
docs: [DOC_OPENCLAW_HOST],
|
|
160
|
+
});
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
export { assessOpenClawSoftRebindSkill, depositOpenClawSoftRebindSkill };
|
|
164
|
+
//# sourceMappingURL=openclaw-soft-rebind.js.map
|
|
@@ -119,6 +119,11 @@ export declare const ASSIST_SCRATCH_ROOT_PREFIXES: readonly [".deft-scratch/", "
|
|
|
119
119
|
* True when the write target is under an allowlisted disposable scratch root (#1802).
|
|
120
120
|
* Fail closed on null/empty/unparseable targets and on path escape (`..`).
|
|
121
121
|
* Does not authorize tracked product paths even under assist posture.
|
|
122
|
+
*
|
|
123
|
+
* #3186: after lexical allowlist match, realpath the scratch root and require
|
|
124
|
+
* {@link assertProjectionContained}; refuse symlink scratch roots and any
|
|
125
|
+
* realpath outside the project (host Write would follow the link).
|
|
126
|
+
* When the project root cannot be realpath'd (unit fixtures), lexical classification wins.
|
|
122
127
|
*/
|
|
123
128
|
export declare function isAllowlistedAssistScratchPath(projectRoot: string, targetPath: string | null): boolean;
|
|
124
129
|
/**
|
|
@@ -140,7 +145,8 @@ export declare function decideHook(input: HookDispatchInput, seams?: HookPolicyS
|
|
|
140
145
|
* Cursor deposits use `failClosed: true`. Cursor treats empty/null stdout as a
|
|
141
146
|
* hook failure and blocks the tool — so Cursor allows must emit explicit
|
|
142
147
|
* `{"permission":"allow"}`. Other hosts keep empty allow so the host permission
|
|
143
|
-
* flow is unchanged.
|
|
148
|
+
* flow is unchanged — except session.start / session.compact soft re-bind
|
|
149
|
+
* injection (#3171), which surfaces checklist text without a write tool.
|
|
144
150
|
*
|
|
145
151
|
* Cursor stdout always includes `code` (stable machine-readable decision code)
|
|
146
152
|
* so agents can distinguish policy denials from host-integration failures
|
package/dist/hooks/dispatcher.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { realpathSync } from "node:fs";
|
|
1
|
+
import { lstatSync, realpathSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { appendAuthzAudit, classifyHookAuthzOps, evaluateAuthzMutation, evidenceSatisfiesImplementationApproval, listActiveHumanGrants, loadAuthzStateResult, markGrantUsed, shouldConsumeSingleUseGrant, utcIso, } from "../authz/index.js";
|
|
4
|
+
import { assertProjectionContained, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
4
5
|
import { hasArtifactSuffix } from "../layout/resolve.js";
|
|
5
6
|
import { detectDeftDirectiveDisable, formatDeftDirectiveDisableMessage, isDeftDirectiveDisableActive, } from "../policy/deft-directive-disable.js";
|
|
6
7
|
import { evaluateIntentCeilingFromEnv } from "../policy/intent-ceiling.js";
|
|
7
8
|
import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, } from "../policy/no-deft-directive.js";
|
|
8
9
|
import { classifyMcpTool, DEFAULT_RUNTIME_AUTHORITY_POLICY, evaluateRuntimeAuthorityDirectWrite, evaluateRuntimeAuthorityShellOp, listShellOps, loadRuntimeAuthorityFromProject, } from "../policy/runtime-authority.js";
|
|
9
10
|
import { loadStoryWriteFenceFromPath, resolveWriteFence } from "../policy/write-fence.js";
|
|
11
|
+
import { appendSoftAgentsRebindToMessage, decisionCarriesSoftAgentsRebind, formatSoftAgentsRebindChecklist, } from "../session/compact-ritual.js";
|
|
10
12
|
import { detectBranch } from "../session/git.js";
|
|
11
13
|
import { emitSessionRitualBlockedProcessCost } from "../session/process-cost.js";
|
|
12
14
|
import { markRitualStaleAfterCompact } from "../session/ritual-sentinel.js";
|
|
@@ -109,6 +111,11 @@ export const ASSIST_SCRATCH_ROOT_PREFIXES = [".deft-scratch/", "temp/"];
|
|
|
109
111
|
* True when the write target is under an allowlisted disposable scratch root (#1802).
|
|
110
112
|
* Fail closed on null/empty/unparseable targets and on path escape (`..`).
|
|
111
113
|
* Does not authorize tracked product paths even under assist posture.
|
|
114
|
+
*
|
|
115
|
+
* #3186: after lexical allowlist match, realpath the scratch root and require
|
|
116
|
+
* {@link assertProjectionContained}; refuse symlink scratch roots and any
|
|
117
|
+
* realpath outside the project (host Write would follow the link).
|
|
118
|
+
* When the project root cannot be realpath'd (unit fixtures), lexical classification wins.
|
|
112
119
|
*/
|
|
113
120
|
export function isAllowlistedAssistScratchPath(projectRoot, targetPath) {
|
|
114
121
|
if (targetPath === null || targetPath.trim().length === 0)
|
|
@@ -119,11 +126,55 @@ export function isAllowlistedAssistScratchPath(projectRoot, targetPath) {
|
|
|
119
126
|
return false;
|
|
120
127
|
if (isLexicalOutsideProjectRoot(posix))
|
|
121
128
|
return false;
|
|
129
|
+
let matchedPrefix = null;
|
|
122
130
|
for (const prefix of ASSIST_SCRATCH_ROOT_PREFIXES) {
|
|
123
|
-
if (posix === prefix.slice(0, -1) || posix.startsWith(prefix))
|
|
124
|
-
|
|
131
|
+
if (posix === prefix.slice(0, -1) || posix.startsWith(prefix)) {
|
|
132
|
+
matchedPrefix = prefix;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
125
135
|
}
|
|
126
|
-
|
|
136
|
+
if (matchedPrefix === null)
|
|
137
|
+
return false;
|
|
138
|
+
// #3186 containment: realpath scratch root + projection fence (symlink escape).
|
|
139
|
+
const projectAbs = resolve(projectRoot);
|
|
140
|
+
try {
|
|
141
|
+
realpathSync(projectAbs);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Unit fixtures / missing project dir — lexical allowlist only.
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
const scratchRootName = matchedPrefix.slice(0, -1); // ".deft-scratch" | "temp"
|
|
148
|
+
const scratchRootAbs = resolve(projectAbs, scratchRootName);
|
|
149
|
+
try {
|
|
150
|
+
// Refuse when the scratch root (or any parent on the path) escapes via symlink.
|
|
151
|
+
assertProjectionContained(projectAbs, scratchRootAbs);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
if (err instanceof ProjectionContainmentError)
|
|
155
|
+
return false;
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
// Refuse symlink scratch roots entirely (#3186) — even in-tree links can divert Write.
|
|
159
|
+
try {
|
|
160
|
+
const st = lstatSync(scratchRootAbs);
|
|
161
|
+
if (st.isSymbolicLink())
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Scratch root does not exist yet — mkdir will create a real directory; allow.
|
|
166
|
+
}
|
|
167
|
+
// Also fence the concrete write target when it already exists on disk.
|
|
168
|
+
const targetAbs = resolve(projectAbs, targetPath.replace(/\\/g, "/"));
|
|
169
|
+
try {
|
|
170
|
+
assertProjectionContained(projectAbs, targetAbs);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
if (err instanceof ProjectionContainmentError)
|
|
174
|
+
return false;
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
127
178
|
}
|
|
128
179
|
/**
|
|
129
180
|
* Assist/ephemeral classification for scratch-write carve-out (#1802).
|
|
@@ -716,7 +767,8 @@ export function decideHook(input, seams = {}) {
|
|
|
716
767
|
host: input.host,
|
|
717
768
|
toolName: null,
|
|
718
769
|
projectRoot,
|
|
719
|
-
|
|
770
|
+
// Soft checklist still surfaces after compact even when ritual was absent (#3171).
|
|
771
|
+
message: appendSoftAgentsRebindToMessage(result.message),
|
|
720
772
|
scopePath: null,
|
|
721
773
|
};
|
|
722
774
|
}
|
|
@@ -727,7 +779,7 @@ export function decideHook(input, seams = {}) {
|
|
|
727
779
|
host: input.host,
|
|
728
780
|
toolName: null,
|
|
729
781
|
projectRoot,
|
|
730
|
-
message: result.message,
|
|
782
|
+
message: appendSoftAgentsRebindToMessage(result.message),
|
|
731
783
|
scopePath: null,
|
|
732
784
|
};
|
|
733
785
|
}
|
|
@@ -739,8 +791,8 @@ export function decideHook(input, seams = {}) {
|
|
|
739
791
|
host: input.host,
|
|
740
792
|
toolName: null,
|
|
741
793
|
projectRoot,
|
|
742
|
-
message: "Directive compact re-arm bookkeeping failed on its non-blocking path: " +
|
|
743
|
-
`${String(cause)}
|
|
794
|
+
message: appendSoftAgentsRebindToMessage("Directive compact re-arm bookkeeping failed on its non-blocking path: " +
|
|
795
|
+
`${String(cause)}`),
|
|
744
796
|
scopePath: null,
|
|
745
797
|
};
|
|
746
798
|
}
|
|
@@ -775,8 +827,9 @@ export function decideHook(input, seams = {}) {
|
|
|
775
827
|
host: input.host,
|
|
776
828
|
toolName: null,
|
|
777
829
|
projectRoot,
|
|
778
|
-
|
|
779
|
-
|
|
830
|
+
// Best-effort soft cue on SessionStart (Codex gap + resume/amnesia) (#3171).
|
|
831
|
+
message: appendSoftAgentsRebindToMessage(`Directive SessionStart bookkeeping reported exit ${result.code} on its non-blocking path` +
|
|
832
|
+
`${detail.length > 0 ? `: ${detail}` : "."}`),
|
|
780
833
|
scopePath: null,
|
|
781
834
|
};
|
|
782
835
|
}
|
|
@@ -789,7 +842,7 @@ export function decideHook(input, seams = {}) {
|
|
|
789
842
|
host: input.host,
|
|
790
843
|
toolName: null,
|
|
791
844
|
projectRoot,
|
|
792
|
-
message: `Directive SessionStart bookkeeping failed on its non-blocking path: ${String(cause)}
|
|
845
|
+
message: appendSoftAgentsRebindToMessage(`Directive SessionStart bookkeeping failed on its non-blocking path: ${String(cause)}`),
|
|
793
846
|
scopePath: null,
|
|
794
847
|
};
|
|
795
848
|
}
|
|
@@ -800,7 +853,8 @@ export function decideHook(input, seams = {}) {
|
|
|
800
853
|
host: input.host,
|
|
801
854
|
toolName: null,
|
|
802
855
|
projectRoot,
|
|
803
|
-
|
|
856
|
+
// Soft AGENTS re-bind on SessionStart without requiring a write tool (#3171 / #2769).
|
|
857
|
+
message: appendSoftAgentsRebindToMessage("SessionStart bookkeeping completed on a non-blocking path."),
|
|
804
858
|
scopePath: null,
|
|
805
859
|
};
|
|
806
860
|
}
|
|
@@ -881,13 +935,27 @@ export function decideHook(input, seams = {}) {
|
|
|
881
935
|
}
|
|
882
936
|
return inspectMutationGates(input, toolName, seams, { proposedLifecycleExempt: true });
|
|
883
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* Soft AGENTS re-bind text for host injection when decision carries soft path (#3171).
|
|
940
|
+
* Prefer the checklist portion of the decision message; fall back to SoT formatter.
|
|
941
|
+
*/
|
|
942
|
+
function softAgentsRebindWireText(decision) {
|
|
943
|
+
if (!decisionCarriesSoftAgentsRebind({ event: decision.event, code: decision.code })) {
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
if (decision.message.includes("Soft AGENTS re-bind checklist:")) {
|
|
947
|
+
return decision.message;
|
|
948
|
+
}
|
|
949
|
+
return formatSoftAgentsRebindChecklist();
|
|
950
|
+
}
|
|
884
951
|
/**
|
|
885
952
|
* Render host-facing hook output.
|
|
886
953
|
*
|
|
887
954
|
* Cursor deposits use `failClosed: true`. Cursor treats empty/null stdout as a
|
|
888
955
|
* hook failure and blocks the tool — so Cursor allows must emit explicit
|
|
889
956
|
* `{"permission":"allow"}`. Other hosts keep empty allow so the host permission
|
|
890
|
-
* flow is unchanged.
|
|
957
|
+
* flow is unchanged — except session.start / session.compact soft re-bind
|
|
958
|
+
* injection (#3171), which surfaces checklist text without a write tool.
|
|
891
959
|
*
|
|
892
960
|
* Cursor stdout always includes `code` (stable machine-readable decision code)
|
|
893
961
|
* so agents can distinguish policy denials from host-integration failures
|
|
@@ -896,9 +964,45 @@ export function decideHook(input, seams = {}) {
|
|
|
896
964
|
*/
|
|
897
965
|
export function renderHostDecision(host, decision) {
|
|
898
966
|
if (decision.verdict === "allow") {
|
|
967
|
+
const soft = softAgentsRebindWireText(decision);
|
|
899
968
|
if (host === "cursor") {
|
|
969
|
+
if (decision.event === "session.start" && soft !== null) {
|
|
970
|
+
// Cursor sessionStart injects additional_context into the conversation.
|
|
971
|
+
return JSON.stringify({
|
|
972
|
+
permission: "allow",
|
|
973
|
+
code: decision.code,
|
|
974
|
+
additional_context: soft,
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
if (decision.event === "session.compact" && soft !== null) {
|
|
978
|
+
// Cursor preCompact is observational; user_message surfaces the soft cue.
|
|
979
|
+
// Hard re-arm still ran via bookkeeping; agent path also gets SessionStart on resume.
|
|
980
|
+
return JSON.stringify({
|
|
981
|
+
user_message: soft,
|
|
982
|
+
code: decision.code,
|
|
983
|
+
});
|
|
984
|
+
}
|
|
900
985
|
return JSON.stringify({ permission: "allow", code: decision.code });
|
|
901
986
|
}
|
|
987
|
+
if (soft !== null &&
|
|
988
|
+
(decision.event === "session.start" || decision.event === "session.compact")) {
|
|
989
|
+
if (host === "claude" || host === "codex") {
|
|
990
|
+
const hookEventName = decision.event === "session.start" ? "SessionStart" : "PostCompact";
|
|
991
|
+
return JSON.stringify({
|
|
992
|
+
hookSpecificOutput: {
|
|
993
|
+
hookEventName,
|
|
994
|
+
additionalContext: soft,
|
|
995
|
+
},
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
if (host === "grok") {
|
|
999
|
+
return JSON.stringify({
|
|
1000
|
+
decision: "allow",
|
|
1001
|
+
reason: soft,
|
|
1002
|
+
additional_context: soft,
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
902
1006
|
return "";
|
|
903
1007
|
}
|
|
904
1008
|
switch (host) {
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,9 @@ export * as branch from "./branch/index.js";
|
|
|
14
14
|
export * as cache from "./cache/index.js";
|
|
15
15
|
export * as capacity from "./capacity/index.js";
|
|
16
16
|
export * as codebase from "./codebase/index.js";
|
|
17
|
+
export * as consumerCheckContract from "./consumer-check-contract/index.js";
|
|
17
18
|
export * from "./coverage-hotspots/index.js";
|
|
19
|
+
export * as deliveryAttempt from "./delivery-attempt/index.js";
|
|
18
20
|
export * as doctor from "./doctor/index.js";
|
|
19
21
|
export * from "./encoding/index.js";
|
|
20
22
|
export * as escalation from "./escalation/index.js";
|
|
@@ -52,11 +54,13 @@ export * as render from "./render/index.js";
|
|
|
52
54
|
export * as resolution from "./resolution/index.js";
|
|
53
55
|
export * as scm from "./scm/index.js";
|
|
54
56
|
export * as scope from "./scope/index.js";
|
|
57
|
+
export * as scopeProvenance from "./scope-provenance/index.js";
|
|
55
58
|
export * as session from "./session/index.js";
|
|
56
59
|
export * as slash from "./slash/index.js";
|
|
57
60
|
export * as slice from "./slice/index.js";
|
|
58
61
|
export * as storyReady from "./story-ready/index.js";
|
|
59
62
|
export * as swarm from "./swarm/index.js";
|
|
63
|
+
export * as testBoundary from "./test-boundary/index.js";
|
|
60
64
|
export * as toolEvents from "./tool-events/index.js";
|
|
61
65
|
export * as triage from "./triage/index.js";
|
|
62
66
|
export * as userConfig from "./user-config/index.js";
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,9 @@ export * as branch from "./branch/index.js";
|
|
|
14
14
|
export * as cache from "./cache/index.js";
|
|
15
15
|
export * as capacity from "./capacity/index.js";
|
|
16
16
|
export * as codebase from "./codebase/index.js";
|
|
17
|
+
export * as consumerCheckContract from "./consumer-check-contract/index.js";
|
|
17
18
|
export * from "./coverage-hotspots/index.js";
|
|
19
|
+
export * as deliveryAttempt from "./delivery-attempt/index.js";
|
|
18
20
|
export * as doctor from "./doctor/index.js";
|
|
19
21
|
export * from "./encoding/index.js";
|
|
20
22
|
export * as escalation from "./escalation/index.js";
|
|
@@ -53,11 +55,13 @@ export * as render from "./render/index.js";
|
|
|
53
55
|
export * as resolution from "./resolution/index.js";
|
|
54
56
|
export * as scm from "./scm/index.js";
|
|
55
57
|
export * as scope from "./scope/index.js";
|
|
58
|
+
export * as scopeProvenance from "./scope-provenance/index.js";
|
|
56
59
|
export * as session from "./session/index.js";
|
|
57
60
|
export * as slash from "./slash/index.js";
|
|
58
61
|
export * as slice from "./slice/index.js";
|
|
59
62
|
export * as storyReady from "./story-ready/index.js";
|
|
60
63
|
export * as swarm from "./swarm/index.js";
|
|
64
|
+
export * as testBoundary from "./test-boundary/index.js";
|
|
61
65
|
export * as toolEvents from "./tool-events/index.js";
|
|
62
66
|
export * as triage from "./triage/index.js";
|
|
63
67
|
export * as userConfig from "./user-config/index.js";
|
|
@@ -37,6 +37,11 @@ export const CANONICAL_GITIGNORE_BASELINE = [
|
|
|
37
37
|
"vbrief/.triage-cache/scope-lifecycle.jsonl",
|
|
38
38
|
"vbrief/.triage-cache/decompositions/",
|
|
39
39
|
"vbrief/.triage-cache/doctor-state.json",
|
|
40
|
+
// Per-clone session state (#3146): throttle / release-availability JSON
|
|
41
|
+
// written under the lifecycle .triage-cache; selective ignore only (hybrid
|
|
42
|
+
// #1144 — never blanket-ignore the whole .triage-cache directory).
|
|
43
|
+
"vbrief/.triage-cache/staleness-tickler-state.json",
|
|
44
|
+
"vbrief/.triage-cache/release-availability-state.json",
|
|
40
45
|
// Symmetric `xbrief/` layout entries (#2348). On the migrated `xbrief/` tree
|
|
41
46
|
// the engine writes operator-private triage-cache files to
|
|
42
47
|
// `xbrief/.triage-cache/`; without these the paths are trackable, violating
|
|
@@ -47,6 +52,8 @@ export const CANONICAL_GITIGNORE_BASELINE = [
|
|
|
47
52
|
"xbrief/.triage-cache/scope-lifecycle.jsonl",
|
|
48
53
|
"xbrief/.triage-cache/decompositions/",
|
|
49
54
|
"xbrief/.triage-cache/doctor-state.json",
|
|
55
|
+
"xbrief/.triage-cache/staleness-tickler-state.json",
|
|
56
|
+
"xbrief/.triage-cache/release-availability-state.json",
|
|
50
57
|
"vbrief/*.lock",
|
|
51
58
|
".deft/core.bak-*/",
|
|
52
59
|
".deft/*.bak-*",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* and scope briefs are never installer-managed; if they reappear in
|
|
9
9
|
* `installerManagedMatchers()`, unit tests and deposit-time assert fail closed.
|
|
10
10
|
*
|
|
11
|
-
* Refs #1576, #1453, #1430, #3029, #3030, #3127, #3117.
|
|
11
|
+
* Refs #1576, #1453, #1430, #3029, #3030, #3127, #3117, #3193.
|
|
12
12
|
*/
|
|
13
13
|
import { type InitDepositIo } from "./constants.js";
|
|
14
14
|
export declare const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
|
|
@@ -51,13 +51,82 @@ export interface MixedCoreAndAppClassification {
|
|
|
51
51
|
readonly app: string[];
|
|
52
52
|
/** True when both core and app are non-empty — the deposited guard fails. */
|
|
53
53
|
readonly wouldFail: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Pin/lock paths that were path-allowlisted but failed content-aware checks
|
|
56
|
+
* when co-travelling with core (#3193). Empty when path-only classification.
|
|
57
|
+
*/
|
|
58
|
+
readonly pinContentRejected?: string[];
|
|
59
|
+
}
|
|
60
|
+
/** Paths that path-allowlist for upgrade co-travel but need content checks with core (#3193). */
|
|
61
|
+
export declare const UPGRADE_PIN_CONTENT_PATHS: readonly ["package.json", "package-lock.json", "pnpm-lock.yaml", "yarn.lock"];
|
|
62
|
+
export type UpgradePinContentPath = (typeof UPGRADE_PIN_CONTENT_PATHS)[number];
|
|
63
|
+
/** package.json dependency map fields that may hold @deftai/directive* pins. */
|
|
64
|
+
export declare const PACKAGE_JSON_DEP_FIELDS: readonly ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
|
|
65
|
+
/**
|
|
66
|
+
* True when `name` is an `@deftai/directive*` dependency **key** (#3193).
|
|
67
|
+
* Substring hits in scripts/settings values do not qualify.
|
|
68
|
+
*/
|
|
69
|
+
export declare function isDirectiveDependencyKey(name: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* package.json co-travel is allowed only when the sole differences are
|
|
72
|
+
* `@deftai/directive*` dependency-key pins under the standard dep maps (#3193).
|
|
73
|
+
* Scripts/settings/metadata that merely contain the substring still fail.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isPackageJsonDirectivePinOnlyDiff(baseRaw: string, headRaw: string): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* package-lock.json follow-through: non-@deftai/directive* root direct dependency
|
|
78
|
+
* identities (and their `node_modules/<name>` package entries when present) must
|
|
79
|
+
* be unchanged. Directive pin identity + transitive/resolution churn may change.
|
|
80
|
+
*/
|
|
81
|
+
export declare function isPackageLockDirectivePinFollowThrough(baseRaw: string, headRaw: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Minimal pnpm-lock.yaml (v6/v9) root importer (`.`) direct-dep extractor.
|
|
84
|
+
* Avoids a YAML dependency; sufficient for pin follow-through identity checks.
|
|
85
|
+
*/
|
|
86
|
+
export declare function pnpmLockRootDirectDeps(raw: string): Record<string, string>;
|
|
87
|
+
/**
|
|
88
|
+
* Extract pnpm `packages:` section records keyed by the package name (not
|
|
89
|
+
* name@version). Used to freeze product package resolution blocks.
|
|
90
|
+
*/
|
|
91
|
+
export declare function pnpmPackagesByName(raw: string): Map<string, string>;
|
|
92
|
+
export declare function isPnpmLockDirectivePinFollowThrough(baseRaw: string, headRaw: string): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* yarn.lock (v1) package identity blocks: every non-@deftai/directive* package
|
|
95
|
+
* must keep identical block text (or fail if added/removed). Directive package
|
|
96
|
+
* blocks may change freely as pin follow-through (#3193).
|
|
97
|
+
*/
|
|
98
|
+
export declare function isYarnLockDirectivePinFollowThrough(baseRaw: string, headRaw: string): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Content-aware check for a single upgrade pin path (#3193).
|
|
101
|
+
* Returns true when the path may co-travel with `.deft/core/**`.
|
|
102
|
+
*/
|
|
103
|
+
export declare function isUpgradePinPathContentAllowed(path: string, baseRaw: string, headRaw: string): boolean;
|
|
104
|
+
export interface PinContentFilePair {
|
|
105
|
+
readonly base: string;
|
|
106
|
+
readonly head: string;
|
|
54
107
|
}
|
|
55
108
|
/**
|
|
56
109
|
* TS twin of Go `classifyChangedPaths` / deposited shell guard (#1430).
|
|
57
110
|
* Core = `.deft/core/**`; installer-managed = allowlist; app = everything else.
|
|
58
111
|
* Guard fails iff both core and app are non-empty.
|
|
112
|
+
*
|
|
113
|
+
* Path-only: does not inspect package/lock contents. Prefer
|
|
114
|
+
* {@link classifyMixedCoreAndAppContentAware} when base/head blobs are available
|
|
115
|
+
* (deposited guard + unit tests for #3193).
|
|
59
116
|
*/
|
|
60
117
|
export declare function classifyMixedCoreAndApp(changedPaths: readonly string[], matchers?: readonly InstallerManagedMatcher[]): MixedCoreAndAppClassification;
|
|
118
|
+
/**
|
|
119
|
+
* Content-aware upgrade co-travel classifier (#3193).
|
|
120
|
+
*
|
|
121
|
+
* Starts from path classification (#3127 allowlist), then when `.deft/core/**`
|
|
122
|
+
* is present reclassifies package.json / lockfile paths as **app** unless their
|
|
123
|
+
* base→head content is the Directive pin unit (or lock follow-through).
|
|
124
|
+
* `.deft/GENERATION.json` remains path-allowlisted with no content constraint.
|
|
125
|
+
*
|
|
126
|
+
* Missing content for a pin path co-travelling with core fails closed (treated
|
|
127
|
+
* as app) so partial fixtures cannot silently re-open the path-only hole.
|
|
128
|
+
*/
|
|
129
|
+
export declare function classifyMixedCoreAndAppContentAware(changedPaths: readonly string[], fileContents: Readonly<Partial<Record<string, PinContentFilePair>>>, matchers?: readonly InstallerManagedMatcher[]): MixedCoreAndAppClassification;
|
|
61
130
|
export interface FrameworkStagePathsOptions {
|
|
62
131
|
/**
|
|
63
132
|
* Include the vendored `.deft/core` payload in the stage set. Defaults to
|