@deftai/directive-core 0.95.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.
Files changed (116) hide show
  1. package/dist/cache/archive.d.ts +134 -0
  2. package/dist/cache/archive.js +636 -0
  3. package/dist/cache/index.d.ts +1 -0
  4. package/dist/cache/index.js +1 -0
  5. package/dist/cache/main.js +298 -1
  6. package/dist/check/gate-lists.js +8 -0
  7. package/dist/consumer-check-contract/evaluate.d.ts +124 -0
  8. package/dist/consumer-check-contract/evaluate.js +699 -0
  9. package/dist/consumer-check-contract/index.d.ts +5 -0
  10. package/dist/consumer-check-contract/index.js +5 -0
  11. package/dist/content-contracts/skills/helpers.d.ts +1 -1
  12. package/dist/content-contracts/skills/helpers.js +1 -0
  13. package/dist/delivery-attempt/disk-begin.d.ts +51 -0
  14. package/dist/delivery-attempt/disk-begin.js +68 -0
  15. package/dist/delivery-attempt/evaluate.d.ts +26 -0
  16. package/dist/delivery-attempt/evaluate.js +443 -0
  17. package/dist/delivery-attempt/fingerprint.d.ts +32 -0
  18. package/dist/delivery-attempt/fingerprint.js +100 -0
  19. package/dist/delivery-attempt/handoff.d.ts +25 -0
  20. package/dist/delivery-attempt/handoff.js +102 -0
  21. package/dist/delivery-attempt/index.d.ts +17 -0
  22. package/dist/delivery-attempt/index.js +17 -0
  23. package/dist/delivery-attempt/ledger.d.ts +169 -0
  24. package/dist/delivery-attempt/ledger.js +758 -0
  25. package/dist/delivery-attempt/material-delta.d.ts +38 -0
  26. package/dist/delivery-attempt/material-delta.js +126 -0
  27. package/dist/delivery-attempt/types.d.ts +210 -0
  28. package/dist/delivery-attempt/types.js +77 -0
  29. package/dist/doctor/index.d.ts +1 -0
  30. package/dist/doctor/index.js +1 -0
  31. package/dist/doctor/main.js +12 -0
  32. package/dist/doctor/openclaw-soft-rebind.d.ts +26 -0
  33. package/dist/doctor/openclaw-soft-rebind.js +164 -0
  34. package/dist/hooks/dispatcher.d.ts +2 -1
  35. package/dist/hooks/dispatcher.js +63 -9
  36. package/dist/index.d.ts +5 -0
  37. package/dist/index.js +5 -0
  38. package/dist/init-deposit/gitignore.js +7 -0
  39. package/dist/init-deposit/hygiene.d.ts +1 -1
  40. package/dist/init-deposit/hygiene.js +16 -4
  41. package/dist/init-deposit/init-deposit.js +5 -0
  42. package/dist/init-deposit/refresh.js +3 -0
  43. package/dist/init-deposit/scaffold.js +6 -5
  44. package/dist/parent-turn-shape/evaluate.d.ts +84 -0
  45. package/dist/parent-turn-shape/evaluate.js +353 -0
  46. package/dist/parent-turn-shape/index.d.ts +8 -0
  47. package/dist/parent-turn-shape/index.js +8 -0
  48. package/dist/pr-merge-readiness/ci-gate.d.ts +29 -1
  49. package/dist/pr-merge-readiness/ci-gate.js +191 -24
  50. package/dist/pr-merge-readiness/compute.js +10 -1
  51. package/dist/pr-merge-readiness/index.d.ts +2 -1
  52. package/dist/pr-merge-readiness/index.js +2 -1
  53. package/dist/pr-merge-readiness/output.js +14 -0
  54. package/dist/pr-merge-readiness/platform-status.d.ts +29 -0
  55. package/dist/pr-merge-readiness/platform-status.js +49 -0
  56. package/dist/pr-watch/constants.d.ts +10 -0
  57. package/dist/pr-watch/constants.js +12 -1
  58. package/dist/pr-watch/main.js +16 -1
  59. package/dist/pr-watch/probe.js +13 -1
  60. package/dist/pr-watch/types.d.ts +3 -2
  61. package/dist/pr-watch/watch.js +14 -7
  62. package/dist/review-monitor/constants.js +3 -2
  63. package/dist/review-monitor/tier-detection.d.ts +7 -3
  64. package/dist/review-monitor/tier-detection.js +18 -1
  65. package/dist/review-monitor/verify.js +18 -0
  66. package/dist/scope/index.d.ts +2 -0
  67. package/dist/scope/index.js +2 -0
  68. package/dist/scope/main.d.ts +10 -0
  69. package/dist/scope/main.js +109 -24
  70. package/dist/scope/promote-from-issue.d.ts +49 -0
  71. package/dist/scope/promote-from-issue.js +367 -0
  72. package/dist/scope/promote-path.d.ts +39 -0
  73. package/dist/scope/promote-path.js +105 -0
  74. package/dist/scope-provenance/digest.d.ts +67 -0
  75. package/dist/scope-provenance/digest.js +188 -0
  76. package/dist/scope-provenance/evaluate.d.ts +82 -0
  77. package/dist/scope-provenance/evaluate.js +528 -0
  78. package/dist/scope-provenance/index.d.ts +6 -0
  79. package/dist/scope-provenance/index.js +6 -0
  80. package/dist/session/compact-ritual.d.ts +96 -0
  81. package/dist/session/compact-ritual.js +237 -0
  82. package/dist/session/compact-ritual.spec.d.ts +2 -0
  83. package/dist/session/compact-ritual.spec.js +21 -0
  84. package/dist/session/index.d.ts +2 -0
  85. package/dist/session/index.js +2 -0
  86. package/dist/session/openclaw-soft-rebind-deposit.d.ts +46 -0
  87. package/dist/session/openclaw-soft-rebind-deposit.js +165 -0
  88. package/dist/swarm/routing.d.ts +4 -2
  89. package/dist/swarm/routing.js +26 -4
  90. package/dist/test-boundary/evaluate.d.ts +54 -0
  91. package/dist/test-boundary/evaluate.js +368 -0
  92. package/dist/test-boundary/index.d.ts +6 -0
  93. package/dist/test-boundary/index.js +6 -0
  94. package/dist/test-boundary/policy.d.ts +52 -0
  95. package/dist/test-boundary/policy.js +182 -0
  96. package/dist/triage/actions/index.js +62 -2
  97. package/dist/triage/actions/types.d.ts +8 -1
  98. package/dist/triage/author-filter.d.ts +51 -0
  99. package/dist/triage/author-filter.js +152 -0
  100. package/dist/triage/bootstrap/gitignore.d.ts +1 -1
  101. package/dist/triage/bootstrap/gitignore.js +15 -1
  102. package/dist/triage/classify/index.d.ts +2 -2
  103. package/dist/triage/classify/index.js +2 -2
  104. package/dist/triage/classify/label-mirror.d.ts +68 -5
  105. package/dist/triage/classify/label-mirror.js +261 -31
  106. package/dist/triage/help/registry-data.d.ts +49 -38
  107. package/dist/triage/help/registry-data.js +115 -40
  108. package/dist/triage/index.d.ts +1 -0
  109. package/dist/triage/index.js +1 -0
  110. package/dist/triage/queue/index.d.ts +1 -0
  111. package/dist/triage/queue/index.js +1 -0
  112. package/dist/triage/queue/render.d.ts +2 -0
  113. package/dist/triage/queue/render.js +6 -0
  114. package/dist/vbrief-activate/activate.js +22 -6
  115. package/dist/xbrief/styles.js +33 -17
  116. package/package.json +11 -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
