@deftai/directive-core 0.96.0 → 0.97.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/cache/archive.js +10 -4
- package/dist/check/gate-lists.js +8 -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/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 +2 -1
- package/dist/hooks/dispatcher.js +63 -9
- 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/init-deposit.js +5 -0
- package/dist/init-deposit/refresh.js +3 -0
- 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/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/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/openclaw-soft-rebind-deposit.d.ts +46 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +165 -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/vbrief-activate/activate.js +22 -6
- package/dist/xbrief/styles.js +33 -17
- package/package.json +7 -3
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soft post-compact / post-amnesia AGENTS re-bind (#3171 / epic #2769 pass-3).
|
|
3
|
+
*
|
|
4
|
+
* Single in-repo source of truth for the soft checklist. File-host compact and
|
|
5
|
+
* SessionStart hooks, Codex best-effort SessionStart, and the OpenClaw durable
|
|
6
|
+
* skill adapter all derive from this module — no divergent host-only rule text.
|
|
7
|
+
*
|
|
8
|
+
* Soft complements hard Tier-1 compact re-arm (#2113). Soft never replaces,
|
|
9
|
+
* weakens, or short-circuits PreToolUse deny / mutation ritual.
|
|
10
|
+
*/
|
|
11
|
+
/** Epic + implement child anchors for docs and wire markers. */
|
|
12
|
+
export const SOFT_AGENTS_REBIND_EPIC = 2769;
|
|
13
|
+
export const SOFT_AGENTS_REBIND_ISSUE = 3171;
|
|
14
|
+
export const SOFT_AGENTS_REBIND_EVIDENCE_ISSUE = 3161;
|
|
15
|
+
/** Stable marker substring present in every host-facing soft deposit. */
|
|
16
|
+
export const SOFT_AGENTS_REBIND_MARKER = "Directive soft post-compact AGENTS re-bind (#3171 / #2769)";
|
|
17
|
+
/** Managed OpenClaw skill directory name (doctor/init deposit). */
|
|
18
|
+
export const OPENCLAW_SOFT_REBIND_SKILL_ID = "deft-directive-post-compact-rebind";
|
|
19
|
+
/** Sentinel inside managed OpenClaw skill bodies for rewrite detection. */
|
|
20
|
+
export const OPENCLAW_SOFT_REBIND_MANAGED_MARKER = "<!-- deft:managed soft-agents-rebind #3171 -->";
|
|
21
|
+
/**
|
|
22
|
+
* Pass-3 §2 checklist obligations — one ordered list, all hosts consume.
|
|
23
|
+
* Host adapters may wrap packaging; they must not invent alternate rule text.
|
|
24
|
+
*/
|
|
25
|
+
export const SOFT_AGENTS_REBIND_CHECKLIST = [
|
|
26
|
+
{
|
|
27
|
+
id: "reread-agents",
|
|
28
|
+
text: "Re-read managed AGENTS.md session routing (#2176). Do not treat the " +
|
|
29
|
+
"compaction summary or host runbook as SoT.",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "confirm-learned",
|
|
33
|
+
text: "Confirm-what-you-learned in the user-visible reply (brief): alignment " +
|
|
34
|
+
"posture; addressing-name when USER.md applies; product SoT is not the " +
|
|
35
|
+
"compaction/summary.",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "deposit-integrity",
|
|
39
|
+
text: "Deposit integrity: if .deft/core/main.md (or managed deposit) is missing " +
|
|
40
|
+
"or broken while deft may still be on PATH, fail closed to doctor / " +
|
|
41
|
+
"QUICK-START before product shell. (deft on PATH ≠ healthy deposit.)",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "summary-not-sot",
|
|
45
|
+
text: "Summary ≠ SoT: compaction/session runbooks (demo DBs, demo apps, ports, " +
|
|
46
|
+
'"start the frontend") are hypotheses. Re-verify against PROJECT-DEFINITION ' +
|
|
47
|
+
"/ USER.md / real local config before starting services or inventing demo " +
|
|
48
|
+
"stacks. (#3161 G)",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "operational-ask-trap",
|
|
52
|
+
text: 'Operational-ask trap: "open / start / run the app", "bring it up locally", ' +
|
|
53
|
+
'"open the browser" are still session-routed. They are not an escape hatch ' +
|
|
54
|
+
"from AGENTS.md, USER.md, or PROJECT-DEFINITION. (#3161 F)",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "mutation-vs-readonly",
|
|
58
|
+
text: "Mutation vs read-only: read-only / advisory / pure orientation → soft " +
|
|
59
|
+
"checklist only (do not force full cold session:start). Mutation intent " +
|
|
60
|
+
"(writes, implement, start-of-story) → soft checklist plus hard re-arm " +
|
|
61
|
+
"where the host supports it (session:ready / session:start --rearm or cold) " +
|
|
62
|
+
"before gated tools. Soft never authorizes skipping the mutation ritual for writes.",
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* Five-row host matrix (Cursor / Claude / Grok / Codex / OpenClaw).
|
|
67
|
+
* Docs and deposit tests must stay aligned with this table.
|
|
68
|
+
*/
|
|
69
|
+
export const SOFT_REBIND_HOST_MATRIX = [
|
|
70
|
+
{
|
|
71
|
+
host: "cursor",
|
|
72
|
+
family: "file-host hooks",
|
|
73
|
+
hardCompact: "deposited",
|
|
74
|
+
softRebind: "required",
|
|
75
|
+
wire: "session.compact (preCompact) + session.start additional_context",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
host: "claude",
|
|
79
|
+
family: "file-host hooks",
|
|
80
|
+
hardCompact: "deposited",
|
|
81
|
+
softRebind: "required",
|
|
82
|
+
wire: "PreCompact/PostCompact + SessionStart additionalContext",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
host: "grok",
|
|
86
|
+
family: "file-host hooks",
|
|
87
|
+
hardCompact: "deposited",
|
|
88
|
+
softRebind: "required",
|
|
89
|
+
wire: "PreCompact/PostCompact + SessionStart soft cue (#3161 dogfood host)",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
host: "codex",
|
|
93
|
+
family: "file-host (limited)",
|
|
94
|
+
hardCompact: "unsupported",
|
|
95
|
+
softRebind: "docs-best-effort",
|
|
96
|
+
wire: "SessionStart soft cue only — no native compact hook",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
host: "openclaw",
|
|
100
|
+
family: "Family-2 session host",
|
|
101
|
+
hardCompact: "unsupported",
|
|
102
|
+
softRebind: "required",
|
|
103
|
+
wire: "durable workspace skill via doctor/init; not file-host hooks alone",
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
/** Forbidden soft wording — soft must never invite skipping mutation ritual. */
|
|
107
|
+
export const SOFT_REBIND_FORBIDDEN_PHRASES = [
|
|
108
|
+
"skip session:start for writes",
|
|
109
|
+
"skip the mutation ritual",
|
|
110
|
+
"you may skip session:start",
|
|
111
|
+
"ritual not required for writes",
|
|
112
|
+
"soft replaces hard",
|
|
113
|
+
];
|
|
114
|
+
/**
|
|
115
|
+
* Format the shared soft checklist for host injection / skill bodies.
|
|
116
|
+
*/
|
|
117
|
+
export function formatSoftAgentsRebindChecklist(options = {}) {
|
|
118
|
+
const includeHeader = options.includeHeader !== false;
|
|
119
|
+
const lines = [];
|
|
120
|
+
if (includeHeader) {
|
|
121
|
+
lines.push(SOFT_AGENTS_REBIND_MARKER);
|
|
122
|
+
lines.push("After compaction, resume, or long-session amnesia: restore AGENTS rule " +
|
|
123
|
+
"memory before product action. Soft complements hard compact re-arm; " +
|
|
124
|
+
"it does not complete or replace the gated mutation ritual.");
|
|
125
|
+
lines.push("");
|
|
126
|
+
}
|
|
127
|
+
lines.push("Soft AGENTS re-bind checklist:");
|
|
128
|
+
SOFT_AGENTS_REBIND_CHECKLIST.forEach((item, index) => {
|
|
129
|
+
lines.push(`${index + 1}. ${item.text}`);
|
|
130
|
+
});
|
|
131
|
+
return lines.join("\n");
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Append soft checklist to a hard compact re-arm (or SessionStart) message.
|
|
135
|
+
* Hard text stays first so existing recoveries remain discoverable.
|
|
136
|
+
*/
|
|
137
|
+
export function appendSoftAgentsRebindToMessage(hardMessage) {
|
|
138
|
+
const hard = hardMessage.trim();
|
|
139
|
+
const soft = formatSoftAgentsRebindChecklist();
|
|
140
|
+
if (hard.length === 0)
|
|
141
|
+
return soft;
|
|
142
|
+
if (hard.includes(SOFT_AGENTS_REBIND_MARKER))
|
|
143
|
+
return hard;
|
|
144
|
+
return `${hard}\n\n${soft}`;
|
|
145
|
+
}
|
|
146
|
+
/** True when text carries the soft re-bind deposit (tests + doctor). */
|
|
147
|
+
export function isSoftAgentsRebindText(text) {
|
|
148
|
+
return text.includes(SOFT_AGENTS_REBIND_MARKER);
|
|
149
|
+
}
|
|
150
|
+
/** Soft cue events that should inject checklist without requiring a write tool. */
|
|
151
|
+
export function isSoftAgentsRebindEvent(event) {
|
|
152
|
+
return event === "session.start" || event === "session.compact";
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Whether this decision should carry soft injection on the host wire.
|
|
156
|
+
* Disabled / opt-out codes stay hard-message only.
|
|
157
|
+
*/
|
|
158
|
+
export function decisionCarriesSoftAgentsRebind(input) {
|
|
159
|
+
if (!isSoftAgentsRebindEvent(input.event))
|
|
160
|
+
return false;
|
|
161
|
+
if (input.code === "session-start-disabled")
|
|
162
|
+
return false;
|
|
163
|
+
if (input.code === "directive-disabled")
|
|
164
|
+
return false;
|
|
165
|
+
return (input.code === "session-start" ||
|
|
166
|
+
input.code === "session-start-degraded" ||
|
|
167
|
+
input.code === "session-compact-rearm" ||
|
|
168
|
+
input.code === "session-compact-rearm-degraded" ||
|
|
169
|
+
input.code === "session-compact-noop");
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Managed OpenClaw skill body generated from the same SoT checklist.
|
|
173
|
+
* Skill-shaped packaging; obligations identical to file-host soft text.
|
|
174
|
+
*/
|
|
175
|
+
export function formatOpenClawSoftRebindSkillMarkdown() {
|
|
176
|
+
const checklist = formatSoftAgentsRebindChecklist({ includeHeader: false });
|
|
177
|
+
return [
|
|
178
|
+
"---",
|
|
179
|
+
`name: ${OPENCLAW_SOFT_REBIND_SKILL_ID}`,
|
|
180
|
+
"description: >-",
|
|
181
|
+
" Soft post-compact / post-amnesia AGENTS re-bind for OpenClaw (#3171 / #2769).",
|
|
182
|
+
" Load after session start, gateway restart, or long-session amnesia.",
|
|
183
|
+
" Complements hard file-host compact re-arm; does not replace mutation ritual.",
|
|
184
|
+
"---",
|
|
185
|
+
OPENCLAW_SOFT_REBIND_MANAGED_MARKER,
|
|
186
|
+
"",
|
|
187
|
+
"# Soft post-compact AGENTS re-bind (OpenClaw)",
|
|
188
|
+
"",
|
|
189
|
+
SOFT_AGENTS_REBIND_MARKER,
|
|
190
|
+
"",
|
|
191
|
+
"OpenClaw is a **Family-2** session host. It does **not** claim file-host",
|
|
192
|
+
"`PreCompact` / `PreToolUse` hard compact re-arm alone. This skill is the",
|
|
193
|
+
"**required** durable soft surface: same checklist obligations as Cursor,",
|
|
194
|
+
"Claude Code, and Grok Build.",
|
|
195
|
+
"",
|
|
196
|
+
"## When to load",
|
|
197
|
+
"",
|
|
198
|
+
"- Session start / resume after amnesia boundary",
|
|
199
|
+
"- Gateway restart or new OpenClaw session context",
|
|
200
|
+
"- Long-running session where AGENTS.md session routing may have dropped",
|
|
201
|
+
"- Operational asks after summary resume (start/open app, demo stack)",
|
|
202
|
+
"",
|
|
203
|
+
"## Checklist (shared SoT — do not invent host-only rules)",
|
|
204
|
+
"",
|
|
205
|
+
checklist,
|
|
206
|
+
"",
|
|
207
|
+
"## Hard path honesty",
|
|
208
|
+
"",
|
|
209
|
+
"- Soft **never** authorizes skipping `session:start` / `session:ready` /",
|
|
210
|
+
" gated ritual for mutation (writes, implement, story start).",
|
|
211
|
+
"- Where OpenClaw lacks file-host compact PreToolUse, operators still run",
|
|
212
|
+
" the mutation ritual via skills / CLI before gated writes.",
|
|
213
|
+
"- Pins present ≠ session ritual completed.",
|
|
214
|
+
"",
|
|
215
|
+
"## Operator recovery",
|
|
216
|
+
"",
|
|
217
|
+
"- Deposit / refresh: `deft doctor --fix` (OpenClaw detected) or",
|
|
218
|
+
" `deft update` / `directive init` when OpenClaw signals are present.",
|
|
219
|
+
"- After deposit: restart the OpenClaw gateway or start a new session so",
|
|
220
|
+
" skills reload.",
|
|
221
|
+
"- Docs: `docs/openclaw-agent-host.md` § Soft post-compact AGENTS re-bind.",
|
|
222
|
+
"",
|
|
223
|
+
].join("\n");
|
|
224
|
+
}
|
|
225
|
+
/** True when skill body is the managed #3171 soft re-bind deposit. */
|
|
226
|
+
export function isManagedOpenClawSoftRebindSkill(body) {
|
|
227
|
+
return body.includes(OPENCLAW_SOFT_REBIND_MANAGED_MARKER);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Assert soft text never invites skipping mutation ritual (unit + deposit tests).
|
|
231
|
+
* Returns empty array when clean.
|
|
232
|
+
*/
|
|
233
|
+
export function softAgentsRebindForbiddenHits(text) {
|
|
234
|
+
const lower = text.toLowerCase();
|
|
235
|
+
return SOFT_REBIND_FORBIDDEN_PHRASES.filter((phrase) => lower.includes(phrase.toLowerCase()));
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=compact-ritual.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forward-coverage companion for new compact-ritual.ts (#3171 / #1310).
|
|
3
|
+
* Hard-path #2113 cases live in compact-ritual.test.ts (pre-existing).
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { formatSoftAgentsRebindChecklist, isSoftAgentsRebindText, SOFT_AGENTS_REBIND_CHECKLIST, SOFT_AGENTS_REBIND_MARKER, SOFT_REBIND_HOST_MATRIX, softAgentsRebindForbiddenHits, } from "./compact-ritual.js";
|
|
7
|
+
describe("compact-ritual soft SoT forward coverage (#3171)", () => {
|
|
8
|
+
it("exports a six-item shared checklist SoT", () => {
|
|
9
|
+
expect(SOFT_AGENTS_REBIND_CHECKLIST).toHaveLength(6);
|
|
10
|
+
const text = formatSoftAgentsRebindChecklist();
|
|
11
|
+
expect(isSoftAgentsRebindText(text)).toBe(true);
|
|
12
|
+
expect(text).toContain(SOFT_AGENTS_REBIND_MARKER);
|
|
13
|
+
expect(softAgentsRebindForbiddenHits(text)).toEqual([]);
|
|
14
|
+
});
|
|
15
|
+
it("lists five hosts with Codex gap and OpenClaw required soft", () => {
|
|
16
|
+
expect(SOFT_REBIND_HOST_MATRIX).toHaveLength(5);
|
|
17
|
+
expect(SOFT_REBIND_HOST_MATRIX.find((r) => r.host === "codex")?.softRebind).toBe("docs-best-effort");
|
|
18
|
+
expect(SOFT_REBIND_HOST_MATRIX.find((r) => r.host === "openclaw")?.softRebind).toBe("required");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=compact-ritual.spec.js.map
|
package/dist/session/index.d.ts
CHANGED
package/dist/session/index.js
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw durable soft AGENTS re-bind skill deposit (#3171 / #2769).
|
|
3
|
+
*
|
|
4
|
+
* Deposits a managed workspace skill generated from the shared checklist SoT
|
|
5
|
+
* in compact-ritual.ts. Fail-closed when OpenClaw is not detected (unless
|
|
6
|
+
* forceDeposit for tests). Real directory copy — not symlink-escape into npm.
|
|
7
|
+
*/
|
|
8
|
+
import { type OpenClawDetectResult } from "../doctor/openclaw-skills.js";
|
|
9
|
+
export interface OpenClawSoftRebindDepositResult {
|
|
10
|
+
readonly changed: boolean;
|
|
11
|
+
readonly writtenPaths: string[];
|
|
12
|
+
readonly preservedCustomPaths: string[];
|
|
13
|
+
readonly skillsDirs: string[];
|
|
14
|
+
readonly skipped: boolean;
|
|
15
|
+
readonly skipReason?: string;
|
|
16
|
+
readonly detect: OpenClawDetectResult | null;
|
|
17
|
+
readonly present: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface OpenClawSoftRebindDepositOptions {
|
|
20
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
21
|
+
readonly homeDir?: string;
|
|
22
|
+
readonly allAgents?: boolean;
|
|
23
|
+
readonly forceDeposit?: boolean;
|
|
24
|
+
readonly skillsDirs?: readonly string[];
|
|
25
|
+
readonly isDir?: (path: string) => boolean;
|
|
26
|
+
readonly readDir?: (path: string) => string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Assess whether the managed soft re-bind skill is present and current.
|
|
30
|
+
*
|
|
31
|
+
* Unmanaged/custom content at the required slug is NOT present (#3171 P1 /
|
|
32
|
+
* Greptile): the soft checklist obligations are the health criterion, not
|
|
33
|
+
* merely occupying the skill directory name.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assessOpenClawSoftRebindSkill(skillsDir: string, options?: {
|
|
36
|
+
readonly expectedBody?: string;
|
|
37
|
+
}): {
|
|
38
|
+
readonly present: boolean;
|
|
39
|
+
readonly custom: boolean;
|
|
40
|
+
readonly path: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Deposit the managed OpenClaw soft re-bind skill into workspace skills roots.
|
|
44
|
+
*/
|
|
45
|
+
export declare function depositOpenClawSoftRebindSkill(options?: OpenClawSoftRebindDepositOptions): OpenClawSoftRebindDepositResult;
|
|
46
|
+
//# sourceMappingURL=openclaw-soft-rebind-deposit.d.ts.map
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw durable soft AGENTS re-bind skill deposit (#3171 / #2769).
|
|
3
|
+
*
|
|
4
|
+
* Deposits a managed workspace skill generated from the shared checklist SoT
|
|
5
|
+
* in compact-ritual.ts. Fail-closed when OpenClaw is not detected (unless
|
|
6
|
+
* forceDeposit for tests). Real directory copy — not symlink-escape into npm.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { detectOpenClaw, isEscapingSkillSymlink, listInScopeSkillsDirs, } from "../doctor/openclaw-skills.js";
|
|
12
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
13
|
+
import { formatOpenClawSoftRebindSkillMarkdown, isManagedOpenClawSoftRebindSkill, OPENCLAW_SOFT_REBIND_SKILL_ID, } from "./compact-ritual.js";
|
|
14
|
+
function defaultIsDir(path) {
|
|
15
|
+
try {
|
|
16
|
+
return statSync(path).isDirectory();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function defaultLstatKind(path) {
|
|
23
|
+
try {
|
|
24
|
+
const st = lstatSync(path);
|
|
25
|
+
if (st.isSymbolicLink())
|
|
26
|
+
return "symlink";
|
|
27
|
+
if (st.isDirectory())
|
|
28
|
+
return "dir";
|
|
29
|
+
if (st.isFile())
|
|
30
|
+
return "file";
|
|
31
|
+
return "other";
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return "missing";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Assess whether the managed soft re-bind skill is present and current.
|
|
39
|
+
*
|
|
40
|
+
* Unmanaged/custom content at the required slug is NOT present (#3171 P1 /
|
|
41
|
+
* Greptile): the soft checklist obligations are the health criterion, not
|
|
42
|
+
* merely occupying the skill directory name.
|
|
43
|
+
*/
|
|
44
|
+
export function assessOpenClawSoftRebindSkill(skillsDir, options = {}) {
|
|
45
|
+
const path = join(skillsDir, OPENCLAW_SOFT_REBIND_SKILL_ID, "SKILL.md");
|
|
46
|
+
if (!existsSync(path)) {
|
|
47
|
+
return { present: false, custom: false, path };
|
|
48
|
+
}
|
|
49
|
+
let body;
|
|
50
|
+
try {
|
|
51
|
+
body = readFileSync(path, "utf8");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { present: false, custom: false, path };
|
|
55
|
+
}
|
|
56
|
+
if (!isManagedOpenClawSoftRebindSkill(body)) {
|
|
57
|
+
// Occupying the slug with unrelated content fails health (#3171 Greptile P1).
|
|
58
|
+
return { present: false, custom: true, path };
|
|
59
|
+
}
|
|
60
|
+
const expected = options.expectedBody ?? formatOpenClawSoftRebindSkillMarkdown();
|
|
61
|
+
return { present: body === expected, custom: false, path };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Deposit the managed OpenClaw soft re-bind skill into workspace skills roots.
|
|
65
|
+
*/
|
|
66
|
+
export function depositOpenClawSoftRebindSkill(options = {}) {
|
|
67
|
+
const env = options.env ?? process.env;
|
|
68
|
+
const home = options.homeDir ?? homedir();
|
|
69
|
+
const isDir = options.isDir ?? defaultIsDir;
|
|
70
|
+
const detect = detectOpenClaw(env, { homeDir: home, isDir });
|
|
71
|
+
if (!detect.detected && !options.forceDeposit) {
|
|
72
|
+
return {
|
|
73
|
+
changed: false,
|
|
74
|
+
writtenPaths: [],
|
|
75
|
+
preservedCustomPaths: [],
|
|
76
|
+
skillsDirs: [],
|
|
77
|
+
skipped: true,
|
|
78
|
+
skipReason: "openclaw-not-detected",
|
|
79
|
+
detect,
|
|
80
|
+
present: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const skillsDirs = options.skillsDirs !== undefined
|
|
84
|
+
? [...options.skillsDirs]
|
|
85
|
+
: listInScopeSkillsDirs(detect.stateDir, options.allAgents === true, {
|
|
86
|
+
isDir,
|
|
87
|
+
readDir: options.readDir,
|
|
88
|
+
});
|
|
89
|
+
const expected = formatOpenClawSoftRebindSkillMarkdown();
|
|
90
|
+
const writtenPaths = [];
|
|
91
|
+
const preservedCustomPaths = [];
|
|
92
|
+
let changed = false;
|
|
93
|
+
let presentCount = 0;
|
|
94
|
+
for (const skillsDir of skillsDirs) {
|
|
95
|
+
const skillDir = join(skillsDir, OPENCLAW_SOFT_REBIND_SKILL_ID);
|
|
96
|
+
const skillFile = join(skillDir, "SKILL.md");
|
|
97
|
+
const kind = defaultLstatKind(skillDir);
|
|
98
|
+
if (kind === "symlink" && isEscapingSkillSymlink(skillsDir, skillDir)) {
|
|
99
|
+
// Replace unusable escaping symlink with a real copy.
|
|
100
|
+
try {
|
|
101
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// best-effort
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (existsSync(skillFile)) {
|
|
108
|
+
let existing = "";
|
|
109
|
+
try {
|
|
110
|
+
existing = readFileSync(skillFile, "utf8");
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
existing = "";
|
|
114
|
+
}
|
|
115
|
+
// Required soft re-bind surface: unmanaged content at this slug is a gap,
|
|
116
|
+
// not a preserve-as-present path (Greptile P1 on #3171). Overwrite with
|
|
117
|
+
// the managed checklist so doctor/init/update cannot report success while
|
|
118
|
+
// the operational-ask obligations are absent.
|
|
119
|
+
if (existing === expected) {
|
|
120
|
+
presentCount += 1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
mkdirSync(skillDir, { recursive: true });
|
|
125
|
+
const tmpName = `SKILL.md.deft-${process.pid}-${Date.now().toString(36)}.tmp`;
|
|
126
|
+
const staging = join(skillDir, tmpName);
|
|
127
|
+
try {
|
|
128
|
+
// Contained write under skills root (#2951) — refuses symlink escape.
|
|
129
|
+
containedWrite({
|
|
130
|
+
root: skillsDir,
|
|
131
|
+
target: staging,
|
|
132
|
+
data: expected,
|
|
133
|
+
mode: "replace",
|
|
134
|
+
});
|
|
135
|
+
renameSync(staging, skillFile);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
try {
|
|
139
|
+
rmSync(staging, { force: true });
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
/* best-effort */
|
|
143
|
+
}
|
|
144
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
145
|
+
if (/contained write refused|symlink|escape/i.test(msg)) {
|
|
146
|
+
preservedCustomPaths.push(skillFile);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
writtenPaths.push(skillFile);
|
|
152
|
+
changed = true;
|
|
153
|
+
presentCount += 1;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
changed,
|
|
157
|
+
writtenPaths,
|
|
158
|
+
preservedCustomPaths,
|
|
159
|
+
skillsDirs,
|
|
160
|
+
skipped: false,
|
|
161
|
+
detect,
|
|
162
|
+
present: presentCount === skillsDirs.length && skillsDirs.length > 0,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=openclaw-soft-rebind-deposit.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* verify:test-boundary evaluation (#3145).
|
|
3
|
+
*
|
|
4
|
+
* Rejects recognized test artifacts under production roots and production
|
|
5
|
+
* references to test/fixture roots unless allowlisted or classified as
|
|
6
|
+
* production-liveness. Three-state exit: 0 clean / 1 violation / 2 config.
|
|
7
|
+
*/
|
|
8
|
+
import { type TestBoundaryPolicy } from "./policy.js";
|
|
9
|
+
export type TestBoundaryViolationKind = "test-under-source-root" | "production-references-test-root";
|
|
10
|
+
export interface TestBoundaryFinding {
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly kind: TestBoundaryViolationKind;
|
|
13
|
+
readonly detail: string;
|
|
14
|
+
readonly remediation: string;
|
|
15
|
+
}
|
|
16
|
+
export interface TestBoundaryResult {
|
|
17
|
+
readonly exitCode: 0 | 1 | 2;
|
|
18
|
+
readonly findings: readonly TestBoundaryFinding[];
|
|
19
|
+
readonly message: string;
|
|
20
|
+
readonly policy: TestBoundaryPolicy | null;
|
|
21
|
+
}
|
|
22
|
+
export interface TestBoundaryOptions {
|
|
23
|
+
readonly policyPath?: string | null;
|
|
24
|
+
/** Inject policy for pure unit tests (skips disk load). */
|
|
25
|
+
readonly policy?: TestBoundaryPolicy;
|
|
26
|
+
/** Inject file list for pure unit tests. */
|
|
27
|
+
readonly files?: readonly string[];
|
|
28
|
+
/** Inject file contents for reference scanning (posix path -> text). */
|
|
29
|
+
readonly fileContents?: ReadonlyMap<string, string>;
|
|
30
|
+
/** Force enforcement mode override. */
|
|
31
|
+
readonly enforce?: boolean;
|
|
32
|
+
readonly quiet?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Glob match with double-star as zero-or-more path segments.
|
|
36
|
+
* Plain fnmatchCase treats double-star as two greedy stars and fails nested
|
|
37
|
+
* colocated paths (e.g. packages/cli/src/foo/bar.test.ts vs packages star globs).
|
|
38
|
+
*/
|
|
39
|
+
export declare function matchPolicyGlob(relPath: string, pattern: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Match repo-relative path against a policy root glob.
|
|
42
|
+
* Supports foo/**-style prefixes, packages star-src globs, and basename patterns.
|
|
43
|
+
*/
|
|
44
|
+
export declare function matchesRootGlob(relPath: string, rootGlob: string): boolean;
|
|
45
|
+
/** True when basename/path matches a test-file pattern (fnmatch / ** globs). */
|
|
46
|
+
export declare function matchesTestFilePattern(relPath: string, patterns: readonly string[]): boolean;
|
|
47
|
+
/** Recognise conventional test basenames without full glob (fast path). */
|
|
48
|
+
export declare function isRecognizedTestBasename(relPath: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Evaluate test/source boundary for a project.
|
|
51
|
+
* Pure when `policy` + `files` (+ optional `fileContents`) are injected.
|
|
52
|
+
*/
|
|
53
|
+
export declare function evaluateTestBoundary(projectRoot: string, options?: TestBoundaryOptions): TestBoundaryResult;
|
|
54
|
+
//# sourceMappingURL=evaluate.d.ts.map
|