arkgate 4.8.23 → 4.8.25
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 +153 -0
- package/README.md +7 -5
- package/bin/ark-check-runtime.mjs +41 -91
- package/bin/ark-dashboard.mjs +56 -53
- package/bin/ark-layer-match.mjs +707 -121
- package/bin/ark-mcp-runtime.mjs +448 -169
- package/bin/ark-mcp.mjs +68 -4
- package/bin/ark-shared.mjs +42 -22
- 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/adoption-stance.mjs +2 -2
- package/bin/lib/adr-path.mjs +3 -3
- package/bin/lib/agent-gates.mjs +0 -61
- package/bin/lib/agent-homes.mjs +12 -21
- package/bin/lib/agent-projection-command.mjs +9 -5
- package/bin/lib/ambient-state.mjs +9 -30
- package/bin/lib/analysis-engine.mjs +15 -11
- package/bin/lib/architecture-scan.mjs +61 -15
- package/bin/lib/ark-order-doctor.mjs +4 -29
- package/bin/lib/ark-order-facts.mjs +319 -17
- package/bin/lib/ark-order-sensors.mjs +28 -101
- package/bin/lib/ark-order-types.mjs +3 -2
- package/bin/lib/ark-run-doctor.mjs +17 -28
- package/bin/lib/ark-run-facts.mjs +461 -48
- package/bin/lib/ark-run-sensors.mjs +6 -4
- package/bin/lib/arkgate-pins.mjs +91 -0
- package/bin/lib/arkrule-file-hints.mjs +2 -47
- package/bin/lib/arkrules-contract.mjs +124 -133
- package/bin/lib/arkrules-sensors.mjs +55 -252
- package/bin/lib/check-args.mjs +85 -1
- package/bin/lib/ci-and-commands.mjs +24 -78
- package/bin/lib/ci-merge-boundary.mjs +1 -1
- package/bin/lib/class-source-scan.mjs +479 -0
- package/bin/lib/clone-detection.mjs +474 -0
- package/bin/lib/codex-home.mjs +5 -3
- package/bin/lib/config-contract-slices.mjs +739 -0
- package/bin/lib/config-contract.mjs +42 -607
- package/bin/lib/config-extras.mjs +16 -22
- package/bin/lib/config-warnings.mjs +32 -0
- package/bin/lib/contract-smells.mjs +33 -15
- package/bin/lib/core-ratchet.mjs +0 -2
- package/bin/lib/dashboard-cli.mjs +62 -0
- package/bin/lib/deep-module-coach.mjs +2 -4
- package/bin/lib/deploy-path.mjs +13 -38
- package/bin/lib/design-delta.mjs +1 -1
- package/bin/lib/design-smells.mjs +2 -2
- package/bin/lib/diagnostic-catalog.mjs +23 -9
- package/bin/lib/doctor-advisories.mjs +78 -8
- package/bin/lib/doctor-green-cite.mjs +1 -1
- package/bin/lib/doctor-human.mjs +1 -1
- package/bin/lib/doctor-next-actions.mjs +19 -4
- package/bin/lib/doctor-plan.mjs +9 -13
- package/bin/lib/duplication-io.mjs +572 -0
- package/bin/lib/effective-contract-load.mjs +282 -83
- package/bin/lib/enforcement-profiles.mjs +40 -4
- package/bin/lib/entry-points-io.mjs +424 -0
- package/bin/lib/extra-merge-teeth.mjs +36 -0
- package/bin/lib/field-install.mjs +38 -14
- package/bin/lib/first-run-help.mjs +10 -5
- package/bin/lib/flat-parent-importers.mjs +139 -0
- package/bin/lib/flat-parent-pilot.mjs +40 -100
- package/bin/lib/gate-files.mjs +58 -90
- package/bin/lib/git-change-scope.mjs +109 -0
- package/bin/lib/github-enforcement.mjs +166 -29
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/hook-templates.mjs +17 -15
- package/bin/lib/host-support-matrix.mjs +3 -1
- package/bin/lib/html-report-advisories.mjs +4 -0
- package/bin/lib/html-report-depth.mjs +2 -2
- package/bin/lib/html-report.mjs +2 -2
- package/bin/lib/import-graph-projection.mjs +112 -0
- package/bin/lib/import-resolve.mjs +13 -1
- package/bin/lib/improvement-compass-doctor.mjs +1 -24
- package/bin/lib/install-migrate.mjs +50 -73
- package/bin/lib/invariant-coverage-io.mjs +4 -3
- package/bin/lib/invariant-coverage.mjs +85 -11
- package/bin/lib/invariant-probe-cli.mjs +347 -0
- package/bin/lib/invariant-probe-io.mjs +165 -0
- package/bin/lib/invariant-probe-runner.mjs +268 -0
- package/bin/lib/invariant-probe-sites.mjs +174 -0
- package/bin/lib/invariant-probe-workspace.mjs +183 -0
- package/bin/lib/invariant-probe.mjs +461 -0
- package/bin/lib/invariant-tests-path.mjs +16 -22
- package/bin/lib/literal-path-drift.mjs +4 -2
- package/bin/lib/managed-upgrade.mjs +2 -8
- package/bin/lib/mcp-adoption.mjs +13 -6
- package/bin/lib/mcp-hook-payload.mjs +403 -63
- package/bin/lib/orphan-modules-io.mjs +458 -0
- package/bin/lib/orphan-modules.mjs +251 -0
- package/bin/lib/outside-importers.mjs +197 -0
- package/bin/lib/package-manager.mjs +60 -2
- package/bin/lib/parse-health.mjs +1 -1
- package/bin/lib/passthrough-commands.mjs +83 -0
- package/bin/lib/persistence-write-hint.mjs +106 -0
- package/bin/lib/pin-dependent-gates.mjs +117 -0
- package/bin/lib/policy-delta-io.mjs +219 -14
- package/bin/lib/post-green-path.mjs +1 -1
- package/bin/lib/prepare-change.mjs +115 -7
- package/bin/lib/prepare-write.mjs +2 -2
- package/bin/lib/presets.mjs +59 -73
- package/bin/lib/product-copy.mjs +1 -6
- package/bin/lib/project-root.mjs +31 -62
- package/bin/lib/remediation.mjs +23 -4
- package/bin/lib/resolved-candidate-facts.mjs +405 -117
- package/bin/lib/rules-inventory.mjs +22 -3
- package/bin/lib/rules-under-contract.mjs +247 -9
- package/bin/lib/scan-files.mjs +36 -4
- package/bin/lib/schema-validation.mjs +130 -0
- package/bin/lib/sensor-promote-cli.mjs +2 -11
- package/bin/lib/sensor-promote-io.mjs +33 -22
- package/bin/lib/sensor-promotion.mjs +1 -1
- package/bin/lib/skill-catalog-apply.mjs +1 -1
- package/bin/lib/skill-install.mjs +42 -38
- package/bin/lib/snippet-analysis.mjs +140 -6
- package/bin/lib/source-policy.mjs +34 -0
- package/bin/lib/start-install-recovery.mjs +72 -2
- package/bin/lib/start-preview.mjs +31 -1
- package/bin/lib/states-transitions-presence.mjs +3 -3
- package/bin/lib/status-command.mjs +35 -22
- package/bin/lib/status-manifest.mjs +50 -30
- package/bin/lib/status-transition-catalog.mjs +0 -6
- package/bin/lib/suggestions.mjs +3 -3
- package/bin/lib/team-parliament-io.mjs +76 -88
- package/bin/lib/team-parliament.mjs +21 -10
- package/bin/lib/upgrade-behavior-changes.mjs +2 -2
- package/bin/lib/upgrade-command.mjs +2 -24
- package/bin/lib/upgrade-outcome.mjs +1 -1
- package/bin/lib/upstream-report.mjs +21 -10
- package/bin/lib/violations.mjs +10 -9
- package/bin/lib/weakest-link.mjs +13 -0
- package/bin/lib/write-path-capabilities.mjs +6 -4
- package/dist/chunk-A5EIY3HI.cjs +5 -0
- package/dist/chunk-AHACEKSV.js +16 -0
- package/dist/chunk-SVSQVGFJ.js +5 -0
- package/dist/chunk-UN5EH4QV.cjs +16 -0
- package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-GPvvdleM.d.cts} +30 -4
- package/dist/configTypes-GPvvdleM.d.ts +257 -0
- package/dist/eslint/index.cjs +18 -5
- package/dist/eslint/index.d.cts +368 -0
- package/dist/eslint/index.d.ts +139 -156
- package/dist/eslint/index.js +11 -5
- package/dist/index.cjs +36 -35
- package/dist/index.d.cts +5547 -0
- package/dist/index.d.ts +3283 -198
- package/dist/index.js +36 -35
- 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-35mrwAHe.d.ts → runtime/index.d.cts} +828 -276
- package/dist/runtime/index.d.ts +2741 -133
- package/dist/runtime/index.js +13 -30
- package/dist/{types-Bdj32QwA.d.ts → types-CnauMQQL.d.cts} +607 -163
- package/dist/types-CnauMQQL.d.ts +1800 -0
- package/docs/README.md +3 -1
- package/docs/agent-guide.md +217 -16
- package/docs/ai-gates.md +174 -26
- package/docs/arkorder.md +32 -15
- package/docs/brownfield-adoption.md +5 -1
- package/docs/configuration.md +230 -31
- package/docs/develop.md +2 -1
- package/docs/diagnostics.md +212 -19
- package/docs/package-surface.md +35 -9
- package/docs/product-voice.md +7 -1
- package/package.json +69 -19
- package/schemas/ark.analysis-result.schema.json +15 -0
- package/schemas/ark.config.schema.json +77 -12
- package/schemas/ark.invariant-probe.schema.json +368 -0
- 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 +5 -1
- package/templates/agent-skills/ark-contract/SKILL.md +17 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
- package/templates/agent-skills/ark-explore/SKILL.md +7 -0
- package/templates/agent-skills/ark-fix/SKILL.md +20 -0
- package/templates/agent-skills/ark-order/SKILL.md +4 -2
- package/templates/agent-skills/ark-place/SKILL.md +8 -0
- 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 +5 -1
- package/templates/skills/ark-contract.md +17 -0
- package/templates/skills/ark-coverage.md +2 -0
- package/templates/skills/ark-explore.md +7 -0
- package/templates/skills/ark-fix.md +20 -0
- package/templates/skills/ark-order.md +4 -2
- package/templates/skills/ark-place.md +8 -0
- package/templates/skills/ark-runtime.md +6 -1
- package/bin/lib/ark-order-error.mjs +0 -18
- package/bin/lib/ark-order-invariants.mjs +0 -346
- package/dist/types-Ik_OakcI.d.ts +0 -183
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
invariantIdsFromCatalog,
|
|
17
17
|
loadInvariantCoverageInputs,
|
|
18
18
|
} from './invariant-coverage-io.mjs';
|
|
19
|
+
import { attachProbeEvidence } from './invariant-probe-io.mjs';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* The root as the filesystem sees it. Comparing a realpath against a lexical
|
|
@@ -79,28 +80,11 @@ export function loadSensorMap(root, config, facts) {
|
|
|
79
80
|
}));
|
|
80
81
|
|
|
81
82
|
const catalogued = loaded.arkRules.invariants ?? [];
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
...coverageOptionsFromConfig(config),
|
|
88
|
-
});
|
|
89
|
-
const coverage = evaluateInvariantCoverage({
|
|
90
|
-
arkRules: loaded.arkRules,
|
|
91
|
-
fileContents: inputs.fileContents,
|
|
92
|
-
testFiles: inputs.testFiles,
|
|
93
|
-
testGlobsMissing: inputs.testGlobsMissing,
|
|
94
|
-
coverageBudgetExhausted: inputs.coverageBudgetExhausted === true,
|
|
95
|
-
...(inputs.stats ? { coverageStats: inputs.stats } : {}),
|
|
96
|
-
// The declared roots decide `outsideDeclaredRoots`, which is one of the
|
|
97
|
-
// four reasons canPromoteInvariant refuses. Dropping them here would make
|
|
98
|
-
// this surface promise a promotion the gate then denies.
|
|
99
|
-
...(inputs.coverageRoots ? { coverageRoots: inputs.coverageRoots } : {}),
|
|
100
|
-
});
|
|
101
|
-
coverageRows = coverage.coverage ?? [];
|
|
102
|
-
partial = coverage.partial === true;
|
|
103
|
-
}
|
|
83
|
+
const evaluated = evaluateCatalogCoverage(root, config, facts, loaded.arkRules);
|
|
84
|
+
// ADR 0039: a committed probe artifact can only subtract promotability.
|
|
85
|
+
// Read-only here — this path never runs a test.
|
|
86
|
+
const coverageRows = attachProbeEvidence(root, evaluated.rows, catalogued).rows;
|
|
87
|
+
const partial = evaluated.partial;
|
|
104
88
|
const evidenceById = new Map(coverageRows.map((row) => [row.invariantId, row]));
|
|
105
89
|
|
|
106
90
|
const invariants = catalogued.map((entry) => ({
|
|
@@ -120,6 +104,33 @@ export function loadSensorMap(root, config, facts) {
|
|
|
120
104
|
};
|
|
121
105
|
}
|
|
122
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Invariant coverage rows for a loaded catalog, evaluated the way the
|
|
109
|
+
* promotion judge needs them (declared `coverageRoots` included). `inputs` is
|
|
110
|
+
* the loaded file set, reused by the probe to pick covering tests.
|
|
111
|
+
*/
|
|
112
|
+
export function evaluateCatalogCoverage(root, config, facts, arkRules) {
|
|
113
|
+
const catalogued = arkRules?.invariants ?? [];
|
|
114
|
+
if (catalogued.length === 0) return { rows: [], partial: false, inputs: null };
|
|
115
|
+
const inputs = loadInvariantCoverageInputs(root, facts ?? { files: [] }, {
|
|
116
|
+
invariantIds: invariantIdsFromCatalog(arkRules),
|
|
117
|
+
...coverageOptionsFromConfig(config),
|
|
118
|
+
});
|
|
119
|
+
const coverage = evaluateInvariantCoverage({
|
|
120
|
+
arkRules,
|
|
121
|
+
fileContents: inputs.fileContents,
|
|
122
|
+
testFiles: inputs.testFiles,
|
|
123
|
+
testGlobsMissing: inputs.testGlobsMissing,
|
|
124
|
+
coverageBudgetExhausted: inputs.coverageBudgetExhausted === true,
|
|
125
|
+
...(inputs.stats ? { coverageStats: inputs.stats } : {}),
|
|
126
|
+
// The declared roots decide `outsideDeclaredRoots`, which is one of the
|
|
127
|
+
// four reasons canPromoteInvariant refuses. Dropping them here would make
|
|
128
|
+
// this surface promise a promotion the gate then denies.
|
|
129
|
+
...(inputs.coverageRoots ? { coverageRoots: inputs.coverageRoots } : {}),
|
|
130
|
+
});
|
|
131
|
+
return { rows: coverage.coverage ?? [], partial: coverage.partial === true, inputs };
|
|
132
|
+
}
|
|
133
|
+
|
|
123
134
|
/**
|
|
124
135
|
* Count the findings each declared rule produced in an analysis that already
|
|
125
136
|
* ran. One run answers for every rule: the loop this replaces was one full
|
|
@@ -188,7 +188,7 @@ export function buildSensorMap(input) {
|
|
|
188
188
|
sourceFile,
|
|
189
189
|
description: nullable(rule.description),
|
|
190
190
|
promotable: verdict.ok,
|
|
191
|
-
blocker: verdict.ok ? null : 'no-coverage-evidence',
|
|
191
|
+
blocker: verdict.ok ? null : verdict.blocker ?? 'no-coverage-evidence',
|
|
192
192
|
reason: `${locate(rule.id, sourceFile)}: ${verdict.reason}`,
|
|
193
193
|
coverageEvaluated: rule.coverage != null,
|
|
194
194
|
...ambiguity(rule.id),
|
|
@@ -19,7 +19,7 @@ function skillName(skill) {
|
|
|
19
19
|
return Array.isArray(skill) ? skill[0] : skill?.name || skill;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
function projectCatalogReady(root, skillNames) {
|
|
23
23
|
return skillNames.some((name) => fs.existsSync(path.join(root, canonicalSkillPath(name))));
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -7,7 +7,6 @@ import os from 'node:os';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { arkCommand } from '../ark-shared.mjs';
|
|
9
9
|
import {
|
|
10
|
-
parseSkillDescriptionVersion,
|
|
11
10
|
stampSkillDescription,
|
|
12
11
|
stripSkillDescriptionVersion,
|
|
13
12
|
} from './agent-skills-package.mjs';
|
|
@@ -227,14 +226,14 @@ export function canonicalSkillPath(name) {
|
|
|
227
226
|
* Hosts that natively load `.agents/skills` — do not also copy bytes there under
|
|
228
227
|
* a second name. Cursor/Codex list every path they scan; two copies = two picker rows.
|
|
229
228
|
*/
|
|
230
|
-
|
|
229
|
+
const SKILL_NATIVE_AGENTS_HOSTS = Object.freeze(['codex', 'cursor', 'antigravity']);
|
|
231
230
|
|
|
232
231
|
/**
|
|
233
232
|
* Hosts that do not scan `.agents/skills`. Adapter is a relative symlink to the
|
|
234
233
|
* canonical catalog so Grok/Claude/OpenCode see the same bytes.
|
|
235
234
|
* Cursor also scans `.claude/skills` — doctor warns; still one body + visible version.
|
|
236
235
|
*/
|
|
237
|
-
|
|
236
|
+
const SKILL_ADAPTER_LINKS = Object.freeze({
|
|
238
237
|
claude: (name) => ({
|
|
239
238
|
link: `.claude/skills/${name}`,
|
|
240
239
|
target: `../../${SKILL_CANONICAL_DIR}/${name}`,
|
|
@@ -299,10 +298,10 @@ export function arkPackageVersion() {
|
|
|
299
298
|
}
|
|
300
299
|
|
|
301
300
|
/**
|
|
302
|
-
* Rewrite a YAML `description:` line
|
|
303
|
-
*
|
|
301
|
+
* Rewrite the value of a YAML `description:` line, preserving its quoting.
|
|
302
|
+
* `transform` returns the new value, or null to keep the line untouched.
|
|
304
303
|
*/
|
|
305
|
-
function
|
|
304
|
+
function rewriteDescriptionYamlLine(line, transform) {
|
|
306
305
|
const match = String(line).match(/^(description:\s*)(.*)$/);
|
|
307
306
|
if (!match) return line;
|
|
308
307
|
let raw = match[2] ?? '';
|
|
@@ -311,26 +310,26 @@ function stampDescriptionYamlLine(line, version) {
|
|
|
311
310
|
(raw.startsWith("'") && raw.endsWith("'") && raw.length >= 2);
|
|
312
311
|
const quote = quoted ? raw[0] : '';
|
|
313
312
|
const value = quoted ? raw.slice(1, -1) : raw;
|
|
314
|
-
const
|
|
315
|
-
if (
|
|
316
|
-
|
|
313
|
+
const next = transform(value);
|
|
314
|
+
if (next === null) return line;
|
|
315
|
+
if (!quoted) return `${match[1]}${next}`;
|
|
316
|
+
const escaped = next.replaceAll('\\', '\\\\').replaceAll(quote, `\\${quote}`);
|
|
317
317
|
return `${match[1]}${quote}${escaped}${quote}`;
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Rewrite a YAML `description:` line with a visible `arkgate@<version>. ` prefix.
|
|
322
|
+
* Preserves quoting. Hosts show this string in the skill picker (unlike arkVersion).
|
|
323
|
+
*/
|
|
324
|
+
function stampDescriptionYamlLine(line, version) {
|
|
325
|
+
return rewriteDescriptionYamlLine(line, (value) => stampSkillDescription(value, version));
|
|
326
|
+
}
|
|
327
|
+
|
|
320
328
|
function managedDescriptionYamlLine(line) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
(raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) ||
|
|
326
|
-
(raw.startsWith("'") && raw.endsWith("'") && raw.length >= 2);
|
|
327
|
-
const quote = quoted ? raw[0] : '';
|
|
328
|
-
const value = quoted ? raw.slice(1, -1) : raw;
|
|
329
|
-
const stripped = stripSkillDescriptionVersion(value);
|
|
330
|
-
if (stripped === value) return line;
|
|
331
|
-
if (!quoted) return `${match[1]}${stripped}`;
|
|
332
|
-
const escaped = stripped.replaceAll('\\', '\\\\').replaceAll(quote, `\\${quote}`);
|
|
333
|
-
return `${match[1]}${quote}${escaped}${quote}`;
|
|
329
|
+
return rewriteDescriptionYamlLine(line, (value) => {
|
|
330
|
+
const stripped = stripSkillDescriptionVersion(value);
|
|
331
|
+
return stripped === value ? null : stripped;
|
|
332
|
+
});
|
|
334
333
|
}
|
|
335
334
|
|
|
336
335
|
// Insert `arkVersion: <v>` and a visible `arkgate@<v>. ` description prefix.
|
|
@@ -593,7 +592,7 @@ export function skillTemplates() {
|
|
|
593
592
|
* Relative symlink so clones keep working. Fallback copy when the OS refuses links.
|
|
594
593
|
* @returns {'linked'|'copied'|'current'|'skipped-customized'|'missing-canonical'}
|
|
595
594
|
*/
|
|
596
|
-
|
|
595
|
+
function ensureSkillAdapterLink(root, name, adapter, force = false) {
|
|
597
596
|
const canonicalDir = path.join(root, SKILL_CANONICAL_DIR, name);
|
|
598
597
|
const canonicalFile = path.join(canonicalDir, 'SKILL.md');
|
|
599
598
|
if (!fs.existsSync(canonicalFile)) return 'missing-canonical';
|
|
@@ -693,8 +692,6 @@ export function pruneHomeArkSkillDuplicates(root, skillNames = skillTemplateName
|
|
|
693
692
|
return { ok: true, reason: 'pruned', removed };
|
|
694
693
|
}
|
|
695
694
|
|
|
696
|
-
export { parseSkillDescriptionVersion, stripSkillDescriptionVersion };
|
|
697
|
-
|
|
698
695
|
// Skill names only, silent on a missing templates dir — for the freshness
|
|
699
696
|
// advisory below, which must not print packaging warnings on every check run.
|
|
700
697
|
export function skillTemplateNames() {
|
|
@@ -821,20 +818,15 @@ function readCodexHomeCatalogMetadata(file, kind) {
|
|
|
821
818
|
}
|
|
822
819
|
}
|
|
823
820
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
'pending'
|
|
832
|
-
);
|
|
821
|
+
/**
|
|
822
|
+
* Merge a home skill catalog and its pending catalog metadata: the floor is the
|
|
823
|
+
* newer of the two versions; either file present means metadata is in play.
|
|
824
|
+
* @param {{ exists: boolean, valid: boolean, version: string|null }} catalog
|
|
825
|
+
* @param {{ exists: boolean, valid: boolean, version: string|null }} pending
|
|
826
|
+
*/
|
|
827
|
+
export function homeCatalogFloorState(catalog, pending) {
|
|
833
828
|
let floorVersion = catalog.version;
|
|
834
|
-
if (
|
|
835
|
-
pending.version &&
|
|
836
|
-
(!floorVersion || isVersionOlder(floorVersion, pending.version))
|
|
837
|
-
) {
|
|
829
|
+
if (pending.version && (!floorVersion || isVersionOlder(floorVersion, pending.version))) {
|
|
838
830
|
floorVersion = pending.version;
|
|
839
831
|
}
|
|
840
832
|
return {
|
|
@@ -846,6 +838,18 @@ function codexHomeCatalogState(skillsDir) {
|
|
|
846
838
|
};
|
|
847
839
|
}
|
|
848
840
|
|
|
841
|
+
function codexHomeCatalogState(skillsDir) {
|
|
842
|
+
const catalog = readCodexHomeCatalogMetadata(
|
|
843
|
+
path.join(skillsDir, CODEX_HOME_CATALOG),
|
|
844
|
+
'catalog'
|
|
845
|
+
);
|
|
846
|
+
const pending = readCodexHomeCatalogMetadata(
|
|
847
|
+
path.join(skillsDir, CODEX_HOME_PENDING_CATALOG),
|
|
848
|
+
'pending'
|
|
849
|
+
);
|
|
850
|
+
return homeCatalogFloorState(catalog, pending);
|
|
851
|
+
}
|
|
852
|
+
|
|
849
853
|
function newerCodexHomeCatalogVersion(skillsDir, packageVersion, state = null) {
|
|
850
854
|
if (!isValidSemver(packageVersion)) return null;
|
|
851
855
|
const floorVersion = (state ?? codexHomeCatalogState(skillsDir)).floorVersion;
|
|
@@ -2,16 +2,31 @@
|
|
|
2
2
|
import { layerForRelativePath } from '../ark-layer-match.mjs';
|
|
3
3
|
import { ANALYSIS_COMPLETENESS } from './analysis-completeness.mjs';
|
|
4
4
|
import { evaluateArkRunEditorSensorsFromSource } from './ark-run-sensors.mjs';
|
|
5
|
+
import { evaluateArkOrderEditorSensors } from './ark-order-sensors.mjs';
|
|
6
|
+
import {
|
|
7
|
+
buildArkRuleFileHints,
|
|
8
|
+
evaluateArkRuleSensors,
|
|
9
|
+
extractClassShapesFromSource,
|
|
10
|
+
} from './arkrules-sensors.mjs';
|
|
5
11
|
import { getDiagnosticCatalogEntry } from './diagnostic-catalog.mjs';
|
|
12
|
+
import { demoteExtraPlaneTeethUnderClassificationFloor } from './extra-merge-teeth.mjs';
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
/** Hook-only guidance: a hook deny is already the verdict for that write. */
|
|
15
|
+
const HOOK_DENY_PREPARE_CHANGE_NOTE = 'Do not call ark_prepare_change from a hook deny.';
|
|
16
|
+
|
|
17
|
+
function surfaceNote(context) {
|
|
18
|
+
return context?.surface === 'hook' ? ` ${HOOK_DENY_PREPARE_CHANGE_NOTE}` : '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function lexicalEvidenceIncompleteMessage(file, context) {
|
|
8
22
|
const entry = getDiagnosticCatalogEntry('LEXICAL_EVIDENCE_INCOMPLETE');
|
|
9
23
|
const text = [entry?.why, entry?.fix].filter(Boolean).join(' ');
|
|
10
24
|
return {
|
|
11
25
|
code: 'LEXICAL_EVIDENCE_INCOMPLETE',
|
|
12
26
|
message:
|
|
13
|
-
text ||
|
|
14
|
-
|
|
27
|
+
(text ||
|
|
28
|
+
'This check only saw one file, so it cannot fully prove how the import resolves. The result is provisional — `ark-check` on the project is the authority. For a complete verdict, run `npx arkgate-check --root . --config ark.config.json` (or ark_prepare_change over MCP with the full candidate batch).') +
|
|
29
|
+
surfaceNote(context),
|
|
15
30
|
...(file ? { file } : {}),
|
|
16
31
|
};
|
|
17
32
|
}
|
|
@@ -81,14 +96,132 @@ function arkRunSnippetViolations(source, context = {}) {
|
|
|
81
96
|
}));
|
|
82
97
|
}
|
|
83
98
|
|
|
99
|
+
const TS_CLASS_SHAPE_FILE = /\.(tsx?|mts|cts)$/i;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* File-local ArkRules structure sensors on the proposed source — the same sensors,
|
|
103
|
+
* class-shape extraction, and per-file hints ark-check runs, pinned to this one file.
|
|
104
|
+
* Only enforced (failsStrict) findings block; advisory rules stay CI/doctor warnings.
|
|
105
|
+
*/
|
|
106
|
+
function arkRulesSnippetViolations(source, context = {}) {
|
|
107
|
+
const arkRules = context.arkRules;
|
|
108
|
+
const file = context.relFile;
|
|
109
|
+
if (!arkRules || !(arkRules.structure?.length > 0) || typeof file !== 'string' || !file) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
const layers = Array.isArray(context.layers) ? context.layers : [];
|
|
113
|
+
const layerForFile = (pathValue) =>
|
|
114
|
+
pathValue === file && typeof context.layer === 'string'
|
|
115
|
+
? context.layer
|
|
116
|
+
: layerForRelativePath(pathValue, layers);
|
|
117
|
+
let classShapes = [];
|
|
118
|
+
if (TS_CLASS_SHAPE_FILE.test(file)) {
|
|
119
|
+
try {
|
|
120
|
+
classShapes = extractClassShapesFromSource(file, source);
|
|
121
|
+
} catch {
|
|
122
|
+
// Same as the CI resolver: shape extraction never fails the run.
|
|
123
|
+
classShapes = [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const findings = evaluateArkRuleSensors({
|
|
127
|
+
arkRules,
|
|
128
|
+
classShapes,
|
|
129
|
+
files: [file],
|
|
130
|
+
layerForFile,
|
|
131
|
+
fileHints: buildArkRuleFileHints({ [file]: source }),
|
|
132
|
+
});
|
|
133
|
+
// Same classification floor ark-check applies before merge (teeth only on a classified tree).
|
|
134
|
+
return demoteExtraPlaneTeethUnderClassificationFloor(findings, context.classification ?? {})
|
|
135
|
+
.filter((finding) => finding.failsStrict)
|
|
136
|
+
.map((finding) => ({
|
|
137
|
+
...finding,
|
|
138
|
+
nextAction:
|
|
139
|
+
getDiagnosticCatalogEntry('ARKRULE_STRUCTURE')?.fix ??
|
|
140
|
+
'Fix the ArkRules structure finding in this file, then retry the write.',
|
|
141
|
+
failsStrict: true,
|
|
142
|
+
severity: 'error',
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** ArkOrder editor sensors (generic update, kernel-in-domain, ξ writes) on one source. */
|
|
147
|
+
function arkOrderSnippetViolations(source, context = {}) {
|
|
148
|
+
const arkOrder = context.arkOrder;
|
|
149
|
+
const file = context.relFile;
|
|
150
|
+
if (!arkOrder || typeof file !== 'string' || !file) return [];
|
|
151
|
+
const layers = Array.isArray(context.layers) ? context.layers : [];
|
|
152
|
+
const fromLayer =
|
|
153
|
+
typeof context.layer === 'string' ? context.layer : layerForRelativePath(file, layers);
|
|
154
|
+
const layerMeta = layers.find((layer) => layer?.name === fromLayer);
|
|
155
|
+
return evaluateArkOrderEditorSensors({
|
|
156
|
+
arkOrder,
|
|
157
|
+
file,
|
|
158
|
+
source,
|
|
159
|
+
fromLayer,
|
|
160
|
+
intentPrefixes: layerMeta?.intentPrefixes ?? [],
|
|
161
|
+
...(context.classification ? { classification: context.classification } : {}),
|
|
162
|
+
})
|
|
163
|
+
.filter((finding) => finding.failsStrict)
|
|
164
|
+
.map((finding) => ({
|
|
165
|
+
ruleId: finding.ruleId,
|
|
166
|
+
code: finding.ruleId,
|
|
167
|
+
message: finding.message,
|
|
168
|
+
file: finding.file,
|
|
169
|
+
line: finding.line,
|
|
170
|
+
...(finding.fromLayer ? { fromLayer: finding.fromLayer } : {}),
|
|
171
|
+
...(finding.target ? { target: finding.target } : {}),
|
|
172
|
+
nextAction: finding.nextAction,
|
|
173
|
+
failsStrict: true,
|
|
174
|
+
severity: 'error',
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The write gate could not load part of its own contract (e.g. a referenced ArkRules
|
|
180
|
+
* file is missing). CI fails closed on the same input, so the write path does too.
|
|
181
|
+
*/
|
|
182
|
+
function contractLoadViolations(context = {}) {
|
|
183
|
+
const errors = Array.isArray(context.contractErrors) ? context.contractErrors : [];
|
|
184
|
+
return errors.map((error) => ({
|
|
185
|
+
ruleId: 'WRITE_GATE_UNAVAILABLE',
|
|
186
|
+
code: 'WRITE_GATE_UNAVAILABLE',
|
|
187
|
+
message: `Invalid Effective Contract: ${
|
|
188
|
+
typeof error === 'string' ? error : `${error?.path ?? ''}: ${error?.message ?? ''}`.trim()
|
|
189
|
+
}`,
|
|
190
|
+
...(context.relFile ? { file: context.relFile } : {}),
|
|
191
|
+
nextAction:
|
|
192
|
+
getDiagnosticCatalogEntry('WRITE_GATE_UNAVAILABLE')?.fix ??
|
|
193
|
+
'Fix the ArkRules reference in ark.config.json, then retry the write.',
|
|
194
|
+
failsStrict: true,
|
|
195
|
+
severity: 'error',
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Extras (ArkRules structure + ArkOrder) run only on files CI would scan. A file
|
|
201
|
+
* outside include / excluded is never judged by these planes in CI either.
|
|
202
|
+
*/
|
|
203
|
+
function extraPlaneSnippetViolations(source, context = {}) {
|
|
204
|
+
if (context.inScope === false) return [];
|
|
205
|
+
return [
|
|
206
|
+
...contractLoadViolations(context),
|
|
207
|
+
...arkRulesSnippetViolations(source, context),
|
|
208
|
+
...arkOrderSnippetViolations(source, context),
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
|
|
84
212
|
export function validateSnippetAnalysis({ gate, ts, source, context = {} }) {
|
|
85
213
|
const observed = gate.validate(source, context);
|
|
86
214
|
const arkRunViolations = arkRunSnippetViolations(source, context);
|
|
215
|
+
const extraViolations = extraPlaneSnippetViolations(source, context);
|
|
87
216
|
const base = {
|
|
88
|
-
valid:
|
|
217
|
+
valid:
|
|
218
|
+
Boolean(observed.lexicalValid ?? observed.valid) &&
|
|
219
|
+
arkRunViolations.length === 0 &&
|
|
220
|
+
extraViolations.length === 0,
|
|
89
221
|
violations: [
|
|
90
222
|
...(Array.isArray(observed.violations) ? observed.violations : []),
|
|
91
223
|
...arkRunViolations,
|
|
224
|
+
...extraViolations,
|
|
92
225
|
],
|
|
93
226
|
};
|
|
94
227
|
const file = context.filePath;
|
|
@@ -154,7 +287,8 @@ export function validateSnippetAnalysis({ gate, ts, source, context = {} }) {
|
|
|
154
287
|
'ANALYSIS_PARSE_INCOMPLETE',
|
|
155
288
|
`Analysis partial: ${detail}`,
|
|
156
289
|
file,
|
|
157
|
-
'Incremental mid-edit parse errors are normal. Finish the source, then re-run `npx arkgate-check` (or the write hook).
|
|
290
|
+
'Incremental mid-edit parse errors are normal. Finish the source, then re-run `npx arkgate-check` (or the write hook).' +
|
|
291
|
+
surfaceNote(context)
|
|
158
292
|
),
|
|
159
293
|
line: first?.line ?? 1,
|
|
160
294
|
column: first?.column ?? 1,
|
|
@@ -169,7 +303,7 @@ export function validateSnippetAnalysis({ gate, ts, source, context = {} }) {
|
|
|
169
303
|
valid: false,
|
|
170
304
|
lexicalValid: base.valid,
|
|
171
305
|
completeness: ANALYSIS_COMPLETENESS.partial,
|
|
172
|
-
completenessReasons: [lexicalEvidenceIncompleteMessage(file)],
|
|
306
|
+
completenessReasons: [lexicalEvidenceIncompleteMessage(file, context)],
|
|
173
307
|
};
|
|
174
308
|
} catch {
|
|
175
309
|
return {
|
|
@@ -25,6 +25,40 @@ export const DEFAULT_INTENT_PREFIXES = Object.freeze([
|
|
|
25
25
|
{ layer: 'SecurityAuditObservability', prefixes: ['Security.', 'Audit.', 'Observability.'] },
|
|
26
26
|
{ layer: 'Kernel', prefixes: ['Kernel.'] },
|
|
27
27
|
]);
|
|
28
|
+
/**
|
|
29
|
+
* Runtime intent prefixes for one config layer: its declared `intentPrefixes`,
|
|
30
|
+
* else the built-in prefixes when the layer uses a canonical 11-layer name
|
|
31
|
+
* (`DomainModel` → `Domain.`, …; the same table the static gate falls back to),
|
|
32
|
+
* else none. Keeps `ark init` configs, which name canonical layers without
|
|
33
|
+
* declaring prefixes, resolvable by the ArkRun kernel.
|
|
34
|
+
*/
|
|
35
|
+
export function effectiveIntentPrefixes(layer) {
|
|
36
|
+
const declared = (layer.intentPrefixes ?? []).filter((prefix) => prefix.trim().length > 0);
|
|
37
|
+
if (declared.length > 0)
|
|
38
|
+
return declared;
|
|
39
|
+
return DEFAULT_INTENT_PREFIXES.find((entry) => entry.layer === layer.name)?.prefixes ?? [];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Layers named by an `allowed: false` (non-peerIsolation) rule that no intent can map to at runtime
|
|
43
|
+
* (no declared `intentPrefixes` and not a canonical layer name). Hard observed
|
|
44
|
+
* layer flow can never fire for such a rule. Sorted, unique.
|
|
45
|
+
*/
|
|
46
|
+
export function unresolvableLayerFlowLayers(config) {
|
|
47
|
+
const resolvable = new Set(config.layers
|
|
48
|
+
.filter((layer) => effectiveIntentPrefixes(layer).length > 0)
|
|
49
|
+
.map((layer) => layer.name));
|
|
50
|
+
const missing = new Set();
|
|
51
|
+
for (const rule of config.rules ?? []) {
|
|
52
|
+
// peerIsolation walls are file-path slice rules; runtime flow never evaluates them.
|
|
53
|
+
if (rule.allowed !== false || rule.peerIsolation === true)
|
|
54
|
+
continue;
|
|
55
|
+
for (const name of [rule.from, rule.to]) {
|
|
56
|
+
if (!resolvable.has(name))
|
|
57
|
+
missing.add(name);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return [...missing].sort();
|
|
61
|
+
}
|
|
28
62
|
/** Longest matching prefix wins; declaration order resolves an identical-prefix tie. */
|
|
29
63
|
export function resolveIntentLayer(intent, layers) {
|
|
30
64
|
const candidates = layers.flatMap((layer, layerIndex) => (layer.prefixes ?? layer.intentPrefixes ?? []).map((prefix) => ({
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* pin-aware npx, and AgeExclude-must-be-in-config stay behind this module.
|
|
6
6
|
*/
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
-
import
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { pinArkgateDevDependency } from './field-install.mjs';
|
|
11
|
+
import { arkPackageRecoveryCommand, packageInstallArgv } from './package-manager.mjs';
|
|
9
12
|
|
|
10
13
|
const AGE_MARK = /ERR_PNPM_NO_MATURE_MATCHING_VERSION|minimumReleaseAge/i;
|
|
11
14
|
const AGE_EXCLUDE_LINE =
|
|
@@ -32,7 +35,7 @@ function hostOutputTail(hostOutput) {
|
|
|
32
35
|
* @param {string|undefined} installCommand
|
|
33
36
|
* @returns {string|null}
|
|
34
37
|
*/
|
|
35
|
-
|
|
38
|
+
function resolveStartInstallPackagePin(packageVersion, installCommand) {
|
|
36
39
|
const fromArg = String(packageVersion ?? '')
|
|
37
40
|
.trim()
|
|
38
41
|
.replace(/^arkgate@/i, '')
|
|
@@ -154,3 +157,70 @@ export function runStartPackageInstall(command, commandArgs, cwd) {
|
|
|
154
157
|
stderr: result.stderr ?? '',
|
|
155
158
|
};
|
|
156
159
|
}
|
|
160
|
+
|
|
161
|
+
// Setup-time arkgate pin shared by `ark start` and `ark init` (kept out of bin/ark.mjs).
|
|
162
|
+
/**
|
|
163
|
+
* Pin arkgate in package.json (and optionally run the package manager).
|
|
164
|
+
* start calls this so CI/`npx` is not forced to rely on a stale global install.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} root
|
|
167
|
+
* @param {{ install?: boolean, runPackageManager?: boolean }} [opts]
|
|
168
|
+
*/
|
|
169
|
+
export function ensureProjectArkgateDependency(root, opts = {}) {
|
|
170
|
+
const install = opts.install !== false;
|
|
171
|
+
const runPm = opts.runPackageManager === true;
|
|
172
|
+
if (!install) {
|
|
173
|
+
return { pinned: { changed: false, reason: 'skipped-no-install' }, installStatus: null };
|
|
174
|
+
}
|
|
175
|
+
const pinned = pinArkgateDevDependency(root);
|
|
176
|
+
let installStatus = null;
|
|
177
|
+
let hostOutput = '';
|
|
178
|
+
// Only run the package manager after a successful pin change — avoid surprise
|
|
179
|
+
// network on every start when arkgate is already listed.
|
|
180
|
+
if (runPm && pinned.changed) {
|
|
181
|
+
const [command, commandArgs] = packageInstallArgv(root, pinned.version);
|
|
182
|
+
const install = runStartPackageInstall(command, commandArgs, root);
|
|
183
|
+
installStatus = install.status;
|
|
184
|
+
hostOutput = `${install.stdout}\n${install.stderr}`;
|
|
185
|
+
}
|
|
186
|
+
return { pinned, installStatus, hostOutput };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Setup-time pin shared by `ark start` and `ark init`. With the pin, generated CI and host
|
|
191
|
+
* hooks call the local `ark-check` / `arkgate-mcp` bins through the project runner. Without
|
|
192
|
+
* it they run `npx -y -p arkgate@<this exact version> <bin>`, because a bare `npx ark-check`
|
|
193
|
+
* asks npm for a package named `ark-check` (404) or runs a stale global.
|
|
194
|
+
*/
|
|
195
|
+
export function pinArkgateForSetup(root, args, cliVersion) {
|
|
196
|
+
if (!args.install) {
|
|
197
|
+
console.log(' Skipping arkgate package pin (--no-install).');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!fs.existsSync(path.join(root, 'package.json'))) {
|
|
201
|
+
console.log(
|
|
202
|
+
` Warning: no package.json — the generated CI workflow and host hooks run npx -y -p arkgate@${cliVersion()} (downloaded on each run). To run a local install instead, create package.json, add arkgate@^${cliVersion()} to devDependencies, then re-run setup.`
|
|
203
|
+
);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const { pinned, installStatus, hostOutput } = ensureProjectArkgateDependency(root, {
|
|
207
|
+
install: true,
|
|
208
|
+
runPackageManager: !args.skipPackageManager,
|
|
209
|
+
});
|
|
210
|
+
if (pinned.changed) {
|
|
211
|
+
console.log(` Pinned arkgate@${pinned.version} in package.json devDependencies.`);
|
|
212
|
+
if (installStatus !== null && installStatus !== 0) {
|
|
213
|
+
const [command, commandArgs] = packageInstallArgv(root, pinned.version);
|
|
214
|
+
console.error(
|
|
215
|
+
formatStartPackageInstallFailure({
|
|
216
|
+
exitStatus: installStatus,
|
|
217
|
+
installCommand: `${command} ${commandArgs.join(' ')}`,
|
|
218
|
+
hostOutput,
|
|
219
|
+
packageVersion: cliVersion(),
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
} else if (pinned.reason === 'already-present') {
|
|
224
|
+
console.log(` arkgate already in package.json (${pinned.version}).`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -20,6 +20,9 @@ import {
|
|
|
20
20
|
grokHooks,
|
|
21
21
|
grokProjectConfig,
|
|
22
22
|
opencodeProjectConfig,
|
|
23
|
+
removeAntigravityArkHook,
|
|
24
|
+
removeClaudeStyleArkHooks,
|
|
25
|
+
removeCursorArkHook,
|
|
23
26
|
} from './hook-templates.mjs';
|
|
24
27
|
import { codexPrimaryTable, codexProjectMcpIsValid } from './codex-home.mjs';
|
|
25
28
|
import { codexRuntimeActivation } from './enforcement-state.mjs';
|
|
@@ -56,6 +59,17 @@ const COMPACT_HOST_TEMPLATES = {
|
|
|
56
59
|
gemini: (root) => [['GEMINI.md', instructionRule(root)]],
|
|
57
60
|
};
|
|
58
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Host hook files the installer merges into (keeping user keys). Removal drops only the
|
|
64
|
+
* Ark-owned entries, with the same identity as the merge helper.
|
|
65
|
+
*/
|
|
66
|
+
const MERGED_HOOK_FILE_REMOVERS = {
|
|
67
|
+
'.claude/settings.json': removeClaudeStyleArkHooks,
|
|
68
|
+
'.codex/hooks.json': removeClaudeStyleArkHooks,
|
|
69
|
+
'.cursor/hooks.json': removeCursorArkHook,
|
|
70
|
+
'.agents/hooks.json': removeAntigravityArkHook,
|
|
71
|
+
};
|
|
72
|
+
|
|
59
73
|
function treeFiles(root) {
|
|
60
74
|
const files = new Map();
|
|
61
75
|
const visit = (dir) => {
|
|
@@ -133,7 +147,7 @@ export function setupBudget(changes) {
|
|
|
133
147
|
}
|
|
134
148
|
|
|
135
149
|
/** One next write when compact start is the wrong size. `--force` does not unlock. */
|
|
136
|
-
|
|
150
|
+
const START_SETUP_BUDGET_NEXT = 'arkgate-check --init';
|
|
137
151
|
|
|
138
152
|
function formatSetupKb(bytes) {
|
|
139
153
|
return `${Math.max(1, Math.round(Number(bytes) / 1024))} KB`;
|
|
@@ -334,6 +348,22 @@ function planHostRemoval(args, helpers) {
|
|
|
334
348
|
changes.push(change(relativePath, before, after));
|
|
335
349
|
continue;
|
|
336
350
|
}
|
|
351
|
+
const remover = MERGED_HOOK_FILE_REMOVERS[relativePath];
|
|
352
|
+
if (remover && before.toString('utf8') !== expected) {
|
|
353
|
+
const removal = remover(before.toString('utf8'), expected);
|
|
354
|
+
if (removal.status === 'removed') {
|
|
355
|
+
changes.push(
|
|
356
|
+
change(relativePath, before, removal.text == null ? null : Buffer.from(removal.text))
|
|
357
|
+
);
|
|
358
|
+
} else if (removal.status === 'customized') {
|
|
359
|
+
unresolvedDecisions.push(
|
|
360
|
+
`${relativePath}: the Ark hook entry was customized and was left untouched.`
|
|
361
|
+
);
|
|
362
|
+
} else if (removal.status === 'unreadable') {
|
|
363
|
+
unresolvedDecisions.push(`${relativePath} is not readable JSON and was left untouched.`);
|
|
364
|
+
}
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
337
367
|
if (before.toString('utf8') !== expected) {
|
|
338
368
|
unresolvedDecisions.push(`${relativePath} was customized and was left untouched.`);
|
|
339
369
|
continue;
|
|
@@ -9,7 +9,7 @@ import fs from 'node:fs';
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
|
|
11
11
|
/** Product-domain / domain-model homes. Closed list — no tree walk. */
|
|
12
|
-
|
|
12
|
+
const STATES_TRANSITIONS_DOMAIN_HOMES = Object.freeze([
|
|
13
13
|
'docs/domain.md',
|
|
14
14
|
'docs/data-model.md',
|
|
15
15
|
'docs/product-domain.md',
|
|
@@ -19,7 +19,7 @@ export const STATES_TRANSITIONS_DOMAIN_HOMES = Object.freeze([
|
|
|
19
19
|
]);
|
|
20
20
|
|
|
21
21
|
/** Evolved dedicated homes. Adopt if present; do not force a filename. */
|
|
22
|
-
|
|
22
|
+
const STATES_TRANSITIONS_DEDICATED_HOMES = Object.freeze([
|
|
23
23
|
'docs/states.md',
|
|
24
24
|
'docs/transitions.md',
|
|
25
25
|
'docs/states-transitions.md',
|
|
@@ -32,7 +32,7 @@ export const STATES_TRANSITIONS_ASK_MISSING =
|
|
|
32
32
|
export const STATES_TRANSITIONS_ASK_INCOMPLETE =
|
|
33
33
|
'The states heading is there, but it is not a map yet — no table or link.';
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
const STATES_TRANSITIONS_NEXT =
|
|
36
36
|
'Add a short table (entity · states · allowed from → to) — or one link — on the domain doc. Names from the code. No flag soup.';
|
|
37
37
|
|
|
38
38
|
function readMarkdown(abs) {
|