@sabaiway/agent-workflow-kit 14.23.0 → 14.25.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/CHANGELOG.md +55 -0
- package/README.md +16 -5
- package/SKILL.md +1 -1
- package/bin/install.mjs +15 -15
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/bootstrap.md +5 -1
- package/references/modes/mcp.md +9 -4
- package/references/modes/recommendations.md +20 -11
- package/references/modes/upgrade.md +3 -2
- package/references/modes/velocity.md +1 -1
- package/tools/ack-write.mjs +1 -1
- package/tools/bridge-sandbox-recipe.mjs +40 -0
- package/tools/init-preflight.mjs +92 -0
- package/tools/init-project.mjs +400 -0
- package/tools/manifest/schema.md +9 -9
- package/tools/manifest/validate.mjs +5 -5
- package/tools/mcp.mjs +77 -19
- package/tools/recommendations.mjs +71 -108
- package/tools/velocity-profile.mjs +64 -14
- package/tools/write-lanes.mjs +171 -0
|
@@ -14,6 +14,7 @@ import { findOnPath } from './detect-backends.mjs';
|
|
|
14
14
|
import { SHELL_METACHARACTERS, hasShellMetacharacter, isSeedablePathToken } from './repo-lex.mjs';
|
|
15
15
|
import { isDirectRun } from './direct-run.mjs';
|
|
16
16
|
import { compareSemver } from './semver-lite.mjs';
|
|
17
|
+
import { bundledSandboxRecipe } from './bridge-sandbox-recipe.mjs';
|
|
17
18
|
// The declared-path resolution + segment containment the allowWrite degrade shares with the
|
|
18
19
|
// advisor's worktrees-dir convergence lane — ONE leaf, so the two readings cannot drift.
|
|
19
20
|
import { resolveDeclaredDir, dirCovers, isResolvableDeclaredEntry } from './declared-paths.mjs';
|
|
@@ -388,9 +389,11 @@ dry-run preview byte-strings. Never touches settings.local.json.
|
|
|
388
389
|
--bridge-tier (own consent) seeds the bridge REVIEW wrappers' CODE mode for PLACED bridges
|
|
389
390
|
(codex-review code, agy-review code - never the execution/probe wrappers, never plan/diff modes)
|
|
390
391
|
+ the quoted grounding pre-step rule, and the wrapper names into sandbox.excludedCommands (they
|
|
391
|
-
need network - ${HOST_HONORS_QUALIFIER} the harness runs them outside the sandbox).
|
|
392
|
-
|
|
393
|
-
|
|
392
|
+
need network - ${HOST_HONORS_QUALIFIER} the harness runs them outside the sandbox). It also
|
|
393
|
+
seeds the placed bridges' declared hosts into sandbox.network.allowedDomains - ${HOST_HONORS_QUALIFIER}
|
|
394
|
+
every sandboxed command of the project can then reach those hosts; the writable state dirs stay
|
|
395
|
+
hand-apply. Consented posture: an auto-allowed review wrapper runs UNATTENDED and sends the
|
|
396
|
+
assembled repo payload to its subscription backend (see the printed tier notice).
|
|
394
397
|
|
|
395
398
|
--autonomy renders docs/ai/autonomy.json into the settings blocks it OWNS — the sandbox block +
|
|
396
399
|
permissions.ask/deny red-lines + permissions.defaultMode. POLICY-ONLY: never seeds the allowlist and
|
|
@@ -634,7 +637,7 @@ const assertTargetWritable = (absPath, deps = {}) => {
|
|
|
634
637
|
|
|
635
638
|
const formatJson = (data, eol) => `${JSON.stringify(data, null, SETTINGS_JSON_INDENT).replace(JSON_NEWLINE_PATTERN, eol)}${eol}`;
|
|
636
639
|
|
|
637
|
-
const mergeProjectSettings = (projectData, toAdd, acceptEdits, excludedToAdd = []) => {
|
|
640
|
+
const mergeProjectSettings = (projectData, toAdd, acceptEdits, excludedToAdd = [], hostsToAdd = []) => {
|
|
638
641
|
const base = projectData ?? {};
|
|
639
642
|
const permissions = getPermissions(base);
|
|
640
643
|
const allow = getAllowEntries(base);
|
|
@@ -644,15 +647,26 @@ const mergeProjectSettings = (projectData, toAdd, acceptEdits, excludedToAdd = [
|
|
|
644
647
|
allow: mergedAllow,
|
|
645
648
|
...(acceptEdits === true ? { defaultMode: ACCEPT_EDITS_MODE } : {}),
|
|
646
649
|
};
|
|
647
|
-
// The bridge tier's second
|
|
648
|
-
// entries and sandbox sub-keys
|
|
649
|
-
//
|
|
650
|
+
// The bridge tier's second and third surfaces: sandbox.excludedCommands and
|
|
651
|
+
// sandbox.network.allowedDomains (merge-don't-clobber — foreign entries and sandbox sub-keys
|
|
652
|
+
// preserved; only the tier's entries append, deduped). The flagless/kit-tools paths pass no
|
|
653
|
+
// entries, so the sandbox block stays untouched for them.
|
|
650
654
|
const existingSandbox = isJsonObject(base.sandbox) ? base.sandbox : {};
|
|
651
655
|
const existingExcluded = Array.isArray(existingSandbox.excludedCommands) ? existingSandbox.excludedCommands : [];
|
|
652
656
|
const newExcluded = excludedToAdd.filter((cmd) => !existingExcluded.includes(cmd));
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
657
|
+
const existingNetwork = isJsonObject(existingSandbox.network) ? existingSandbox.network : {};
|
|
658
|
+
const existingHosts = Array.isArray(existingNetwork.allowedDomains) ? existingNetwork.allowedDomains : [];
|
|
659
|
+
const newHosts = hostsToAdd.filter((host) => !existingHosts.includes(host));
|
|
660
|
+
const sandboxBlock =
|
|
661
|
+
newExcluded.length || newHosts.length
|
|
662
|
+
? {
|
|
663
|
+
sandbox: {
|
|
664
|
+
...existingSandbox,
|
|
665
|
+
...(newExcluded.length ? { excludedCommands: [...existingExcluded, ...newExcluded] } : {}),
|
|
666
|
+
...(newHosts.length ? { network: { ...existingNetwork, allowedDomains: [...existingHosts, ...newHosts] } } : {}),
|
|
667
|
+
},
|
|
668
|
+
}
|
|
669
|
+
: {};
|
|
656
670
|
return { ...base, permissions: mergedPermissions, ...sandboxBlock };
|
|
657
671
|
};
|
|
658
672
|
|
|
@@ -683,9 +697,11 @@ const formatKitTier = (result) =>
|
|
|
683
697
|
// resolution states the exfiltration surface, never pretends it away.
|
|
684
698
|
export const KIT_BRIDGE_TIER_NOTICE =
|
|
685
699
|
'bridge-wrappers tier: seeds the REVIEW wrappers only, and only their CODE mode (`codex-review code`, `agy-review code` — never codex-exec/agy-run: delegated execution keeps its human prompt; never the plan/diff modes: their file arguments can point outside the repo, so they keep their prompt), each derived ONLY when its bridge is PLACED on PATH, plus the grounding pre-step rule in its rendered quoted byte-form. POSTURE (what this consent covers): an auto-allowed review wrapper runs UNATTENDED — it reads any repo file it is pointed at and sends the assembled payload to its subscription backend, and prefix rules cannot inspect arguments, so a code-mode argument that names a readable file (agy\'s --facts/--decided) rides the same consent — the same documented residual class as the autonomy red-line rules; that is the tier\'s PURPOSE (unattended council review runs) and its residual — tier entries get NO PreToolUse-hook coverage. The grounding entry\'s writer surface is bounded by grounding.mjs\'s OWN scratch-destination guard (a tracked or in-repo-not-ignored --out is refused by the tool). The wrapper names are ALSO seeded into sandbox.excludedCommands IN THE PROJECT settings.json (an exclusion only in settings.local.json was live-observed NOT to route — the wrapper then runs sandboxed and dies on a read-only HOME): ' +
|
|
686
|
-
// The
|
|
700
|
+
// The interpolated seams in this notice: the shared host-conditional qualifier, so the tier's
|
|
687
701
|
// routing promise and the render's degrade lines can never drift apart.
|
|
688
|
-
`${HOST_HONORS_QUALIFIER} the harness runs an excluded command OUTSIDE the sandbox (the wrappers need network), so a plain allowlisted invocation triggers no sandbox-bypass approval — whether a host honors them is not knowable from here, and where it does not the wrapper simply starts sandboxed (fail-safe, never a silent widening).
|
|
702
|
+
`${HOST_HONORS_QUALIFIER} the harness runs an excluded command OUTSIDE the sandbox (the wrappers need network), so a plain allowlisted invocation triggers no sandbox-bypass approval — whether a host honors them is not knowable from here, and where it does not the wrapper simply starts sandboxed (fail-safe, never a silent widening). ` +
|
|
703
|
+
`The placed bridges' declared hosts (each bundled manifest's networkHosts) are seeded into sandbox.network.allowedDomains IN THE PROJECT settings.json: ${HOST_HONORS_QUALIFIER}, a wrapper the harness keeps sandboxed still reaches them, and every sandboxed command of the project can then reach those hosts, not only the wrappers — that widening is part of this consent, and the --autonomy preview reports it as a network weakening; the writable state dirs (writableDirs) stay hand-apply. ` +
|
|
704
|
+
'INVOCATION SHAPE: a prefix rule matches only a PLAIN invocation starting with the wrapper name — an env-var prefix or a compound chain never matches (redirects are fine).';
|
|
689
705
|
|
|
690
706
|
const formatBridgeTier = (result) =>
|
|
691
707
|
result.bridgeTier
|
|
@@ -696,6 +712,13 @@ const formatBridgeTier = (result) =>
|
|
|
696
712
|
`${result.wrote ? 'added' : 'would add'} sandbox.excludedCommands entries: ${result.excludedToAdd.length}`,
|
|
697
713
|
...formatEntryList(result.excludedToAdd),
|
|
698
714
|
`already present (excludedCommands): ${result.excludedAlreadyPresent.length}`,
|
|
715
|
+
...(result.hostsToAdd.length + result.hostsAlreadyPresent.length
|
|
716
|
+
? [
|
|
717
|
+
`${result.wrote ? 'added' : 'would add'} sandbox.network.allowedDomains entries: ${result.hostsToAdd.length}`,
|
|
718
|
+
...formatEntryList(result.hostsToAdd),
|
|
719
|
+
`already present (allowedDomains): ${result.hostsAlreadyPresent.length}`,
|
|
720
|
+
]
|
|
721
|
+
: []),
|
|
699
722
|
...result.bridgeSkips.map((s) => ` skipped: ${s.reason}`),
|
|
700
723
|
KIT_BRIDGE_TIER_NOTICE,
|
|
701
724
|
]
|
|
@@ -947,7 +970,19 @@ export const preflightVelocityProfile = ({ cwd }, deps = {}) => {
|
|
|
947
970
|
};
|
|
948
971
|
};
|
|
949
972
|
|
|
950
|
-
|
|
973
|
+
// The tier merges into the project's sandbox.network.allowedDomains, so a wrong-typed value there
|
|
974
|
+
// STOPs the tier with zero writes; every other mode keeps today's reading of the two keys.
|
|
975
|
+
const assertNetworkMergeable = (sandbox) => {
|
|
976
|
+
if (sandbox.network !== undefined && !isJsonObject(sandbox.network)) {
|
|
977
|
+
throw makeVelocityProfileError(VELOCITY_MALFORMED, `${SETTINGS_FILE}: sandbox.network must be a JSON object`);
|
|
978
|
+
}
|
|
979
|
+
if (sandbox.network?.allowedDomains !== undefined && !Array.isArray(sandbox.network.allowedDomains)) {
|
|
980
|
+
throw makeVelocityProfileError(VELOCITY_MALFORMED, `${SETTINGS_FILE}: sandbox.network.allowedDomains must be an array`);
|
|
981
|
+
}
|
|
982
|
+
return sandbox.network?.allowedDomains ?? [];
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
export const planVelocityProfile = (preflight, { acceptEdits, kitTools, bridgeTier, findWrapper, bundleRoot, readdir, readFile } = {}) => {
|
|
951
986
|
const projectAllow = getAllowEntries(preflight.projectSettings?.data);
|
|
952
987
|
const toAdd = UNIVERSAL_READONLY_ALLOWLIST.filter((entry) => !projectAllow.includes(entry));
|
|
953
988
|
const alreadyPresent = UNIVERSAL_READONLY_ALLOWLIST.filter((entry) => projectAllow.includes(entry));
|
|
@@ -961,6 +996,10 @@ export const planVelocityProfile = (preflight, { acceptEdits, kitTools, bridgeTi
|
|
|
961
996
|
const existingExcluded = Array.isArray(existingSandbox.excludedCommands) ? existingSandbox.excludedCommands : [];
|
|
962
997
|
const excludedToAdd = bridge.excludedCommands.filter((cmd) => !existingExcluded.includes(cmd));
|
|
963
998
|
const excludedAlreadyPresent = bridge.excludedCommands.filter((cmd) => existingExcluded.includes(cmd));
|
|
999
|
+
const existingHosts = bridgeTier === true ? assertNetworkMergeable(existingSandbox) : [];
|
|
1000
|
+
const hosts = bridgeTier === true ? bundledSandboxRecipe(bridge.placed, { bundleRoot, readdir, readFile }).hosts : [];
|
|
1001
|
+
const hostsToAdd = hosts.filter((host) => !existingHosts.includes(host));
|
|
1002
|
+
const hostsAlreadyPresent = hosts.filter((host) => existingHosts.includes(host));
|
|
964
1003
|
return {
|
|
965
1004
|
toAdd,
|
|
966
1005
|
alreadyPresent,
|
|
@@ -973,6 +1012,8 @@ export const planVelocityProfile = (preflight, { acceptEdits, kitTools, bridgeTi
|
|
|
973
1012
|
bridgeSkips: bridge.skips,
|
|
974
1013
|
excludedToAdd,
|
|
975
1014
|
excludedAlreadyPresent,
|
|
1015
|
+
hostsToAdd,
|
|
1016
|
+
hostsAlreadyPresent,
|
|
976
1017
|
setsDefaultMode: acceptEdits === true,
|
|
977
1018
|
};
|
|
978
1019
|
};
|
|
@@ -980,7 +1021,15 @@ export const planVelocityProfile = (preflight, { acceptEdits, kitTools, bridgeTi
|
|
|
980
1021
|
export const writeVelocityProfile = ({ cwd, acceptEdits = false, dryRun = true, kitTools = false, bridgeTier = false } = {}, deps = {}) => {
|
|
981
1022
|
const projectDir = cwd ?? deps.cwd ?? process.cwd();
|
|
982
1023
|
const preflight = preflightVelocityProfile({ cwd: projectDir }, deps);
|
|
983
|
-
const plan = planVelocityProfile(preflight, {
|
|
1024
|
+
const plan = planVelocityProfile(preflight, {
|
|
1025
|
+
acceptEdits,
|
|
1026
|
+
kitTools,
|
|
1027
|
+
bridgeTier,
|
|
1028
|
+
findWrapper: deps.findWrapper,
|
|
1029
|
+
bundleRoot: deps.bundleRoot,
|
|
1030
|
+
readdir: deps.readdir,
|
|
1031
|
+
readFile: deps.readFile,
|
|
1032
|
+
});
|
|
984
1033
|
// Drift guard runs on BOTH dry-run and apply (so a dry-run faithfully predicts the apply) and
|
|
985
1034
|
// validates the FULL audited core, not just the to-add delta — a drifted core entry is caught even
|
|
986
1035
|
// when it is already present in the user's allow list (and toAdd is a subset of the core anyway).
|
|
@@ -1016,6 +1065,7 @@ export const writeVelocityProfile = ({ cwd, acceptEdits = false, dryRun = true,
|
|
|
1016
1065
|
[...plan.toAdd, ...plan.tierToAdd, ...plan.bridgeToAdd],
|
|
1017
1066
|
acceptEdits,
|
|
1018
1067
|
plan.excludedToAdd,
|
|
1068
|
+
plan.hostsToAdd,
|
|
1019
1069
|
);
|
|
1020
1070
|
fs.writeFile(settingsPath, formatJson(merged, preflight.projectSettings.eol ?? LF), UTF8);
|
|
1021
1071
|
return { wrote: true, dryRun: false, settingsPath, ...resultBase };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path';
|
|
2
|
+
import { dirCovers } from './declared-paths.mjs';
|
|
3
|
+
import { claudeDirOf, skillTargets } from './jev-facts.mjs';
|
|
4
|
+
import { targetsFor } from './jev-connect.mjs';
|
|
5
|
+
|
|
6
|
+
export const LANE_CONSOLE = 'console';
|
|
7
|
+
export const LANE_CHAT = 'chat';
|
|
8
|
+
|
|
9
|
+
export const VARIANT_LANES = Object.freeze({
|
|
10
|
+
'velocity-core': LANE_CONSOLE,
|
|
11
|
+
'kit-tools-tier': LANE_CONSOLE,
|
|
12
|
+
'bridge-tier': LANE_CONSOLE,
|
|
13
|
+
'autonomy-policy': LANE_CHAT,
|
|
14
|
+
'autonomy-render': LANE_CONSOLE,
|
|
15
|
+
'sandbox-provision': LANE_CONSOLE,
|
|
16
|
+
'sandbox-provision.installable': LANE_CONSOLE,
|
|
17
|
+
'review-recipe': LANE_CHAT,
|
|
18
|
+
'gates-declaration': LANE_CHAT,
|
|
19
|
+
'gates-inert': LANE_CHAT,
|
|
20
|
+
'gates-inert.no-verification': LANE_CHAT,
|
|
21
|
+
'gates-inert.producer-unrecognized': LANE_CHAT,
|
|
22
|
+
'gates-inert.coverage-domain-narrow': LANE_CHAT,
|
|
23
|
+
'source-size': LANE_CHAT,
|
|
24
|
+
'source-size.unminted': LANE_CHAT,
|
|
25
|
+
'source-size.adopted-elsewhere': LANE_CHAT,
|
|
26
|
+
'source-size.id-squatter': LANE_CHAT,
|
|
27
|
+
'gate-hook': LANE_CONSOLE,
|
|
28
|
+
'gate-hook.marker-stale': LANE_CONSOLE,
|
|
29
|
+
'commit-guard': LANE_CONSOLE,
|
|
30
|
+
enforcement: LANE_CONSOLE,
|
|
31
|
+
'read-lane': LANE_CHAT,
|
|
32
|
+
'read-lane.stale': LANE_CONSOLE,
|
|
33
|
+
'read-lane.missing': LANE_CONSOLE,
|
|
34
|
+
'state-block': LANE_CONSOLE,
|
|
35
|
+
'mcp-channel': LANE_CONSOLE,
|
|
36
|
+
'mcp-channel.differing': LANE_CONSOLE,
|
|
37
|
+
agents: LANE_CONSOLE,
|
|
38
|
+
'executor-vehicle': LANE_CONSOLE,
|
|
39
|
+
'family-freshness': LANE_CONSOLE,
|
|
40
|
+
'adr-store-migration': LANE_CHAT,
|
|
41
|
+
'sandbox-masks': LANE_CHAT,
|
|
42
|
+
'sandbox-masks.unfenced-mount': LANE_CHAT,
|
|
43
|
+
'sandbox-lane': LANE_CHAT,
|
|
44
|
+
'worktrees-dir': LANE_CHAT,
|
|
45
|
+
'spec-adoption': LANE_CHAT,
|
|
46
|
+
'profile-gap': LANE_CHAT,
|
|
47
|
+
'jev-connect': LANE_CONSOLE,
|
|
48
|
+
'jev-skill': LANE_CONSOLE,
|
|
49
|
+
'jev-skill.earlier': LANE_CONSOLE,
|
|
50
|
+
'spec-adoption.adopting': LANE_CHAT,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const laneOf = (variant) => {
|
|
54
|
+
if (!Object.hasOwn(VARIANT_LANES, variant)) {
|
|
55
|
+
throw new Error(`unregistered variant: ${variant}`);
|
|
56
|
+
}
|
|
57
|
+
return VARIANT_LANES[variant];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const SETTINGS_PATH = join('.claude', 'settings.json');
|
|
61
|
+
const PROTECTED_FILES = [
|
|
62
|
+
SETTINGS_PATH,
|
|
63
|
+
join('.claude', 'settings.local.json'),
|
|
64
|
+
'.mcp.json',
|
|
65
|
+
join('.git', 'config'),
|
|
66
|
+
];
|
|
67
|
+
const PROTECTED_DIRS = ['agents', 'hooks', 'skills'].map((name) => join('.claude', name));
|
|
68
|
+
|
|
69
|
+
export const isProtectedPath = (abs, { root, home, hooksDir }) => {
|
|
70
|
+
const project = resolve(root);
|
|
71
|
+
const hooks = resolve(hooksDir);
|
|
72
|
+
const path = resolve(abs);
|
|
73
|
+
if (!dirCovers(project, hooks)) {
|
|
74
|
+
throw new Error(`hooksDir outside the project: ${hooksDir}`);
|
|
75
|
+
}
|
|
76
|
+
if (!dirCovers(project, path)) return dirCovers(resolve(home), path);
|
|
77
|
+
return PROTECTED_FILES.some((file) => path === join(project, file))
|
|
78
|
+
|| [...PROTECTED_DIRS.map((dir) => join(project, dir)), hooks].some((dir) => dirCovers(dir, path));
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const ROUTE = "applies at your next npx @sabaiway/agent-workflow-kit@latest init, run from this project's folder";
|
|
82
|
+
const RESTART = '; if init reports it not pending, restart the agent from that console';
|
|
83
|
+
const SETTINGS_WRITES = [SETTINGS_PATH];
|
|
84
|
+
const HOOK_WRITES = [join('.claude', 'hooks', 'agent-workflow-gates.mjs'), ...SETTINGS_WRITES];
|
|
85
|
+
const MCP_WRITES = ['.mcp.json', ...SETTINGS_WRITES];
|
|
86
|
+
const AGENT_WRITES = [join('.claude', 'agents')];
|
|
87
|
+
|
|
88
|
+
const getSkillPaths = ({ env, home, platform }) => {
|
|
89
|
+
const claudeDir = claudeDirOf(env, home, platform).dir;
|
|
90
|
+
return skillTargets({ home, claudeDir }).map(({ path }) => path);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const getStartupPaths = ({ env, home, platform }) => targetsFor({
|
|
94
|
+
shell: env.SHELL,
|
|
95
|
+
home,
|
|
96
|
+
zdotdir: env.ZDOTDIR,
|
|
97
|
+
xdgConfigHome: env.XDG_CONFIG_HOME,
|
|
98
|
+
platform,
|
|
99
|
+
}).filter(({ path }) => path !== null).map(({ path }) => path);
|
|
100
|
+
|
|
101
|
+
const createForm = (file, preview, apply, writes, restart = false) => Object.freeze({
|
|
102
|
+
file,
|
|
103
|
+
preview,
|
|
104
|
+
apply,
|
|
105
|
+
writes,
|
|
106
|
+
route: restart ? ROUTE + RESTART : ROUTE,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const CORE_FORM = createForm('velocity-profile.mjs', ['--cwd', '<root>'], ['--apply', '--cwd', '<root>'], SETTINGS_WRITES);
|
|
110
|
+
const HOOK_FORM = createForm('gate-hook.mjs', ['--cwd', '<root>'], ['--apply', '--cwd', '<root>'], HOOK_WRITES);
|
|
111
|
+
const AGENT_FORM = createForm('cheap-agents.mjs', ['--cwd', '<root>'], ['--apply', '--cwd', '<root>'], AGENT_WRITES);
|
|
112
|
+
const SKILL_FORM = createForm('jev-skill.mjs', ['--claude-dir', '<dir>'], ['--apply', '--claude-dir', '<dir>'], getSkillPaths, true);
|
|
113
|
+
|
|
114
|
+
const FORMS = Object.freeze({
|
|
115
|
+
'velocity-core': CORE_FORM,
|
|
116
|
+
'kit-tools-tier': createForm('velocity-profile.mjs',
|
|
117
|
+
['--kit-tools', '--cwd', '<root>'], ['--apply', '--kit-tools', '--cwd', '<root>'], SETTINGS_WRITES),
|
|
118
|
+
'bridge-tier': createForm('velocity-profile.mjs',
|
|
119
|
+
['--bridge-tier', '--cwd', '<root>'], ['--apply', '--bridge-tier', '--cwd', '<root>'], SETTINGS_WRITES, true),
|
|
120
|
+
'autonomy-render': createForm('velocity-profile.mjs',
|
|
121
|
+
['--autonomy', '--cwd', '<root>'], ['--autonomy', '--apply', '--cwd', '<root>'], SETTINGS_WRITES),
|
|
122
|
+
'gate-hook': HOOK_FORM,
|
|
123
|
+
'read-lane.missing': HOOK_FORM,
|
|
124
|
+
'mcp-channel': createForm('mcp.mjs', ['--cwd', '<root>'], ['--apply', '--cwd', '<root>'], MCP_WRITES),
|
|
125
|
+
'mcp-channel.differing': createForm('mcp.mjs',
|
|
126
|
+
['--replace', '--cwd', '<root>'], ['--replace', '--apply', '--cwd', '<root>'], MCP_WRITES),
|
|
127
|
+
agents: AGENT_FORM,
|
|
128
|
+
'executor-vehicle': AGENT_FORM,
|
|
129
|
+
'sandbox-provision.installable': createForm('autonomy-doctor.mjs', [], ['--apply', '<tuple>'], [], true),
|
|
130
|
+
'jev-skill': SKILL_FORM,
|
|
131
|
+
'jev-skill.earlier': SKILL_FORM,
|
|
132
|
+
'jev-connect': createForm('jev-connect.mjs', null, [], getStartupPaths, true),
|
|
133
|
+
'family-freshness': { route: ROUTE },
|
|
134
|
+
'sandbox-provision': null,
|
|
135
|
+
'commit-guard': null,
|
|
136
|
+
'gate-hook.marker-stale': null,
|
|
137
|
+
'read-lane.stale': null,
|
|
138
|
+
'state-block': null,
|
|
139
|
+
enforcement: null,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const resolveArgument = (arg, facts) => {
|
|
143
|
+
if (arg === '<root>') return facts.root;
|
|
144
|
+
if (arg === '<tuple>') return facts.tuple;
|
|
145
|
+
if (arg === '<dir>') return claudeDirOf(facts.env, facts.home, facts.platform).dir;
|
|
146
|
+
return arg;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const consoleArgv = (variant, facts) => {
|
|
150
|
+
laneOf(variant);
|
|
151
|
+
const form = FORMS[variant];
|
|
152
|
+
if (!form?.file) return null;
|
|
153
|
+
const argv = (args) => ['node', join(facts.toolsDir, form.file), ...args.map((arg) => resolveArgument(arg, facts))];
|
|
154
|
+
return {
|
|
155
|
+
preview: form.preview === null ? null : argv(form.preview),
|
|
156
|
+
apply: argv(form.apply),
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const writesOf = (variant, facts) => {
|
|
161
|
+
laneOf(variant);
|
|
162
|
+
const form = FORMS[variant];
|
|
163
|
+
if (!form?.file) return null;
|
|
164
|
+
if (typeof form.writes === 'function') return form.writes(facts);
|
|
165
|
+
return form.writes.map((path) => join(facts.root, path));
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const routeLine = (variant) => {
|
|
169
|
+
laneOf(variant);
|
|
170
|
+
return FORMS[variant]?.route ?? null;
|
|
171
|
+
};
|