@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.
- package/dist/doctor/constants.d.ts +1 -1
- package/dist/doctor/constants.js +2 -0
- package/dist/doctor/flags.js +21 -1
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +22 -0
- package/dist/doctor/openclaw-skills.d.ts +143 -0
- package/dist/doctor/openclaw-skills.js +706 -0
- package/dist/doctor/types.d.ts +11 -0
- package/dist/eval/health.d.ts +10 -1
- package/dist/eval/health.js +16 -16
- package/dist/hooks/dispatcher.js +20 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/hygiene.js +3 -0
- package/dist/init-deposit/init-deposit.d.ts +5 -0
- package/dist/init-deposit/init-deposit.js +37 -2
- package/dist/intake/github-auth-modes.d.ts +1 -1
- package/dist/intake/github-auth-modes.js +25 -3
- package/dist/intake/issue-ingest-cli.js +13 -0
- package/dist/intake/reconcile-issues-cli.js +13 -0
- package/dist/lifecycle/events.js +2 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/lifecycle/stats.d.ts +53 -0
- package/dist/lifecycle/stats.js +286 -0
- package/dist/policy/plan-extensions.d.ts +10 -0
- package/dist/policy/plan-extensions.js +16 -0
- package/dist/pr-closing-keywords/detect.d.ts +13 -0
- package/dist/pr-closing-keywords/detect.js +73 -8
- package/dist/pr-closing-keywords/index.d.ts +2 -2
- package/dist/pr-closing-keywords/index.js +1 -1
- package/dist/pr-closing-keywords/main.js +119 -90
- package/dist/pr-closing-keywords/types.d.ts +10 -0
- package/dist/release/spawn.js +13 -0
- package/dist/release-e2e/git-ops.d.ts +7 -0
- package/dist/release-e2e/git-ops.js +35 -2
- package/dist/render/constants.d.ts +16 -0
- package/dist/render/constants.js +16 -1
- package/dist/render/export-spec.d.ts +7 -1
- package/dist/render/export-spec.js +51 -5
- package/dist/render/index.d.ts +3 -2
- package/dist/render/index.js +2 -2
- package/dist/render/roadmap-render.js +164 -60
- package/dist/render/scope-outlook.d.ts +5 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.d.ts +28 -2
- package/dist/render/spec-render.js +111 -14
- package/dist/render/text-utils.d.ts +6 -0
- package/dist/render/text-utils.js +23 -0
- package/dist/scm/binary.d.ts +3 -0
- package/dist/scm/binary.js +10 -2
- package/dist/scm/call.d.ts +5 -0
- package/dist/scm/call.js +5 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +5 -0
- package/dist/scm/main.js +27 -0
- package/dist/scm/readiness-cli.d.ts +24 -0
- package/dist/scm/readiness-cli.js +85 -0
- package/dist/scm/readiness.d.ts +104 -0
- package/dist/scm/readiness.js +421 -0
- package/dist/scope/batch-promote.d.ts +26 -0
- package/dist/scope/batch-promote.js +167 -0
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/main.d.ts +2 -0
- package/dist/scope/main.js +58 -7
- package/dist/scope/wip-cap-check.d.ts +6 -0
- package/dist/scope/wip-cap-check.js +14 -2
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/process-cost-constants.d.ts +9 -0
- package/dist/session/process-cost-constants.js +11 -0
- package/dist/session/process-cost.d.ts +53 -0
- package/dist/session/process-cost.js +82 -0
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +12 -1
- package/dist/session/session-ready.d.ts +67 -0
- package/dist/session/session-ready.js +264 -0
- package/dist/session/session-start.d.ts +63 -1
- package/dist/session/session-start.js +432 -24
- package/dist/session/verify-session-ritual.d.ts +8 -0
- package/dist/session/verify-session-ritual.js +95 -35
- package/dist/tool-events/classify.d.ts +20 -0
- package/dist/tool-events/classify.js +634 -0
- package/dist/tool-events/index.d.ts +10 -0
- package/dist/tool-events/index.js +10 -0
- package/dist/tool-events/summarize.d.ts +34 -0
- package/dist/tool-events/summarize.js +93 -0
- package/dist/tool-events/types.d.ts +52 -0
- package/dist/tool-events/types.js +13 -0
- package/dist/triage/welcome/index.d.ts +1 -1
- package/dist/triage/welcome/index.js +1 -1
- package/dist/triage/welcome/onboard.js +7 -1
- package/dist/triage/welcome/prior-state.d.ts +7 -0
- package/dist/triage/welcome/prior-state.js +16 -2
- package/dist/triage/welcome/writers.d.ts +10 -0
- package/dist/triage/welcome/writers.js +72 -2
- 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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
334
|
-
"@deftai/directive-types": "^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": {
|