@@ -140,7 +140,8 @@ export declare function decideHook(input: HookDispatchInput, seams?: HookPolicyS
140
140
  * Cursor deposits use `failClosed: true`. Cursor treats empty/null stdout as a
141
141
  * hook failure and blocks the tool — so Cursor allows must emit explicit
142
142
  * `{"permission":"allow"}`. Other hosts keep empty allow so the host permission
143
- * flow is unchanged.
143
+ * flow is unchanged — except session.start / session.compact soft re-bind
144
+ * injection (#3171), which surfaces checklist text without a write tool.
144
145
  *
145
146
  * Cursor stdout always includes `code` (stable machine-readable decision code)
146
147
  * so agents can distinguish policy denials from host-integration failures
@@ -7,6 +7,7 @@ import { evaluateIntentCeilingFromEnv } from "../policy/intent-ceiling.js";
7
7
  import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, } from "../policy/no-deft-directive.js";
8
8
  import { classifyMcpTool, DEFAULT_RUNTIME_AUTHORITY_POLICY, evaluateRuntimeAuthorityDirectWrite, evaluateRuntimeAuthorityShellOp, listShellOps, loadRuntimeAuthorityFromProject, } from "../policy/runtime-authority.js";
9
9
  import { loadStoryWriteFenceFromPath, resolveWriteFence } from "../policy/write-fence.js";
10
+ import { appendSoftAgentsRebindToMessage, decisionCarriesSoftAgentsRebind, formatSoftAgentsRebindChecklist, } from "../session/compact-ritual.js";
10
11
  import { detectBranch } from "../session/git.js";
11
12
  import { emitSessionRitualBlockedProcessCost } from "../session/process-cost.js";
12
13
  import { markRitualStaleAfterCompact } from "../session/ritual-sentinel.js";
@@ -716,7 +717,8 @@ export function decideHook(input, seams = {}) {
716
717
  host: input.host,
717
718
  toolName: null,
718
719
  projectRoot,
719
- message: result.message,
720
+ // Soft checklist still surfaces after compact even when ritual was absent (#3171).
721
+ message: appendSoftAgentsRebindToMessage(result.message),
720
722
  scopePath: null,
721
723
  };
722
724
  }
