@deftai/directive-core 0.90.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/openclaw-skills.d.ts +42 -8
- package/dist/doctor/openclaw-skills.js +281 -38
- package/dist/eval/health.d.ts +10 -1
- package/dist/eval/health.js +16 -16
- 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/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/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/session-start.d.ts +7 -0
- package/dist/session/session-start.js +54 -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 +3 -3
package/dist/scope/main.js
CHANGED
|
@@ -3,6 +3,7 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { maybeRunStalenessTickler } from "../staleness-tickler/run.js";
|
|
4
4
|
import { reconcileUmbrellas, renderUmbrellasReport } from "../vbrief-reconcile/umbrellas.js";
|
|
5
5
|
import { canonicalLogPath, readAll } from "./audit-log.js";
|
|
6
|
+
import { batchPromote } from "./batch-promote.js";
|
|
6
7
|
import { TRANSITIONS } from "./constants.js";
|
|
7
8
|
import { batchDemote, DEFAULT_OLDER_THAN_DAYS, demoteOne, resolveDemoteFilePath, resolveFilePath, resolveProjectRootStrict, } from "./demote.js";
|
|
8
9
|
import { completedPathForScopeMove, findOpenUmbrellaReferences, renderOpenUmbrellaWarning, } from "./open-umbrella-warning.js";
|
|
@@ -10,12 +11,13 @@ import { resolveProjectRoot } from "./project-context.js";
|
|
|
10
11
|
import { recordWipCapOverride, runTransition } from "./transition.js";
|
|
11
12
|
import { findByDecisionId, isAlreadyUndone, REVERSIBLE_ACTIONS, undoBatch, undoOne, } from "./undo.js";
|
|
12
13
|
import { checkWipCap, formatWipCapRefusal } from "./wip-cap-check.js";
|
|
13
|
-
const LIFECYCLE_USAGE_STDERR = "usage: scope_lifecycle.py [-h] [--project-root PROJECT_ROOT] [--force]\n" +
|
|
14
|
+
const LIFECYCLE_USAGE_STDERR = "usage: scope_lifecycle.py [-h] [--project-root PROJECT_ROOT] [--force] [--batch]\n" +
|
|
14
15
|
" {activate,block,cancel,complete,fail,promote,restore,unblock}\n" +
|
|
15
|
-
" file\n" +
|
|
16
|
-
"scope_lifecycle.py: error: the following arguments are required: action, file\n"
|
|
16
|
+
" [file ...]\n" +
|
|
17
|
+
"scope_lifecycle.py: error: the following arguments are required: action, file\n" +
|
|
18
|
+
"(promote --batch may omit file and promotes all proposed/ scopes; #3011)\n";
|
|
17
19
|
function parseLifecycleArgv(argv) {
|
|
18
|
-
if (argv.length <
|
|
20
|
+
if (argv.length < 1) {
|
|
19
21
|
return { args: null, error: "usage" };
|
|
20
22
|
}
|
|
21
23
|
const action = argv[0] ?? "";
|
|
@@ -25,6 +27,8 @@ function parseLifecycleArgv(argv) {
|
|
|
25
27
|
let file = "";
|
|
26
28
|
let projectRoot;
|
|
27
29
|
let force = false;
|
|
30
|
+
let batch = false;
|
|
31
|
+
const batchFiles = [];
|
|
28
32
|
for (let i = 1; i < argv.length; i += 1) {
|
|
29
33
|
const arg = argv[i];
|
|
30
34
|
if (arg === undefined) {
|
|
@@ -33,6 +37,9 @@ function parseLifecycleArgv(argv) {
|
|
|
33
37
|
if (arg === "--force") {
|
|
34
38
|
force = true;
|
|
35
39
|
}
|
|
40
|
+
else if (arg === "--batch") {
|
|
41
|
+
batch = true;
|
|
42
|
+
}
|
|
36
43
|
else if (arg === "--project-root") {
|
|
37
44
|
projectRoot = argv[i + 1];
|
|
38
45
|
i += 1;
|
|
@@ -40,13 +47,36 @@ function parseLifecycleArgv(argv) {
|
|
|
40
47
|
else if (arg?.startsWith("--project-root=")) {
|
|
41
48
|
projectRoot = arg.slice("--project-root=".length);
|
|
42
49
|
}
|
|
43
|
-
else if (!arg.startsWith("-")
|
|
44
|
-
|
|
50
|
+
else if (!arg.startsWith("-")) {
|
|
51
|
+
if (batch) {
|
|
52
|
+
batchFiles.push(arg);
|
|
53
|
+
}
|
|
54
|
+
else if (file.length === 0) {
|
|
55
|
+
file = arg;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
return { args: null, error: "usage" };
|
|
59
|
+
}
|
|
45
60
|
}
|
|
46
61
|
else {
|
|
47
62
|
return { args: null, error: "usage" };
|
|
48
63
|
}
|
|
49
64
|
}
|
|
65
|
+
if (batch) {
|
|
66
|
+
if (action !== "promote") {
|
|
67
|
+
return { args: null, error: "usage" };
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
args: {
|
|
71
|
+
action,
|
|
72
|
+
file: file.length > 0 ? file : "",
|
|
73
|
+
projectRoot,
|
|
74
|
+
force,
|
|
75
|
+
batch: true,
|
|
76
|
+
batchFiles: batchFiles.length > 0 ? batchFiles : file.length > 0 ? [file] : [],
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
50
80
|
if (file.length === 0) {
|
|
51
81
|
return { args: null, error: "usage" };
|
|
52
82
|
}
|
|
@@ -61,7 +91,28 @@ export function lifecycleMain(argv) {
|
|
|
61
91
|
}
|
|
62
92
|
return 2;
|
|
63
93
|
}
|
|
64
|
-
const { action, file, projectRoot, force } = parsed.args;
|
|
94
|
+
const { action, file, projectRoot, force, batch, batchFiles } = parsed.args;
|
|
95
|
+
if (batch === true && action === "promote") {
|
|
96
|
+
const result = batchPromote({
|
|
97
|
+
files: batchFiles && batchFiles.length > 0 ? batchFiles : undefined,
|
|
98
|
+
force: force === true,
|
|
99
|
+
projectRoot,
|
|
100
|
+
});
|
|
101
|
+
for (const line of result.messages) {
|
|
102
|
+
process.stdout.write(`${line}\n`);
|
|
103
|
+
}
|
|
104
|
+
for (const line of result.skipped) {
|
|
105
|
+
process.stdout.write(` skipped: ${line}\n`);
|
|
106
|
+
}
|
|
107
|
+
if (!result.ok && result.exitCode !== 0) {
|
|
108
|
+
for (const line of result.messages) {
|
|
109
|
+
if (line.startsWith("ERROR:") || line.includes("WIP cap")) {
|
|
110
|
+
process.stderr.write(`${line}\n`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result.exitCode;
|
|
115
|
+
}
|
|
65
116
|
const [filePath, error] = resolveFilePath(file, projectRoot);
|
|
66
117
|
if (error !== null || filePath === null) {
|
|
67
118
|
process.stderr.write(`Error: ${error}\n`);
|
|
@@ -7,5 +7,11 @@ export interface WipCapCheck {
|
|
|
7
7
|
}
|
|
8
8
|
/** Resolve WIP cap and decide if promotion is allowed (#1124). */
|
|
9
9
|
export declare function checkWipCap(projectRoot: string, force?: boolean): WipCapCheck;
|
|
10
|
+
/**
|
|
11
|
+
* WIP check for promoting `additional` scopes in one batch (#3011).
|
|
12
|
+
* Single promote is `additional = 1` (same as historic count >= cap refuse).
|
|
13
|
+
* Batch of N refuses when `count + N > cap` unless `--force`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkWipCapForAdditional(projectRoot: string, additional: number, force?: boolean): WipCapCheck;
|
|
10
16
|
export declare function formatWipCapRefusal(check: WipCapCheck): string;
|
|
11
17
|
//# sourceMappingURL=wip-cap-check.d.ts.map
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { countVbriefWip, resolveWipCap } from "../policy/wip.js";
|
|
2
2
|
/** Resolve WIP cap and decide if promotion is allowed (#1124). */
|
|
3
3
|
export function checkWipCap(projectRoot, force = false) {
|
|
4
|
+
return checkWipCapForAdditional(projectRoot, 1, force);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* WIP check for promoting `additional` scopes in one batch (#3011).
|
|
8
|
+
* Single promote is `additional = 1` (same as historic count >= cap refuse).
|
|
9
|
+
* Batch of N refuses when `count + N > cap` unless `--force`.
|
|
10
|
+
*/
|
|
11
|
+
export function checkWipCapForAdditional(projectRoot, additional, force = false) {
|
|
4
12
|
const capResult = resolveWipCap(projectRoot);
|
|
5
13
|
const cap = capResult.cap;
|
|
6
14
|
const count = countVbriefWip(projectRoot);
|
|
7
|
-
const
|
|
15
|
+
const add = Math.max(0, Math.floor(additional));
|
|
16
|
+
// Single-promote parity: refuse when already at/over cap before adding one.
|
|
17
|
+
// Batch: refuse when current + additional would exceed the cap.
|
|
18
|
+
const overCap = add <= 1 ? count >= cap : count + add > cap;
|
|
8
19
|
if (!overCap) {
|
|
9
20
|
return { allowed: true, cap, count, source: capResult.source, forceOverride: false };
|
|
10
21
|
}
|
|
@@ -18,6 +29,7 @@ export function formatWipCapRefusal(check) {
|
|
|
18
29
|
"Either:\n" +
|
|
19
30
|
" task scope:demote <existing> # return one to proposed/\n" +
|
|
20
31
|
" task scope:demote --batch --older-than-days 30 # bulk relief (D9 folded into D1)\n" +
|
|
21
|
-
" task scope:promote <file> --force # override (logged)"
|
|
32
|
+
" task scope:promote <file> --force # override (logged)\n" +
|
|
33
|
+
" task scope:promote --batch --force # batch override (#3011)");
|
|
22
34
|
}
|
|
23
35
|
//# sourceMappingURL=wip-cap-check.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type EnvironmentContext } from "../platform/shell-context.js";
|
|
2
|
+
import { type ProbeScmReadinessOptions, type ScmReadinessReport } from "../scm/readiness.js";
|
|
2
3
|
import { type ResolveUserMdResult } from "../user-config/resolve-user-md.js";
|
|
3
4
|
import type { GitRunner } from "./git.js";
|
|
4
5
|
import { type ReleaseAvailabilityProbeOptions } from "./release-availability.js";
|
|
@@ -86,6 +87,12 @@ export interface SessionStartOptions {
|
|
|
86
87
|
readonly allowOptionalNetwork?: boolean;
|
|
87
88
|
/** Process env for network opt-in resolution (tests inject). */
|
|
88
89
|
readonly env?: NodeJS.ProcessEnv;
|
|
90
|
+
/**
|
|
91
|
+
* #2275: SCM tooling + auth readiness probe for mismatched/headless envs.
|
|
92
|
+
* Defaults to probeScmReadiness; shallow on hot path, deep when optional
|
|
93
|
+
* network is enabled. Inject in tests.
|
|
94
|
+
*/
|
|
95
|
+
readonly probeScm?: (options: ProbeScmReadinessOptions) => ScmReadinessReport;
|
|
89
96
|
}
|
|
90
97
|
/** Format preferred `session:start` recovery command for cold vs re-arm (#2992). */
|
|
91
98
|
export declare function formatSessionStartRecoveryCommand(tier?: SessionCeremonyTier): string;
|
|
@@ -9,6 +9,7 @@ import { humanMergeDisclosureLine, resolveHumanMergePolicy, } from "../policy/re
|
|
|
9
9
|
import { resolvePolicy } from "../policy/resolve.js";
|
|
10
10
|
import { maybeFormatProductSignalConsentPrompt } from "../product-signal/consent-prompt.js";
|
|
11
11
|
import { formatFrameworkCommand } from "../render/framework-commands.js";
|
|
12
|
+
import { formatScmReadinessLines, probeScmReadiness, scmReadinessToDict, } from "../scm/readiness.js";
|
|
12
13
|
import { maybeRunStalenessTickler } from "../staleness-tickler/run.js";
|
|
13
14
|
import { runDefaultMode } from "../triage/welcome/default-mode.js";
|
|
14
15
|
import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
|
|
@@ -292,6 +293,44 @@ export function defaultBranchSync(projectRoot, runGit = defaultGitRunner) {
|
|
|
292
293
|
}
|
|
293
294
|
return { branch, upstream, ahead, behind, warning };
|
|
294
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Resolve SCM readiness for session orientation (#2275).
|
|
298
|
+
* Shallow by default (PATH + auth status); deep when optional network is on.
|
|
299
|
+
* Never throws — session:start must not hard-block on SCM absence.
|
|
300
|
+
*/
|
|
301
|
+
function resolveSessionScmReadiness(options, allowOptionalNetwork) {
|
|
302
|
+
const probe = options.probeScm ?? probeScmReadiness;
|
|
303
|
+
try {
|
|
304
|
+
return probe({
|
|
305
|
+
depth: allowOptionalNetwork ? "deep" : "shallow",
|
|
306
|
+
env: options.env,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return {
|
|
311
|
+
ready: false,
|
|
312
|
+
binary: null,
|
|
313
|
+
binaryPath: null,
|
|
314
|
+
authState: "unknown",
|
|
315
|
+
githubAuthMode: "host-gh",
|
|
316
|
+
runtimeMode: "local-unsandboxed",
|
|
317
|
+
injectedTokenPresent: false,
|
|
318
|
+
depth: allowOptionalNetwork ? "deep" : "shallow",
|
|
319
|
+
detail: "SCM readiness probe failed unexpectedly; treating SCM-dependent gates as skipped",
|
|
320
|
+
remediation: null,
|
|
321
|
+
skippedGates: [
|
|
322
|
+
"triage:queue",
|
|
323
|
+
"issue:ingest",
|
|
324
|
+
"pr:*",
|
|
325
|
+
"reconcile:issues",
|
|
326
|
+
"cache:fetch-all",
|
|
327
|
+
"scm:*",
|
|
328
|
+
],
|
|
329
|
+
login: null,
|
|
330
|
+
failureKind: "probe_error",
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
295
334
|
function runReadOnlySessionStart(projectRoot, options, instant, environment) {
|
|
296
335
|
const lines = [];
|
|
297
336
|
const resolveUserMd = options.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
|
|
@@ -301,9 +340,12 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
|
|
|
301
340
|
const userMdLine = userMd.found
|
|
302
341
|
? `USER.md resolved (${userMd.rung}): ${safePath}`
|
|
303
342
|
: safeDiagnostic;
|
|
343
|
+
// #2275: report SCM availability even on read-only alignment (shallow; no network).
|
|
344
|
+
const scm = resolveSessionScmReadiness(options, false);
|
|
304
345
|
lines.push(READ_ONLY_ALIGNMENT_MESSAGE);
|
|
305
346
|
lines.push(userMdLine);
|
|
306
347
|
lines.push(formatEnvironmentContext(environment));
|
|
348
|
+
lines.push(...formatScmReadinessLines(scm));
|
|
307
349
|
const resultPayload = {
|
|
308
350
|
ready: true,
|
|
309
351
|
exit_code: 0,
|
|
@@ -324,6 +366,7 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
|
|
|
324
366
|
diagnostic: userMd.diagnostic,
|
|
325
367
|
},
|
|
326
368
|
environment: environmentContextToDict(environment),
|
|
369
|
+
scm: scmReadinessToDict(scm),
|
|
327
370
|
message: READ_ONLY_RESULT_MESSAGE,
|
|
328
371
|
};
|
|
329
372
|
return { code: 0, payload: resultPayload, lines };
|
|
@@ -364,10 +407,13 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
364
407
|
? `USER.md resolved (${userMd.rung}): ${safePath}`
|
|
365
408
|
: safeDiagnostic;
|
|
366
409
|
const alignmentMessage = `${READ_ONLY_ALIGNMENT_MESSAGE} ${userMdLine}`;
|
|
410
|
+
// #2275: re-arm still reports SCM state (shallow; no network).
|
|
411
|
+
const scm = resolveSessionScmReadiness(options, false);
|
|
367
412
|
const lines = [
|
|
368
413
|
READ_ONLY_ALIGNMENT_MESSAGE,
|
|
369
414
|
userMdLine,
|
|
370
415
|
formatEnvironmentContext(environment),
|
|
416
|
+
...formatScmReadinessLines(scm),
|
|
371
417
|
REARM_SKIPPED_FAT_PATH_MESSAGE,
|
|
372
418
|
];
|
|
373
419
|
// Light branch-policy disclosure (local only) so re-arm still surfaces policy state.
|
|
@@ -484,6 +530,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
484
530
|
diagnostic: userMd.diagnostic,
|
|
485
531
|
},
|
|
486
532
|
environment: environmentContextToDict(environment),
|
|
533
|
+
scm: scmReadinessToDict(scm),
|
|
487
534
|
message: code === 0 ? "session ritual re-armed" : "session ritual re-arm failed",
|
|
488
535
|
},
|
|
489
536
|
lines,
|
|
@@ -594,6 +641,12 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
594
641
|
stepTimings.push({ name: "alignment", duration_ms: 0, skipped: true });
|
|
595
642
|
}
|
|
596
643
|
lines.push(formatEnvironmentContext(environment));
|
|
644
|
+
// #2275: SCM tooling + auth readiness — shallow on hot path, deep with --with-network.
|
|
645
|
+
// Never fails session:start; reports which SCM-dependent gates are skipped.
|
|
646
|
+
const scmStepStarted = performance.now();
|
|
647
|
+
const scm = resolveSessionScmReadiness(options, allowOptionalNetwork);
|
|
648
|
+
lines.push(...formatScmReadinessLines(scm));
|
|
649
|
+
stepTimings.push({ name: "scm_readiness", duration_ms: elapsedMs(scmStepStarted) });
|
|
597
650
|
if (!quickSteps.branch_policy) {
|
|
598
651
|
const stepStarted = performance.now();
|
|
599
652
|
const result = resolvePolicy(projectRoot);
|
|
@@ -793,6 +846,7 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
793
846
|
diagnostic: userMd.diagnostic,
|
|
794
847
|
},
|
|
795
848
|
environment: environmentContextToDict(environment),
|
|
849
|
+
scm: scmReadinessToDict(scm),
|
|
796
850
|
message: code === 0 ? "session ritual recorded" : "session ritual failed",
|
|
797
851
|
};
|
|
798
852
|
// #2994: local process-cost event (best-effort; never blocks ceremony).
|
|
@@ -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",
|
|
@@ -334,8 +334,8 @@
|
|
|
334
334
|
"provenance": true
|
|
335
335
|
},
|
|
336
336
|
"dependencies": {
|
|
337
|
-
"@deftai/directive-content": "^0.
|
|
338
|
-
"@deftai/directive-types": "^0.
|
|
337
|
+
"@deftai/directive-content": "^0.91.0",
|
|
338
|
+
"@deftai/directive-types": "^0.91.0",
|
|
339
339
|
"archiver": "^8.0.0"
|
|
340
340
|
},
|
|
341
341
|
"scripts": {
|