arkgate 3.8.2 → 3.9.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 +91 -0
- package/README.md +94 -345
- package/bin/ark-mcp-runtime.mjs +137 -11
- package/bin/ark-shared.mjs +89 -2
- package/bin/ark.mjs +41 -19
- package/bin/lib/agent-gates.mjs +4 -0
- package/bin/lib/ci-and-commands.mjs +28 -21
- package/bin/lib/doctor-plan.mjs +37 -28
- package/bin/lib/hook-templates.mjs +13 -9
- package/bin/lib/host-support-matrix.mjs +64 -4
- package/bin/lib/install-migrate.mjs +96 -1
- package/bin/lib/managed-upgrade.mjs +30 -1
- package/bin/lib/mcp-adoption.mjs +60 -2
- package/bin/lib/post-green-path.mjs +2 -2
- package/bin/lib/skill-install.mjs +46 -2
- package/bin/lib/start-preview.mjs +23 -7
- package/bin/lib/upgrade-command.mjs +57 -15
- package/bin/lib/write-path-capabilities.mjs +67 -18
- package/bin/lib/write-path-detect.mjs +11 -7
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/README.md +70 -0
- package/docs/agent-guide.md +59 -17
- package/docs/ai-gates.md +97 -11
- package/docs/develop.md +127 -0
- package/docs/enthusiast/README.md +2 -0
- package/docs/package-surface.md +3 -3
- package/docs/product-voice.md +194 -0
- package/docs/use.md +88 -0
- package/package.json +5 -1
- package/server.json +2 -2
- package/templates/hooks/opencode-ark-write-gate.mjs +85 -0
- package/templates/skills/ark-autopilot.md +20 -7
- package/templates/skills/ark-explore.md +17 -4
package/bin/ark-mcp-runtime.mjs
CHANGED
|
@@ -177,13 +177,90 @@ function isResolvedAnalysisInput(relativePath, args, compilerInputs = new Set())
|
|
|
177
177
|
return /(?:^|\/)configs?\/[^/]+\.jsonc?$/i.test(relative);
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Map Google Antigravity write tools (PascalCase args) onto Claude Write/Edit/MultiEdit.
|
|
182
|
+
* @returns {{ toolName: string, toolInput: object }|null}
|
|
183
|
+
*/
|
|
184
|
+
function mapAntigravityToolCall(toolCall) {
|
|
185
|
+
if (!toolCall || typeof toolCall !== 'object') return null;
|
|
186
|
+
const name = toolCall.name ?? '';
|
|
187
|
+
const args = toolCall.args && typeof toolCall.args === 'object' ? toolCall.args : {};
|
|
188
|
+
const filePath = args.TargetFile ?? args.targetFile ?? args.file_path ?? args.path;
|
|
189
|
+
if (name === 'write_to_file') {
|
|
190
|
+
return {
|
|
191
|
+
toolName: 'Write',
|
|
192
|
+
toolInput: {
|
|
193
|
+
file_path: filePath,
|
|
194
|
+
content: args.CodeContent ?? args.codeContent ?? args.content ?? '',
|
|
195
|
+
},
|
|
196
|
+
operation: 'write_to_file',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (name === 'replace_file_content') {
|
|
200
|
+
return {
|
|
201
|
+
toolName: 'Edit',
|
|
202
|
+
toolInput: {
|
|
203
|
+
file_path: filePath,
|
|
204
|
+
old_string: args.TargetContent ?? args.targetContent ?? args.old_string ?? '',
|
|
205
|
+
new_string: args.ReplacementContent ?? args.replacementContent ?? args.new_string ?? '',
|
|
206
|
+
replace_all: Boolean(args.AllowMultiple ?? args.allowMultiple),
|
|
207
|
+
},
|
|
208
|
+
operation: 'replace_file_content',
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (name === 'multi_replace_file_content') {
|
|
212
|
+
const chunks = Array.isArray(args.ReplacementChunks)
|
|
213
|
+
? args.ReplacementChunks
|
|
214
|
+
: Array.isArray(args.replacementChunks)
|
|
215
|
+
? args.replacementChunks
|
|
216
|
+
: [];
|
|
217
|
+
return {
|
|
218
|
+
toolName: 'MultiEdit',
|
|
219
|
+
toolInput: {
|
|
220
|
+
file_path: filePath,
|
|
221
|
+
edits: chunks.map((chunk) => ({
|
|
222
|
+
old_string: chunk?.TargetContent ?? chunk?.targetContent ?? chunk?.old_string ?? '',
|
|
223
|
+
new_string:
|
|
224
|
+
chunk?.ReplacementContent ?? chunk?.replacementContent ?? chunk?.new_string ?? '',
|
|
225
|
+
replace_all: Boolean(chunk?.AllowMultiple ?? chunk?.allowMultiple),
|
|
226
|
+
})),
|
|
227
|
+
},
|
|
228
|
+
operation: 'multi_replace_file_content',
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
toolName: name,
|
|
233
|
+
toolInput: { ...args, file_path: filePath },
|
|
234
|
+
operation: name,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
180
238
|
/**
|
|
181
239
|
* Normalize agent PreToolUse payloads.
|
|
182
240
|
* Claude Code: { tool_name, tool_input: { file_path, content | old_string/new_string } }
|
|
183
241
|
* Grok Build: { toolName, toolInput: { file_path, content | old_string/new_string } }
|
|
184
242
|
* (aliases Write/Edit/MultiEdit → write/search_replace; matcher keeps both)
|
|
243
|
+
* Antigravity: { toolCall: { name, args: { TargetFile, CodeContent, … } } }
|
|
185
244
|
*/
|
|
186
245
|
function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_HOOK_EVENT)) {
|
|
246
|
+
const antigravityStyle =
|
|
247
|
+
payload != null && typeof payload === 'object' && 'toolCall' in payload;
|
|
248
|
+
if (antigravityStyle) {
|
|
249
|
+
const mapped = mapAntigravityToolCall(payload.toolCall);
|
|
250
|
+
const filePath =
|
|
251
|
+
mapped?.toolInput?.file_path ??
|
|
252
|
+
mapped?.toolInput?.filePath ??
|
|
253
|
+
mapped?.toolInput?.path ??
|
|
254
|
+
mapped?.toolInput?.target_file;
|
|
255
|
+
return {
|
|
256
|
+
toolName: mapped?.toolName ?? '',
|
|
257
|
+
toolInput: { ...(mapped?.toolInput ?? {}), file_path: filePath },
|
|
258
|
+
grokStyle: true, // decision JSON on stdout (deny)
|
|
259
|
+
antigravityStyle: true,
|
|
260
|
+
operation: mapped?.operation ?? mapped?.toolName ?? null,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
187
264
|
const rawName = payload?.tool_name ?? payload?.toolName ?? '';
|
|
188
265
|
const toolInput = payload?.tool_input ?? payload?.toolInput ?? {};
|
|
189
266
|
const nameMap = {
|
|
@@ -194,6 +271,9 @@ function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_
|
|
|
194
271
|
MultiEdit: 'MultiEdit',
|
|
195
272
|
ApplyPatch: 'ApplyPatch',
|
|
196
273
|
apply_patch: 'ApplyPatch',
|
|
274
|
+
write_to_file: 'Write',
|
|
275
|
+
replace_file_content: 'Edit',
|
|
276
|
+
multi_replace_file_content: 'MultiEdit',
|
|
197
277
|
};
|
|
198
278
|
const toolName = nameMap[rawName] ?? rawName;
|
|
199
279
|
const filePath =
|
|
@@ -205,6 +285,8 @@ function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_
|
|
|
205
285
|
grokStyle:
|
|
206
286
|
grokHookEvent ||
|
|
207
287
|
(payload != null && typeof payload === 'object' && 'toolName' in payload),
|
|
288
|
+
antigravityStyle: false,
|
|
289
|
+
operation: null,
|
|
208
290
|
};
|
|
209
291
|
}
|
|
210
292
|
|
|
@@ -409,8 +491,14 @@ function processHookOutput() {
|
|
|
409
491
|
};
|
|
410
492
|
}
|
|
411
493
|
|
|
494
|
+
/** Antigravity PreToolUse requires stdout `decision` on every response (allow included). */
|
|
495
|
+
function emitAntigravityAllow(output, antigravityStyle) {
|
|
496
|
+
if (!antigravityStyle) return;
|
|
497
|
+
output.stdout(`${JSON.stringify({ decision: 'allow' })}\n`);
|
|
498
|
+
}
|
|
499
|
+
|
|
412
500
|
function runHookPayload(payload, gate, config, args, ts, attemptContext, output = processHookOutput()) {
|
|
413
|
-
const { toolName, toolInput, grokStyle } = normalizeHookPayload(
|
|
501
|
+
const { toolName, toolInput, grokStyle, antigravityStyle, operation } = normalizeHookPayload(
|
|
414
502
|
payload,
|
|
415
503
|
attemptContext?.grokHookEvent ?? Boolean(process.env.GROK_HOOK_EVENT)
|
|
416
504
|
);
|
|
@@ -419,7 +507,10 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
419
507
|
const parsedPatch = codexPatchWrites(patch, args.root);
|
|
420
508
|
// Codex ApplyPatch is only preflighted when Ark can reconstruct every file operation.
|
|
421
509
|
// An incomplete reconstruction must not be mislabeled as atomic or hard enforcement.
|
|
422
|
-
if (!parsedPatch.complete)
|
|
510
|
+
if (!parsedPatch.complete) {
|
|
511
|
+
emitAntigravityAllow(output, antigravityStyle);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
423
514
|
const patchWrites = parsedPatch.writes;
|
|
424
515
|
const sourceWrites = patchWrites.filter((change) =>
|
|
425
516
|
isGovernableSourceFile(path.basename(String(change.path)))
|
|
@@ -482,7 +573,10 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
482
573
|
.join(', ')}; source-only virtual preflight cannot model those contents.`
|
|
483
574
|
);
|
|
484
575
|
}
|
|
485
|
-
if (changes.length === 0)
|
|
576
|
+
if (changes.length === 0) {
|
|
577
|
+
emitAntigravityAllow(output, antigravityStyle);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
486
580
|
result = prepareChangeFromRoot({
|
|
487
581
|
root: args.root,
|
|
488
582
|
config,
|
|
@@ -537,7 +631,10 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
537
631
|
const designDelta = args.failOnNewSmells
|
|
538
632
|
? evaluateWriteDesignDelta({ root: args.root, config, changes, ts })
|
|
539
633
|
: null;
|
|
540
|
-
if (result.valid && (designDelta?.valid ?? true))
|
|
634
|
+
if (result.valid && (designDelta?.valid ?? true)) {
|
|
635
|
+
emitAntigravityAllow(output, antigravityStyle);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
541
638
|
const message = [
|
|
542
639
|
`Ark architecture gate blocked this complete ${toolName} (${changes.length} governed file(s)):`,
|
|
543
640
|
...result.diagnostics.map(
|
|
@@ -567,16 +664,27 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
567
664
|
return;
|
|
568
665
|
}
|
|
569
666
|
const filePath = toolInput.file_path;
|
|
570
|
-
if (!['Write', 'Edit', 'MultiEdit'].includes(toolName))
|
|
667
|
+
if (!['Write', 'Edit', 'MultiEdit'].includes(toolName)) {
|
|
668
|
+
// Non-file tools: fail-open. Antigravity still needs an explicit allow decision.
|
|
669
|
+
emitAntigravityAllow(output, antigravityStyle);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
571
672
|
if (typeof filePath !== 'string' || !SOURCE_FILE.test(filePath) || filePath.endsWith('.d.ts')) {
|
|
673
|
+
emitAntigravityAllow(output, antigravityStyle);
|
|
572
674
|
return;
|
|
573
675
|
}
|
|
574
676
|
const rel = path.relative(args.root, path.resolve(filePath));
|
|
575
677
|
const segments = rel.split(path.sep);
|
|
576
|
-
if (segments[0] === '..' || segments.includes('node_modules'))
|
|
678
|
+
if (segments[0] === '..' || segments.includes('node_modules')) {
|
|
679
|
+
emitAntigravityAllow(output, antigravityStyle);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
577
682
|
|
|
578
683
|
const source = proposedSource(toolName, toolInput);
|
|
579
|
-
if (typeof source !== 'string')
|
|
684
|
+
if (typeof source !== 'string') {
|
|
685
|
+
emitAntigravityAllow(output, antigravityStyle);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
580
688
|
|
|
581
689
|
const layer = inferLayer(filePath, config, args.root);
|
|
582
690
|
const validateOnce = (src) =>
|
|
@@ -611,7 +719,10 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
611
719
|
ts,
|
|
612
720
|
})
|
|
613
721
|
: null;
|
|
614
|
-
if (result.valid && (designDelta?.valid ?? true))
|
|
722
|
+
if (result.valid && (designDelta?.valid ?? true)) {
|
|
723
|
+
emitAntigravityAllow(output, antigravityStyle);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
615
726
|
|
|
616
727
|
// Ratchet semantics (same philosophy as ark-check --baseline): an edit is blocked only
|
|
617
728
|
// when it ADDS violations relative to the file's current on-disk state. Otherwise a
|
|
@@ -639,7 +750,10 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
639
750
|
existingCounts.set(key, remaining - 1);
|
|
640
751
|
return false;
|
|
641
752
|
});
|
|
642
|
-
if (newViolations.length === 0 && (designDelta?.valid ?? true))
|
|
753
|
+
if (newViolations.length === 0 && (designDelta?.valid ?? true)) {
|
|
754
|
+
emitAntigravityAllow(output, antigravityStyle);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
643
757
|
const combinedViolations = [...newViolations, ...designDeltaViolations(designDelta)];
|
|
644
758
|
const adapterResult = createAdapterResult({
|
|
645
759
|
valid: false,
|
|
@@ -698,9 +812,21 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
698
812
|
filePath: normalizedRel,
|
|
699
813
|
enforcement: hookEnforcement(
|
|
700
814
|
args.root,
|
|
701
|
-
attemptContext?.host ??
|
|
815
|
+
attemptContext?.host ??
|
|
816
|
+
(antigravityStyle ? 'antigravity' : grokStyle ? 'grok' : 'claude'),
|
|
702
817
|
attemptContext?.operation ??
|
|
703
|
-
|
|
818
|
+
operation ??
|
|
819
|
+
(antigravityStyle
|
|
820
|
+
? toolName === 'Edit'
|
|
821
|
+
? 'replace_file_content'
|
|
822
|
+
: toolName === 'MultiEdit'
|
|
823
|
+
? 'multi_replace_file_content'
|
|
824
|
+
: 'write_to_file'
|
|
825
|
+
: grokStyle
|
|
826
|
+
? toolName === 'Edit'
|
|
827
|
+
? 'search_replace'
|
|
828
|
+
: 'write'
|
|
829
|
+
: toolName),
|
|
704
830
|
toolName === 'Write' || Boolean(attemptContext?.completePatch)
|
|
705
831
|
),
|
|
706
832
|
...(layer ? { layer } : {}),
|
package/bin/ark-shared.mjs
CHANGED
|
@@ -618,11 +618,98 @@ export function execCommandParts(root, bin, binArgs = []) {
|
|
|
618
618
|
return { command: 'npx', args: [bin, ...binArgs] };
|
|
619
619
|
}
|
|
620
620
|
|
|
621
|
+
/**
|
|
622
|
+
* True when this directory is a pnpm workspace root (needs `pnpm add -w` for root deps).
|
|
623
|
+
* Nested packages under the workspace are not roots.
|
|
624
|
+
*/
|
|
625
|
+
export function isPnpmWorkspaceRoot(root) {
|
|
626
|
+
return fs.existsSync(path.join(root, 'pnpm-workspace.yaml'));
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* True when package.json declares npm/yarn workspaces (yarn classic needs `-W` at root).
|
|
631
|
+
*/
|
|
632
|
+
export function isNpmYarnWorkspaceRoot(root) {
|
|
633
|
+
const pkg = readPackageJson(root);
|
|
634
|
+
if (!pkg) return false;
|
|
635
|
+
const ws = pkg.workspaces;
|
|
636
|
+
return Array.isArray(ws) || (ws && typeof ws === 'object' && Array.isArray(ws.packages));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Normalize a version/range/spec into an installable package argument for arkgate.
|
|
641
|
+
* Accepts `latest`, `^3.8.2`, `arkgate@latest`, or a full package name.
|
|
642
|
+
*/
|
|
643
|
+
export function normalizeArkgateInstallSpec(versionSpec) {
|
|
644
|
+
const raw = typeof versionSpec === 'string' && versionSpec.trim() ? versionSpec.trim() : 'latest';
|
|
645
|
+
if (raw.startsWith('arkgate@') || raw === 'arkgate') return raw === 'arkgate' ? 'arkgate@latest' : raw;
|
|
646
|
+
if (raw.includes('/') || raw.startsWith('file:') || raw.startsWith('link:')) return raw;
|
|
647
|
+
return `arkgate@${raw}`;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Package-manager argv to add a dev dependency (e.g. arkgate@latest).
|
|
652
|
+
* pnpm workspace roots get `-w`; yarn classic workspaces get `-W`.
|
|
653
|
+
*
|
|
654
|
+
* @param {string} root
|
|
655
|
+
* @param {string} [versionSpec] package name or name@version (default arkgate@latest)
|
|
656
|
+
* @returns {[string, string[]]}
|
|
657
|
+
*/
|
|
658
|
+
export function packageInstallArgv(root, versionSpec = 'latest') {
|
|
659
|
+
const pkgSpec = normalizeArkgateInstallSpec(versionSpec);
|
|
660
|
+
const pm = detectPackageManager(root);
|
|
661
|
+
if (pm === 'pnpm') {
|
|
662
|
+
const args = ['add', '-D', pkgSpec];
|
|
663
|
+
if (isPnpmWorkspaceRoot(root)) args.push('-w');
|
|
664
|
+
return ['pnpm', args];
|
|
665
|
+
}
|
|
666
|
+
if (pm === 'yarn') {
|
|
667
|
+
const args = ['add', '-D', pkgSpec];
|
|
668
|
+
if (isNpmYarnWorkspaceRoot(root)) args.push('-W');
|
|
669
|
+
return ['yarn', args];
|
|
670
|
+
}
|
|
671
|
+
return ['npm', ['install', '-D', pkgSpec]];
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Whether an install of arkgate@latest can be skipped because node_modules already
|
|
676
|
+
* resolves the same version as this CLI package.
|
|
677
|
+
*
|
|
678
|
+
* @param {string} root
|
|
679
|
+
* @param {string} [cliVersion] this binary's package version
|
|
680
|
+
* @returns {{ skip: boolean, installedVersion: string|null, reason: string }}
|
|
681
|
+
*/
|
|
682
|
+
export function shouldSkipArkgateInstall(root, cliVersion) {
|
|
683
|
+
const pkgPath = path.join(root, 'node_modules', 'arkgate', 'package.json');
|
|
684
|
+
if (!fs.existsSync(pkgPath)) {
|
|
685
|
+
return { skip: false, installedVersion: null, reason: 'not-installed' };
|
|
686
|
+
}
|
|
687
|
+
let installedVersion = null;
|
|
688
|
+
try {
|
|
689
|
+
installedVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version ?? null;
|
|
690
|
+
} catch {
|
|
691
|
+
return { skip: false, installedVersion: null, reason: 'unreadable' };
|
|
692
|
+
}
|
|
693
|
+
if (
|
|
694
|
+
typeof cliVersion === 'string' &&
|
|
695
|
+
cliVersion &&
|
|
696
|
+
installedVersion &&
|
|
697
|
+
installedVersion === cliVersion
|
|
698
|
+
) {
|
|
699
|
+
return { skip: true, installedVersion, reason: 'already-current' };
|
|
700
|
+
}
|
|
701
|
+
return { skip: false, installedVersion, reason: 'version-differs' };
|
|
702
|
+
}
|
|
703
|
+
|
|
621
704
|
/** Package-manager aware "install a dev dependency" hint (e.g. for a missing typescript). */
|
|
622
705
|
export function installDevHint(root, pkg) {
|
|
623
706
|
const pm = detectPackageManager(root);
|
|
624
|
-
if (pm === 'pnpm')
|
|
625
|
-
|
|
707
|
+
if (pm === 'pnpm') {
|
|
708
|
+
return isPnpmWorkspaceRoot(root) ? `pnpm add -D ${pkg} -w` : `pnpm add -D ${pkg}`;
|
|
709
|
+
}
|
|
710
|
+
if (pm === 'yarn') {
|
|
711
|
+
return isNpmYarnWorkspaceRoot(root) ? `yarn add -D ${pkg} -W` : `yarn add -D ${pkg}`;
|
|
712
|
+
}
|
|
626
713
|
return `npm install -D ${pkg}`;
|
|
627
714
|
}
|
|
628
715
|
|
package/bin/ark.mjs
CHANGED
|
@@ -14,8 +14,10 @@ import {
|
|
|
14
14
|
INIT_WIZARD_CHOICES,
|
|
15
15
|
isValidArchetypeId,
|
|
16
16
|
mapWizardChoiceToArchetype,
|
|
17
|
+
packageInstallArgv,
|
|
17
18
|
resolveArchetypePreset,
|
|
18
19
|
resolveOperatingMode,
|
|
20
|
+
shouldSkipArkgateInstall,
|
|
19
21
|
} from './ark-shared.mjs';
|
|
20
22
|
import { pinArkgateDevDependency, FALSE_GREEN_GAP_ID } from './lib/field-install.mjs';
|
|
21
23
|
import { validateHardWriteRequest } from './lib/enforcement-profiles.mjs';
|
|
@@ -155,9 +157,9 @@ Options:
|
|
|
155
157
|
(Also the implicit default when stdin/stdout are not a TTY — agents never hang on prompts.)
|
|
156
158
|
--force Allow generated files to overwrite existing files.
|
|
157
159
|
--no-strict Skip the final strict ark-check run.
|
|
158
|
-
--install
|
|
160
|
+
--install Pin and install arkgate as a project devDependency (default for start).
|
|
159
161
|
--no-install Skip adding/installing arkgate as a project devDependency (start/upgrade).
|
|
160
|
-
--apply Apply a start plan; for upgrade, update/repreview or apply
|
|
162
|
+
--apply Apply a start plan; for upgrade, update/repreview or apply managed bytes.
|
|
161
163
|
--accept-conflicts
|
|
162
164
|
Allow upgrade to recreate deleted managed assets or replace recorded conflicts.
|
|
163
165
|
--plan-digest Digest emitted by an upgrade preview; required to apply managed bytes.
|
|
@@ -189,20 +191,7 @@ function cliVersion() {
|
|
|
189
191
|
}
|
|
190
192
|
}
|
|
191
193
|
|
|
192
|
-
//
|
|
193
|
-
// Prefer an explicit version/range when pin already chose one (avoid pin=^2.9.0 then
|
|
194
|
-
// `npm i arkgate@latest` rewriting package.json to a different range).
|
|
195
|
-
function packageInstallArgv(root, versionSpec) {
|
|
196
|
-
const range =
|
|
197
|
-
typeof versionSpec === 'string' && versionSpec.trim()
|
|
198
|
-
? versionSpec.trim()
|
|
199
|
-
: 'latest';
|
|
200
|
-
const spec = range.startsWith('arkgate@') ? range : `arkgate@${range}`;
|
|
201
|
-
const pm = detectPackageManager(root);
|
|
202
|
-
if (pm === 'pnpm') return ['pnpm', ['add', '-D', spec]];
|
|
203
|
-
if (pm === 'yarn') return ['yarn', ['add', '-D', spec]];
|
|
204
|
-
return ['npm', ['install', '-D', spec]];
|
|
205
|
-
}
|
|
194
|
+
// packageInstallArgv is imported from ark-shared (workspace-aware -w / -W).
|
|
206
195
|
|
|
207
196
|
function runCommand(command, commandArgs, cwd) {
|
|
208
197
|
const result = spawnSync(command, commandArgs, { cwd, stdio: 'inherit', encoding: 'utf8' });
|
|
@@ -438,6 +427,32 @@ async function start(args) {
|
|
|
438
427
|
if (!args.apply) return 0;
|
|
439
428
|
applyStartPreview(args.root, preview);
|
|
440
429
|
if (!args.json) console.log(`Applied ${preview.changes.length} previewed mutation(s).`);
|
|
430
|
+
// After applying exact preview bytes, install the pinned package when requested
|
|
431
|
+
// (preview itself never runs the package manager — field: start left pin without node_modules).
|
|
432
|
+
if (
|
|
433
|
+
args.install &&
|
|
434
|
+
!args.skipPackageManager &&
|
|
435
|
+
fs.existsSync(path.join(args.root, 'package.json'))
|
|
436
|
+
) {
|
|
437
|
+
const skip = shouldSkipArkgateInstall(args.root, cliVersion());
|
|
438
|
+
if (!skip.skip) {
|
|
439
|
+
const [command, commandArgs] = packageInstallArgv(args.root, `^${cliVersion()}`);
|
|
440
|
+
if (!args.json) console.log(`Installing package: ${command} ${commandArgs.join(' ')}`);
|
|
441
|
+
// Keep stdout clean for --json consumers (package managers are chatty on stdout).
|
|
442
|
+
const status = args.json
|
|
443
|
+
? (spawnSync(command, commandArgs, {
|
|
444
|
+
cwd: args.root,
|
|
445
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
446
|
+
encoding: 'utf8',
|
|
447
|
+
}).status ?? 1)
|
|
448
|
+
: runCommand(command, commandArgs, args.root);
|
|
449
|
+
if (status !== 0 && !args.json) {
|
|
450
|
+
console.log(
|
|
451
|
+
`Package manager exited ${status}. package.json is pinned; run the install command when online.`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
441
456
|
return 0;
|
|
442
457
|
}
|
|
443
458
|
const root = args.root;
|
|
@@ -492,7 +507,8 @@ async function start(args) {
|
|
|
492
507
|
}
|
|
493
508
|
|
|
494
509
|
// 2b) Pin arkgate as a project devDependency so CI/npx do not depend on a stale global.
|
|
495
|
-
|
|
510
|
+
// Default install=true; only --no-install skips. (installExplicit tracks user override for copy.)
|
|
511
|
+
if (args.install && fs.existsSync(path.join(root, 'package.json'))) {
|
|
496
512
|
const { pinned, installStatus } = ensureProjectArkgateDependency(root, {
|
|
497
513
|
install: true,
|
|
498
514
|
runPackageManager: !args.skipPackageManager,
|
|
@@ -507,7 +523,7 @@ async function start(args) {
|
|
|
507
523
|
} else if (pinned.reason === 'already-present') {
|
|
508
524
|
console.log(` arkgate already in package.json (${pinned.version}).`);
|
|
509
525
|
}
|
|
510
|
-
} else if (args.
|
|
526
|
+
} else if (!args.install) {
|
|
511
527
|
console.log(' Skipping arkgate package pin (--no-install).');
|
|
512
528
|
}
|
|
513
529
|
|
|
@@ -789,7 +805,13 @@ async function main() {
|
|
|
789
805
|
|
|
790
806
|
if (args.command === 'upgrade' || args.command === 'update') {
|
|
791
807
|
try {
|
|
792
|
-
return runUpgradeCommand(args, {
|
|
808
|
+
return runUpgradeCommand(args, {
|
|
809
|
+
arkCheck,
|
|
810
|
+
packageInstallArgv,
|
|
811
|
+
runArkCheck,
|
|
812
|
+
cliVersion: cliVersion(),
|
|
813
|
+
shouldSkipArkgateInstall,
|
|
814
|
+
});
|
|
793
815
|
} catch (error) {
|
|
794
816
|
console.error(error instanceof Error ? error.message : String(error));
|
|
795
817
|
return 2;
|
package/bin/lib/agent-gates.mjs
CHANGED
|
@@ -23,10 +23,14 @@ export {
|
|
|
23
23
|
|
|
24
24
|
export {
|
|
25
25
|
PREFERRED_MCP_BIN,
|
|
26
|
+
antigravityHooks,
|
|
26
27
|
claudeSettings,
|
|
27
28
|
codexProjectConfig,
|
|
28
29
|
grokHooks,
|
|
29
30
|
grokProjectConfig,
|
|
31
|
+
mergeAntigravityArkHook,
|
|
32
|
+
mergeOpencodeArkMcp,
|
|
33
|
+
opencodeProjectConfig,
|
|
30
34
|
} from './hook-templates.mjs';
|
|
31
35
|
|
|
32
36
|
export { detectWritePathCapabilities } from './write-path-detect.mjs';
|
|
@@ -128,16 +128,13 @@ export function agentInstructions(root) {
|
|
|
128
128
|
|
|
129
129
|
## Default agent flow (if unsure, do only this)
|
|
130
130
|
|
|
131
|
-
1.
|
|
132
|
-
2.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
Do **not** invent a second architecture curriculum outside the routing table below — when a
|
|
136
|
-
trigger matches, use that skill; when unsure, stay on autopilot.
|
|
137
|
-
3. Status anytime: \`${doctorCmd}\` (status light + next action — not a mode picker).
|
|
138
|
-
4. After ordinary feature edits: run \`${checkCmd}\`. On violations → **\`/ark-fix\`** (or
|
|
139
|
-
\`/ark-place\` for new files, \`/ark-contract\` only if the contract itself is wrong).
|
|
131
|
+
1. Status anytime: \`${doctorCmd}\` — **control plane** (one status light, one next action; not a mode picker).
|
|
132
|
+
2. If \`ark.config.json\` is missing: run \`${startCmd}\` once (preview), then \`${startCmd} --apply\`.
|
|
133
|
+
3. Guided end-to-end work (“make architecture sound”): **\`/ark-autopilot\`** — explore → dual plan A (edges) + B (shape) → mechanical-safe fixes; B only with user OK. Day-zero origin is frozen by \`ark start\`/\`ark init\` (or autopilot if missing) **before** agent docs.
|
|
134
|
+
4. After ordinary feature edits: run \`${checkCmd}\`. On violations → **\`/ark-fix\`** (or \`/ark-place\` for new files, \`/ark-contract\` only if the contract itself is wrong).
|
|
140
135
|
|
|
136
|
+
Do **not** skill-shop the full table for routine work. When unsure, do doctor top action #1 only
|
|
137
|
+
(re-run doctor after). Do **not** jump to \`/ark-autopilot\` unless #1 or a STOP handoff names it.
|
|
141
138
|
Skills are **dual-engine**: deterministic CLI sensors + exploratory read of *this* repo — not JSON-only wrappers.
|
|
142
139
|
When a skill says **STOP — do not continue this skill as complete**, stop and invoke the named handoff skill.
|
|
143
140
|
|
|
@@ -151,14 +148,16 @@ scouts (disjoint path scopes) and merge in the parent. If the host does **not**,
|
|
|
151
148
|
**fall back to sequential** — one cluster/step at a time. Never parallel-write the same
|
|
152
149
|
files; never weaken the gate via subagents.
|
|
153
150
|
|
|
154
|
-
## Skill routing (triggers → skill)
|
|
151
|
+
## Skill routing (expert depth — triggers → skill)
|
|
155
152
|
|
|
156
|
-
Do **not** run overlapping skills for the same job.
|
|
153
|
+
**Escapes, not a second curriculum.** Do **not** run overlapping skills for the same job.
|
|
154
|
+
Pick **one** primary skill. Prefer doctor top action #1 when unsure.
|
|
157
155
|
|
|
158
156
|
| When | Invoke | Not this |
|
|
159
157
|
|------|--------|----------|
|
|
160
|
-
| Unsure
|
|
161
|
-
|
|
|
158
|
+
| Unsure what to do next | **Doctor top action #1** (\`${doctorCmd}\`), then re-run doctor | skill-shopping, defaulting to autopilot |
|
|
159
|
+
| Make architecture sound (guided apply path) | **/ark-autopilot** | explore-only, coverage-only |
|
|
160
|
+
| **Messy / spaghetti / design-weak after green / Shape residual** | **Single path:** \`/ark-explore\` shape-focus → dual-plan B, then \`/ark-autopilot\` only to apply B with OK | coverage, think, loop-as-done, skill-shopping |
|
|
162
161
|
| Map / residual / dual-plan seed only (no apply, already know you want recon) | \`/ark-explore\` | coverage (fitness only) |
|
|
163
162
|
| Greenfield shape / empty tree | \`/ark-architect\` | adopt |
|
|
164
163
|
| Brownfield / wrong contract / false-green | \`/ark-adopt\` then \`/ark-contract\` if globs wrong | architect |
|
|
@@ -209,20 +208,28 @@ export function compactAgentInstructions(root, host = null) {
|
|
|
209
208
|
'ark-check',
|
|
210
209
|
`--install-agent-gates --skills-only --tools ${selectedHost === 'none' ? '<host>' : selectedHost}`
|
|
211
210
|
);
|
|
211
|
+
// Progressive disclosure: primary path only. Full /ark-* catalog is expert depth
|
|
212
|
+
// (install via --skills-only). See docs/product-voice.md.
|
|
212
213
|
return `# Ark Enforcement
|
|
213
214
|
|
|
214
215
|
<!-- arkgate:compact-router host=${selectedHost} -->
|
|
215
216
|
## Compact router
|
|
216
217
|
|
|
217
|
-
|
|
218
|
-
agent router. Before editing TypeScript or JavaScript, read \`ark://manifest\`
|
|
219
|
-
when available; use \`ark_place\` for new files and \`validate_code\` after edits.
|
|
220
|
-
If MCP is unavailable, inspect \`ark.config.json\` and run \`${checkCmd}\`.
|
|
218
|
+
**Primary path (do this):**
|
|
221
219
|
|
|
222
|
-
|
|
223
|
-
\`${
|
|
224
|
-
|
|
225
|
-
|
|
220
|
+
1. Status anytime: \`${doctorCmd}\` — one status light, one next action (control plane).
|
|
221
|
+
2. Day to day: read \`ark://manifest\` when MCP is available; place new files with \`ark_place\`; validate after edits; run \`${checkCmd}\`. On a gate deny, fix the architecture — do not weaken the contract.
|
|
222
|
+
3. If MCP is unavailable: inspect \`ark.config.json\` and run \`${checkCmd}\`.
|
|
223
|
+
|
|
224
|
+
The selected host is \`${selectedHost}\`. Host registration and CI are installed with this file.
|
|
225
|
+
This compact router is enough for normal feature work.
|
|
226
|
+
|
|
227
|
+
## Expert depth (optional)
|
|
228
|
+
|
|
229
|
+
Full \`/ark-*\` skills (including guided end-to-end \`/ark-autopilot\`) are **not** the default
|
|
230
|
+
curriculum. Install them only when doctor top action #1 or a STOP handoff names a skill:
|
|
231
|
+
|
|
232
|
+
\`${installSkills}\`
|
|
226
233
|
`;
|
|
227
234
|
}
|
|
228
235
|
|