@sabaiway/agent-workflow-kit 14.32.0 → 14.34.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.
@@ -23,11 +23,12 @@ import {
23
23
  patternToProbe,
24
24
  } from './known-footprint.mjs';
25
25
  import { isDirectRun } from './direct-run.mjs';
26
- import { isScratchPlanName, plansInFlight, PLANS_REL, shellQuoteArg } from './review-state.mjs';
26
+ import { isScratchPlanName, plansInFlight, PLANS_REL } from './plan-files.mjs';
27
+ import { shellQuoteArg } from './repo-lex.mjs';
27
28
  import { writeContainedFileAtomic } from './atomic-write.mjs';
28
29
  import { assertContainedRealPath } from './fs-safe.mjs';
29
30
  import { GIT_MAX_BUFFER } from './git-env.mjs';
30
- import { isFinalCapableDeclaration } from './run-gates.mjs';
31
+ import { isFinalCapableDeclaration } from './gates-declaration.mjs';
31
32
  import {
32
33
  WORKTREES_STOP, stop, EXIT, handoffBasename, recordValue, hasControlByte, displayValue,
33
34
  composeProvisionRecordSection, composeLandingValue, composeHandoffStub,
@@ -38,6 +39,8 @@ import {
38
39
  readSatelliteIdentity,
39
40
  } from './satellite-locator.mjs';
40
41
  import { composeSatellitePrompt, resolveSeededPlan } from './worktree-prompt.mjs';
42
+ import { applySlot } from './write-lanes.mjs';
43
+ import { dirCovers } from './declared-paths.mjs';
41
44
 
42
45
  // The record format and the satellite locator now live in leaves the dispatch side reads too — but
43
46
  // every name they took away is re-exported here, so no import site and no asserted error `code`
@@ -577,11 +580,25 @@ export const nearestExistingDir = (path, deps = {}) => {
577
580
  return walk(resolve(path));
578
581
  };
579
582
 
580
- // The ONE canonical probe-dir derivation provision AND the recommendations advisor share:
581
- // resolve through the existing-parent realpath, then walk to the nearest existing dir.
583
+ // The provision preflight's probe-dir derivation: resolve through the existing-parent realpath, then
584
+ // walk to the nearest existing dir (the advisor walks worktreesGrantDir's answer the same way).
582
585
  export const resolveProbeDir = (path, deps = {}) =>
583
586
  nearestExistingDir(realpathThroughExistingParent(path, deps), deps);
584
587
 
588
+ // The worktrees write grant names the parent ITSELF (spec bridges/velocity-profile, part worktrees-grant):
589
+ // the writer, the advisor and the danger check read this one derivation, never resolveProbeDir's ancestor.
590
+ export const worktreesGrantDir = (root, deps = {}) => {
591
+ const realRoot = realpathThroughExistingParent(root, deps);
592
+ const { parentDir } = loadWorktreesConfig(realRoot, deps);
593
+ const dir = realpathThroughExistingParent(parentDir == null ? dirname(realRoot) : resolve(realRoot, parentDir), deps);
594
+ const st = lstatNoFollow(fsOf(deps).lstat, dir);
595
+ if (st !== null && !st.isDirectory()) throw stop(`${CONFIG_REL}: parentDir names an existing non-directory: ${dir}`);
596
+ return dir;
597
+ };
598
+
599
+ export const worktreesGrantHoldsHome = (dir, home, deps = {}) =>
600
+ dirCovers(dir, resolve(home)) || dirCovers(dir, realpathThroughExistingParent(resolve(home), deps));
601
+
585
602
  export const probeParentWritable = (parentDir, deps = {}) => {
586
603
  const { mkdirPlain, rmdir, rand } = fsOf(deps);
587
604
  const probe = join(parentDir, `.aw-write-probe-${rand()}`);
@@ -620,10 +637,13 @@ const quoteOwnTool = () => `'${WORKTREES_TOOL_ABS.replace(/'/g, `'\\''`)}'`;
620
637
  const composeOwnToolPrefix = (root) =>
621
638
  `cd ${shellQuoteArg(root)} && node ${quoteOwnTool()}`;
622
639
 
640
+ const composeGrantRoute = (root) => `the kit's worktrees-dir item grants the configured worktrees parent on your yes: the agent runs ${
641
+ applySlot('worktrees-dir', { toolsDir: dirname(WORKTREES_TOOL_ABS), root }).check
642
+ } and, on your yes on its result, its apply line by the consent route, then runs this command again; when that check refuses, prints nothing to change, or grants a dir that does not hold this command's target, use the terminal fallback below`;
643
+
623
644
  const landWritabilityStop = ({ parentDir, root, slug }) => stop([
624
645
  `the worktrees parent dir is not writable from this session: ${parentDir}`,
625
- 'Arm the ONE-TIME consent (then land runs promptless):',
626
- ` .claude/settings.json → sandbox.filesystem.allowWrite += ${JSON.stringify(parentDir)}`,
646
+ composeGrantRoute(root),
627
647
  'Or run the full command yourself in a plain terminal:',
628
648
  ` ${composeOwnToolPrefix(root)} land ${shellQuoteArg(slug)} --prepare`,
629
649
  ].join('\n'));
@@ -638,8 +658,7 @@ const writabilityStop = ({ parentDir, root, slug, flags }) => {
638
658
  return stop(
639
659
  [
640
660
  `the worktrees parent dir is not writable from this session: ${parentDir}`,
641
- 'Arm the ONE-TIME consent (then every provision/cleanup runs promptless):',
642
- ` .claude/settings.json → sandbox.filesystem.allowWrite += ${JSON.stringify(parentDir)}`,
661
+ composeGrantRoute(root),
643
662
  'Or run the full command yourself in a plain terminal:',
644
663
  ` ${composeProvisionArgv({ root, slug, flags })}`,
645
664
  `(from the target repo root, when that checkout carries the kit at agent-workflow-kit/: node agent-workflow-kit/tools/worktrees.mjs provision ${q(slug)} ${provisionFlagsTail(flags, q).join(' ')})`,
@@ -3150,8 +3169,7 @@ const destructiveRecovery = ({ slug, branch }) =>
3150
3169
 
3151
3170
  const cleanupWritabilityStop = ({ parentDir, root, slug, branch, abandon }) => stop([
3152
3171
  `the worktrees parent dir is not writable from this session: ${parentDir}`,
3153
- 'Arm the ONE-TIME consent (then every provision/cleanup runs promptless):',
3154
- ` .claude/settings.json → sandbox.filesystem.allowWrite += ${JSON.stringify(parentDir)}`,
3172
+ composeGrantRoute(root),
3155
3173
  'Or run the full command yourself in a plain terminal:',
3156
3174
  ` ${composeOwnToolPrefix(root)} ${composeCleanupCommand({ slug, branch, abandon })}`,
3157
3175
  ].join('\n'));
@@ -38,11 +38,15 @@ export const VARIANT_LANES = Object.freeze({
38
38
  'mcp-channel.differing': LANE_HARNESS,
39
39
  agents: LANE_HARNESS,
40
40
  'executor-vehicle': LANE_HARNESS,
41
+ 'executor-vehicle.differing': LANE_HARNESS,
42
+ attribution: LANE_HARNESS,
43
+ 'footprint-global': LANE_HARNESS,
41
44
  'family-freshness': LANE_USER,
42
45
  'adr-store-migration': LANE_CHAT,
43
46
  'sandbox-masks': LANE_CHAT,
44
47
  'sandbox-masks.unfenced-mount': LANE_CHAT,
45
- 'worktrees-dir': LANE_CHAT,
48
+ 'worktrees-dir': LANE_HARNESS,
49
+ 'worktrees-dir.home': LANE_USER,
46
50
  'spec-adoption': LANE_CHAT,
47
51
  'profile-gap': LANE_CHAT,
48
52
  'jev-connect': LANE_USER,
@@ -80,6 +84,7 @@ export const isProtectedPath = (abs, { root, home, hooksDir }) => {
80
84
  };
81
85
 
82
86
  const SETTINGS_WRITES = [SETTINGS_PATH];
87
+ const LOCAL_SETTINGS_WRITES = [join('.claude', 'settings.local.json')];
83
88
  const HOOK_WRITES = [join('.claude', 'hooks', 'agent-workflow-gates.mjs'), ...SETTINGS_WRITES];
84
89
  const STOP_HOOK_WRITES = [join('.claude', 'hooks', 'state-block-guard.mjs'), ...SETTINGS_WRITES];
85
90
  const MCP_WRITES = ['.mcp.json', ...SETTINGS_WRITES];
@@ -105,13 +110,15 @@ const getGuardPaths = (facts) => [preCommitOf(facts), join(facts.root, INSTALLER
105
110
  const formatCommand = (argv) => argv.map(quoteArg).join(' ');
106
111
  const TERMINAL_STEP = 'run in a terminal of your own: ';
107
112
 
108
- const createForm = (file, preview, apply, writes, envDependent = false, userStep = null) => Object.freeze({
113
+ // rejudged: the written value derives from state init's record does not watch, so init re-runs its check before the apply.
114
+ const createForm = (file, preview, apply, writes, envDependent = false, userStep = null, rejudged = false) => Object.freeze({
109
115
  file,
110
116
  preview,
111
117
  apply,
112
118
  writes,
113
119
  envDependent,
114
120
  userStep,
121
+ rejudged,
115
122
  });
116
123
 
117
124
  const CORE_FORM = createForm('velocity-profile.mjs', ['--cwd', '<root>'], ['--apply', '--cwd', '<root>'], SETTINGS_WRITES);
@@ -138,6 +145,10 @@ const FORMS = Object.freeze({
138
145
  ['--replace', '--cwd', '<root>'], ['--replace', '--apply', '--cwd', '<root>'], MCP_WRITES),
139
146
  agents: AGENT_FORM,
140
147
  'executor-vehicle': AGENT_FORM,
148
+ 'executor-vehicle.differing': AGENT_FORM,
149
+ attribution: createForm('attribution.mjs', ['--cwd', '<root>'], ['--apply', '--cwd', '<root>'], SETTINGS_WRITES),
150
+ 'footprint-global': createForm('hide-footprint.mjs',
151
+ ['--remove-global', '--dry-run', '--dir', '<root>'], ['--remove-global', '--dir', '<root>'], [], false, null, true),
141
152
  'sandbox-provision.installable': createForm('autonomy-doctor.mjs', [], ['--apply', '<tuple>'], [], true,
142
153
  (facts, argv) => 'your own step (a sudo install; the doctor prints the --apply line to run next) — '
143
154
  + `${TERMINAL_STEP}cd ${quoteArg(facts.root)} && ${formatCommand(argv.preview)}`),
@@ -157,6 +168,11 @@ const FORMS = Object.freeze({
157
168
  ['--enforcement', '--cwd', '<root>'], ['--apply', '--enforcement', '--cwd', '<root>'], getEnforcementPaths),
158
169
  'commit-guard': createForm('git-hooks.mjs',
159
170
  ['--commit-guard', '--cwd', '<root>'], ['--apply', '--commit-guard', '--cwd', '<root>'], getGuardPaths),
171
+ 'worktrees-dir': createForm('velocity-profile.mjs',
172
+ ['--worktrees', '--cwd', '<root>'], ['--apply', '--worktrees', '--cwd', '<root>'], LOCAL_SETTINGS_WRITES, false, null, true),
173
+ 'worktrees-dir.home': Object.freeze({
174
+ userStep: () => 'your own step — point docs/ai/worktrees.json parentDir at a dedicated directory, then run /agent-workflow-kit recommendations',
175
+ }),
160
176
  });
161
177
 
162
178
  const resolveArgument = (arg, facts) => {
@@ -190,6 +206,11 @@ export const isEnvDependent = (variant) => {
190
206
  return FORMS[variant]?.envDependent === true;
191
207
  };
192
208
 
209
+ export const isRejudged = (variant) => {
210
+ laneOf(variant);
211
+ return FORMS[variant]?.rejudged === true;
212
+ };
213
+
193
214
  export const applySlot = (variant, facts) => {
194
215
  const lane = laneOf(variant);
195
216
  const form = FORMS[variant];