@deftai/directive-core 0.68.1 → 0.70.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 (92) hide show
  1. package/dist/deposit/contain.d.ts +41 -0
  2. package/dist/deposit/contain.js +113 -0
  3. package/dist/deposit/copy-tree.js +8 -2
  4. package/dist/doctor/checks.d.ts +11 -0
  5. package/dist/doctor/checks.js +75 -2
  6. package/dist/doctor/constants.d.ts +9 -0
  7. package/dist/doctor/constants.js +11 -0
  8. package/dist/doctor/main.d.ts +47 -1
  9. package/dist/doctor/main.js +406 -3
  10. package/dist/doctor/manifest.d.ts +20 -0
  11. package/dist/doctor/manifest.js +22 -0
  12. package/dist/doctor/payload-staleness.d.ts +8 -0
  13. package/dist/doctor/payload-staleness.js +11 -8
  14. package/dist/doctor/types.d.ts +56 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +2 -0
  17. package/dist/init-deposit/gitignore.d.ts +18 -0
  18. package/dist/init-deposit/gitignore.js +94 -15
  19. package/dist/init-deposit/headless-manifest.d.ts +87 -0
  20. package/dist/init-deposit/headless-manifest.js +261 -0
  21. package/dist/init-deposit/index.d.ts +3 -0
  22. package/dist/init-deposit/index.js +3 -0
  23. package/dist/init-deposit/init-deposit.js +6 -0
  24. package/dist/init-deposit/init-dispatch.d.ts +90 -0
  25. package/dist/init-deposit/init-dispatch.js +170 -0
  26. package/dist/init-deposit/refresh.d.ts +79 -2
  27. package/dist/init-deposit/refresh.js +182 -5
  28. package/dist/init-deposit/scaffold.d.ts +25 -0
  29. package/dist/init-deposit/scaffold.js +57 -0
  30. package/dist/init-deposit/untrack-core.d.ts +84 -0
  31. package/dist/init-deposit/untrack-core.js +150 -0
  32. package/dist/intake/issue-ingest.d.ts +11 -0
  33. package/dist/intake/issue-ingest.js +86 -11
  34. package/dist/policy/plan-extensions.d.ts +31 -0
  35. package/dist/policy/plan-extensions.js +45 -0
  36. package/dist/policy/wip.d.ts +2 -2
  37. package/dist/policy/wip.js +2 -2
  38. package/dist/resolution/classify.d.ts +41 -0
  39. package/dist/resolution/classify.js +138 -0
  40. package/dist/resolution/engine-ladder.d.ts +98 -0
  41. package/dist/resolution/engine-ladder.js +185 -0
  42. package/dist/resolution/index.d.ts +19 -0
  43. package/dist/resolution/index.js +19 -0
  44. package/dist/resolution/integrity.d.ts +48 -0
  45. package/dist/resolution/integrity.js +80 -0
  46. package/dist/resolution/package-manager.d.ts +77 -0
  47. package/dist/resolution/package-manager.js +103 -0
  48. package/dist/resolution/pin.d.ts +73 -0
  49. package/dist/resolution/pin.js +169 -0
  50. package/dist/resolution/plan.d.ts +66 -0
  51. package/dist/resolution/plan.js +219 -0
  52. package/dist/resolution/skew-policy.d.ts +46 -0
  53. package/dist/resolution/skew-policy.js +120 -0
  54. package/dist/session/session-start.d.ts +2 -0
  55. package/dist/session/session-start.js +28 -1
  56. package/dist/swarm/routing.js +10 -3
  57. package/dist/triage/bulk/index.d.ts +11 -2
  58. package/dist/triage/bulk/index.js +41 -4
  59. package/dist/triage/help/registry-data.d.ts +5 -5
  60. package/dist/triage/help/registry-data.js +18 -5
  61. package/dist/triage/scope/cli.d.ts +2 -1
  62. package/dist/triage/scope/cli.js +30 -6
  63. package/dist/triage/scope/mutations.d.ts +10 -0
  64. package/dist/triage/scope/mutations.js +22 -0
  65. package/dist/triage/welcome/constants.d.ts +1 -2
  66. package/dist/triage/welcome/constants.js +1 -2
  67. package/dist/triage/welcome/index.d.ts +1 -0
  68. package/dist/triage/welcome/index.js +1 -0
  69. package/dist/triage/welcome/onboard.d.ts +35 -0
  70. package/dist/triage/welcome/onboard.js +94 -0
  71. package/dist/umbrella-current-shape/index.d.ts +25 -1
  72. package/dist/umbrella-current-shape/index.js +84 -4
  73. package/dist/user-config/index.d.ts +2 -0
  74. package/dist/user-config/index.js +2 -0
  75. package/dist/user-config/resolve-user-md.d.ts +76 -0
  76. package/dist/user-config/resolve-user-md.js +120 -0
  77. package/dist/vbrief-build/project-definition-io.d.ts +6 -0
  78. package/dist/vbrief-build/project-definition-io.js +7 -2
  79. package/dist/vbrief-validate/precutover.js +7 -3
  80. package/dist/xbrief-migrate/constants.d.ts +10 -0
  81. package/dist/xbrief-migrate/constants.js +19 -0
  82. package/dist/xbrief-migrate/detect.d.ts +34 -0
  83. package/dist/xbrief-migrate/detect.js +33 -0
  84. package/dist/xbrief-migrate/fs-helpers.d.ts +13 -0
  85. package/dist/xbrief-migrate/fs-helpers.js +46 -1
  86. package/dist/xbrief-migrate/index.d.ts +4 -2
  87. package/dist/xbrief-migrate/index.js +4 -2
  88. package/dist/xbrief-migrate/migrate-project.d.ts +23 -1
  89. package/dist/xbrief-migrate/migrate-project.js +97 -8
  90. package/dist/xbrief-migrate/signpost.d.ts +7 -1
  91. package/dist/xbrief-migrate/signpost.js +18 -3
  92. package/package.json +7 -3
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Three-band engine-vs-pin skew policy for the resolution spine (#2264, #2199).
3
+ *
4
+ * Content is always forward-migrated (`update`) before any gate runs; this
5
+ * policy governs whether a globally-reachable engine that differs from the pin
6
+ * may be used, and how loudly:
7
+ *
8
+ * - `engine == pin` -> proceed silently (trace only).
9
+ * - `engine > pin` within the skew window -> proceed, emit a loud delta, `update` first.
10
+ * - `engine > pin` beyond the window -> fail closed non-interactively (require
11
+ * `--accept-engine-jump`; prompt when interactive).
12
+ * `DEFT_ACCEPT_ENGINE_SKEW=1` is the CI escape.
13
+ * - `engine < pin` -> reject the global, fall through the ladder.
14
+ */
15
+ import { compareSemver, parseSemver } from "./pin.js";
16
+ /** Default skew window pre-1.0, measured in minor versions. */
17
+ export const DEFAULT_ENGINE_SKEW_WINDOW = 3;
18
+ /** Environment variable that acts as the CI / non-interactive escape hatch. */
19
+ export const ACCEPT_ENGINE_SKEW_ENV = "DEFT_ACCEPT_ENGINE_SKEW";
20
+ function resolveWindow(opts) {
21
+ const raw = opts.engineSkewWindow;
22
+ if (typeof raw === "number" && Number.isInteger(raw) && raw >= 0)
23
+ return raw;
24
+ return DEFAULT_ENGINE_SKEW_WINDOW;
25
+ }
26
+ function escapeHatchActive(opts) {
27
+ if (opts.acceptEngineJump === true)
28
+ return true;
29
+ const env = opts.env ?? process.env;
30
+ return env[ACCEPT_ENGINE_SKEW_ENV] === "1";
31
+ }
32
+ /**
33
+ * Classify the `engine > pin` skew band. Pre-1.0 (pin major 0) the window is
34
+ * measured in minors; post-1.0 the window collapses to "same major".
35
+ */
36
+ function classifyAheadBand(engine, pin, window) {
37
+ const [engineMajor, engineMinor] = engine;
38
+ const [pinMajor, pinMinor] = pin;
39
+ if (pinMajor === 0) {
40
+ if (engineMajor > 0)
41
+ return "beyond-window";
42
+ return engineMinor - pinMinor <= window ? "within-window" : "beyond-window";
43
+ }
44
+ return engineMajor === pinMajor ? "within-window" : "beyond-window";
45
+ }
46
+ /**
47
+ * Evaluate the three-band skew policy for a reachable engine against the pin.
48
+ * Unparseable versions fail closed (a version we cannot order is not safe to run).
49
+ */
50
+ export function evaluateSkew(engineVersion, pinVersion, opts = {}) {
51
+ const engine = parseSemver(engineVersion);
52
+ const pin = parseSemver(pinVersion);
53
+ if (engine === null || pin === null) {
54
+ return {
55
+ band: "unknown",
56
+ decision: "fail-closed",
57
+ message: `cannot order engine (${engineVersion ?? "unknown"}) against pin (${pinVersion ?? "unknown"}); refusing to run an unorderable engine`,
58
+ requiresUpdateFirst: false,
59
+ escapeHatchUsed: false,
60
+ };
61
+ }
62
+ const cmp = compareSemver(engineVersion, pinVersion);
63
+ if (cmp === 0) {
64
+ return {
65
+ band: "match",
66
+ decision: "proceed-silent",
67
+ message: null,
68
+ requiresUpdateFirst: false,
69
+ escapeHatchUsed: false,
70
+ };
71
+ }
72
+ if (cmp === -1) {
73
+ return {
74
+ band: "engine-behind",
75
+ decision: "reject-global",
76
+ message: `engine ${engineVersion} is behind pin ${pinVersion}; rejecting global engine and falling through the ladder`,
77
+ requiresUpdateFirst: false,
78
+ escapeHatchUsed: false,
79
+ };
80
+ }
81
+ // engine > pin
82
+ const window = resolveWindow(opts);
83
+ const band = classifyAheadBand(engine, pin, window);
84
+ if (band === "within-window") {
85
+ return {
86
+ band,
87
+ decision: "proceed-loud-update",
88
+ message: `engine ${engineVersion} is ahead of pin ${pinVersion} within the skew window; proceeding after content update`,
89
+ requiresUpdateFirst: true,
90
+ escapeHatchUsed: false,
91
+ };
92
+ }
93
+ // beyond-window
94
+ if (escapeHatchActive(opts)) {
95
+ return {
96
+ band,
97
+ decision: "proceed-loud-update",
98
+ message: `engine ${engineVersion} is a large jump ahead of pin ${pinVersion}; accepted via escape hatch (--accept-engine-jump / ${ACCEPT_ENGINE_SKEW_ENV}=1)`,
99
+ requiresUpdateFirst: true,
100
+ escapeHatchUsed: true,
101
+ };
102
+ }
103
+ if (opts.interactive === true) {
104
+ return {
105
+ band,
106
+ decision: "prompt",
107
+ message: `engine ${engineVersion} is a large jump ahead of pin ${pinVersion}; prompt the operator to confirm (or pass --accept-engine-jump)`,
108
+ requiresUpdateFirst: true,
109
+ escapeHatchUsed: false,
110
+ };
111
+ }
112
+ return {
113
+ band,
114
+ decision: "fail-closed",
115
+ message: `engine ${engineVersion} is a large jump ahead of pin ${pinVersion}; failing closed. Re-run with --accept-engine-jump or ${ACCEPT_ENGINE_SKEW_ENV}=1`,
116
+ requiresUpdateFirst: false,
117
+ escapeHatchUsed: false,
118
+ };
119
+ }
120
+ //# sourceMappingURL=skew-policy.js.map
@@ -1,3 +1,4 @@
1
+ import { type ResolveUserMdResult } from "../user-config/resolve-user-md.js";
1
2
  import type { GitRunner } from "./git.js";
2
3
  import { ritualStatePath } from "./ritual-sentinel.js";
3
4
  export declare const QUICK_STEPS: readonly ["alignment", "branch_policy", "triage_welcome"];
@@ -30,6 +31,7 @@ export interface SessionStartOptions {
30
31
  readonly verifyTools?: (output: (line: string) => void) => {
31
32
  exitCode: number;
32
33
  };
34
+ readonly resolveUserMd?: (projectRoot: string) => ResolveUserMdResult;
33
35
  }
34
36
  export declare function parseDeferrals(rawValues: readonly string[]): {
35
37
  deferrals: Record<string, string>;
@@ -4,6 +4,7 @@ import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposi
4
4
  import { disclosureLine } from "../policy/disclosure.js";
5
5
  import { resolvePolicy } from "../policy/resolve.js";
6
6
  import { runDefaultMode } from "../triage/welcome/default-mode.js";
7
+ import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
7
8
  import { verifyRequiredTools } from "../verify-env/verify-tools.js";
8
9
  import { defaultGitRunner, gitHead, worktreePath } from "./git.js";
9
10
  import { newRitualStatePayload, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
@@ -194,10 +195,30 @@ export function runSessionStart(projectRoot, options = {}) {
194
195
  const quickSteps = recordDeferredSteps(QUICK_STEPS, deferrals, instant);
195
196
  const gatedSteps = recordDeferredSteps(GATED_STEPS, deferrals, instant);
196
197
  const lines = [];
198
+ // Resolve USER.md via the shared first-hit-wins resolver so the alignment
199
+ // step finds preferences automatically in mismatched / headless sandboxes
200
+ // with zero manual DEFT_USER_PATH (#2271 / #2124). Never throws: an absent
201
+ // USER.md degrades to a clear diagnostic below.
202
+ const resolveUserMd = options.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
203
+ const userMd = resolveUserMd(projectRoot);
197
204
  if (!quickSteps.alignment) {
198
205
  const message = "Deft Directive active -- AGENTS.md loaded.";
199
- quickSteps.alignment = ritualStep({ ok: true, ts: instant, message });
206
+ // Sanitize newlines on the data-derived path/diagnostic before they land in
207
+ // the ritual-step message / terminal output (matches doctor's CWE-116
208
+ // handling); DEFT_USER_PATH is only trimmed, so an embedded newline could
209
+ // otherwise survive into multi-line output.
210
+ const safePath = userMd.path.replace(/\r?\n/g, " ");
211
+ const safeDiagnostic = userMd.diagnostic.replace(/\r?\n/g, " ");
212
+ const userMdLine = userMd.found
213
+ ? `USER.md resolved (${userMd.rung}): ${safePath}`
214
+ : safeDiagnostic;
215
+ quickSteps.alignment = ritualStep({
216
+ ok: true,
217
+ ts: instant,
218
+ message: `${message} ${userMdLine}`,
219
+ });
200
220
  lines.push(message);
221
+ lines.push(userMdLine);
201
222
  }
202
223
  if (!quickSteps.branch_policy) {
203
224
  const result = resolvePolicy(projectRoot);
@@ -287,6 +308,12 @@ export function runSessionStart(projectRoot, options = {}) {
287
308
  state_path: statePath,
288
309
  quick_steps: quickSteps,
289
310
  gated_steps: gatedSteps,
311
+ user_md: {
312
+ path: userMd.path,
313
+ rung: userMd.rung,
314
+ found: userMd.found,
315
+ diagnostic: userMd.diagnostic,
316
+ },
290
317
  message: code === 0 ? "session ritual recorded" : "session ritual failed",
291
318
  };
292
319
  return { code, payload: resultPayload, lines };
@@ -186,7 +186,9 @@ export function resolveDispatchProvider(environ = process.env) {
186
186
  * Keys that would mutate the prototype chain rather than set an own property
187
187
  * if used as a computed object key. Rejected for provider/role names so a
188
188
  * malicious routing input cannot pollute `Object.prototype` (CodeQL
189
- * js/prototype-polluting-assignment).
189
+ * js/prototype-polluting-assignment). This validation guard produces the
190
+ * caller-facing error; the null-prototype write targets in
191
+ * `writeModelDecision` are the structural backstop CodeQL recognizes.
190
192
  */
191
193
  const FORBIDDEN_ROUTING_KEYS = new Set(["__proto__", "constructor", "prototype"]);
192
194
  function assertSafeRoutingKey(kind, key) {
@@ -203,9 +205,14 @@ export function writeModelDecision(path, provider, role, decision) {
203
205
  assertSafeRoutingKey("provider", provider);
204
206
  assertSafeRoutingKey("role", role);
205
207
  const { data } = loadRoutingFile(path);
206
- const file = data ?? {};
208
+ // Null-prototype write targets so a computed provider/role key can only ever
209
+ // set an own property and can never reach `Object.prototype`, even if the
210
+ // validation guard above regresses. CodeQL's js/prototype-polluting-assignment
211
+ // barrier does not track the interprocedural `assertSafeRoutingKey` guard, so
212
+ // this structural sink is what closes alert #52.
213
+ const file = Object.assign(Object.create(null), data ?? {});
207
214
  const existing = providerBlockOf(file, provider);
208
- const block = existing ?? {};
215
+ const block = Object.assign(Object.create(null), existing ?? {});
209
216
  block[role] = {
210
217
  model: decision.model,
211
218
  mode: decision.mode ??
@@ -88,12 +88,21 @@ export declare function excludeLogged(candidates: Iterable<IssuePayload>, option
88
88
  export declare function bulkAction(actionKey: string, repo: string, options?: BulkActionOptions): number;
89
89
  export declare function createFilesystemCacheModule(): CacheModule;
90
90
  export declare function createFilesystemCandidatesLogModule(logPath?: string): CandidatesLogModule;
91
+ /**
92
+ * Native TypeScript bulk actions module (#2279).
93
+ *
94
+ * The bulk path defaults to this factory instead of {@link createPythonActionsModule}
95
+ * so `triage:bulk-*` never spawns a project-local `scripts/triage_actions.py`
96
+ * (an arbitrary-code-execution trust boundary a malicious checkout could plant).
97
+ * Each verb delegates to the already-native per-issue triage actions
98
+ * (`@deftai/directive-core/triage/actions`, #1725) bound to `createDefaultDeps(projectRoot)`.
99
+ */
100
+ export declare function createNativeActionsModule(projectRoot: string): TriageActionsModule;
91
101
  export declare function createPythonActionsModule(scriptsDir: string): TriageActionsModule;
92
102
  export interface DefaultBulkDepsOptions {
93
103
  readonly cacheRoot?: string;
94
104
  readonly candidatesLogPath?: string;
95
- readonly scriptsDir?: string;
96
- readonly deftRoot?: string;
105
+ readonly projectRoot?: string;
97
106
  }
98
107
  export declare function bulkActionWithDefaults(actionKey: string, repo: string, options?: BulkActionOptions & DefaultBulkDepsOptions): number;
99
108
  //# sourceMappingURL=index.d.ts.map
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
2
2
  import { existsSync, readdirSync, readFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { resolveEvalPath } from "../../layout/resolve.js";
5
+ import { createDefaultDeps, accept as nativeAccept, deferAction as nativeDefer, needsAc as nativeNeedsAc, reject as nativeReject, } from "../actions/index.js";
5
6
  export const ACTION_FN_NAMES = {
6
7
  accept: "accept",
7
8
  reject: "reject",
@@ -402,6 +403,40 @@ export function createFilesystemCandidatesLogModule(logPath = resolveEvalPath(pr
402
403
  },
403
404
  };
404
405
  }
406
+ /**
407
+ * Native TypeScript bulk actions module (#2279).
408
+ *
409
+ * The bulk path defaults to this factory instead of {@link createPythonActionsModule}
410
+ * so `triage:bulk-*` never spawns a project-local `scripts/triage_actions.py`
411
+ * (an arbitrary-code-execution trust boundary a malicious checkout could plant).
412
+ * Each verb delegates to the already-native per-issue triage actions
413
+ * (`@deftai/directive-core/triage/actions`, #1725) bound to `createDefaultDeps(projectRoot)`.
414
+ */
415
+ export function createNativeActionsModule(projectRoot) {
416
+ const deps = createDefaultDeps(projectRoot);
417
+ return {
418
+ accept(n, repo) {
419
+ nativeAccept(n, repo, deps, { projectRoot });
420
+ },
421
+ reject(n, repo, ...args) {
422
+ let reason = "";
423
+ const first = args[0];
424
+ if (typeof first === "object" && first !== null && "reason" in first) {
425
+ reason = String(first.reason);
426
+ }
427
+ else if (typeof first === "string") {
428
+ reason = first;
429
+ }
430
+ nativeReject(n, repo, reason, deps, { projectRoot });
431
+ },
432
+ defer(n, repo) {
433
+ nativeDefer(n, repo, "bulk defer", deps, { projectRoot });
434
+ },
435
+ needs_ac(n, repo) {
436
+ nativeNeedsAc(n, repo, deps, { projectRoot });
437
+ },
438
+ };
439
+ }
405
440
  export function createPythonActionsModule(scriptsDir) {
406
441
  const runAction = (cmd, issueNumber, repo, extra = []) => {
407
442
  const result = spawnSync("uv", [
@@ -442,14 +477,16 @@ export function createPythonActionsModule(scriptsDir) {
442
477
  };
443
478
  }
444
479
  export function bulkActionWithDefaults(actionKey, repo, options = {}) {
445
- const deftRoot = options.deftRoot ?? process.cwd();
446
- const scriptsDir = options.scriptsDir ?? join(deftRoot, "scripts");
480
+ // #2279: the bulk path no longer resolves a `scripts/` dir from DEFT_ROOT /
481
+ // the project cwd. It defaults to the native TypeScript actions module so a
482
+ // malicious project-local `scripts/triage_actions.py` can never be executed.
483
+ const projectRoot = options.projectRoot ?? process.cwd();
447
484
  return bulkAction(actionKey, repo, {
448
485
  ...options,
449
- cacheRoot: options.cacheRoot ?? join(deftRoot, ".deft-cache"),
486
+ cacheRoot: options.cacheRoot ?? join(projectRoot, ".deft-cache"),
450
487
  cacheModule: options.cacheModule ?? createFilesystemCacheModule(),
451
488
  candidatesLogModule: options.candidatesLogModule ?? createFilesystemCandidatesLogModule(options.candidatesLogPath),
452
- actionsModule: options.actionsModule ?? createPythonActionsModule(scriptsDir),
489
+ actionsModule: options.actionsModule ?? createNativeActionsModule(projectRoot),
453
490
  });
454
491
  }
455
492
  //# sourceMappingURL=index.js.map
@@ -191,11 +191,11 @@ export declare const registryData: {
191
191
  readonly "task triage:welcome": {
192
192
  readonly name: "task triage:welcome";
193
193
  readonly summary: "Single-entry-point upgrade ritual";
194
- readonly refs: "(N3 / #1143)";
195
- readonly description: "6-phase onboarding ritual: detect prior state, prompt for subscription scope, run triage:bootstrap, prompt for wipCap, offer WIP relief, print triage:summary. Idempotent on re-run; safe entrypoint for fresh consumers.";
196
- readonly usage: "task triage:welcome [-- --no-subprocess]";
197
- readonly flags: readonly [readonly ["--no-subprocess", "(off)", "Dry-mode: don't shell out to sibling tasks."]];
198
- readonly examples: readonly ["task triage:welcome"];
194
+ readonly refs: "(N3 / #1143, #2295)";
195
+ readonly description: "6-phase onboarding ritual: detect prior state, prompt for subscription scope, run triage:bootstrap, prompt for wipCap, offer WIP relief, print triage:summary. Idempotent on re-run; safe entrypoint for fresh consumers. Pass --onboard for the non-interactive path that applies a triage-scope preset (and optional --wip-cap) without prompting -- the form agents/CI use.";
196
+ readonly usage: "task triage:welcome [-- --onboard [--preset small|mid|mega] [--wip-cap N]] [--no-subprocess]";
197
+ readonly flags: readonly [readonly ["--onboard", "(off)", "Non-interactive onboarding: apply a triage-scope preset (and optional WIP cap) without prompting."], readonly ["--preset small|mid|mega", "small", "Subscription-scope preset applied by --onboard."], readonly ["--wip-cap N", "(policy default)", "Persist an explicit in-flight scope cap during --onboard (positive integer)."], readonly ["--no-subprocess", "(off)", "Dry-mode: don't shell out to sibling tasks."]];
198
+ readonly examples: readonly ["task triage:welcome", "task triage:welcome -- --onboard --preset small"];
199
199
  readonly see_also: readonly ["task triage:bootstrap", "task triage:summary", "#1119 / N3"];
200
200
  readonly placeholder: false;
201
201
  };
@@ -296,11 +296,24 @@ export const registryData = {
296
296
  "task triage:welcome": {
297
297
  name: "task triage:welcome",
298
298
  summary: "Single-entry-point upgrade ritual",
299
- refs: "(N3 / #1143)",
300
- description: "6-phase onboarding ritual: detect prior state, prompt for subscription scope, run triage:bootstrap, prompt for wipCap, offer WIP relief, print triage:summary. Idempotent on re-run; safe entrypoint for fresh consumers.",
301
- usage: "task triage:welcome [-- --no-subprocess]",
302
- flags: [["--no-subprocess", "(off)", "Dry-mode: don't shell out to sibling tasks."]],
303
- examples: ["task triage:welcome"],
299
+ refs: "(N3 / #1143, #2295)",
300
+ description: "6-phase onboarding ritual: detect prior state, prompt for subscription scope, run triage:bootstrap, prompt for wipCap, offer WIP relief, print triage:summary. Idempotent on re-run; safe entrypoint for fresh consumers. Pass --onboard for the non-interactive path that applies a triage-scope preset (and optional --wip-cap) without prompting -- the form agents/CI use.",
301
+ usage: "task triage:welcome [-- --onboard [--preset small|mid|mega] [--wip-cap N]] [--no-subprocess]",
302
+ flags: [
303
+ [
304
+ "--onboard",
305
+ "(off)",
306
+ "Non-interactive onboarding: apply a triage-scope preset (and optional WIP cap) without prompting.",
307
+ ],
308
+ ["--preset small|mid|mega", "small", "Subscription-scope preset applied by --onboard."],
309
+ [
310
+ "--wip-cap N",
311
+ "(policy default)",
312
+ "Persist an explicit in-flight scope cap during --onboard (positive integer).",
313
+ ],
314
+ ["--no-subprocess", "(off)", "Dry-mode: don't shell out to sibling tasks."],
315
+ ],
316
+ examples: ["task triage:welcome", "task triage:welcome -- --onboard --preset small"],
304
317
  see_also: ["task triage:bootstrap", "task triage:summary", "#1119 / N3"],
305
318
  placeholder: false,
306
319
  },
@@ -6,13 +6,14 @@ export interface ParsedCliArgs {
6
6
  addLabel: string | undefined;
7
7
  addMilestone: string | undefined;
8
8
  ignoreLabel: string | undefined;
9
+ setPreset: string | undefined;
9
10
  diffFromUpstream: boolean;
10
11
  source: string;
11
12
  cacheRoot: string | undefined;
12
13
  count: number | undefined;
13
14
  }
14
15
  export declare function parseCliArgs(argv: string[]): ParsedCliArgs;
15
- export declare const CLI_HELP = "usage: triage_scope.py [-h] [--project-root PROJECT_ROOT] [--list]\n [--refresh-denominator] [--repo REPO]\n [--add-label ADD_LABEL] [--add-milestone ADD_MILESTONE]\n [--ignore-label IGNORE_LABEL] [--diff-from-upstream]\n [--source SOURCE] [--cache-root CACHE_ROOT]\n [--count COUNT]\n\nInspect, mutate, and diff the typed plan.policy.triageScope[] subscription +\nplan.policy.triageScopeIgnores[] (#1131 / D12, #1133 / D14, #1182 / D14c).\nRead paths never trigger a recompute; use --refresh-denominator to update the\ncoverage cache. Mutation flags --add-label / --add-milestone / --ignore-label\nare idempotent and atomic; every mutation appends a subscription-change audit\nentry to vbrief/.eval/subscription-history.jsonl.\n";
16
+ export declare const CLI_HELP = "usage: triage_scope.py [-h] [--project-root PROJECT_ROOT] [--list]\n [--refresh-denominator] [--repo REPO]\n [--add-label ADD_LABEL] [--add-milestone ADD_MILESTONE]\n [--ignore-label IGNORE_LABEL] [--set-preset PRESET]\n [--diff-from-upstream]\n [--source SOURCE] [--cache-root CACHE_ROOT]\n [--count COUNT]\n\nInspect, mutate, and diff the typed plan.policy.triageScope[] subscription +\nplan.policy.triageScopeIgnores[] (#1131 / D12, #1133 / D14, #1182 / D14c).\nRead paths never trigger a recompute; use --refresh-denominator to update the\ncoverage cache. Mutation flags --add-label / --add-milestone / --ignore-label /\n--set-preset are idempotent and atomic; every mutation appends a\nsubscription-change audit entry to vbrief/.eval/subscription-history.jsonl.\n--set-preset small|mid|mega overwrites plan.policy.triageScope[] with a named\nsubscription preset via the same writer that backs triage:welcome --onboard (#2301).\n";
16
17
  /** Run triage:scope CLI; returns exit code and captured output for parity. */
17
18
  export declare function runCliCapture(argv: string[]): {
18
19
  code: number;
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { coveragePath, writeCoverageDenominator } from "./coverage.js";
5
- import { addLabelToIgnores, addLabelToScope, addMilestoneToScope, computeDiffFromUpstream, fetchUpstreamLabelsAndMilestones, renderDiffReport, } from "./mutations.js";
5
+ import { addLabelToIgnores, addLabelToScope, addMilestoneToScope, computeDiffFromUpstream, fetchUpstreamLabelsAndMilestones, renderDiffReport, setScopePreset, } from "./mutations.js";
6
6
  import { subscriptionHash } from "./normalize.js";
7
7
  import { pyListRepr } from "./python-repr.js";
8
8
  import { renderIgnores, renderList } from "./renderers.js";
@@ -17,6 +17,7 @@ export function parseCliArgs(argv) {
17
17
  addLabel: undefined,
18
18
  addMilestone: undefined,
19
19
  ignoreLabel: undefined,
20
+ setPreset: undefined,
20
21
  diffFromUpstream: false,
21
22
  source: "github-issue",
22
23
  cacheRoot: undefined,
@@ -60,6 +61,12 @@ export function parseCliArgs(argv) {
60
61
  }
61
62
  else if (arg?.startsWith("--ignore-label="))
62
63
  parsed.ignoreLabel = arg.slice("--ignore-label=".length);
64
+ else if (arg === "--set-preset") {
65
+ parsed.setPreset = argv[i + 1];
66
+ i += 1;
67
+ }
68
+ else if (arg?.startsWith("--set-preset="))
69
+ parsed.setPreset = arg.slice("--set-preset=".length);
63
70
  else if (arg === "--source") {
64
71
  parsed.source = argv[i + 1] ?? "github-issue";
65
72
  i += 1;
@@ -86,16 +93,19 @@ export function parseCliArgs(argv) {
86
93
  export const CLI_HELP = `usage: triage_scope.py [-h] [--project-root PROJECT_ROOT] [--list]
87
94
  [--refresh-denominator] [--repo REPO]
88
95
  [--add-label ADD_LABEL] [--add-milestone ADD_MILESTONE]
89
- [--ignore-label IGNORE_LABEL] [--diff-from-upstream]
96
+ [--ignore-label IGNORE_LABEL] [--set-preset PRESET]
97
+ [--diff-from-upstream]
90
98
  [--source SOURCE] [--cache-root CACHE_ROOT]
91
99
  [--count COUNT]
92
100
 
93
101
  Inspect, mutate, and diff the typed plan.policy.triageScope[] subscription +
94
102
  plan.policy.triageScopeIgnores[] (#1131 / D12, #1133 / D14, #1182 / D14c).
95
103
  Read paths never trigger a recompute; use --refresh-denominator to update the
96
- coverage cache. Mutation flags --add-label / --add-milestone / --ignore-label
97
- are idempotent and atomic; every mutation appends a subscription-change audit
98
- entry to vbrief/.eval/subscription-history.jsonl.
104
+ coverage cache. Mutation flags --add-label / --add-milestone / --ignore-label /
105
+ --set-preset are idempotent and atomic; every mutation appends a
106
+ subscription-change audit entry to vbrief/.eval/subscription-history.jsonl.
107
+ --set-preset small|mid|mega overwrites plan.policy.triageScope[] with a named
108
+ subscription preset via the same writer that backs triage:welcome --onboard (#2301).
99
109
  `;
100
110
  function handleMutation(projectRoot, args) {
101
111
  const out = { stdout: [], stderr: [] };
@@ -115,6 +125,10 @@ function handleMutation(projectRoot, args) {
115
125
  [changed, message] = addLabelToIgnores(projectRoot, args.ignoreLabel);
116
126
  verb = "ignore-label";
117
127
  }
128
+ else if (args.setPreset !== undefined) {
129
+ [changed, message] = setScopePreset(projectRoot, args.setPreset);
130
+ verb = "set-preset";
131
+ }
118
132
  else {
119
133
  throw new Error("internal: mutation flag set but no handler matched");
120
134
  }
@@ -148,12 +162,13 @@ export function runCliCapture(argv) {
148
162
  args.addLabel !== undefined ? "--add-label" : null,
149
163
  args.addMilestone !== undefined ? "--add-milestone" : null,
150
164
  args.ignoreLabel !== undefined ? "--ignore-label" : null,
165
+ args.setPreset !== undefined ? "--set-preset" : null,
151
166
  ].filter((f) => f !== null);
152
167
  if (mutationFlags.length > 1) {
153
168
  return {
154
169
  code: 2,
155
170
  stdout: "",
156
- stderr: "triage:scope: --add-label / --add-milestone / --ignore-label " +
171
+ stderr: "triage:scope: --add-label / --add-milestone / --ignore-label / --set-preset " +
157
172
  `are mutually exclusive (got ${pyListRepr(mutationFlags)}).\n`,
158
173
  };
159
174
  }
@@ -171,6 +186,15 @@ export function runCliCapture(argv) {
171
186
  if (mutation.code !== 0) {
172
187
  return { code: mutation.code, stdout: stdout.join(""), stderr: stderr.join("") };
173
188
  }
189
+ // A preset write comes from the trusted SUBSCRIPTION_PRESETS constant (the
190
+ // same source the onboard path writes). The `mega` preset intentionally
191
+ // ships an `explicit-watch` scaffold with an empty `issues` list for the
192
+ // operator to fill in, which the strict hand-edit validator below flags.
193
+ // Bypass that re-validation for a preset write so `--set-preset` reports the
194
+ // successful, persisted mutation instead of a spurious exit 1 (#2301).
195
+ if (args.setPreset !== undefined) {
196
+ return { code: 0, stdout: stdout.join(""), stderr: stderr.join("") };
197
+ }
174
198
  }
175
199
  const data = loadProjectDefinition(projectRoot);
176
200
  const rules = resolveScopeRules(projectRoot, data);
@@ -18,4 +18,14 @@ export declare function fetchUpstreamLabelsAndMilestones(repo: string, binary?:
18
18
  export declare function addLabelToScope(projectRoot: string, label: string, actor?: string | null): [boolean, string];
19
19
  export declare function addMilestoneToScope(projectRoot: string, milestone: string, actor?: string | null): [boolean, string];
20
20
  export declare function addLabelToIgnores(projectRoot: string, label: string): [boolean, string];
21
+ /** Valid subscription preset keys for `triage:scope --set-preset` (#2301). */
22
+ export declare function scopePresetKeys(): string[];
23
+ /**
24
+ * Overwrite plan.policy.triageScope with a named subscription preset (#2301).
25
+ * Reuses the shared `subscriptionPreset` + `writeTriageScope` helper that backs
26
+ * `triage:welcome --onboard`, so the write path (namespaced key, legacy-key
27
+ * migration, audit trail, mutation lock) stays identical across surfaces rather
28
+ * than being duplicated here.
29
+ */
30
+ export declare function setScopePreset(projectRoot: string, presetKey: string): [boolean, string];
21
31
  //# sourceMappingURL=mutations.d.ts.map
@@ -1,5 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { SUBPROCESS_MAX_BUFFER } from "../../subprocess/max-buffer.js";
3
+ import { SUBSCRIPTION_PRESETS } from "../welcome/constants.js";
4
+ import { subscriptionPreset, writeTriageScope } from "../welcome/writers.js";
3
5
  import { collectMilestoneSubscribedNames, rulesRequestIsOpen } from "./milestone.js";
4
6
  import { addIgnore, subscribe } from "./mutations-core.js";
5
7
  import { resolveScopeIgnores, resolveScopeRules } from "./resolve.js";
@@ -200,4 +202,24 @@ export function addLabelToIgnores(projectRoot, label) {
200
202
  throw new Error(`label must be a non-empty string; got ${JSON.stringify(label)}`);
201
203
  return addIgnore(projectRoot, label);
202
204
  }
205
+ /** Valid subscription preset keys for `triage:scope --set-preset` (#2301). */
206
+ export function scopePresetKeys() {
207
+ return Object.keys(SUBSCRIPTION_PRESETS);
208
+ }
209
+ /**
210
+ * Overwrite plan.policy.triageScope with a named subscription preset (#2301).
211
+ * Reuses the shared `subscriptionPreset` + `writeTriageScope` helper that backs
212
+ * `triage:welcome --onboard`, so the write path (namespaced key, legacy-key
213
+ * migration, audit trail, mutation lock) stays identical across surfaces rather
214
+ * than being duplicated here.
215
+ */
216
+ export function setScopePreset(projectRoot, presetKey) {
217
+ const key = presetKey.trim();
218
+ const validKeys = scopePresetKeys();
219
+ if (!validKeys.includes(key)) {
220
+ throw new Error(`unknown preset ${JSON.stringify(presetKey)}; valid presets: ${validKeys.join(", ")}`);
221
+ }
222
+ const rules = subscriptionPreset(key);
223
+ return writeTriageScope(projectRoot, rules, { presetLabel: key });
224
+ }
203
225
  //# sourceMappingURL=mutations.js.map
@@ -1,10 +1,9 @@
1
- export declare const PROJECT_DEFINITION_REL_PATH = "vbrief/PROJECT-DEFINITION.vbrief.json";
2
1
  export declare const CACHE_DIR_NAME = ".deft-cache";
3
2
  export declare const CACHE_SOURCE = "github-issue";
4
3
  export declare const CANDIDATES_RELPATH: readonly ["vbrief", ".eval", "candidates.jsonl"];
5
4
  export declare const WIP_LIFECYCLE_DIRS: readonly ["pending", "active"];
6
5
  export declare const AUDIT_LOG_REL_PATH = "meta/policy-changes.log";
7
- export declare const DEFAULT_WIP_CAP = 10;
6
+ export declare const DEFAULT_WIP_CAP = 20;
8
7
  export declare const DEFAULT_RELIEF_AGE_DAYS = 30;
9
8
  export declare const TRIAGE_SKILL_PATH = "skills/deft-directive-triage/SKILL.md";
10
9
  export declare const WELCOME_AUDIT_TAG = "triage-welcome";
@@ -1,10 +1,9 @@
1
- export const PROJECT_DEFINITION_REL_PATH = "vbrief/PROJECT-DEFINITION.vbrief.json";
2
1
  export const CACHE_DIR_NAME = ".deft-cache";
3
2
  export const CACHE_SOURCE = "github-issue";
4
3
  export const CANDIDATES_RELPATH = ["vbrief", ".eval", "candidates.jsonl"];
5
4
  export const WIP_LIFECYCLE_DIRS = ["pending", "active"];
6
5
  export const AUDIT_LOG_REL_PATH = "meta/policy-changes.log";
7
- export const DEFAULT_WIP_CAP = 10;
6
+ export const DEFAULT_WIP_CAP = 20;
8
7
  export const DEFAULT_RELIEF_AGE_DAYS = 30;
9
8
  export const TRIAGE_SKILL_PATH = "skills/deft-directive-triage/SKILL.md";
10
9
  export const WELCOME_AUDIT_TAG = "triage-welcome";
@@ -1,5 +1,6 @@
1
1
  export * from "./constants.js";
2
2
  export { type DefaultModeOptions, formatWelcomeCommand, normalizeTaskPrefix, runDefaultMode, type WelcomeOutcome, } from "./default-mode.js";
3
+ export { DEFAULT_ONBOARD_PRESET, type OnboardOptions, type OnboardOutcome, runOnboardMode, } from "./onboard.js";
3
4
  export { candidatesLogPath, classifyOnboarding, detectPriorState, type PriorState, pendingDecisionsNudgeLine, } from "./prior-state.js";
4
5
  export { computeSummary, emitOneliner, formatOneLiner, formatSummary, type SummaryResult, } from "./summary.js";
5
6
  export { appendAuditEntry, previewWipRelief, type ReliefPreview, subscriptionPreset, writeTriageScope, writeWipCap, } from "./writers.js";
@@ -1,5 +1,6 @@
1
1
  export * from "./constants.js";
2
2
  export { formatWelcomeCommand, normalizeTaskPrefix, runDefaultMode, } from "./default-mode.js";
3
+ export { DEFAULT_ONBOARD_PRESET, runOnboardMode, } from "./onboard.js";
3
4
  export { candidatesLogPath, classifyOnboarding, detectPriorState, pendingDecisionsNudgeLine, } from "./prior-state.js";
4
5
  export { computeSummary, emitOneliner, formatOneLiner, formatSummary, } from "./summary.js";
5
6
  export { appendAuditEntry, previewWipRelief, subscriptionPreset, writeTriageScope, writeWipCap, } from "./writers.js";
@@ -0,0 +1,35 @@
1
+ /** Default triage-scope preset applied when `--onboard` is run without `--preset`. */
2
+ export declare const DEFAULT_ONBOARD_PRESET = "small";
3
+ export interface OnboardOptions {
4
+ /** Triage-scope preset key; defaults to {@link DEFAULT_ONBOARD_PRESET}. */
5
+ readonly preset?: string | null;
6
+ /** Explicit WIP cap to persist; omitted keeps the current/default cap. */
7
+ readonly wipCap?: number | null;
8
+ readonly output?: (line: string) => void;
9
+ readonly writeHistory?: boolean;
10
+ readonly taskPrefix?: string | null;
11
+ readonly selfHealFn?: (projectRoot: string) => void;
12
+ readonly now?: Date;
13
+ }
14
+ export interface OnboardOutcome {
15
+ readonly exitCode: number;
16
+ readonly presetApplied: string | null;
17
+ readonly triageScopeChanged: boolean;
18
+ readonly wipCapApplied: number | null;
19
+ readonly wipCapChanged: boolean;
20
+ readonly reliefOffered: boolean;
21
+ }
22
+ /**
23
+ * Non-interactive onboarding for `deft triage:welcome --onboard` (#2295).
24
+ *
25
+ * The welcome nudges point every fresh consumer at `--onboard`; this wires that
26
+ * command to the already-tested core writers instead of the old "not
27
+ * implemented" stub. It writes the chosen triage-scope preset and (optionally)
28
+ * a WIP cap via {@link writeTriageScope} / {@link writeWipCap} -- which already
29
+ * target the canonical `x-directive/policy` key and migrate any legacy bare
30
+ * block -- previews WIP relief when at/over cap, then prints a completion
31
+ * summary and the next-step guidance. Flag-driven by design: agents/CI pass
32
+ * `--preset` / `--wip-cap`; sensible defaults apply otherwise.
33
+ */
34
+ export declare function runOnboardMode(projectRoot: string, options?: OnboardOptions): OnboardOutcome;
35
+ //# sourceMappingURL=onboard.d.ts.map