@@ -727,7 +729,7 @@ export function decideHook(input, seams = {}) {
727
729
  host: input.host,
728
730
  toolName: null,
729
731
  projectRoot,
730
- message: result.message,
732
+ message: appendSoftAgentsRebindToMessage(result.message),
731
733
  scopePath: null,
732
734
  };
733
735
  }
@@ -739,8 +741,8 @@ export function decideHook(input, seams = {}) {
739
741
  host: input.host,
740
742
  toolName: null,
741
743
  projectRoot,
742
- message: "Directive compact re-arm bookkeeping failed on its non-blocking path: " +
743
- `${String(cause)}`,
744
+ message: appendSoftAgentsRebindToMessage("Directive compact re-arm bookkeeping failed on its non-blocking path: " +
745
+ `${String(cause)}`),
744
746
  scopePath: null,
745
747
  };
746
748
  }
@@ -775,8 +777,9 @@ export function decideHook(input, seams = {}) {
775
777
  host: input.host,
776
778
  toolName: null,
777
779
  projectRoot,
778
- message: `Directive SessionStart bookkeeping reported exit ${result.code} on its non-blocking path` +
779
- `${detail.length > 0 ? `: ${detail}` : "."}`,
780
+ // Best-effort soft cue on SessionStart (Codex gap + resume/amnesia) (#3171).
781
+ message: appendSoftAgentsRebindToMessage(`Directive SessionStart bookkeeping reported exit ${result.code} on its non-blocking path` +
782
+ `${detail.length > 0 ? `: ${detail}` : "."}`),
780
783
  scopePath: null,
781
784
  };
782
785
  }
