@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.
Files changed (114) hide show
  1. package/dist/authz/classify.js +291 -0
  2. package/dist/cache/archive.js +10 -4
  3. package/dist/check/cached-orchestrator.d.ts +5 -0
  4. package/dist/check/cached-orchestrator.js +18 -1
  5. package/dist/check/gate-lists.d.ts +20 -0
  6. package/dist/check/gate-lists.js +54 -9
  7. package/dist/check/index.d.ts +1 -1
  8. package/dist/check/index.js +1 -1
  9. package/dist/check/orchestrator.d.ts +4 -0
  10. package/dist/check/orchestrator.js +4 -0
  11. package/dist/consumer-check-contract/evaluate.d.ts +124 -0
  12. package/dist/consumer-check-contract/evaluate.js +699 -0
  13. package/dist/consumer-check-contract/index.d.ts +5 -0
  14. package/dist/consumer-check-contract/index.js +5 -0
  15. package/dist/delivery-attempt/disk-begin.d.ts +51 -0
  16. package/dist/delivery-attempt/disk-begin.js +68 -0
  17. package/dist/delivery-attempt/evaluate.d.ts +26 -0
  18. package/dist/delivery-attempt/evaluate.js +443 -0
  19. package/dist/delivery-attempt/fingerprint.d.ts +32 -0
  20. package/dist/delivery-attempt/fingerprint.js +100 -0
  21. package/dist/delivery-attempt/handoff.d.ts +25 -0
  22. package/dist/delivery-attempt/handoff.js +102 -0
  23. package/dist/delivery-attempt/index.d.ts +17 -0
  24. package/dist/delivery-attempt/index.js +17 -0
  25. package/dist/delivery-attempt/ledger.d.ts +169 -0
  26. package/dist/delivery-attempt/ledger.js +758 -0
  27. package/dist/delivery-attempt/material-delta.d.ts +38 -0
  28. package/dist/delivery-attempt/material-delta.js +126 -0
  29. package/dist/delivery-attempt/types.d.ts +210 -0
  30. package/dist/delivery-attempt/types.js +77 -0
  31. package/dist/doctor/checks.d.ts +7 -0
  32. package/dist/doctor/checks.js +83 -0
  33. package/dist/doctor/index.d.ts +1 -0
  34. package/dist/doctor/index.js +1 -0
  35. package/dist/doctor/main.js +12 -0
  36. package/dist/doctor/openclaw-soft-rebind.d.ts +26 -0
  37. package/dist/doctor/openclaw-soft-rebind.js +164 -0
  38. package/dist/hooks/dispatcher.d.ts +7 -1
  39. package/dist/hooks/dispatcher.js +117 -13
  40. package/dist/index.d.ts +4 -0
  41. package/dist/index.js +4 -0
  42. package/dist/init-deposit/gitignore.js +7 -0
  43. package/dist/init-deposit/hygiene.d.ts +70 -1
  44. package/dist/init-deposit/hygiene.js +582 -8
  45. package/dist/init-deposit/init-deposit.js +5 -0
  46. package/dist/init-deposit/refresh.js +3 -0
  47. package/dist/init-deposit/scaffold.js +277 -4
  48. package/dist/init-deposit/skill-discovery-deposit.js +15 -0
  49. package/dist/policy/check-resume.d.ts +72 -0
  50. package/dist/policy/check-resume.js +253 -0
  51. package/dist/policy/coverage-check-resume-presets.d.ts +46 -0
  52. package/dist/policy/coverage-check-resume-presets.js +228 -0
  53. package/dist/policy/coverage-debt.d.ts +76 -0
  54. package/dist/policy/coverage-debt.js +262 -0
  55. package/dist/policy/index.d.ts +3 -0
  56. package/dist/policy/index.js +50 -21
  57. package/dist/pr-merge-readiness/ci-gate.d.ts +29 -1
  58. package/dist/pr-merge-readiness/ci-gate.js +191 -24
  59. package/dist/pr-merge-readiness/compute.js +10 -1
  60. package/dist/pr-merge-readiness/index.d.ts +2 -1
  61. package/dist/pr-merge-readiness/index.js +2 -1
  62. package/dist/pr-merge-readiness/output.js +14 -0
  63. package/dist/pr-merge-readiness/platform-status.d.ts +29 -0
  64. package/dist/pr-merge-readiness/platform-status.js +49 -0
  65. package/dist/pr-watch/constants.d.ts +10 -0
  66. package/dist/pr-watch/constants.js +12 -1
  67. package/dist/pr-watch/main.js +16 -1
  68. package/dist/pr-watch/probe.js +13 -1
  69. package/dist/pr-watch/types.d.ts +3 -2
  70. package/dist/pr-watch/watch.js +14 -7
  71. package/dist/release/auto-hatch.d.ts +114 -0
  72. package/dist/release/auto-hatch.js +301 -0
  73. package/dist/release/coverage-debt-ledger.d.ts +22 -0
  74. package/dist/release/coverage-debt-ledger.js +157 -0
  75. package/dist/release/index.d.ts +3 -0
  76. package/dist/release/index.js +3 -0
  77. package/dist/release/pipeline.js +164 -12
  78. package/dist/release/suite-stamp.d.ts +44 -0
  79. package/dist/release/suite-stamp.js +133 -0
  80. package/dist/release/types.d.ts +19 -0
  81. package/dist/scope-provenance/digest.d.ts +67 -0
  82. package/dist/scope-provenance/digest.js +188 -0
  83. package/dist/scope-provenance/evaluate.d.ts +82 -0
  84. package/dist/scope-provenance/evaluate.js +528 -0
  85. package/dist/scope-provenance/index.d.ts +6 -0
  86. package/dist/scope-provenance/index.js +6 -0
  87. package/dist/session/compact-ritual.d.ts +96 -0
  88. package/dist/session/compact-ritual.js +237 -0
  89. package/dist/session/compact-ritual.spec.d.ts +2 -0
  90. package/dist/session/compact-ritual.spec.js +21 -0
  91. package/dist/session/coverage-check-resume-nudge.d.ts +34 -0
  92. package/dist/session/coverage-check-resume-nudge.js +66 -0
  93. package/dist/session/index.d.ts +3 -0
  94. package/dist/session/index.js +3 -0
  95. package/dist/session/openclaw-soft-rebind-deposit.d.ts +46 -0
  96. package/dist/session/openclaw-soft-rebind-deposit.js +165 -0
  97. package/dist/session/session-start.js +21 -0
  98. package/dist/test-boundary/evaluate.d.ts +54 -0
  99. package/dist/test-boundary/evaluate.js +368 -0
  100. package/dist/test-boundary/index.d.ts +6 -0
  101. package/dist/test-boundary/index.js +6 -0
  102. package/dist/test-boundary/policy.d.ts +52 -0
  103. package/dist/test-boundary/policy.js +182 -0
  104. package/dist/triage/bootstrap/gitignore.d.ts +1 -1
  105. package/dist/triage/bootstrap/gitignore.js +15 -1
  106. package/dist/triage/classify/label-mirror.d.ts +31 -1
  107. package/dist/triage/classify/label-mirror.js +78 -6
  108. package/dist/triage/help/registry-data.d.ts +6 -6
  109. package/dist/triage/help/registry-data.js +12 -3
  110. package/dist/vbrief-activate/activate.js +22 -6
  111. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  112. package/dist/vbrief-validate/plan-hooks.js +54 -0
  113. package/dist/xbrief/styles.js +33 -17
  114. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=compact-ritual.spec.d.ts.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
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Session-start skippable nudge for coverageDebt + checkResume (#3189).
3
+ *
4
+ * Why / what + Strict / Hatch-aware / Later (+ Discuss / Back for #1470).
5
+ * Later does not mark decided; re-nag next interactive mutation ritual.
6
+ * Headless / CI / non-TTY fail-open (no block).
7
+ */
8
+ import { type HeadlessDetectionOptions } from "../product-signal/headless.js";
9
+ /** Why block -- must appear in the nudge. */
10
+ export declare const COVERAGE_CHECK_RESUME_NUDGE_WHY: string;
11
+ /** What block -- bundled presets, not five micro-toggles. */
12
+ export declare const COVERAGE_CHECK_RESUME_NUDGE_WHAT: string;
13
+ export declare const COVERAGE_CHECK_RESUME_NUDGE_BODY: string;
14
+ export interface CoverageCheckResumeNudgeEligibilityOptions extends HeadlessDetectionOptions {
15
+ readonly projectRoot: string;
16
+ }
17
+ /**
18
+ * True when an interactive mutation session-start ritual should surface the
19
+ * coverage/check-resume decision nudge (#3189).
20
+ */
21
+ export declare function isCoverageCheckResumeNudgeEligible(options: CoverageCheckResumeNudgeEligibilityOptions): boolean;
22
+ /** Format the full operator-facing nudge. */
23
+ export declare function formatCoverageCheckResumeNudge(): string;
24
+ /**
25
+ * Emit the nudge when eligible; headless / decided callers get an empty string.
26
+ * Never blocks -- session-start always continues.
27
+ *
28
+ * Design note (#3189): session-start is non-interactive for agent hosts (same
29
+ * class as product-signal D17). Choice dispatch is via CLI verbs named in the
30
+ * nudge body (`policy:coverage-check-resume-preset|later|dismiss`), not a
31
+ * blocking TTY menu inside session:start.
32
+ */
33
+ export declare function maybeFormatCoverageCheckResumeNudge(options: CoverageCheckResumeNudgeEligibilityOptions): string;
34
+ //# sourceMappingURL=coverage-check-resume-nudge.d.ts.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Session-start skippable nudge for coverageDebt + checkResume (#3189).
3
+ *
4
+ * Why / what + Strict / Hatch-aware / Later (+ Discuss / Back for #1470).
5
+ * Later does not mark decided; re-nag next interactive mutation ritual.
6
+ * Headless / CI / non-TTY fail-open (no block).
7
+ */
8
+ import { isCoverageCheckResumeUndecided } from "../policy/coverage-check-resume-presets.js";
9
+ import { policyColonInvocation } from "../policy/policy-invocation.js";
10
+ import { isHeadlessSession } from "../product-signal/headless.js";
11
+ /** Why block -- must appear in the nudge. */
12
+ export const COVERAGE_CHECK_RESUME_NUDGE_WHY = "Why: Long checks often fail late on small gates, or barely miss coverage. " +
13
+ "The project can fail closed, warn, or hatch with a tracked debt issue on THIS repo. " +
14
+ "Local machines may resume a suite that already passed at the same commit; " +
15
+ "CI must not trust a laptop stamp.";
16
+ /** What block -- bundled presets, not five micro-toggles. */
17
+ export const COVERAGE_CHECK_RESUME_NUDGE_WHAT = "What we need: one bundled project decision (not USER.md personal prefs; not npm publish; " +
18
+ "not turning off required CI):\n" +
19
+ " * Strict (recommended for most apps) -- coverageDebt.mode=off, checkResume.localStamp=off\n" +
20
+ " * Hatch-aware -- coverageDebt.mode=hatch (autoFile=false by default), localStamp=on for DX\n" +
21
+ " * Later -- skip this session; does NOT set status=decided; nag again next ritual\n" +
22
+ " * Discuss -- talk through the trade-offs\n" +
23
+ " * Back -- leave this prompt without choosing\n" +
24
+ "Apply path (writes PROJECT-DEFINITION):\n" +
25
+ " Strict -> `" +
26
+ policyColonInvocation("coverage-check-resume-preset", " -- --preset strict") +
27
+ "`\n" +
28
+ " Hatch-aware -> `" +
29
+ policyColonInvocation("coverage-check-resume-preset", " -- --preset hatch-aware") +
30
+ "`\n" +
31
+ " Later -> `" +
32
+ policyColonInvocation("coverage-check-resume-later") +
33
+ "` (no PD write)\n" +
34
+ " Dismiss-with-reason -> `" +
35
+ policyColonInvocation("coverage-check-resume-dismiss", ' -- --reason "…"') +
36
+ "`\n" +
37
+ "Stop nag only after Strict / Hatch-aware (status=decided) or dismiss-with-reason " +
38
+ `(visible via \`${policyColonInvocation("show", " --field=coverageDebt")}\` / doctor).`;
39
+ export const COVERAGE_CHECK_RESUME_NUDGE_BODY = `${COVERAGE_CHECK_RESUME_NUDGE_WHY}\n\n${COVERAGE_CHECK_RESUME_NUDGE_WHAT}`;
40
+ /**
41
+ * True when an interactive mutation session-start ritual should surface the
42
+ * coverage/check-resume decision nudge (#3189).
43
+ */
44
+ export function isCoverageCheckResumeNudgeEligible(options) {
45
+ if (isHeadlessSession(options)) {
46
+ return false;
47
+ }
48
+ return isCoverageCheckResumeUndecided(options.projectRoot);
49
+ }
50
+ /** Format the full operator-facing nudge. */
51
+ export function formatCoverageCheckResumeNudge() {
52
+ return `[deft policy] coverageDebt + checkResume undecided:\n${COVERAGE_CHECK_RESUME_NUDGE_BODY}\n`;
53
+ }
54
+ /**
55
+ * Emit the nudge when eligible; headless / decided callers get an empty string.
56
+ * Never blocks -- session-start always continues.
57
+ *
58
+ * Design note (#3189): session-start is non-interactive for agent hosts (same
59
+ * class as product-signal D17). Choice dispatch is via CLI verbs named in the
60
+ * nudge body (`policy:coverage-check-resume-preset|later|dismiss`), not a
61
+ * blocking TTY menu inside session:start.
62
+ */
63
+ export function maybeFormatCoverageCheckResumeNudge(options) {
64
+ return isCoverageCheckResumeNudgeEligible(options) ? formatCoverageCheckResumeNudge() : "";
65
+ }
66
+ //# sourceMappingURL=coverage-check-resume-nudge.js.map
@@ -1,5 +1,8 @@
1
+ export * from "./compact-ritual.js";
2
+ export * from "./coverage-check-resume-nudge.js";
1
3
  export * from "./git.js";
2
4
  export * from "./json.js";
5
+ export * from "./openclaw-soft-rebind-deposit.js";
3
6
  export * from "./posture.js";
4
7
  export * from "./process-cost.js";
5
8
  export * from "./resume-conditions.js";
@@ -1,5 +1,8 @@
1
+ export * from "./compact-ritual.js";
2
+ export * from "./coverage-check-resume-nudge.js";
1
3
  export * from "./git.js";
2
4
  export * from "./json.js";
5
+ export * from "./openclaw-soft-rebind-deposit.js";
3
6
  export * from "./posture.js";
4
7
  export * from "./process-cost.js";
5
8
  export * from "./resume-conditions.js";
@@ -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
@@ -18,6 +18,7 @@ import { runDefaultMode } from "../triage/welcome/default-mode.js";
18
18
  import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
19
19
  import { emitSessionValueReadback } from "../value/readback.js";
20
20
  import { verifyRequiredTools } from "../verify-env/verify-tools.js";
21
+ import { maybeFormatCoverageCheckResumeNudge } from "./coverage-check-resume-nudge.js";
21
22
  import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
22
23
  import { emitSessionStartProcessCost } from "./process-cost.js";
23
24
  import { probeSessionReleaseAvailability, } from "./release-availability.js";
@@ -433,6 +434,16 @@ function runSessionRearm(projectRoot, options, instant, environment) {
433
434
  if (humanMergeLine !== null) {
434
435
  lines.push(humanMergeLine);
435
436
  }
437
+ // #3189: re-arm still surfaces undecided coverage/check-resume once per ritual.
438
+ try {
439
+ const coverageNudge = maybeFormatCoverageCheckResumeNudge({ projectRoot });
440
+ if (coverageNudge.length > 0) {
441
+ lines.push(coverageNudge.trimEnd());
442
+ }
443
+ }
444
+ catch {
445
+ // best-effort — re-arm must not abort
446
+ }
436
447
  const priorQuick = eligibility.state.quickSteps;
437
448
  const priorTriage = priorQuick.triage_welcome ?? ritualStep({ ok: true, ts: instant });
438
449
  const policyOk = policyResult.error === null || policyResult.source === "default-fail-closed";
@@ -863,6 +874,16 @@ export function runSessionStart(projectRoot, options = {}) {
863
874
  if (consentPrompt.length > 0) {
864
875
  lines.push(consentPrompt.trimEnd());
865
876
  }
877
+ // #3189: skippable coverageDebt/checkResume project-decision nudge (fail-open; never blocks).
878
+ try {
879
+ const coverageNudge = maybeFormatCoverageCheckResumeNudge({ projectRoot });
880
+ if (coverageNudge.length > 0) {
881
+ lines.push(coverageNudge.trimEnd());
882
+ }
883
+ }
884
+ catch {
885
+ // best-effort operator advisory — session start must not abort
886
+ }
866
887
  const writeStarted = performance.now();
867
888
  const coldSessionId = (options.newSessionId ?? randomUUID)();
868
889
  const payload = newRitualStatePayload({