@deftai/directive-core 0.89.0 → 0.91.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 (100) hide show
  1. package/dist/doctor/constants.d.ts +1 -1
  2. package/dist/doctor/constants.js +2 -0
  3. package/dist/doctor/flags.js +21 -1
  4. package/dist/doctor/index.d.ts +1 -0
  5. package/dist/doctor/index.js +1 -0
  6. package/dist/doctor/main.js +22 -0
  7. package/dist/doctor/openclaw-skills.d.ts +143 -0
  8. package/dist/doctor/openclaw-skills.js +706 -0
  9. package/dist/doctor/types.d.ts +11 -0
  10. package/dist/eval/health.d.ts +10 -1
  11. package/dist/eval/health.js +16 -16
  12. package/dist/hooks/dispatcher.js +20 -5
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +1 -0
  15. package/dist/init-deposit/hygiene.js +3 -0
  16. package/dist/init-deposit/init-deposit.d.ts +5 -0
  17. package/dist/init-deposit/init-deposit.js +37 -2
  18. package/dist/intake/github-auth-modes.d.ts +1 -1
  19. package/dist/intake/github-auth-modes.js +25 -3
  20. package/dist/intake/issue-ingest-cli.js +13 -0
  21. package/dist/intake/reconcile-issues-cli.js +13 -0
  22. package/dist/lifecycle/events.js +2 -0
  23. package/dist/lifecycle/index.d.ts +1 -0
  24. package/dist/lifecycle/index.js +1 -0
  25. package/dist/lifecycle/stats.d.ts +53 -0
  26. package/dist/lifecycle/stats.js +286 -0
  27. package/dist/policy/plan-extensions.d.ts +10 -0
  28. package/dist/policy/plan-extensions.js +16 -0
  29. package/dist/pr-closing-keywords/detect.d.ts +13 -0
  30. package/dist/pr-closing-keywords/detect.js +73 -8
  31. package/dist/pr-closing-keywords/index.d.ts +2 -2
  32. package/dist/pr-closing-keywords/index.js +1 -1
  33. package/dist/pr-closing-keywords/main.js +119 -90
  34. package/dist/pr-closing-keywords/types.d.ts +10 -0
  35. package/dist/release/spawn.js +13 -0
  36. package/dist/release-e2e/git-ops.d.ts +7 -0
  37. package/dist/release-e2e/git-ops.js +35 -2
  38. package/dist/render/constants.d.ts +16 -0
  39. package/dist/render/constants.js +16 -1
  40. package/dist/render/export-spec.d.ts +7 -1
  41. package/dist/render/export-spec.js +51 -5
  42. package/dist/render/index.d.ts +3 -2
  43. package/dist/render/index.js +2 -2
  44. package/dist/render/roadmap-render.js +164 -60
  45. package/dist/render/scope-outlook.d.ts +5 -0
  46. package/dist/render/scope-outlook.js +3 -0
  47. package/dist/render/spec-render.d.ts +28 -2
  48. package/dist/render/spec-render.js +111 -14
  49. package/dist/render/text-utils.d.ts +6 -0
  50. package/dist/render/text-utils.js +23 -0
  51. package/dist/scm/binary.d.ts +3 -0
  52. package/dist/scm/binary.js +10 -2
  53. package/dist/scm/call.d.ts +5 -0
  54. package/dist/scm/call.js +5 -0
  55. package/dist/scm/index.d.ts +1 -0
  56. package/dist/scm/index.js +1 -0
  57. package/dist/scm/main.d.ts +5 -0
  58. package/dist/scm/main.js +27 -0
  59. package/dist/scm/readiness-cli.d.ts +24 -0
  60. package/dist/scm/readiness-cli.js +85 -0
  61. package/dist/scm/readiness.d.ts +104 -0
  62. package/dist/scm/readiness.js +421 -0
  63. package/dist/scope/batch-promote.d.ts +26 -0
  64. package/dist/scope/batch-promote.js +167 -0
  65. package/dist/scope/index.d.ts +1 -0
  66. package/dist/scope/index.js +1 -0
  67. package/dist/scope/main.d.ts +2 -0
  68. package/dist/scope/main.js +58 -7
  69. package/dist/scope/wip-cap-check.d.ts +6 -0
  70. package/dist/scope/wip-cap-check.js +14 -2
  71. package/dist/session/index.d.ts +2 -0
  72. package/dist/session/index.js +2 -0
  73. package/dist/session/process-cost-constants.d.ts +9 -0
  74. package/dist/session/process-cost-constants.js +11 -0
  75. package/dist/session/process-cost.d.ts +53 -0
  76. package/dist/session/process-cost.js +82 -0
  77. package/dist/session/ritual-sentinel.d.ts +5 -0
  78. package/dist/session/ritual-sentinel.js +12 -1
  79. package/dist/session/session-ready.d.ts +67 -0
  80. package/dist/session/session-ready.js +264 -0
  81. package/dist/session/session-start.d.ts +63 -1
  82. package/dist/session/session-start.js +432 -24
  83. package/dist/session/verify-session-ritual.d.ts +8 -0
  84. package/dist/session/verify-session-ritual.js +95 -35
  85. package/dist/tool-events/classify.d.ts +20 -0
  86. package/dist/tool-events/classify.js +634 -0
  87. package/dist/tool-events/index.d.ts +10 -0
  88. package/dist/tool-events/index.js +10 -0
  89. package/dist/tool-events/summarize.d.ts +34 -0
  90. package/dist/tool-events/summarize.js +93 -0
  91. package/dist/tool-events/types.d.ts +52 -0
  92. package/dist/tool-events/types.js +13 -0
  93. package/dist/triage/welcome/index.d.ts +1 -1
  94. package/dist/triage/welcome/index.js +1 -1
  95. package/dist/triage/welcome/onboard.js +7 -1
  96. package/dist/triage/welcome/prior-state.d.ts +7 -0
  97. package/dist/triage/welcome/prior-state.js +16 -2
  98. package/dist/triage/welcome/writers.d.ts +10 -0
  99. package/dist/triage/welcome/writers.js +72 -2
  100. package/package.json +7 -3
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Aggregate tool-event classifications into counts, anomalies, and status lines (#2967).
3
+ * Pure: no I/O.
4
+ */
5
+ import { classifyToolEvent } from "./classify.js";
6
+ import { TOOL_EVENT_BUCKETS, } from "./types.js";
7
+ export function emptyBucketCounts() {
8
+ return {
9
+ explore: 0,
10
+ commit: 0,
11
+ verify: 0,
12
+ coordinate: 0,
13
+ unknown: 0,
14
+ };
15
+ }
16
+ /** Count classified buckets for a sequence of tool events. */
17
+ export function countToolEventBuckets(events) {
18
+ const counts = {
19
+ explore: 0,
20
+ commit: 0,
21
+ verify: 0,
22
+ coordinate: 0,
23
+ unknown: 0,
24
+ };
25
+ for (const event of events) {
26
+ const { bucket } = classifyToolEvent(event);
27
+ counts[bucket] += 1;
28
+ }
29
+ return counts;
30
+ }
31
+ /**
32
+ * Detect operator-facing anomalies from bucket counts.
33
+ *
34
+ * Rules (conservative — only fire when the mix is clearly wrong):
35
+ * - commit-without-explore: commit > 0 and explore === 0
36
+ * - verify-skipped: commit > 0 and verify === 0 (ship without gates)
37
+ * - explore-only: explore > 0, commit === 0, verify === 0, and total >= 3
38
+ * (thrash / stuck-reading signal; small explore-only sessions stay quiet)
39
+ */
40
+ export function detectToolEventAnomalies(counts) {
41
+ const anomalies = [];
42
+ const total = counts.explore + counts.commit + counts.verify + counts.coordinate + counts.unknown;
43
+ if (counts.commit > 0 && counts.explore === 0) {
44
+ anomalies.push({
45
+ code: "commit-without-explore",
46
+ message: `commit-without-explore: ${counts.commit} commit event(s) with 0 explore`,
47
+ });
48
+ }
49
+ if (counts.commit > 0 && counts.verify === 0) {
50
+ anomalies.push({
51
+ code: "verify-skipped",
52
+ message: `verify-skipped: ${counts.commit} commit event(s) with 0 verify`,
53
+ });
54
+ }
55
+ if (counts.explore > 0 && counts.commit === 0 && counts.verify === 0 && total >= 3) {
56
+ anomalies.push({
57
+ code: "explore-only",
58
+ message: `explore-only: ${counts.explore} explore event(s) with 0 commit and 0 verify (possible thrash)`,
59
+ });
60
+ }
61
+ return anomalies;
62
+ }
63
+ /**
64
+ * Compact status line for swarm monitor / review-cycle batch brief.
65
+ * Example: `tools: explore=3 commit=2 verify=1 coordinate=0 unknown=0`
66
+ */
67
+ export function formatToolEventStatusLine(counts) {
68
+ const parts = TOOL_EVENT_BUCKETS.map((b) => `${b}=${counts[b]}`);
69
+ return `tools: ${parts.join(" ")}`;
70
+ }
71
+ /**
72
+ * One-line anomaly suffix for status surfaces.
73
+ * Empty string when no anomalies.
74
+ */
75
+ export function formatToolEventAnomalyLine(anomalies) {
76
+ if (anomalies.length === 0)
77
+ return "";
78
+ return `anomalies: ${anomalies.map((a) => a.code).join(",")}`;
79
+ }
80
+ /**
81
+ * Full summary for a tool-event sequence — counts, anomalies, status line.
82
+ * When anomalies exist, statusLine appends `| anomalies: …`.
83
+ */
84
+ export function summarizeToolEvents(events) {
85
+ const counts = countToolEventBuckets(events);
86
+ const anomalies = detectToolEventAnomalies(counts);
87
+ const total = counts.explore + counts.commit + counts.verify + counts.coordinate + counts.unknown;
88
+ const base = formatToolEventStatusLine(counts);
89
+ const anomalyLine = formatToolEventAnomalyLine(anomalies);
90
+ const statusLine = anomalyLine.length > 0 ? `${base} | ${anomalyLine}` : base;
91
+ return { total, counts, anomalies, statusLine };
92
+ }
93
+ //# sourceMappingURL=summarize.js.map
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Tool-event taxonomy types for explore/commit/verify classification (#2967).
3
+ * Pure types only — no I/O.
4
+ */
5
+ /** Deterministic activity buckets for swarm/review operator skim. */
6
+ export type ToolEventBucket = "explore" | "commit" | "verify" | "coordinate" | "unknown";
7
+ /** All buckets in stable report order (unknown last). */
8
+ export declare const TOOL_EVENT_BUCKETS: readonly ToolEventBucket[];
9
+ /**
10
+ * Minimal tool-event input. Prefer tool name + coarse args; never require
11
+ * full host payloads. Shell tools should pass `command` when known.
12
+ */
13
+ export interface ToolEventInput {
14
+ /** Host tool name (e.g. `Read`, `Shell`, `Write`, `Task`). */
15
+ readonly name: string;
16
+ /**
17
+ * Coarse args bag — common keys: `command`, `cmd`, `path`, `file_path`,
18
+ * `query`, `pattern`. Values may be strings or other JSON-ish primitives.
19
+ */
20
+ readonly args?: Readonly<Record<string, unknown>> | null;
21
+ /**
22
+ * Explicit shell/command string when not nested under `args`.
23
+ * Wins over `args.command` / `args.cmd` when both are set.
24
+ */
25
+ readonly command?: string | null;
26
+ }
27
+ /** Result of classifying one tool event. */
28
+ export interface ClassifyToolEventResult {
29
+ readonly bucket: ToolEventBucket;
30
+ /** Stable machine-oriented reason code for tests and debug. */
31
+ readonly reason: string;
32
+ }
33
+ /** Per-bucket event counts (always includes every bucket key). */
34
+ export type ToolEventBucketCounts = Readonly<Record<ToolEventBucket, number>>;
35
+ /**
36
+ * Operator-facing anomaly codes derived from a sequence of classified events.
37
+ * Detection is conservative: only fire when the mix is clearly wrong.
38
+ */
39
+ export type ToolEventAnomalyCode = "commit-without-explore" | "verify-skipped" | "explore-only";
40
+ export interface ToolEventAnomaly {
41
+ readonly code: ToolEventAnomalyCode;
42
+ readonly message: string;
43
+ }
44
+ /** Aggregate summary for monitor status lines and batch briefs. */
45
+ export interface ToolEventSummary {
46
+ readonly total: number;
47
+ readonly counts: ToolEventBucketCounts;
48
+ readonly anomalies: readonly ToolEventAnomaly[];
49
+ /** Compact one-liner: `tools: explore=3 commit=2 verify=1 coordinate=0 unknown=0`. */
50
+ readonly statusLine: string;
51
+ }
52
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tool-event taxonomy types for explore/commit/verify classification (#2967).
3
+ * Pure types only — no I/O.
4
+ */
5
+ /** All buckets in stable report order (unknown last). */
6
+ export const TOOL_EVENT_BUCKETS = [
7
+ "explore",
8
+ "commit",
9
+ "verify",
10
+ "coordinate",
11
+ "unknown",
12
+ ];
13
+ //# sourceMappingURL=types.js.map
@@ -3,5 +3,5 @@ export { type DefaultModeOptions, formatWelcomeCommand, normalizeTaskPrefix, run
3
3
  export { DEFAULT_ONBOARD_PRESET, type OnboardOptions, type OnboardOutcome, runOnboardMode, } from "./onboard.js";
4
4
  export { candidatesLogPath, classifyOnboarding, detectPriorState, type PriorState, pendingDecisionsNudgeLine, } from "./prior-state.js";
5
5
  export { computeSummary, emitOneliner, formatOneLiner, formatSummary, type SummaryResult, } from "./summary.js";
6
- export { appendAuditEntry, previewWipRelief, type ReliefPreview, subscriptionPreset, writeTriageScope, writeWipCap, } from "./writers.js";
6
+ export { appendAuditEntry, previewWipRelief, type ReliefPreview, subscriptionPreset, writeTriageScope, writeWipCap, writeWipCapDecision, } from "./writers.js";
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -3,5 +3,5 @@ export { formatWelcomeCommand, normalizeTaskPrefix, runDefaultMode, } from "./de
3
3
  export { DEFAULT_ONBOARD_PRESET, runOnboardMode, } from "./onboard.js";
4
4
  export { candidatesLogPath, classifyOnboarding, detectPriorState, pendingDecisionsNudgeLine, } from "./prior-state.js";
5
5
  export { computeSummary, emitOneliner, formatOneLiner, formatSummary, } from "./summary.js";
6
- export { appendAuditEntry, previewWipRelief, subscriptionPreset, writeTriageScope, writeWipCap, } from "./writers.js";
6
+ export { appendAuditEntry, previewWipRelief, subscriptionPreset, writeTriageScope, writeWipCap, writeWipCapDecision, } from "./writers.js";
7
7
  //# sourceMappingURL=index.js.map
@@ -7,7 +7,7 @@ import { DEFAULT_WIP_CAP, SUBSCRIPTION_PRESETS, TRIAGE_SKILL_PATH } from "./cons
7
7
  import { formatWelcomeCommand } from "./default-mode.js";
8
8
  import { detectPriorState } from "./prior-state.js";
9
9
  import { emitOneliner } from "./summary.js";
10
- import { previewWipRelief, subscriptionPreset, writeTriageScope, writeWipCap } from "./writers.js";
10
+ import { previewWipRelief, subscriptionPreset, writeTriageScope, writeWipCap, writeWipCapDecision, } from "./writers.js";
11
11
  /** Default triage-scope preset applied when `--onboard` is run without `--preset`. */
12
12
  export const DEFAULT_ONBOARD_PRESET = "small";
13
13
  function failure(exitCode) {
@@ -66,8 +66,14 @@ export function runOnboardMode(projectRoot, options = {}) {
66
66
  const [triageScopeChanged] = writeTriageScope(projectRoot, rules, { presetLabel: preset });
67
67
  let wipCapChanged = false;
68
68
  if (wipCap !== null) {
69
+ // writeWipCap also records out-of-band decision-provenance (#1694).
69
70
  [wipCapChanged] = writeWipCap(projectRoot, wipCap);
70
71
  }
72
+ else {
73
+ // Accepting the framework default is a real onboarding decision: record it
74
+ // without materializing plan.policy.wipCap (#1694 / #1186 D1 / #1250).
75
+ writeWipCapDecision(projectRoot, { acceptedDefault: true });
76
+ }
71
77
  emitOneliner(projectRoot, {
72
78
  writeHistory: options.writeHistory !== false,
73
79
  now: options.now,
@@ -3,7 +3,14 @@ export interface PriorState {
3
3
  readonly triageScopeSummary: string;
4
4
  readonly cacheEmpty: boolean;
5
5
  readonly cacheEntryCount: number;
6
+ /** True when plan.policy.wipCap is materialized (deliberate non-default override). */
6
7
  readonly wipCapSet: boolean;
8
+ /**
9
+ * True when an out-of-band decision-provenance marker records that the
10
+ * operator considered WIP cap (#1694). Orthogonal to {@link wipCapSet}:
11
+ * accepting the framework default sets this without materializing the field.
12
+ */
13
+ readonly wipCapDecided: boolean;
7
14
  readonly wipCap: number;
8
15
  readonly wipCount: number;
9
16
  readonly auditLogPresent: boolean;
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
3
  import { hasArtifactSuffix, resolveAuditPath, resolveLifecycleRoot, resolveProjectDefinitionPath, } from "../../layout/resolve.js";
4
- import { readPlanPolicy } from "../../policy/plan-extensions.js";
4
+ import { readPlanOnboarding, readPlanPolicy } from "../../policy/plan-extensions.js";
5
5
  import { resolveCandidatesLogPath } from "../cache-path.js";
6
6
  import { CACHE_DIR_NAME, CACHE_SOURCE, DEFAULT_WIP_CAP, SUBSCRIPTION_PRESETS, WIP_LIFECYCLE_DIRS, } from "./constants.js";
7
7
  function loadProjectDefinition(projectRoot) {
@@ -152,6 +152,17 @@ export function detectPriorState(projectRoot) {
152
152
  wipCapSet = true;
153
153
  }
154
154
  }
155
+ // Decision-provenance is out-of-band from the value field (#1694 / #1695).
156
+ // Accepting the framework default records wipCapDecided without materializing
157
+ // plan.policy.wipCap. A materialized override also counts as a decision.
158
+ const onboarding = readPlanOnboarding(plan);
159
+ let wipCapDecided = false;
160
+ if (typeof onboarding === "object" && onboarding !== null && !Array.isArray(onboarding)) {
161
+ wipCapDecided = onboarding.wipCapDecided === true;
162
+ }
163
+ if (wipCapSet) {
164
+ wipCapDecided = true;
165
+ }
155
166
  const [scopeSet, scopeLabel] = summarizeScope(scopeRules);
156
167
  const cacheCount = countCacheEntries(projectRoot);
157
168
  return {
@@ -160,6 +171,7 @@ export function detectPriorState(projectRoot) {
160
171
  cacheEmpty: cacheCount === 0,
161
172
  cacheEntryCount: cacheCount,
162
173
  wipCapSet,
174
+ wipCapDecided,
163
175
  wipCap,
164
176
  wipCount: countWip(projectRoot),
165
177
  auditLogPresent: existsSync(candidatesLogPath(projectRoot)),
@@ -167,10 +179,12 @@ export function detectPriorState(projectRoot) {
167
179
  };
168
180
  }
169
181
  export function classifyOnboarding(state) {
182
+ // wipCap onboarding completeness is decision-provenance, not field presence
183
+ // (#1694). Omission of plan.policy.wipCap with wipCapDecided=true is fully set up.
170
184
  const signals = {
171
185
  "candidates.jsonl": state.auditLogPresent,
172
186
  triageScope: state.triageScopeSet,
173
- wipCap: state.wipCapSet,
187
+ wipCap: state.wipCapDecided,
174
188
  };
175
189
  const present = Object.keys(signals).filter((k) => signals[k]);
176
190
  const missing = Object.keys(signals).filter((k) => !signals[k]);
@@ -3,6 +3,16 @@ export declare function writeTriageScope(projectRoot: string, rules: Array<Recor
3
3
  presetLabel: string;
4
4
  actor?: string;
5
5
  }): [boolean, string];
6
+ /**
7
+ * Record out-of-band WIP-cap decision provenance without materializing
8
+ * `plan.policy.wipCap` (#1694). Accepting the framework default is a real
9
+ * onboarding decision and MUST be representable without writing the value field.
10
+ */
11
+ export declare function writeWipCapDecision(projectRoot: string, options?: {
12
+ actor?: string;
13
+ acceptedDefault?: boolean;
14
+ value?: number | null;
15
+ }): [boolean, string];
6
16
  export declare function writeWipCap(projectRoot: string, wipCap: number, options?: {
7
17
  actor?: string;
8
18
  }): [boolean, string];
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, s
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { containedWrite } from "../../fs/contained-write.js";
4
4
  import { hasArtifactSuffix, resolveLifecycleFolder, resolveProjectDefinitionPath, } from "../../layout/resolve.js";
5
- import { migrateLegacyPolicyKey, PLAN_POLICY_KEY } from "../../policy/plan-extensions.js";
5
+ import { migrateLegacyPolicyKey, PLAN_ONBOARDING_KEY, PLAN_POLICY_KEY, } from "../../policy/plan-extensions.js";
6
6
  import { projectDefinitionMutationLock } from "../../vbrief-build/project-definition-io.js";
7
7
  import { AUDIT_LOG_REL_PATH, DEFAULT_RELIEF_AGE_DAYS, DEFAULT_WIP_CAP, SUBSCRIPTION_PRESETS, WELCOME_AUDIT_TAG, } from "./constants.js";
8
8
  function projectDefinitionPath(projectRoot) {
@@ -97,6 +97,57 @@ export function writeTriageScope(projectRoot, rules, options = { presetLabel: "c
97
97
  return [changed, auditEntry];
98
98
  });
99
99
  }
100
+ /**
101
+ * Record out-of-band WIP-cap decision provenance without materializing
102
+ * `plan.policy.wipCap` (#1694). Accepting the framework default is a real
103
+ * onboarding decision and MUST be representable without writing the value field.
104
+ */
105
+ export function writeWipCapDecision(projectRoot, options = {}) {
106
+ return projectDefinitionMutationLock(projectRoot, () => {
107
+ const path = projectDefinitionPath(projectRoot);
108
+ if (!existsSync(path))
109
+ throw new Error(`PROJECT-DEFINITION not found at ${path}`);
110
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
111
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
112
+ throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
113
+ }
114
+ const data = parsed;
115
+ const plan = data.plan;
116
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
117
+ throw new Error("PROJECT-DEFINITION 'plan' is not an object");
118
+ }
119
+ const planRec = plan;
120
+ const previous = planRec[PLAN_ONBOARDING_KEY];
121
+ const previousRec = typeof previous === "object" && previous !== null && !Array.isArray(previous)
122
+ ? previous
123
+ : {};
124
+ const alreadyDecided = previousRec.wipCapDecided === true;
125
+ const acceptedDefault = options.acceptedDefault ?? true;
126
+ const actor = options.actor ?? WELCOME_AUDIT_TAG;
127
+ // Rebuild cleanly so acceptedDefault=true never retains a prior custom value.
128
+ const next = {
129
+ wipCapDecided: true,
130
+ acceptedDefault,
131
+ decidedAt: utcIso(),
132
+ actor,
133
+ };
134
+ if (!acceptedDefault && options.value !== undefined && options.value !== null) {
135
+ next.value = options.value;
136
+ }
137
+ planRec[PLAN_ONBOARDING_KEY] = next;
138
+ atomicWrite(projectRoot, path, data);
139
+ const changed = !alreadyDecided || previousRec.acceptedDefault !== acceptedDefault;
140
+ const auditEntry = [
141
+ `actor=${actor}`,
142
+ `field=${PLAN_ONBOARDING_KEY}.wipCapDecided`,
143
+ "value=true",
144
+ `acceptedDefault=${acceptedDefault ? "true" : "false"}`,
145
+ `changed=${changed ? "true" : "false"}`,
146
+ ].join(" ");
147
+ appendAuditEntry(projectRoot, auditEntry);
148
+ return [changed, auditEntry];
149
+ });
150
+ }
100
151
  export function writeWipCap(projectRoot, wipCap, options = {}) {
101
152
  if (!Number.isInteger(wipCap) || wipCap < 1) {
102
153
  throw new Error(`wipCap must be a positive int, got ${JSON.stringify(wipCap)}`);
@@ -125,8 +176,27 @@ export function writeWipCap(projectRoot, wipCap, options = {}) {
125
176
  const policyRec = policy;
126
177
  const previous = policyRec.wipCap;
127
178
  const actor = options.actor ?? WELCOME_AUDIT_TAG;
179
+ // Always record decision-provenance out-of-band (#1694). Accepting the
180
+ // default omits the value field but still marks the decision complete.
181
+ // Rebuild the onboarding record (do not spread a prior custom `value` into
182
+ // an acceptedDefault=true payload -- that is contradictory provenance).
183
+ const acceptedDefault = wipCap === DEFAULT_WIP_CAP;
184
+ const onboardingRecord = {
185
+ wipCapDecided: true,
186
+ acceptedDefault,
187
+ decidedAt: utcIso(),
188
+ actor,
189
+ };
190
+ if (!acceptedDefault) {
191
+ onboardingRecord.value = wipCap;
192
+ }
193
+ planRec[PLAN_ONBOARDING_KEY] = onboardingRecord;
128
194
  if (previous === undefined && wipCap === DEFAULT_WIP_CAP) {
129
- return [false, ""];
195
+ atomicWrite(projectRoot, path, data);
196
+ const auditEntry = `actor=${actor} field=${PLAN_ONBOARDING_KEY}.wipCapDecided value=true ` +
197
+ `acceptedDefault=true changed=true note=default-no-materialize`;
198
+ appendAuditEntry(projectRoot, auditEntry);
199
+ return [true, auditEntry];
130
200
  }
131
201
  if (previous !== undefined && wipCap === DEFAULT_WIP_CAP) {
132
202
  delete policyRec.wipCap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-core",
3
- "version": "0.89.0",
3
+ "version": "0.91.0",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -263,6 +263,10 @@
263
263
  "types": "./dist/swarm/index.d.ts",
264
264
  "default": "./dist/swarm/index.js"
265
265
  },
266
+ "./tool-events": {
267
+ "types": "./dist/tool-events/index.d.ts",
268
+ "default": "./dist/tool-events/index.js"
269
+ },
266
270
  "./review-monitor": {
267
271
  "types": "./dist/review-monitor/index.d.ts",
268
272
  "default": "./dist/review-monitor/index.js"
@@ -330,8 +334,8 @@
330
334
  "provenance": true
331
335
  },
332
336
  "dependencies": {
333
- "@deftai/directive-content": "^0.89.0",
334
- "@deftai/directive-types": "^0.89.0",
337
+ "@deftai/directive-content": "^0.91.0",
338
+ "@deftai/directive-types": "^0.91.0",
335
339
  "archiver": "^8.0.0"
336
340
  },
337
341
  "scripts": {