@@ -789,7 +792,7 @@ export function decideHook(input, seams = {}) {
789
792
  host: input.host,
790
793
  toolName: null,
791
794
  projectRoot,
792
- message: `Directive SessionStart bookkeeping failed on its non-blocking path: ${String(cause)}`,
795
+ message: appendSoftAgentsRebindToMessage(`Directive SessionStart bookkeeping failed on its non-blocking path: ${String(cause)}`),
793
796
  scopePath: null,
794
797
  };
795
798
  }
@@ -800,7 +803,8 @@ export function decideHook(input, seams = {}) {
800
803
  host: input.host,
801
804
  toolName: null,
802
805
  projectRoot,
803
- message: "SessionStart bookkeeping completed on a non-blocking path.",
806
+ // Soft AGENTS re-bind on SessionStart without requiring a write tool (#3171 / #2769).
807
+ message: appendSoftAgentsRebindToMessage("SessionStart bookkeeping completed on a non-blocking path."),
804
808
  scopePath: null,
805
809
  };
806
810
  }
@@ -881,13 +885,27 @@ export function decideHook(input, seams = {}) {
881
885
  }
882
886
  return inspectMutationGates(input, toolName, seams, { proposedLifecycleExempt: true });
883
887
  }
888
+ /**
889
+ * Soft AGENTS re-bind text for host injection when decision carries soft path (#3171).
890
+ * Prefer the checklist portion of the decision message; fall back to SoT formatter.
891
+ */
892
+ function softAgentsRebindWireText(decision) {
893
+ if (!decisionCarriesSoftAgentsRebind({ event: decision.event, code: decision.code })) {
894
+ return null;
895
+ }
896
+ if (decision.message.includes("Soft AGENTS re-bind checklist:")) {
897
+ return decision.message;
898
+ }
899
+ return formatSoftAgentsRebindChecklist();
900
+ }
884
901
  /**
885
902
  * Render host-facing hook output.
886
903
  *
887
904
  * Cursor deposits use `failClosed: true`. Cursor treats empty/null stdout as a
888
905
  * hook failure and blocks the tool — so Cursor allows must emit explicit
889
906
  * `{"permission":"allow"}`. Other hosts keep empty allow so the host permission
890
- * flow is unchanged.
907
+ * flow is unchanged — except session.start / session.compact soft re-bind
908
+ * injection (#3171), which surfaces checklist text without a write tool.
891
909
  *
892
910
  * Cursor stdout always includes `code` (stable machine-readable decision code)
893
911
  * so agents can distinguish policy denials from host-integration failures
@@ -896,9 +914,45 @@ export function decideHook(input, seams = {}) {
896
914
  */
