arkgate 2.9.0 → 2.9.2
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 +62 -0
- package/README.md +8 -2
- package/bin/ark-check.mjs +19 -1
- package/bin/ark.mjs +131 -20
- package/bin/lib/agent-gates.mjs +66 -6
- package/bin/lib/field-install.mjs +334 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +1 -1
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.js +1 -1
- package/dist/nestjs/index.js.map +1 -1
- package/dist/runtime/index.cjs +1 -1
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/templates/skills/ark-adopt.md +57 -10
- package/templates/skills/ark-architect.md +33 -2
- package/templates/skills/ark-autopilot.md +76 -14
- package/templates/skills/ark-contract.md +36 -0
- package/templates/skills/ark-coverage.md +81 -27
- package/templates/skills/ark-explain.md +34 -1
- package/templates/skills/ark-explore.md +119 -0
- package/templates/skills/ark-fix.md +47 -2
- package/templates/skills/ark-loop.md +48 -2
- package/templates/skills/ark-place.md +36 -0
- package/templates/skills/ark-runtime.md +36 -0
- package/templates/skills/ark-think.md +63 -12
- package/templates/skills/ark-upgrade.md +33 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,68 @@ All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are do
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 2.9.2 — 2026-07-09
|
|
8
|
+
|
|
9
|
+
Skill surface hardening: dual-engine, explore, STOP handoffs, AGENTS routing, subagent fan-out.
|
|
10
|
+
**No intentional CLI flag or JSON shape breaks.**
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`/ark-explore` skill:** exploratory architecture reconnaissance — product map, entry
|
|
15
|
+
points, coupling hotspots, false-green risk, ranked *suggestions* (not only residual
|
|
16
|
+
violations). CLI remains a sensor; host agent reads the real tree.
|
|
17
|
+
- **Skill completion contract:** every `/ark-*` template ends with fixed
|
|
18
|
+
`### Completion` fields (Sensor / Opened / Result / Handoff / Incomplete?) —
|
|
19
|
+
**skill incomplete if missing**.
|
|
20
|
+
- **Hard STOP handoffs:** critical paths (false-green, concentrated edge, bulk debt,
|
|
21
|
+
wrong skill) use `STOP — do not continue this skill as complete` + named next skill.
|
|
22
|
+
- **AGENTS skill routing table:** trigger → skill map in generated `AGENTS.md` while
|
|
23
|
+
keeping `/ark-autopilot` as the default when unsure.
|
|
24
|
+
- **Subagent fan-out protocol:** every `/ark-*` skill documents optional **parallel
|
|
25
|
+
subagents** when the host supports them (disjoint read-only scopes + parent merge);
|
|
26
|
+
otherwise **fall back to sequential**. AGENTS.md repeats the rule.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **Skills dual-engine (deterministic + exploratory):** **all** shipped skill templates
|
|
31
|
+
require dual-engine behavior (CLI sensor + real source/product pass where applicable).
|
|
32
|
+
Plan empty ≠ architecture healthy without explore. Refresh installed skills with
|
|
33
|
+
`ark-check --install-agent-gates --skills-only --force`.
|
|
34
|
+
|
|
35
|
+
## 2.9.1 — 2026-07-09
|
|
36
|
+
|
|
37
|
+
Field-install honesty: non-TTY start, baseline→CI sync, pin, false-green soft block, Grok defaults.
|
|
38
|
+
**No intentional CLI flag or JSON shape breaks.**
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **Non-TTY `ark start` / `ark init`:** when stdin/stdout are not a TTY and `--yes` was
|
|
43
|
+
omitted, guided setup no longer throws on a null readline interface. Non-interactive
|
|
44
|
+
sessions use the same defaults as `--yes` (agents never hang on prompts).
|
|
45
|
+
- **Baseline → CI/scripts sync:** after a successful `--update-baseline`, existing
|
|
46
|
+
`package.json` scripts and GitHub Actions workflows that already run `ark-check` gain
|
|
47
|
+
`--baseline .ark-baseline.json` without a full `--force` reinstall of gate templates.
|
|
48
|
+
- **Grok in default agent tools:** no-signal `--install-agent-gates` now installs
|
|
49
|
+
claude + cursor + codex + **grok**; `GROK_BUILD` / `XAI_GROK` env also adds Grok when
|
|
50
|
+
other hosts are detected.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **`start` pins `arkgate` as a devDependency** (opt out with `--no-install`) so CI/`npx`
|
|
55
|
+
are not forced to rely on a stale global install.
|
|
56
|
+
- **False-green contract soft block:** doctor adoption gap
|
|
57
|
+
`contract-false-green-io-under-application` when Domain/Persistence are empty while
|
|
58
|
+
Application globs still cover I/O dirs (airtable/supabase/prisma/…). `ark start`
|
|
59
|
+
wrap-up and `/ark-autopilot` steer to `/ark-adopt` / `/ark-contract` instead of pure
|
|
60
|
+
ENFORCE victory.
|
|
61
|
+
- **`bin/lib/field-install.mjs`:** field-install helpers (baseline sync, pin, false-green)
|
|
62
|
+
extracted from the agent-gates surface for scannability; re-exported from `agent-gates.mjs`.
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- **Public ROADMAP:** active backlog is **Track W** (constrained write → verified repair:
|
|
67
|
+
W1–W6). Finished foundation tracks live under Shipped.
|
|
68
|
+
|
|
7
69
|
## 2.9.0 — 2026-07-09
|
|
8
70
|
|
|
9
71
|
Track P: slice isolation, vertical-slice + DDD presets, skill surface, and adoption depth.
|
package/README.md
CHANGED
|
@@ -144,19 +144,25 @@ npx arkgate-check --coverage
|
|
|
144
144
|
Install once: `npx arkgate-check --install-agent-gates`
|
|
145
145
|
(`--tools claude,cursor,codex,grok` to pick hosts.)
|
|
146
146
|
|
|
147
|
-
**Default is always `/ark-autopilot`.** The rest are escapes, not a second curriculum
|
|
147
|
+
**Default is always `/ark-autopilot`.** The rest are escapes, not a second curriculum.
|
|
148
|
+
Generated `AGENTS.md` includes a **skill routing table** (trigger → skill). Skills are
|
|
149
|
+
**dual-engine** (CLI sensor + read real source) and end with a fixed **completion contract**;
|
|
150
|
+
critical handoffs say **STOP** and name the next skill (hosts must follow — markdown cannot chain calls).
|
|
151
|
+
When the host allows it, skills may **fan out parallel subagents** (disjoint scopes);
|
|
152
|
+
otherwise they **fall back to sequential**.
|
|
148
153
|
|
|
149
154
|
| Need | Skill |
|
|
150
155
|
|------|--------|
|
|
151
156
|
| Only the apply loop (plan already exists) | `/ark-loop` |
|
|
152
157
|
| Empty greenfield shape/scaffold | `/ark-architect` |
|
|
153
158
|
| Deep brownfield / manifest mining alone | `/ark-adopt` |
|
|
159
|
+
| Exploratory map of the real product (no apply) | `/ark-explore` |
|
|
154
160
|
| New file placement | `/ark-place` |
|
|
155
161
|
| Gate violation on a change | `/ark-fix` |
|
|
156
162
|
| Design trade-offs within the contract (no package LLM) | `/ark-think` |
|
|
157
163
|
| Edit `ark.config.json` safely | `/ark-contract` |
|
|
158
164
|
| Plain-language tour of the report | `/ark-explain` |
|
|
159
|
-
| Deep
|
|
165
|
+
| Deep coverage + opportunities audit | `/ark-coverage` |
|
|
160
166
|
| Migrate hand-rolled bus/outbox (TS) | `/ark-runtime` |
|
|
161
167
|
| Bump ArkGate + refresh all agent hosts | `/ark-upgrade` |
|
|
162
168
|
|
package/bin/ark-check.mjs
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
REQUIRED_GATE_FILES,
|
|
45
45
|
codexPromptsDir,
|
|
46
46
|
} from './lib/agent-gates.mjs';
|
|
47
|
+
import { syncBaselineIntoCheckSurfaces } from './lib/field-install.mjs';
|
|
47
48
|
import {
|
|
48
49
|
detectEnforcement,
|
|
49
50
|
renderHtmlReport,
|
|
@@ -1087,8 +1088,25 @@ async function main() {
|
|
|
1087
1088
|
console.log('Gate with: ark-check --root . --config ark.config.json --strict-config');
|
|
1088
1089
|
return;
|
|
1089
1090
|
}
|
|
1090
|
-
const { fullPath, count } = writeBaseline(root,
|
|
1091
|
+
const { fullPath, count } = writeBaseline(root, baselineName, violations);
|
|
1091
1092
|
console.log(`Wrote ${fullPath} with ${count} frozen violation key(s).`);
|
|
1093
|
+
// Keep existing package.json scripts + CI workflows on the ratchet without a
|
|
1094
|
+
// full --force reinstall (field log: baseline after start left CI without --baseline).
|
|
1095
|
+
const baselineRel = path.isAbsolute(baselineName)
|
|
1096
|
+
? path.relative(root, baselineName).split(path.sep).join('/')
|
|
1097
|
+
: String(baselineName).replace(/^\.\/+/, '');
|
|
1098
|
+
const sync = syncBaselineIntoCheckSurfaces(root, {
|
|
1099
|
+
baselineRel: baselineRel || '.ark-baseline.json',
|
|
1100
|
+
});
|
|
1101
|
+
if (sync.changed.length > 0) {
|
|
1102
|
+
console.log(
|
|
1103
|
+
`Synced --baseline into: ${sync.changed.map((c) => c.file).join(', ')}`
|
|
1104
|
+
);
|
|
1105
|
+
} else {
|
|
1106
|
+
console.log(
|
|
1107
|
+
'No existing check scripts/workflows needed a --baseline patch (add check:architecture or re-run --install-agent-gates).'
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1092
1110
|
console.log('Commit it and gate CI with: ark-check --baseline (only NEW violations fail).');
|
|
1093
1111
|
if (summary.total > 0) printViolationBreakdown(summary);
|
|
1094
1112
|
return;
|
package/bin/ark.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
resolveArchetypePreset,
|
|
19
19
|
resolveOperatingMode,
|
|
20
20
|
} from './ark-shared.mjs';
|
|
21
|
+
import { pinArkgateDevDependency, FALSE_GREEN_GAP_ID } from './lib/field-install.mjs';
|
|
21
22
|
|
|
22
23
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
23
24
|
const arkCheck = path.join(here, 'ark-check.mjs');
|
|
@@ -70,24 +71,32 @@ Commands:
|
|
|
70
71
|
|
|
71
72
|
Options:
|
|
72
73
|
--yes Non-interactive defaults: create config if needed, install gate templates, run strict check.
|
|
74
|
+
(Also the implicit default when stdin/stdout are not a TTY — agents never hang on prompts.)
|
|
73
75
|
--force Allow generated files to overwrite existing files.
|
|
74
76
|
--no-strict Skip the final strict ark-check run.
|
|
75
|
-
--no-install
|
|
77
|
+
--no-install Skip adding/installing arkgate as a project devDependency (start/upgrade).
|
|
76
78
|
--preset Start from a named architecture preset instead of detection.
|
|
77
79
|
--archetype Application shape from templates/architecture-playbook.json (maps to the matching preset).
|
|
78
80
|
Valid ids: crud-product, api-backend, frontend-surface, library-sdk, cli-utility,
|
|
79
81
|
worker-pipeline, event-coordinator, integration-bridge, multi-app-workspace, prototype-spike,
|
|
80
82
|
vertical-slice-product, ddd-bounded-contexts.
|
|
81
|
-
--tools Comma-separated agents to gate (claude,cursor,codex,windsurf,cline,copilot,kiro,roo,continue,gemini).
|
|
82
|
-
Omit to auto-detect from each tool's config dir, falling back to claude+cursor+codex.
|
|
83
|
+
--tools Comma-separated agents to gate (claude,cursor,codex,grok,windsurf,cline,copilot,kiro,roo,continue,gemini).
|
|
84
|
+
Omit to auto-detect from each tool's config dir, falling back to claude+cursor+codex+grok.
|
|
83
85
|
|
|
84
86
|
Interactive mode (TTY, no --yes): asks what application shape you are building and maps it to a preset.
|
|
87
|
+
Non-interactive (no TTY): uses the same defaults as --yes — never calls readline on a null interface.
|
|
85
88
|
`;
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
// The package-manager command that adds arkgate
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
// The package-manager command that adds arkgate as a dev dependency.
|
|
92
|
+
// Prefer an explicit version/range when pin already chose one (avoid pin=^2.9.0 then
|
|
93
|
+
// `npm i arkgate@latest` rewriting package.json to a different range).
|
|
94
|
+
function packageInstallArgv(root, versionSpec) {
|
|
95
|
+
const range =
|
|
96
|
+
typeof versionSpec === 'string' && versionSpec.trim()
|
|
97
|
+
? versionSpec.trim()
|
|
98
|
+
: 'latest';
|
|
99
|
+
const spec = range.startsWith('arkgate@') ? range : `arkgate@${range}`;
|
|
91
100
|
const pm = detectPackageManager(root);
|
|
92
101
|
if (pm === 'pnpm') return ['pnpm', ['add', '-D', spec]];
|
|
93
102
|
if (pm === 'yarn') return ['yarn', ['add', '-D', spec]];
|
|
@@ -166,13 +175,49 @@ function isInteractiveTty() {
|
|
|
166
175
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
167
176
|
}
|
|
168
177
|
|
|
178
|
+
/**
|
|
179
|
+
* True when prompts should be skipped and guided defaults applied.
|
|
180
|
+
* Agents typically have no TTY — never call readline on a null interface.
|
|
181
|
+
*/
|
|
182
|
+
export function shouldUseNonInteractiveDefaults(args, tty = isInteractiveTty()) {
|
|
183
|
+
return Boolean(args?.yes || !tty);
|
|
184
|
+
}
|
|
185
|
+
|
|
169
186
|
async function askYesNo(rl, question, defaultYes = true) {
|
|
187
|
+
if (!rl) {
|
|
188
|
+
// Defensive: non-TTY callers must not reach here; return the default rather than throw.
|
|
189
|
+
return defaultYes;
|
|
190
|
+
}
|
|
170
191
|
const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
|
|
171
192
|
const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
|
|
172
193
|
if (!answer) return defaultYes;
|
|
173
194
|
return answer === 'y' || answer === 'yes';
|
|
174
195
|
}
|
|
175
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Pin arkgate in package.json (and optionally run the package manager).
|
|
199
|
+
* start calls this so CI/`npx` is not forced to rely on a stale global install.
|
|
200
|
+
*
|
|
201
|
+
* @param {string} root
|
|
202
|
+
* @param {{ install?: boolean, runPackageManager?: boolean }} [opts]
|
|
203
|
+
*/
|
|
204
|
+
export function ensureProjectArkgateDependency(root, opts = {}) {
|
|
205
|
+
const install = opts.install !== false;
|
|
206
|
+
const runPm = opts.runPackageManager === true;
|
|
207
|
+
if (!install) {
|
|
208
|
+
return { pinned: { changed: false, reason: 'skipped-no-install' }, installStatus: null };
|
|
209
|
+
}
|
|
210
|
+
const pinned = pinArkgateDevDependency(root);
|
|
211
|
+
let installStatus = null;
|
|
212
|
+
// Only run the package manager after a successful pin change — avoid surprise
|
|
213
|
+
// network on every start when arkgate is already listed.
|
|
214
|
+
if (runPm && pinned.changed) {
|
|
215
|
+
const [command, commandArgs] = packageInstallArgv(root, pinned.version);
|
|
216
|
+
installStatus = runCommand(command, commandArgs, root);
|
|
217
|
+
}
|
|
218
|
+
return { pinned, installStatus };
|
|
219
|
+
}
|
|
220
|
+
|
|
176
221
|
async function resolveArchetypeInteractive(rl, root) {
|
|
177
222
|
console.log('');
|
|
178
223
|
console.log('What are you building? (application shape — not a framework name)');
|
|
@@ -214,12 +259,19 @@ function resolveInitPreset(args) {
|
|
|
214
259
|
async function init(args) {
|
|
215
260
|
const root = args.root;
|
|
216
261
|
const configPath = path.join(root, 'ark.config.json');
|
|
217
|
-
const
|
|
262
|
+
const nonInteractive = shouldUseNonInteractiveDefaults(args);
|
|
263
|
+
const interactive = !nonInteractive;
|
|
218
264
|
const rl = interactive
|
|
219
265
|
? readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
220
266
|
: null;
|
|
221
267
|
|
|
222
268
|
try {
|
|
269
|
+
if (nonInteractive && !args.yes) {
|
|
270
|
+
console.log(
|
|
271
|
+
'Non-interactive session (no TTY) — using guided defaults (same as --yes). Pass flags to override.'
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
223
275
|
let archetype = args.archetype;
|
|
224
276
|
let preset = args.preset;
|
|
225
277
|
|
|
@@ -231,9 +283,8 @@ async function init(args) {
|
|
|
231
283
|
const resolved = resolveArchetypePreset(archetype);
|
|
232
284
|
preset = resolved.preset;
|
|
233
285
|
console.log(`Using archetype ${archetype} → preset ${preset} (${resolved.label})`);
|
|
234
|
-
} else if (!preset &&
|
|
235
|
-
// non-TTY
|
|
236
|
-
} else if (!preset && args.yes && !archetype) {
|
|
286
|
+
} else if (!preset && nonInteractive && !archetype) {
|
|
287
|
+
// non-TTY / --yes without explicit shape: recommend → preset
|
|
237
288
|
const rec = buildArchitectureRecommendation(root);
|
|
238
289
|
preset = rec.preset;
|
|
239
290
|
archetype = rec.archetype;
|
|
@@ -244,7 +295,7 @@ async function init(args) {
|
|
|
244
295
|
if (fs.existsSync(configPath)) {
|
|
245
296
|
shouldInit = args.force
|
|
246
297
|
? true
|
|
247
|
-
:
|
|
298
|
+
: nonInteractive
|
|
248
299
|
? false
|
|
249
300
|
: await askYesNo(rl, 'ark.config.json already exists. Regenerate it?', false);
|
|
250
301
|
}
|
|
@@ -259,7 +310,8 @@ async function init(args) {
|
|
|
259
310
|
console.log('Skipped ark.config.json generation.');
|
|
260
311
|
}
|
|
261
312
|
|
|
262
|
-
const installGates =
|
|
313
|
+
const installGates =
|
|
314
|
+
nonInteractive || (await askYesNo(rl, 'Configure agent and CI gate templates?', true));
|
|
263
315
|
if (installGates) {
|
|
264
316
|
const gateArgs = ['--root', root, '--install-agent-gates'];
|
|
265
317
|
if (args.tools) gateArgs.push('--tools', args.tools);
|
|
@@ -269,7 +321,8 @@ async function init(args) {
|
|
|
269
321
|
}
|
|
270
322
|
|
|
271
323
|
const runStrict =
|
|
272
|
-
args.strict &&
|
|
324
|
+
args.strict &&
|
|
325
|
+
(nonInteractive || (await askYesNo(rl, 'Run strict architecture check now?', true)));
|
|
273
326
|
if (runStrict) {
|
|
274
327
|
return runArkCheck(
|
|
275
328
|
['--root', root, '--config', 'ark.config.json', '--strict-config'],
|
|
@@ -299,7 +352,8 @@ async function init(args) {
|
|
|
299
352
|
// orchestrates existing steps (recommend → init → --plan) and frames each in outcome terms.
|
|
300
353
|
async function start(args) {
|
|
301
354
|
const root = args.root;
|
|
302
|
-
const
|
|
355
|
+
const nonInteractive = shouldUseNonInteractiveDefaults(args);
|
|
356
|
+
const interactive = !nonInteractive;
|
|
303
357
|
const rl = interactive
|
|
304
358
|
? readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
305
359
|
: null;
|
|
@@ -310,6 +364,11 @@ async function start(args) {
|
|
|
310
364
|
"I'll look at your code, suggest a shape, set up the guardrails, and show you a plan."
|
|
311
365
|
);
|
|
312
366
|
console.log('Nothing in your code is changed — this only adds Ark configuration.');
|
|
367
|
+
if (nonInteractive && !args.yes) {
|
|
368
|
+
console.log(
|
|
369
|
+
'Non-interactive session (no TTY) — using guided defaults (same as --yes). Pass flags to override.'
|
|
370
|
+
);
|
|
371
|
+
}
|
|
313
372
|
|
|
314
373
|
// 1) Look at the project.
|
|
315
374
|
let rec;
|
|
@@ -332,7 +391,8 @@ async function start(args) {
|
|
|
332
391
|
);
|
|
333
392
|
console.log('match the contract to how your code is already organized, and flag only genuine issues.');
|
|
334
393
|
}
|
|
335
|
-
const proceed =
|
|
394
|
+
const proceed =
|
|
395
|
+
nonInteractive || (await askYesNo(rl, '\nSet Ark up for this shape?', true));
|
|
336
396
|
if (!proceed) {
|
|
337
397
|
archetype = interactive ? await resolveArchetypeInteractive(rl, root) : rec.archetype;
|
|
338
398
|
}
|
|
@@ -340,6 +400,26 @@ async function start(args) {
|
|
|
340
400
|
archetype = await resolveArchetypeInteractive(rl, root);
|
|
341
401
|
}
|
|
342
402
|
|
|
403
|
+
// 2b) Pin arkgate as a project devDependency so CI/npx do not depend on a stale global.
|
|
404
|
+
if (args.install !== false && fs.existsSync(path.join(root, 'package.json'))) {
|
|
405
|
+
const { pinned, installStatus } = ensureProjectArkgateDependency(root, {
|
|
406
|
+
install: true,
|
|
407
|
+
runPackageManager: true,
|
|
408
|
+
});
|
|
409
|
+
if (pinned.changed) {
|
|
410
|
+
console.log(` Pinned arkgate@${pinned.version} in package.json devDependencies.`);
|
|
411
|
+
if (installStatus !== null && installStatus !== 0) {
|
|
412
|
+
console.log(
|
|
413
|
+
` Package manager install exited ${installStatus} — package.json is still pinned; run install when online.`
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
} else if (pinned.reason === 'already-present') {
|
|
417
|
+
console.log(` arkgate already in package.json (${pinned.version}).`);
|
|
418
|
+
}
|
|
419
|
+
} else if (args.install === false) {
|
|
420
|
+
console.log(' Skipping arkgate package pin (--no-install).');
|
|
421
|
+
}
|
|
422
|
+
|
|
343
423
|
// 3) Set up config + gates. Greenfield → the shape's preset; an established repo → detection,
|
|
344
424
|
// so the contract anchors to the directories you already have instead of aspirational globs.
|
|
345
425
|
console.log('');
|
|
@@ -473,8 +553,28 @@ async function start(args) {
|
|
|
473
553
|
|
|
474
554
|
// 5) Plain-language wrap-up — one next step, status light only.
|
|
475
555
|
// Modes are detected (Suggest/Adapt/Enforce), not user-picked settings.
|
|
556
|
+
// Soft-block false-green using the same doctor adoption gap (no second detector).
|
|
557
|
+
let falseGreenGap = null;
|
|
558
|
+
try {
|
|
559
|
+
const doc = JSON.parse(doctorCapture.stdout || '{}');
|
|
560
|
+
falseGreenGap = (doc.doctor?.adoption?.gaps ?? []).find(
|
|
561
|
+
(g) => g?.id === FALSE_GREEN_GAP_ID
|
|
562
|
+
);
|
|
563
|
+
} catch {
|
|
564
|
+
falseGreenGap = null;
|
|
565
|
+
}
|
|
566
|
+
if (falseGreenGap && mode === 'enforce') {
|
|
567
|
+
mode = 'adapt';
|
|
568
|
+
planOk = false;
|
|
569
|
+
}
|
|
570
|
+
|
|
476
571
|
console.log('');
|
|
477
|
-
if (
|
|
572
|
+
if (falseGreenGap) {
|
|
573
|
+
console.log('Done — status: ADAPT (contract may be a false green — do not stop at a clean plan).');
|
|
574
|
+
console.log('What happens now:');
|
|
575
|
+
console.log(` • ${falseGreenGap.message}`);
|
|
576
|
+
console.log(` • Next: ${falseGreenGap.fix}`);
|
|
577
|
+
} else if (mode === 'enforce' && planOk) {
|
|
478
578
|
console.log('Done — status: ENFORCE (gates can honestly protect you).');
|
|
479
579
|
console.log('What happens now:');
|
|
480
580
|
console.log(' • Every edit is checked (in CI and, if wired, at write time).');
|
|
@@ -495,11 +595,16 @@ async function start(args) {
|
|
|
495
595
|
}
|
|
496
596
|
console.log('');
|
|
497
597
|
console.log('Next (the only flow you need):');
|
|
498
|
-
|
|
499
|
-
|
|
598
|
+
if (falseGreenGap) {
|
|
599
|
+
console.log(' 1. In your agent: /ark-adopt (or /ark-contract) — fix the contract first');
|
|
600
|
+
console.log(' → reclassify I/O dirs out of Application; then /ark-autopilot for residual debt.');
|
|
601
|
+
} else {
|
|
602
|
+
console.log(' 1. In your agent: /ark-autopilot');
|
|
603
|
+
console.log(' → origin report, adoption, plan, safe fixes, leave gates on.');
|
|
604
|
+
}
|
|
500
605
|
console.log(` 2. Status anytime: ${arkCommand(root, 'ark-check', '--doctor')}`);
|
|
501
606
|
console.log(` 3. After edits: ${arkCommand(root, 'ark-check', '--root . --config ark.config.json --strict-config')}`);
|
|
502
|
-
if (mode === 'adapt' && planOk) {
|
|
607
|
+
if (mode === 'adapt' && planOk && !falseGreenGap) {
|
|
503
608
|
console.log(
|
|
504
609
|
` 4. When green but cores still optional: ${arkCommand(root, 'ark-check', '--ratchet-cores')} → honest ENFORCE`
|
|
505
610
|
);
|
|
@@ -560,4 +665,10 @@ async function main() {
|
|
|
560
665
|
return 2;
|
|
561
666
|
}
|
|
562
667
|
|
|
563
|
-
|
|
668
|
+
// Only run when executed as the CLI entry (not when imported by unit tests).
|
|
669
|
+
const isMain =
|
|
670
|
+
Boolean(process.argv[1]) &&
|
|
671
|
+
path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
|
|
672
|
+
if (isMain) {
|
|
673
|
+
process.exitCode = await main();
|
|
674
|
+
}
|
package/bin/lib/agent-gates.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
applyFrameworkLayoutOverlays,
|
|
24
24
|
} from '../ark-shared.mjs';
|
|
25
25
|
import { CORE_LAYER_NAMES } from './core-ratchet.mjs';
|
|
26
|
+
import { falseGreenAdoptionGap } from './field-install.mjs';
|
|
26
27
|
import {
|
|
27
28
|
assessCodexHomeMcp,
|
|
28
29
|
codexArkBlockHasPreferredBin,
|
|
@@ -365,6 +366,18 @@ export function checkArgsForRoot(root, { requireGates = false } = {}) {
|
|
|
365
366
|
return `--root . --config ark.config.json --strict-config${baselineFlag}${gatesFlag}`;
|
|
366
367
|
}
|
|
367
368
|
|
|
369
|
+
// Field-install helpers live in field-install.mjs (keep agent-gates scannable).
|
|
370
|
+
// Re-export for callers that already import from this module.
|
|
371
|
+
export {
|
|
372
|
+
ensureBaselineFlagInCheckCommand,
|
|
373
|
+
syncBaselineIntoCheckSurfaces,
|
|
374
|
+
pinArkgateDevDependency,
|
|
375
|
+
IO_DIR_SEGMENTS,
|
|
376
|
+
detectContractFalseGreenRisk,
|
|
377
|
+
FALSE_GREEN_GAP_ID,
|
|
378
|
+
falseGreenAdoptionGap,
|
|
379
|
+
} from './field-install.mjs';
|
|
380
|
+
|
|
368
381
|
export function packageManager(root) {
|
|
369
382
|
// CI always require-gates; baseline follows checkArgsForRoot.
|
|
370
383
|
const checkArgs = checkArgsForRoot(root, { requireGates: true });
|
|
@@ -452,13 +465,39 @@ export function agentInstructions(root) {
|
|
|
452
465
|
|
|
453
466
|
1. If \`ark.config.json\` is missing: run \`${startCmd}\` once.
|
|
454
467
|
2. For adoption / cleanup / “make architecture sound”: run the **\`/ark-autopilot\`** skill
|
|
455
|
-
(origin report → adopt → plan → safe fixes → gates). Do **not** invent a
|
|
456
|
-
|
|
468
|
+
(origin report → adopt → plan → safe fixes → gates). Do **not** invent a second
|
|
469
|
+
architecture curriculum outside the routing table below — when a trigger matches, use
|
|
470
|
+
that skill; when unsure, stay on autopilot.
|
|
457
471
|
3. Status anytime: \`${doctorCmd}\` (status light + next action — not a mode picker).
|
|
458
472
|
4. After ordinary feature edits: run \`${checkCmd}\`. On violations → **\`/ark-fix\`** (or
|
|
459
473
|
\`/ark-place\` for new files, \`/ark-contract\` only if the contract itself is wrong).
|
|
460
474
|
|
|
461
|
-
|
|
475
|
+
Skills are **dual-engine**: deterministic CLI sensors + exploratory read of *this* repo — not JSON-only wrappers.
|
|
476
|
+
When a skill says **STOP — do not continue this skill as complete**, stop and invoke the named handoff skill.
|
|
477
|
+
|
|
478
|
+
### Subagent fan-out
|
|
479
|
+
If the host supports **parallel subagents**, skills may ask you to fan out **read-only**
|
|
480
|
+
scouts (disjoint path scopes) and merge in the parent. If the host does **not**,
|
|
481
|
+
**fall back to sequential** — one cluster/step at a time. Never parallel-write the same
|
|
482
|
+
files; never weaken the gate via subagents.
|
|
483
|
+
|
|
484
|
+
## Skill routing (triggers → skill)
|
|
485
|
+
|
|
486
|
+
| When | Invoke |
|
|
487
|
+
|------|--------|
|
|
488
|
+
| Unsure / make architecture sound | **/ark-autopilot** (default) |
|
|
489
|
+
| Need map / opportunities only (no apply) | \`/ark-explore\` |
|
|
490
|
+
| Greenfield shape / empty tree | \`/ark-architect\` |
|
|
491
|
+
| Brownfield / wrong contract / false-green | \`/ark-adopt\` then \`/ark-contract\` if globs wrong |
|
|
492
|
+
| Edit \`ark.config.json\` layers/rules/intents | \`/ark-contract\` |
|
|
493
|
+
| New file “where does this go?” | \`/ark-place\` |
|
|
494
|
+
| Gate violation on a change | \`/ark-fix\` |
|
|
495
|
+
| Drive plan to goal.met | \`/ark-loop\` |
|
|
496
|
+
| Deep coverage + ranked audit | \`/ark-coverage\` |
|
|
497
|
+
| Design trade-offs (no package LLM) | \`/ark-think\` |
|
|
498
|
+
| Explain / HTML report tour | \`/ark-explain\` |
|
|
499
|
+
| Bump arkgate + refresh hosts | \`/ark-upgrade\` |
|
|
500
|
+
| Optional runtime kernel migrate | \`/ark-runtime\` |
|
|
462
501
|
|
|
463
502
|
## Before editing TypeScript or JavaScript source files
|
|
464
503
|
|
|
@@ -769,10 +808,20 @@ export function resolveTools(args) {
|
|
|
769
808
|
if (fs.existsSync(path.join(root, '.gemini'))) detected.add('gemini');
|
|
770
809
|
// copilot has no reliable directory signal (.github exists in most repos),
|
|
771
810
|
// so it is explicit-only via --tools.
|
|
772
|
-
//
|
|
773
|
-
//
|
|
811
|
+
// Host signals: Grok Build / xAI agents often have no project `.grok/` yet but
|
|
812
|
+
// set an env marker (or run with GROK_*). Include Grok so skills install there.
|
|
813
|
+
if (
|
|
814
|
+
process.env.GROK_BUILD === '1' ||
|
|
815
|
+
process.env.GROK_BUILD === 'true' ||
|
|
816
|
+
process.env.XAI_GROK === '1' ||
|
|
817
|
+
process.env.XAI_GROK === 'true'
|
|
818
|
+
) {
|
|
819
|
+
detected.add('grok');
|
|
820
|
+
}
|
|
821
|
+
// No signal at all: fall back to a complete starter set including Grok (field
|
|
822
|
+
// log: default claude+cursor+codex silently omitted Grok skills for Grok hosts).
|
|
774
823
|
if (detected.size === 0) {
|
|
775
|
-
return { tools: new Set(['claude', 'cursor', 'codex']), source: 'default' };
|
|
824
|
+
return { tools: new Set(['claude', 'cursor', 'codex', 'grok']), source: 'default' };
|
|
776
825
|
}
|
|
777
826
|
return { tools: detected, source: 'detected' };
|
|
778
827
|
}
|
|
@@ -1539,6 +1588,16 @@ export function collectAdoptionGaps(root, config, coverage) {
|
|
|
1539
1588
|
}
|
|
1540
1589
|
}
|
|
1541
1590
|
|
|
1591
|
+
// --- False-green contract (field-install detector; doctor skillGaps already cover missing skills) ---
|
|
1592
|
+
let contractFalseGreen = null;
|
|
1593
|
+
if (!isProducer && config) {
|
|
1594
|
+
const gap = falseGreenAdoptionGap(root, config, coverage);
|
|
1595
|
+
if (gap) {
|
|
1596
|
+
contractFalseGreen = { risk: true, message: gap.message, fix: gap.fix };
|
|
1597
|
+
gaps.push(gap);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1542
1601
|
return {
|
|
1543
1602
|
gaps,
|
|
1544
1603
|
hosts,
|
|
@@ -1549,6 +1608,7 @@ export function collectAdoptionGaps(root, config, coverage) {
|
|
|
1549
1608
|
baseline,
|
|
1550
1609
|
layerBalance,
|
|
1551
1610
|
deployPath,
|
|
1611
|
+
contractFalseGreen,
|
|
1552
1612
|
};
|
|
1553
1613
|
}
|
|
1554
1614
|
|