arkgate 4.8.22 → 4.8.24
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 +7 -5
- package/bin/ark-check-runtime.mjs +27 -78
- package/bin/ark-dashboard.mjs +56 -53
- package/bin/ark-layer-match.mjs +1117 -38
- package/bin/ark-mcp-runtime.mjs +412 -138
- package/bin/ark-mcp.mjs +68 -4
- package/bin/ark-shared.mjs +39 -0
- package/bin/ark.mjs +65 -97
- package/bin/lib/adapter-contract-types.mjs +5 -0
- package/bin/lib/adapter-contract.mjs +15 -0
- package/bin/lib/agent-projection-command.mjs +5 -1
- package/bin/lib/analysis-engine.mjs +15 -11
- package/bin/lib/architecture-scan.mjs +50 -6
- package/bin/lib/ark-order-doctor.mjs +1 -1
- package/bin/lib/ark-order-facts.mjs +410 -15
- package/bin/lib/ark-order-invariants.mjs +51 -9
- package/bin/lib/ark-order-sensors.mjs +28 -101
- package/bin/lib/ark-order-types.mjs +2 -0
- package/bin/lib/ark-run-doctor.mjs +14 -0
- package/bin/lib/ark-run-facts.mjs +434 -11
- package/bin/lib/ark-run-sensors.mjs +5 -3
- package/bin/lib/arkgate-pins.mjs +91 -0
- package/bin/lib/arkrules-sensors.mjs +50 -249
- package/bin/lib/check-args.mjs +23 -1
- package/bin/lib/ci-and-commands.mjs +16 -6
- package/bin/lib/class-source-scan.mjs +479 -0
- package/bin/lib/codex-home.mjs +4 -2
- package/bin/lib/config-contract-slices.mjs +701 -0
- package/bin/lib/config-contract.mjs +5 -2
- package/bin/lib/config-warnings.mjs +32 -0
- package/bin/lib/dashboard-cli.mjs +62 -0
- package/bin/lib/design-delta.mjs +1 -1
- package/bin/lib/diagnostic-catalog.mjs +45 -6
- package/bin/lib/doctor-advisories.mjs +84 -1
- package/bin/lib/doctor-next-actions.mjs +17 -2
- package/bin/lib/doctor-plan.mjs +7 -9
- package/bin/lib/effective-contract-load.mjs +121 -59
- package/bin/lib/enforcement-honesty.mjs +22 -0
- package/bin/lib/enforcement-profiles.mjs +40 -4
- package/bin/lib/first-run-help.mjs +3 -2
- package/bin/lib/flat-parent-importers.mjs +137 -0
- package/bin/lib/flat-parent-pilot.mjs +192 -0
- package/bin/lib/gate-files.mjs +29 -2
- package/bin/lib/git-change-scope.mjs +109 -0
- package/bin/lib/github-enforcement.mjs +1 -1
- package/bin/lib/hook-templates.mjs +18 -16
- package/bin/lib/host-support-matrix.mjs +3 -1
- package/bin/lib/html-report-advisories.mjs +2 -0
- package/bin/lib/html-report-depth.mjs +3 -1
- package/bin/lib/import-resolve.mjs +13 -1
- package/bin/lib/install-migrate.mjs +50 -73
- package/bin/lib/invariant-coverage.mjs +59 -11
- package/bin/lib/mcp-adoption.mjs +9 -1
- package/bin/lib/mcp-hook-payload.mjs +401 -61
- package/bin/lib/package-manager.mjs +59 -1
- package/bin/lib/passthrough-commands.mjs +83 -0
- package/bin/lib/persistence-write-hint.mjs +81 -0
- package/bin/lib/physical-cohesion.mjs +16 -18
- package/bin/lib/pilot-loop.mjs +21 -8
- package/bin/lib/pin-dependent-gates.mjs +117 -0
- package/bin/lib/policy-delta-io.mjs +168 -10
- package/bin/lib/prepare-change.mjs +120 -6
- package/bin/lib/prepare-write.mjs +1 -0
- package/bin/lib/presets.mjs +3 -5
- package/bin/lib/remediation.mjs +23 -4
- package/bin/lib/resolved-candidate-facts.mjs +315 -55
- package/bin/lib/rules-inventory.mjs +22 -3
- package/bin/lib/rules-under-contract.mjs +179 -5
- package/bin/lib/scan-files.mjs +32 -0
- package/bin/lib/snippet-analysis.mjs +140 -6
- package/bin/lib/source-policy.mjs +34 -0
- package/bin/lib/stable-hash.mjs +90 -0
- package/bin/lib/start-install-recovery.mjs +71 -1
- package/bin/lib/start-preview.mjs +30 -0
- package/bin/lib/status-command.mjs +33 -4
- package/bin/lib/status-manifest.mjs +32 -4
- package/bin/lib/team-parliament-io.mjs +79 -75
- package/bin/lib/team-parliament.mjs +6 -1
- package/bin/lib/write-path-capabilities.mjs +4 -2
- package/dist/chunk-A5EIY3HI.cjs +5 -0
- package/dist/chunk-MBXCFLO4.js +16 -0
- package/dist/chunk-RNQHRDCQ.cjs +16 -0
- package/dist/chunk-SVSQVGFJ.js +5 -0
- package/dist/{configTypes-CTELo87-.d.ts → configTypes-BJbaeoj4.d.cts} +53 -4
- package/dist/configTypes-BJbaeoj4.d.ts +246 -0
- package/dist/eslint/index.cjs +17 -4
- package/dist/eslint/index.d.cts +359 -0
- package/dist/eslint/index.d.ts +178 -158
- package/dist/eslint/index.js +10 -4
- package/dist/index.cjs +38 -37
- package/dist/index.d.cts +5475 -0
- package/dist/index.d.ts +3408 -296
- package/dist/index.js +38 -37
- package/dist/inspectorListen-6DQVJ4DC.js +1 -0
- package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
- package/dist/nestjs/index.cjs +1 -18
- package/dist/nestjs/index.d.cts +22 -0
- package/dist/nestjs/index.d.ts +1 -3
- package/dist/nestjs/index.js +1 -18
- package/dist/order/index.cjs +1 -1
- package/dist/order/index.d.cts +155 -0
- package/dist/order/index.d.ts +15 -3
- package/dist/order/index.js +1 -1
- package/dist/runtime/index.cjs +13 -30
- package/dist/{diagnosticCatalog-WD2TPSwZ.d.ts → runtime/index.d.cts} +814 -290
- package/dist/runtime/index.d.ts +2678 -108
- package/dist/runtime/index.js +13 -30
- package/dist/{types-yDNEQ603.d.ts → types-DRinG-vT.d.cts} +576 -140
- package/dist/types-DRinG-vT.d.ts +1792 -0
- package/docs/README.md +3 -1
- package/docs/agent-guide.md +92 -15
- package/docs/ai-gates.md +160 -26
- package/docs/arkorder.md +32 -15
- package/docs/brownfield-adoption.md +5 -1
- package/docs/configuration.md +273 -12
- package/docs/develop.md +2 -1
- package/docs/diagnostics.md +122 -13
- package/docs/package-surface.md +31 -8
- package/docs/product-voice.md +1 -1
- package/package.json +59 -18
- package/schemas/ark.analysis-result.schema.json +15 -0
- package/schemas/ark.config.schema.json +163 -4
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/schemas/ark.status-manifest.schema.json +22 -0
- package/server.json +3 -9
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +4 -1
- package/templates/agent-skills/ark-order/SKILL.md +4 -2
- package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
- package/templates/arkrules/PersistenceAdapters.json +1 -1
- package/templates/arkrules/PresentationAdapters.json +1 -1
- package/templates/skills/ark-adopt.md +4 -1
- package/templates/skills/ark-order.md +4 -2
- package/templates/skills/ark-runtime.md +6 -1
- package/dist/types-fQ9-KrBj.d.ts +0 -183
package/bin/ark-mcp.mjs
CHANGED
|
@@ -23,6 +23,8 @@ function launcherArgs(argv) {
|
|
|
23
23
|
failOnNewSmells: /^(?:1|true|yes|on)$/i.test(
|
|
24
24
|
String(process.env.ARK_FAIL_ON_NEW_SMELLS ?? '').trim()
|
|
25
25
|
),
|
|
26
|
+
help: false,
|
|
27
|
+
version: false,
|
|
26
28
|
};
|
|
27
29
|
for (let index = 2; index < argv.length; index += 1) {
|
|
28
30
|
const value = argv[index];
|
|
@@ -35,6 +37,8 @@ function launcherArgs(argv) {
|
|
|
35
37
|
else if (value === '--config' && argv[index + 1]) args.config = argv[++index];
|
|
36
38
|
else if (value === '--manifest' && argv[index + 1]) args.manifest = argv[++index];
|
|
37
39
|
else if (value === '--tsconfig' && argv[index + 1]) args.tsconfig = argv[++index];
|
|
40
|
+
else if (value === '--help' || value === '-h') args.help = true;
|
|
41
|
+
else if (value === '--version' || value === '-V' || value === '-v') args.version = true;
|
|
38
42
|
}
|
|
39
43
|
return args;
|
|
40
44
|
}
|
|
@@ -92,13 +96,56 @@ async function tryResidentHook(args, hookInput) {
|
|
|
92
96
|
return response;
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
const USAGE = `Usage: arkgate-mcp [--root <dir>] [--config <file>] [--manifest <file>] [--tsconfig <file>]
|
|
100
|
+
[--hook | --hook-repair] [--fail-on-new-smells] [--root-env <VAR[,VAR]>]
|
|
101
|
+
[--session-context]
|
|
102
|
+
|
|
103
|
+
Starts the ArkGate MCP stdio server (JSON-RPC on stdin/stdout). With --hook, runs one
|
|
104
|
+
PreToolUse write-gate evaluation from the host payload on stdin: exit 0 allows, exit 2
|
|
105
|
+
blocks (a gate that cannot run blocks governed source writes too).
|
|
106
|
+
|
|
107
|
+
-h, --help Show this help
|
|
108
|
+
-V, --version Print the arkgate version
|
|
109
|
+
`;
|
|
110
|
+
|
|
95
111
|
const args = launcherArgs(process.argv);
|
|
112
|
+
if (args.version) {
|
|
113
|
+
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
114
|
+
process.stdout.write(`${pkg.version}\n`);
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
if (args.help) {
|
|
118
|
+
process.stdout.write(USAGE);
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
96
121
|
let hookInput;
|
|
122
|
+
let hookRoot;
|
|
97
123
|
let residentHandled = false;
|
|
98
124
|
try {
|
|
99
125
|
if (args.hook) {
|
|
100
126
|
hookInput = fs.readFileSync(0, 'utf8');
|
|
101
|
-
|
|
127
|
+
// Root: a set --root-env var or a root holding the config, else walk up from the
|
|
128
|
+
// payload (file, cwd, workspace roots) to the nearest config, like ark-check.
|
|
129
|
+
const { discoverHookRoot, hookOutsideArkProjectResponse } = await import(
|
|
130
|
+
'./lib/mcp-hook-payload.mjs'
|
|
131
|
+
);
|
|
132
|
+
const discovered = discoverHookRoot({ argv: process.argv, hookInput });
|
|
133
|
+
if (!discovered.configFound && discovered.configExplicit) {
|
|
134
|
+
// No Ark project owns this write (4.8.23 never blocked it either).
|
|
135
|
+
const response = hookOutsideArkProjectResponse({
|
|
136
|
+
hookInput,
|
|
137
|
+
config: args.config,
|
|
138
|
+
grokHookEvent: Boolean(process.env.GROK_HOOK_EVENT),
|
|
139
|
+
});
|
|
140
|
+
if (response.stdout) process.stdout.write(response.stdout);
|
|
141
|
+
if (response.stderr) process.stderr.write(response.stderr);
|
|
142
|
+
process.exitCode = response.status;
|
|
143
|
+
residentHandled = true;
|
|
144
|
+
} else {
|
|
145
|
+
hookRoot = discovered.root;
|
|
146
|
+
args.root = hookRoot;
|
|
147
|
+
}
|
|
148
|
+
const resident = residentHandled ? null : await tryResidentHook(args, hookInput);
|
|
102
149
|
if (resident) {
|
|
103
150
|
if (resident.stdout) process.stdout.write(resident.stdout);
|
|
104
151
|
if (resident.stderr) process.stderr.write(resident.stderr);
|
|
@@ -108,9 +155,26 @@ try {
|
|
|
108
155
|
}
|
|
109
156
|
if (!residentHandled) {
|
|
110
157
|
const runtime = await import('./ark-mcp-runtime.mjs');
|
|
111
|
-
await runtime.runArkMcp({ hookInput });
|
|
158
|
+
await runtime.runArkMcp({ hookInput, hookRoot });
|
|
112
159
|
}
|
|
113
160
|
} catch (error) {
|
|
114
|
-
|
|
115
|
-
|
|
161
|
+
if (args.hook) {
|
|
162
|
+
// Hosts treat exit 1 as non-blocking: a gate that cannot run must not fail open
|
|
163
|
+
// for governed source writes. Plumbing cases (bad payload, non-source) still allow.
|
|
164
|
+
const { hookFailClosedResponse, hookRootFromArgv } = await import('./lib/mcp-hook-payload.mjs');
|
|
165
|
+
const response = hookFailClosedResponse({
|
|
166
|
+
hookInput,
|
|
167
|
+
error,
|
|
168
|
+
root: hookRoot ?? hookRootFromArgv(process.argv),
|
|
169
|
+
grokHookEvent: Boolean(process.env.GROK_HOOK_EVENT),
|
|
170
|
+
});
|
|
171
|
+
if (response.stdout) process.stdout.write(response.stdout);
|
|
172
|
+
process.stderr.write(
|
|
173
|
+
response.stderr || `${error instanceof Error ? error.message : String(error)}\n`
|
|
174
|
+
);
|
|
175
|
+
process.exitCode = response.status;
|
|
176
|
+
} else {
|
|
177
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
178
|
+
process.exitCode = 1;
|
|
179
|
+
}
|
|
116
180
|
}
|
package/bin/ark-shared.mjs
CHANGED
|
@@ -2262,3 +2262,42 @@ export function loadPolicyPackMeta(packId, packsPath = defaultPolicyPacksPath())
|
|
|
2262
2262
|
}
|
|
2263
2263
|
return pack;
|
|
2264
2264
|
}
|
|
2265
|
+
|
|
2266
|
+
/**
|
|
2267
|
+
* Unique starter ArkRules ids. Rule ids must be unique across arkrules/*.json
|
|
2268
|
+
* (`ark-check --promote <id> --apply` refuses an id declared in two documents), and several
|
|
2269
|
+
* project layers can clone the same archetype template. A clone for a layer other than the
|
|
2270
|
+
* archetype's own therefore gets its ids prefixed with the kebab-cased layer name
|
|
2271
|
+
* (`INV-*` invariants keep their prefix: `INV-<LAYER>-…`).
|
|
2272
|
+
*/
|
|
2273
|
+
|
|
2274
|
+
export function kebabLayerName(layerName) {
|
|
2275
|
+
return String(layerName)
|
|
2276
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
2277
|
+
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
2278
|
+
.replace(/^-|-$/g, '')
|
|
2279
|
+
.toLowerCase();
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
/**
|
|
2283
|
+
* @param {{ layer?: string, structure?: unknown[], invariants?: unknown[] }} archetype parsed template
|
|
2284
|
+
* @param {string} layerName target project layer
|
|
2285
|
+
*/
|
|
2286
|
+
export function cloneArchetypeForLayer(archetype, layerName) {
|
|
2287
|
+
const prefix =
|
|
2288
|
+
archetype.layer && archetype.layer !== layerName ? `${kebabLayerName(layerName)}-` : '';
|
|
2289
|
+
const prefixedId = (id) =>
|
|
2290
|
+
id.startsWith('INV-') ? `INV-${prefix.toUpperCase()}${id.slice('INV-'.length)}` : `${prefix}${id}`;
|
|
2291
|
+
const withPrefix = (rules) =>
|
|
2292
|
+
Array.isArray(rules)
|
|
2293
|
+
? rules.map((rule) =>
|
|
2294
|
+
prefix && rule && typeof rule.id === 'string' ? { ...rule, id: prefixedId(rule.id) } : rule
|
|
2295
|
+
)
|
|
2296
|
+
: rules;
|
|
2297
|
+
return {
|
|
2298
|
+
...archetype,
|
|
2299
|
+
layer: layerName,
|
|
2300
|
+
structure: withPrefix(archetype.structure),
|
|
2301
|
+
invariants: withPrefix(archetype.invariants),
|
|
2302
|
+
};
|
|
2303
|
+
}
|
package/bin/ark.mjs
CHANGED
|
@@ -20,7 +20,9 @@ import {
|
|
|
20
20
|
resolveOperatingMode,
|
|
21
21
|
shouldSkipArkgateInstall,
|
|
22
22
|
} from './ark-shared.mjs';
|
|
23
|
-
import {
|
|
23
|
+
import { FALSE_GREEN_GAP_ID } from './lib/field-install.mjs';
|
|
24
|
+
|
|
25
|
+
export { ensureProjectArkgateDependency };
|
|
24
26
|
import { validateHardWriteRequest } from './lib/enforcement-profiles.mjs';
|
|
25
27
|
import {
|
|
26
28
|
applyStartPreview,
|
|
@@ -30,7 +32,9 @@ import {
|
|
|
30
32
|
renderStartPreview,
|
|
31
33
|
} from './lib/start-preview.mjs';
|
|
32
34
|
import {
|
|
35
|
+
ensureProjectArkgateDependency,
|
|
33
36
|
formatStartPackageInstallFailure,
|
|
37
|
+
pinArkgateForSetup,
|
|
34
38
|
runStartPackageInstall,
|
|
35
39
|
} from './lib/start-install-recovery.mjs';
|
|
36
40
|
import { runUpgradeCommand } from './lib/upgrade-command.mjs';
|
|
@@ -47,50 +51,34 @@ import { runStatusCommand } from './lib/status-command.mjs';
|
|
|
47
51
|
import { runAgentProjectionCommand } from './lib/agent-projection-command.mjs';
|
|
48
52
|
import { setupUsage, setupUsageAll, upgradeUsage } from './lib/first-run-help.mjs';
|
|
49
53
|
import { runUpstreamReportCommand } from './lib/upstream-report.mjs';
|
|
54
|
+
import {
|
|
55
|
+
dashboardHelp,
|
|
56
|
+
isMcpCommand,
|
|
57
|
+
isPassthroughCommand,
|
|
58
|
+
runDashboard,
|
|
59
|
+
runMcp,
|
|
60
|
+
withDashboardHelp,
|
|
61
|
+
} from './lib/passthrough-commands.mjs';
|
|
50
62
|
|
|
51
63
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
52
64
|
const arkCheck = path.join(here, 'ark-check.mjs');
|
|
53
|
-
const arkDashboard = path.join(here, 'ark-dashboard.mjs');
|
|
54
|
-
const dashboardHelp = `arkgate dashboard (alias ark dashboard) — observability TUI.
|
|
55
|
-
Usage: arkgate dashboard [--url <snapshot-url>] [--interval <ms>]
|
|
56
|
-
Polls an ArkRun inspector; it does not start the kernel.`;
|
|
57
|
-
|
|
58
|
-
function withDashboardHelp(text, detailed) {
|
|
59
|
-
const extra = detailed
|
|
60
|
-
? ' arkgate dashboard [--url <snapshot-url>] [--interval <ms>]\n arkgate report [--root <project>] [--json] [--title <text>] [--finding <ref>] [--submit] [--i-confirm-submit]\n'
|
|
61
|
-
: '';
|
|
62
|
-
const extraDesc = detailed
|
|
63
|
-
? ' dashboard ANSI observability TUI (spawns ark-dashboard).\n report Draft an upstream GitHub issue for pedroknigge/arkgate. Create needs --submit plus confirm. --yes does not submit.\n'
|
|
64
|
-
: '';
|
|
65
|
-
if (detailed) {
|
|
66
|
-
return text
|
|
67
|
-
.replace(' arkgate agents-md [--root', `${extra} arkgate agents-md [--root`)
|
|
68
|
-
.replace(' agents-md Version-matched', `${extraDesc} agents-md Version-matched`);
|
|
69
|
-
}
|
|
70
|
-
return text.replace(
|
|
71
|
-
' arkgate-check --doctor status — one next step\n',
|
|
72
|
-
' arkgate-check --doctor status — one next step\n arkgate dashboard observability TUI (inspector)\n arkgate report draft an upstream GitHub issue\n'
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function runDashboard(passthroughArgs) {
|
|
77
|
-
const result = spawnSync(process.execPath, [arkDashboard, ...passthroughArgs], {
|
|
78
|
-
stdio: 'inherit',
|
|
79
|
-
encoding: 'utf8',
|
|
80
|
-
});
|
|
81
|
-
return result.status ?? 1;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
65
|
/**
|
|
85
66
|
* Day-zero architecture picture: freeze origin under `.ark/reports/` as soon as
|
|
86
67
|
* `ark.config.json` exists — **before** agent docs, skills, CI templates, or cleanups.
|
|
87
68
|
* Idempotent: origin is written only once (`--report` archive semantics).
|
|
88
69
|
*/
|
|
70
|
+
/**
|
|
71
|
+
* Day-zero HTML lives under `.ark/reports/`, never as a stray root artifact. The report
|
|
72
|
+
* writer appends an `.ark/` rule only to an existing `.gitignore` (EH03); a fresh project
|
|
73
|
+
* without one still sees `.ark/` as untracked.
|
|
74
|
+
*/
|
|
75
|
+
const DAY_ZERO_REPORT_PATH = path.join('.ark', 'reports', 'latest.html');
|
|
76
|
+
|
|
89
77
|
function freezeDayZeroOrigin(root) {
|
|
90
78
|
const configPath = path.join(root, 'ark.config.json');
|
|
91
79
|
if (!fs.existsSync(configPath)) {
|
|
92
80
|
console.log(
|
|
93
|
-
` Skip origin freeze — no ark.config.json yet. After init: ${arkCommand(root, 'ark-check', '--report ark
|
|
81
|
+
` Skip origin freeze — no ark.config.json yet. After init: ${arkCommand(root, 'ark-check', '--report .ark/reports/latest.html')}`
|
|
94
82
|
);
|
|
95
83
|
return;
|
|
96
84
|
}
|
|
@@ -102,11 +90,14 @@ function freezeDayZeroOrigin(root) {
|
|
|
102
90
|
: 'Freezing day-zero architecture picture (origin) before agent docs / gates…'
|
|
103
91
|
);
|
|
104
92
|
runArkCheck(
|
|
105
|
-
['--root', root, '--config', 'ark.config.json', '--report',
|
|
93
|
+
['--root', root, '--config', 'ark.config.json', '--report', DAY_ZERO_REPORT_PATH],
|
|
106
94
|
{ cwd: root }
|
|
107
95
|
);
|
|
108
96
|
}
|
|
109
97
|
|
|
98
|
+
/** Commands that read a non-default contract file via --config. */
|
|
99
|
+
const CONFIG_FLAG_COMMANDS = new Set(['preflight', 'status', 'agents-md', 'agent-projection']);
|
|
100
|
+
|
|
110
101
|
function parseArgs(argv) {
|
|
111
102
|
const args = {
|
|
112
103
|
command: undefined,
|
|
@@ -191,8 +182,8 @@ function parseArgs(argv) {
|
|
|
191
182
|
else if (arg === '--version' || arg === '-V') args.version = true;
|
|
192
183
|
else if (!arg.startsWith('-') && args.command === undefined) {
|
|
193
184
|
args.command = arg;
|
|
194
|
-
//
|
|
195
|
-
if (arg
|
|
185
|
+
// dashboard / report / mcp own their flags; pass the rest through untouched.
|
|
186
|
+
if (isPassthroughCommand(arg)) {
|
|
196
187
|
args.passthrough = argv.slice(i + 1);
|
|
197
188
|
break;
|
|
198
189
|
}
|
|
@@ -265,33 +256,6 @@ async function askYesNo(rl, question, defaultYes = true) {
|
|
|
265
256
|
return answer === 'y' || answer === 'yes';
|
|
266
257
|
}
|
|
267
258
|
|
|
268
|
-
/**
|
|
269
|
-
* Pin arkgate in package.json (and optionally run the package manager).
|
|
270
|
-
* start calls this so CI/`npx` is not forced to rely on a stale global install.
|
|
271
|
-
*
|
|
272
|
-
* @param {string} root
|
|
273
|
-
* @param {{ install?: boolean, runPackageManager?: boolean }} [opts]
|
|
274
|
-
*/
|
|
275
|
-
export function ensureProjectArkgateDependency(root, opts = {}) {
|
|
276
|
-
const install = opts.install !== false;
|
|
277
|
-
const runPm = opts.runPackageManager === true;
|
|
278
|
-
if (!install) {
|
|
279
|
-
return { pinned: { changed: false, reason: 'skipped-no-install' }, installStatus: null };
|
|
280
|
-
}
|
|
281
|
-
const pinned = pinArkgateDevDependency(root);
|
|
282
|
-
let installStatus = null;
|
|
283
|
-
let hostOutput = '';
|
|
284
|
-
// Only run the package manager after a successful pin change — avoid surprise
|
|
285
|
-
// network on every start when arkgate is already listed.
|
|
286
|
-
if (runPm && pinned.changed) {
|
|
287
|
-
const [command, commandArgs] = packageInstallArgv(root, pinned.version);
|
|
288
|
-
const install = runStartPackageInstall(command, commandArgs, root);
|
|
289
|
-
installStatus = install.status;
|
|
290
|
-
hostOutput = `${install.stdout}\n${install.stderr}`;
|
|
291
|
-
}
|
|
292
|
-
return { pinned, installStatus, hostOutput };
|
|
293
|
-
}
|
|
294
|
-
|
|
295
259
|
async function resolveArchetypeInteractive(rl, root) {
|
|
296
260
|
console.log('');
|
|
297
261
|
console.log('What are you building? (application shape — not a framework name)');
|
|
@@ -384,6 +348,10 @@ async function init(args) {
|
|
|
384
348
|
console.log('Skipped ark.config.json generation.');
|
|
385
349
|
}
|
|
386
350
|
|
|
351
|
+
// Pin arkgate before the gate templates that call its bins (CI workflow, host hooks, MCP).
|
|
352
|
+
console.log('');
|
|
353
|
+
pinArkgateForSetup(root, args, cliVersion);
|
|
354
|
+
|
|
387
355
|
// Origin first: contract-on-tree picture before AGENTS.md / skills / CI templates.
|
|
388
356
|
console.log('');
|
|
389
357
|
freezeDayZeroOrigin(root);
|
|
@@ -447,7 +415,12 @@ async function start(args) {
|
|
|
447
415
|
confidence: preview.analysis?.confidence,
|
|
448
416
|
projectedCoveragePercent: preview.projectedCoverage?.percent,
|
|
449
417
|
totalFiles: preview.projectedCoverage?.totalFiles,
|
|
450
|
-
|
|
418
|
+
// An existing ark.config.json is a locked shape: start keeps it verbatim, so an
|
|
419
|
+
// idempotent re-run (or the remove-host restore command) must not be re-gated on
|
|
420
|
+
// auto-detected confidence. Low coverage still surfaces as an unresolved decision.
|
|
421
|
+
explicitShape:
|
|
422
|
+
Boolean(args.archetype || args.preset) ||
|
|
423
|
+
fs.existsSync(path.join(args.root, 'ark.config.json')),
|
|
451
424
|
force: Boolean(args.force),
|
|
452
425
|
});
|
|
453
426
|
if (!gate.ok) {
|
|
@@ -493,8 +466,11 @@ async function start(args) {
|
|
|
493
466
|
}
|
|
494
467
|
// After applying exact preview bytes, install the pinned package when requested
|
|
495
468
|
// (preview itself never runs the package manager — field: start left pin without node_modules).
|
|
469
|
+
// Host removal never needs the package: no network, no package.json edits.
|
|
496
470
|
if (
|
|
497
471
|
args.install &&
|
|
472
|
+
!args.removeHost &&
|
|
473
|
+
preview.mode !== 'remove-host' &&
|
|
498
474
|
!args.skipPackageManager &&
|
|
499
475
|
fs.existsSync(path.join(args.root, 'package.json'))
|
|
500
476
|
) {
|
|
@@ -549,9 +525,18 @@ async function start(args) {
|
|
|
549
525
|
rec = undefined;
|
|
550
526
|
}
|
|
551
527
|
|
|
552
|
-
// 2) Suggest a shape, in plain language, and confirm.
|
|
553
|
-
|
|
554
|
-
|
|
528
|
+
// 2) Suggest a shape, in plain language, and confirm. An explicit --archetype/--preset
|
|
529
|
+
// locks the shape: it must drive the written contract, not just bypass the preview gate.
|
|
530
|
+
const explicitShape = resolveInitPreset(args);
|
|
531
|
+
let archetype = explicitShape ? explicitShape.archetype : rec?.archetype;
|
|
532
|
+
if (explicitShape) {
|
|
533
|
+
console.log('');
|
|
534
|
+
console.log(
|
|
535
|
+
explicitShape.archetype
|
|
536
|
+
? `Using your shape: ${explicitShape.archetype} → preset ${explicitShape.preset}.`
|
|
537
|
+
: `Using your preset: ${explicitShape.preset}.`
|
|
538
|
+
);
|
|
539
|
+
} else if (rec) {
|
|
555
540
|
console.log('');
|
|
556
541
|
console.log(`Your project looks like: ${rec.label}.`);
|
|
557
542
|
if (rec.analogy) console.log(`In plain terms — ${rec.analogy}`);
|
|
@@ -573,30 +558,7 @@ async function start(args) {
|
|
|
573
558
|
|
|
574
559
|
// 2b) Pin arkgate as a project devDependency so CI/npx do not depend on a stale global.
|
|
575
560
|
// Default install=true; only --no-install skips. (installExplicit tracks user override for copy.)
|
|
576
|
-
|
|
577
|
-
const { pinned, installStatus, hostOutput } = ensureProjectArkgateDependency(root, {
|
|
578
|
-
install: true,
|
|
579
|
-
runPackageManager: !args.skipPackageManager,
|
|
580
|
-
});
|
|
581
|
-
if (pinned.changed) {
|
|
582
|
-
console.log(` Pinned arkgate@${pinned.version} in package.json devDependencies.`);
|
|
583
|
-
if (installStatus !== null && installStatus !== 0) {
|
|
584
|
-
const [command, commandArgs] = packageInstallArgv(root, pinned.version);
|
|
585
|
-
console.error(
|
|
586
|
-
formatStartPackageInstallFailure({
|
|
587
|
-
exitStatus: installStatus,
|
|
588
|
-
installCommand: `${command} ${commandArgs.join(' ')}`,
|
|
589
|
-
hostOutput,
|
|
590
|
-
packageVersion: cliVersion(),
|
|
591
|
-
})
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
} else if (pinned.reason === 'already-present') {
|
|
595
|
-
console.log(` arkgate already in package.json (${pinned.version}).`);
|
|
596
|
-
}
|
|
597
|
-
} else if (!args.install) {
|
|
598
|
-
console.log(' Skipping arkgate package pin (--no-install).');
|
|
599
|
-
}
|
|
561
|
+
pinArkgateForSetup(root, args, cliVersion);
|
|
600
562
|
|
|
601
563
|
// 3) Contract first (config only). Greenfield → shape preset; established repo → detection,
|
|
602
564
|
// so the contract anchors to directories you already have instead of aspirational globs.
|
|
@@ -605,7 +567,10 @@ async function start(args) {
|
|
|
605
567
|
const configPath = path.join(root, 'ark.config.json');
|
|
606
568
|
if (!fs.existsSync(configPath)) {
|
|
607
569
|
const initArgs = ['--root', root, '--init'];
|
|
608
|
-
|
|
570
|
+
// Explicit shape bypasses detection entirely (monorepo/ui-surface heuristics included).
|
|
571
|
+
const startPreset = explicitShape?.preset
|
|
572
|
+
? explicitShape.preset
|
|
573
|
+
: resolveStartInitPreset(root, rec ?? {}, archetype);
|
|
609
574
|
const includeRoots = resolveIncludeRoots(root);
|
|
610
575
|
const tsPackages = detectTsPackageRoots(root);
|
|
611
576
|
const nestedTsPackages = tsPackages.filter((entry) => entry !== '.');
|
|
@@ -814,6 +779,8 @@ async function main() {
|
|
|
814
779
|
console.error(error instanceof Error ? error.message : String(error));
|
|
815
780
|
return 2;
|
|
816
781
|
}
|
|
782
|
+
// Before --version/--help: the MCP server owns stdout (JSON-RPC) and all of its flags.
|
|
783
|
+
if (isMcpCommand(args.command)) return runMcp(args.passthrough);
|
|
817
784
|
if (args.version) {
|
|
818
785
|
console.log(cliVersion());
|
|
819
786
|
return 0;
|
|
@@ -843,12 +810,13 @@ async function main() {
|
|
|
843
810
|
}
|
|
844
811
|
if (
|
|
845
812
|
args.command !== 'preflight' &&
|
|
846
|
-
(args.changes || args.changeMap || args.manifest || args.tsconfig
|
|
847
|
-
args.config !== 'ark.config.json')
|
|
813
|
+
(args.changes || args.changeMap || args.manifest || args.tsconfig)
|
|
848
814
|
) {
|
|
849
|
-
console.error(
|
|
850
|
-
|
|
851
|
-
|
|
815
|
+
console.error('--changes, --change-map, --manifest, and --tsconfig are supported by ark preflight.');
|
|
816
|
+
return 2;
|
|
817
|
+
}
|
|
818
|
+
if (args.config !== 'ark.config.json' && !CONFIG_FLAG_COMMANDS.has(args.command)) {
|
|
819
|
+
console.error('--config is supported by ark preflight, ark status, and ark agents-md.');
|
|
852
820
|
return 2;
|
|
853
821
|
}
|
|
854
822
|
const enforcement = validateHardWriteRequest({
|
|
@@ -118,6 +118,11 @@ export const ARK_ANALYSIS_RESULT_SCHEMA = {
|
|
|
118
118
|
edgeKind: { type: 'string', minLength: 1 },
|
|
119
119
|
arkruleId: { type: 'string', minLength: 1 },
|
|
120
120
|
arkruleSource: { type: 'string', minLength: 1 },
|
|
121
|
+
reasonId: {
|
|
122
|
+
enum: ['CROSS_PARENT_SLICE', 'CROSS_SIBLING_SLICE', 'CROSS_PARENT_VIA_SHARED'],
|
|
123
|
+
},
|
|
124
|
+
universeFrom: { type: 'string', minLength: 1 },
|
|
125
|
+
universeTo: { type: 'string', minLength: 1 },
|
|
121
126
|
},
|
|
122
127
|
},
|
|
123
128
|
nextAction: { type: 'string', minLength: 1 },
|
|
@@ -35,8 +35,19 @@ function nextActionForDiagnostic(ruleId, evidence, violation) {
|
|
|
35
35
|
capability: text(evidence.capability) ?? text(violation.capability) ?? undefined,
|
|
36
36
|
arkruleId: text(evidence.arkruleId) ?? undefined,
|
|
37
37
|
arkruleSource: text(evidence.arkruleSource) ?? undefined,
|
|
38
|
+
reasonId: text(evidence.reasonId) ?? undefined,
|
|
38
39
|
});
|
|
39
40
|
}
|
|
41
|
+
const SLICE_REASON_IDS = new Set(['CROSS_PARENT_SLICE', 'CROSS_SIBLING_SLICE', 'CROSS_PARENT_VIA_SHARED']);
|
|
42
|
+
/** Nested-wall reason from the top level or from gate `details`. */
|
|
43
|
+
function sliceReasonOf(violation) {
|
|
44
|
+
const details = violation.details;
|
|
45
|
+
const nested = details !== null && typeof details === 'object' ? details.reasonId : undefined;
|
|
46
|
+
const raw = text(violation.reasonId) ?? text(nested);
|
|
47
|
+
return raw && SLICE_REASON_IDS.has(raw)
|
|
48
|
+
? raw
|
|
49
|
+
: undefined;
|
|
50
|
+
}
|
|
40
51
|
export function toAdapterDiagnostic(violation, fallbackSeverity = 'error',
|
|
41
52
|
/**
|
|
42
53
|
* Optional precomputed baseline-compatible targetKey (e.g. occurrence-aware from
|
|
@@ -50,6 +61,7 @@ targetKeyOverride) {
|
|
|
50
61
|
(violation.typeOnly === true && violation.peerIsolation !== true)
|
|
51
62
|
? 'warning'
|
|
52
63
|
: fallbackSeverity;
|
|
64
|
+
const reasonId = sliceReasonOf(violation);
|
|
53
65
|
const evidence = {
|
|
54
66
|
...(text(violation.target) ? { target: text(violation.target) } : {}),
|
|
55
67
|
...(text(violation.fromLayer) ? { fromLayer: text(violation.fromLayer) } : {}),
|
|
@@ -74,6 +86,9 @@ targetKeyOverride) {
|
|
|
74
86
|
...(text(violation.edgeKind) ? { edgeKind: text(violation.edgeKind) } : {}),
|
|
75
87
|
...(text(violation.arkruleId) ? { arkruleId: text(violation.arkruleId) } : {}),
|
|
76
88
|
...(text(violation.arkruleSource) ? { arkruleSource: text(violation.arkruleSource) } : {}),
|
|
89
|
+
...(reasonId ? { reasonId } : {}),
|
|
90
|
+
...(reasonId && text(violation.universeFrom) ? { universeFrom: text(violation.universeFrom) } : {}),
|
|
91
|
+
...(reasonId && text(violation.universeTo) ? { universeTo: text(violation.universeTo) } : {}),
|
|
77
92
|
};
|
|
78
93
|
const targetKey = targetKeyOverride ?? adapterFindingTargetKey(violation);
|
|
79
94
|
const findingRef = adapterFindingRefFromTargetKey(targetKey);
|
|
@@ -71,15 +71,18 @@ export function collectAgentProjectionFacts(options = {}) {
|
|
|
71
71
|
const startRoot = path.resolve(options.root || process.cwd());
|
|
72
72
|
const configName = options.config || 'ark.config.json';
|
|
73
73
|
let resolvedRoot = startRoot;
|
|
74
|
+
let configPath = null;
|
|
74
75
|
try {
|
|
75
76
|
const resolved = resolveEffectiveProjectRoot(startRoot, {
|
|
76
77
|
configName,
|
|
77
78
|
writeMode: false,
|
|
78
79
|
});
|
|
79
80
|
resolvedRoot = path.resolve(resolved.root || startRoot);
|
|
81
|
+
if (resolved.configFound && resolved.configPath) configPath = path.resolve(resolved.configPath);
|
|
80
82
|
} catch {
|
|
81
83
|
resolvedRoot = startRoot;
|
|
82
84
|
}
|
|
85
|
+
if (!configPath) configPath = path.resolve(resolvedRoot, configName);
|
|
83
86
|
|
|
84
87
|
const version =
|
|
85
88
|
(typeof options.arkgateVersion === 'string' && options.arkgateVersion.trim()) ||
|
|
@@ -88,7 +91,8 @@ export function collectAgentProjectionFacts(options = {}) {
|
|
|
88
91
|
|
|
89
92
|
let layers = options.layers;
|
|
90
93
|
if (layers === undefined) {
|
|
91
|
-
|
|
94
|
+
// Read the contract the caller named (--config), not a hardcoded ark.config.json.
|
|
95
|
+
layers = loadConfigLayersForAgents(resolvedRoot, configPath);
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
const layerSummaries = Array.isArray(layers)
|