897
915
  export function renderHostDecision(host, decision) {
898
916
  if (decision.verdict === "allow") {
917
+ const soft = softAgentsRebindWireText(decision);
899
918
  if (host === "cursor") {
919
+ if (decision.event === "session.start" && soft !== null) {
920
+ // Cursor sessionStart injects additional_context into the conversation.
921
+ return JSON.stringify({
922
+ permission: "allow",
923
+ code: decision.code,
924
+ additional_context: soft,
925
+ });
926
+ }
927
+ if (decision.event === "session.compact" && soft !== null) {
928
+ // Cursor preCompact is observational; user_message surfaces the soft cue.
929
+ // Hard re-arm still ran via bookkeeping; agent path also gets SessionStart on resume.
930
+ return JSON.stringify({
931
+ user_message: soft,
932
+ code: decision.code,
933
+ });
934
+ }
900
935
  return JSON.stringify({ permission: "allow", code: decision.code });
901
936
  }
937
+ if (soft !== null &&
938
+ (decision.event === "session.start" || decision.event === "session.compact")) {
939
+ if (host === "claude" || host === "codex") {
940
+ const hookEventName = decision.event === "session.start" ? "SessionStart" : "PostCompact";
941
+ return JSON.stringify({
942
+ hookSpecificOutput: {
943
+ hookEventName,
944
+ additionalContext: soft,
945
+ },
946
+ });
947
+ }
948
+ if (host === "grok") {
949
+ return JSON.stringify({
950
+ decision: "allow",
951
+ reason: soft,
952
+ additional_context: soft,
953
+ });
954
+ }
955
+ }
902
956
  return "";
903
957
  }
904
958
  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";
@@ -35,6 +37,7 @@ export * as lifecycle from "./lifecycle/index.js";
35
37
  export * as metrics from "./metrics/index.js";
36
38
  export * as orchestration from "./orchestration/index.js";
37
39
  export * as packs from "./packs/index.js";
40
+ export * as parentTurnShape from "./parent-turn-shape/index.js";
38
41
  export * as platform from "./platform/index.js";
39
42
  export * as policy from "./policy/index.js";
40
43
  export * as prClosingKeywords from "./pr-closing-keywords/index.js";
@@ -51,11 +54,13 @@ export * as render from "./render/index.js";
51
54
  export * as resolution from "./resolution/index.js";
52
55
  export * as scm from "./scm/index.js";
53
56
  export * as scope from "./scope/index.js";
57
+ export * as scopeProvenance from "./scope-provenance/index.js";
54
58
  export * as session from "./session/index.js";
55
59
  export * as slash from "./slash/index.js";
56
60
  export * as slice from "./slice/index.js";
57
61
  export * as storyReady from "./story-ready/index.js";
58
62
  export * as swarm from "./swarm/index.js";
63
+ export * as testBoundary from "./test-boundary/index.js";
59
64
  export * as toolEvents from "./tool-events/index.js";
60
65
  export * as triage from "./triage/index.js";
61
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";
@@ -36,6 +38,7 @@ export * as lifecycle from "./lifecycle/index.js";
36
38
  export * as metrics from "./metrics/index.js";
37
39
  export * as orchestration from "./orchestration/index.js";
38
40
  export * as packs from "./packs/index.js";
41
+ export * as parentTurnShape from "./parent-turn-shape/index.js";
39
42
  export * as platform from "./platform/index.js";
40
43
  export * as policy from "./policy/index.js";
41
44
  export * as prClosingKeywords from "./pr-closing-keywords/index.js";
@@ -52,11 +55,13 @@ export * as render from "./render/index.js";
52
55
  export * as resolution from "./resolution/index.js";
53
56
  export * as scm from "./scm/index.js";
54
57
  export * as scope from "./scope/index.js";
58
+ export * as scopeProvenance from "./scope-provenance/index.js";
55
59
  export * as session from "./session/index.js";
56
60
  export * as slash from "./slash/index.js";
57
61
  export * as slice from "./slice/index.js";
58
62
  export * as storyReady from "./story-ready/index.js";
59
63
  export * as swarm from "./swarm/index.js";
64
+ export * as testBoundary from "./test-boundary/index.js";
60
65
  export * as toolEvents from "./tool-events/index.js";
61
66
  export * as triage from "./triage/index.js";
62
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.
11
+ * Refs #1576, #1453, #1430, #3029, #3030, #3127, #3117.
12
12
  */
13
13
  import { type InitDepositIo } from "./constants.js";
14
14
  export declare const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
@@ -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.
11
+ * Refs #1576, #1453, #1430, #3029, #3030, #3127, #3117.
12
12
  */
13
13
  import { execFileSync } from "node:child_process";
14
14
  import { existsSync, readdirSync, rmSync } from "node:fs";
@@ -82,6 +82,16 @@ export function installerManagedMatchers() {
82
82
  { exact: CODEQL_CONFIG_REL },
83
83
  { exact: CORE_GUARD_WORKFLOW_REL },
84
84
  { exact: "Taskfile.yml" },
85
+ // Upgrade co-travel unit (#3127): npm pin + lock follow-through and the
86
+ // freshness live stamp (#3117) are part of a normal framework upgrade, not
87
+ // product feature work. Path-level allow for v1 (stricter “only
88
+ // @deftai/directive version changed” is a follow-on if needed).
89
+ // Mixing .deft/core/** with true app/product paths still fails (#1430).
90
+ { exact: "package.json" },
91
+ { exact: "package-lock.json" },
92
+ { exact: "pnpm-lock.yaml" },
93
+ { exact: "yarn.lock" },
94
+ { exact: ".deft/GENERATION.json" },
85
95
  // Legacy vbrief/ tree -- retained for not-yet-migrated consumers.
86
96
  { exact: "vbrief/.deft-version" },
87
97
  { exact: "vbrief/vbrief.md" },
@@ -476,9 +486,11 @@ export function printCommitGuidance(io, paths, staged) {
476
486
  if (paths.length === 0)
477
487
  return;
478
488
  const addCmd = `git add ${paths.join(" ")}`;
479
- io.printf("\nCommit hygiene (#1453, #1671): keep the framework deposit in its OWN branch/PR.\n");
480
- io.printf("Do NOT use `git add -A` -- mixing the payload with your own files trips the\n");
489
+ io.printf("\nCommit hygiene (#1453, #1671, #3127): keep the framework upgrade in its OWN branch/PR.\n");
490
+ io.printf("Do NOT use `git add -A` -- mixing the payload with product/app files trips the\n");
481
491
  io.printf("deft-core-guard CI check.\n");
492
+ io.printf("One upgrade PR MAY co-travel: .deft/core/** + installer-managed deposits + package.json\n");
493
+ io.printf("pin/lock + .deft/GENERATION.json. True app/product paths still require a separate PR.\n");
482
494
  if (staged) {
483
495
  io.printf("The installer already staged ONLY these framework + installer-managed paths:\n");
484
496
  io.printf(` ${addCmd}\n`);
@@ -488,7 +500,7 @@ export function printCommitGuidance(io, paths, staged) {
488
500
  io.printf(` ${addCmd}\n`);
489
501
  }
490
502
  io.printf("Then take the framework deposit through the full PR lifecycle so deft-core-guard\n");
491
- io.printf("evaluates a clean, standalone PR:\n");
503
+ io.printf("evaluates a clean, standalone upgrade PR:\n");
492
504
  io.printf(` 1. Branch: git switch -c ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
493
505
  io.printf(' 2. Commit: git commit -m "chore(deft): update framework payload"\n');
494
506
  io.printf(` 3. Push: git push -u origin ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
@@ -15,6 +15,7 @@ import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
15
15
  import { readCorePackageVersion } from "../engine-version.js";
16
16
  import { stampLiveGeneration } from "../freshness/generation.js";
17
17
  import { renderProjectDefinition } from "../render/project-render.js";
18
+ import { depositOpenClawSoftRebindSkill } from "../session/openclaw-soft-rebind-deposit.js";
18
19
  import { depositOpenClawL2ProductCommands } from "../slash/openclaw-deposit.js";
19
20
  import { agentHookReadinessJson, evaluateAgentHookReadiness, evaluateAgentHookReadinessSafely, } from "../verify-env/agent-hook-readiness.js";
20
21
  import { removeStaleMigratedFrameworkNarrative } from "../xbrief-migrate/migrate-project.js";
@@ -201,6 +202,10 @@ export async function runInitDeposit(args, io, seams = {}) {
201
202
  writeConsumerGitHooks(projectDir, deftDir, io, seams.gitHooks);
202
203
  writeAgentHookDeposit(projectDir, io);
203
204
  writeSlashCommandDeposit(projectDir, io);
205
+ // #3171: OpenClaw soft AGENTS re-bind skill when OC signals present (fail-closed otherwise).
206
+ depositOpenClawSoftRebindSkill({
207
+ /* env defaults; skip when OpenClaw not detected */
208
+ });
204
209
  // #3064: OpenClaw L2 product-command skills when OC signals present (fail-closed otherwise).
205
210
  depositOpenClawL2ProductCommands({
206
211
  projectRoot: projectDir,
@@ -22,6 +22,7 @@ import { resolveLifecycleRoot } from "../layout/resolve.js";
22
22
  import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY, } from "../policy/no-deft-directive.js";
23
23
  import { runOrgForceOnMigration } from "../policy/org-force-on-migration.js";
24
24
  import { checkLocalEngineIntegrity, classify, ENGINE_PACKAGE, plan, renderGlobalInstall, resolveEngine, } from "../resolution/index.js";
25
+ import { depositOpenClawSoftRebindSkill } from "../session/openclaw-soft-rebind-deposit.js";
25
26
  import { depositOpenClawL2ProductCommands } from "../slash/openclaw-deposit.js";
26
27
  import { gitPorcelain } from "../story-ready/git.js";
27
28
  import { agentHookReadinessJson, evaluateAgentHookReadiness, evaluateAgentHookReadinessSafely, } from "../verify-env/agent-hook-readiness.js";
@@ -506,6 +507,8 @@ export async function runRefreshDeposit(args, io, seams = {}) {
506
507
  // #75 residual: multi-host thin skill discovery (mirror `.agents/skills` inventory).
507
508
  writeMultiHostSkillDiscovery(projectDir, io);
508
509
  writeSlashCommandDeposit(projectDir, io);
510
+ // #3171: OpenClaw soft AGENTS re-bind skill when OC signals present (fail-closed otherwise).
511
+ depositOpenClawSoftRebindSkill({});
509
512
  // #3064: OpenClaw L2 product-command skills when OC signals present (fail-closed otherwise).
510
513
  depositOpenClawL2ProductCommands({
511
514
  projectRoot: projectDir,
@@ -507,11 +507,12 @@ function coreGuardWorkflowContent() {
507
507
  const baseSha = githubActionsExpr("github.event.pull_request.base.sha");
508
508
  const headSha = githubActionsExpr("github.event.pull_request.head.sha");
509
509
  return ("name: deft-core-guard\n\n" +
510
- "# Deft framework guard (#1430): a single PR should not mix changes to the\n" +
511
- "# vendored framework payload (.deft/core/**) with changes to your own project\n" +
512
- "# files. Framework updates come from `deft-install` / upgrade and should\n" +
513
- "# land in their own PR so reviewers (and bot reviewers) can treat them as\n" +
514
- "# packaged, machine-managed assets. Delete this file if you do not want the guard.\n" +
510
+ "# Deft framework guard (#1430 / #3127): a single PR should not mix changes to the\n" +
511
+ "# vendored framework payload (.deft/core/**) with true application/product files.\n" +
512
+ "# One upgrade PR MAY include deposit + installer-managed paths + package.json pin/lock\n" +
513
+ "# + .deft/GENERATION.json. Framework updates come from `deft update` / install and should\n" +
514
+ "# land without product feature work so reviewers treat the payload as machine-managed.\n" +
515
+ "# Delete this file if you do not want the guard.\n" +
515
516
  "on:\n" +
516
517
  " pull_request:\n\n" +
517
518
  "permissions:\n" +
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Parent turn-shape hard-stop for text-repetition hang (FC14 / #3131).
3
+ *
4
+ * Soft skill prose (#2943) alone is insufficient: parents still burn the full
5
+ * output budget on identical progress sentences with zero tool_use after leaf
6
+ * announce. This pure library is the Directive-side machine-checkable gate.
7
+ *
8
+ * Hosts (OpenClaw, swarm parents, review monitors) SHOULD evaluate the parent
9
+ * turn stream mid-generation and hard-stop when `ok` is false.
10
+ *
11
+ * Illegal shape (MUST NOT):
12
+ * N > maxIdenticalWithoutTool (default 2) near-identical assistant sentences
13
+ * (or streaming text chunks) in one turn with no tool_use and no yield.
14
+ *
15
+ * Legal post-announce shapes:
16
+ * 1. Tool batch (ground-truth) then one consolidate
17
+ * 2. sessions_yield / wait without filler
18
+ * 3. One short user answer that is NOT a repeated progress line
19
+ */
20
+ /** Failure class codes for operator / host recovery surfaces. */
21
+ export type ParentTurnFailClass = "none" | "FC14" | "text-repetition-hang" | "progress-only-no-tool";
22
+ /** Ordered events observed within a single parent turn. */
23
+ export type ParentTurnEvent = {
24
+ kind: "assistant_text";
25
+ text: string;
26
+ } | {
27
+ kind: "tool_use";
28
+ name?: string;
29
+ } | {
30
+ kind: "yield";
31
+ };
32
+ export interface ParentTurnShapeInput {
33
+ /** Ordered events from one parent turn (streaming deltas may be separate). */
34
+ readonly events: readonly ParentTurnEvent[];
35
+ /**
36
+ * When true, multi-sentence progress-only text with zero tools/yield is also
37
+ * illegal even without exact N>2 identity (post-`subagent_announce` policy).
38
+ */
39
+ readonly afterSubagentAnnounce?: boolean;
40
+ /**
41
+ * Max near-identical consecutive (or within-turn) text units allowed without
42
+ * tool_use / yield. Issue contract: MUST NOT emit N>2 → default max = 2.
43
+ */
44
+ readonly maxIdenticalWithoutTool?: number;
45
+ }
46
+ export interface ParentTurnShapeResult {
47
+ readonly ok: boolean;
48
+ /** Primary fail class (`FC14` aliases `text-repetition-hang`). */
49
+ readonly failClass: ParentTurnFailClass;
50
+ readonly reasons: readonly string[];
51
+ /** Highest count of near-identical text units observed without tool/yield. */
52
+ readonly maxIdenticalCount: number;
53
+ readonly hasToolUse: boolean;
54
+ readonly hasYield: boolean;
55
+ }
56
+ /** Canonical fail class for the hang (#3131 / soft FC14 recurrence of #2943). */
57
+ export declare const PARENT_TURN_FAIL_FC14: "FC14";
58
+ /** Default max identical text units without tool/yield (N>2 is illegal). */
59
+ export declare const DEFAULT_MAX_IDENTICAL_WITHOUT_TOOL = 2;
60
+ /**
61
+ * Normalize assistant text for identity comparison.
62
+ * Collapses whitespace, lowercases, strips common trailing punctuation.
63
+ */
64
+ export declare function normalizeTurnText(text: string): string;
65
+ /** Split a blob into sentence-like units (periods, newlines, ellipsis). */
66
+ export declare function splitTextUnits(text: string): string[];
67
+ /**
68
+ * True when two normalized strings are near-identical (exact, containment with
69
+ * high length ratio, or high word Jaccard). Short units never match.
70
+ */
71
+ export declare function isNearIdentical(a: string, b: string): boolean;
72
+ /**
73
+ * Detect when a single blob itself embeds the same sentence many times
74
+ * (model streams one giant assistant message of repeated lines).
75
+ */
76
+ export declare function countRepeatedUnitsInBlob(text: string): number;
77
+ /**
78
+ * Evaluate whether a parent turn shape is legal under the FC14 hard-stop.
79
+ * Pure / side-effect free — safe for mid-stream host gates and unit tests.
80
+ */
81
+ export declare function evaluateParentTurnShape(input: ParentTurnShapeInput): ParentTurnShapeResult;
82
+ /** Convenience: true when the turn is an illegal text-repetition hang. */
83
+ export declare function isTextRepetitionHang(input: ParentTurnShapeInput): boolean;
84
+ //# sourceMappingURL=evaluate.d.ts.map