arkgate 4.1.1 → 4.2.1
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 +107 -3
- package/README.md +16 -4
- package/bin/ark-check-runtime.mjs +16 -5
- package/bin/ark-mcp-runtime.mjs +766 -64
- package/bin/ark-shared.mjs +16 -4
- package/bin/lib/agent-gates.mjs +1 -0
- package/bin/lib/ci-and-commands.mjs +16 -7
- package/bin/lib/codex-home.mjs +90 -8
- package/bin/lib/design-smells.mjs +71 -9
- package/bin/lib/doctor-plan.mjs +36 -36
- package/bin/lib/effective-contract-load.mjs +73 -9
- package/bin/lib/enforcement-state.mjs +1 -1
- package/bin/lib/gate-files.mjs +441 -9
- package/bin/lib/github-enforcement.mjs +16 -3
- package/bin/lib/hook-templates.mjs +12 -11
- package/bin/lib/html-report-evolution.mjs +114 -0
- package/bin/lib/html-report.mjs +11 -89
- package/bin/lib/import-resolve.mjs +33 -11
- package/bin/lib/install-activation.mjs +87 -0
- package/bin/lib/install-migrate.mjs +66 -50
- package/bin/lib/managed-upgrade.mjs +10 -41
- package/bin/lib/mcp-adoption.mjs +15 -5
- package/bin/lib/physical-cohesion.mjs +2 -1
- package/bin/lib/pilot-loop.mjs +25 -8
- package/bin/lib/project-identity.mjs +103 -0
- package/bin/lib/report-snapshot-context.mjs +28 -0
- package/bin/lib/resident-hook.mjs +33 -9
- package/bin/lib/rules-inventory.mjs +100 -8
- package/bin/lib/skill-install.mjs +272 -22
- package/bin/lib/skill-write.mjs +899 -0
- package/bin/lib/start-preview.mjs +84 -1
- package/bin/lib/upgrade-command.mjs +2 -5
- package/dist/index.cjs +13 -13
- package/dist/index.d.ts +194 -2
- package/dist/index.js +13 -13
- package/docs/README.md +5 -3
- package/docs/agent-guide.md +110 -14
- package/docs/ai-gates.md +103 -18
- package/docs/assets/ark-write-gate.svg +2 -2
- package/docs/enthusiast/how-to-agent-gates.md +6 -0
- package/docs/package-surface.md +15 -9
- package/docs/product-voice.md +13 -1
- package/package.json +7 -1
- package/schemas/ark.project-identity.schema.json +116 -0
- package/server.json +2 -2
- package/templates/skills/ark-adopt.md +9 -0
- package/templates/skills/ark-architect.md +12 -2
- package/templates/skills/ark-autopilot.md +9 -0
- package/templates/skills/ark-contract.md +11 -1
- package/templates/skills/ark-coverage.md +9 -0
- package/templates/skills/ark-explain.md +13 -1
- package/templates/skills/ark-explore.md +9 -0
- package/templates/skills/ark-fix.md +10 -1
- package/templates/skills/ark-loop.md +11 -2
- package/templates/skills/ark-place.md +17 -6
- package/templates/skills/ark-runtime.md +8 -0
- package/templates/skills/ark-think.md +14 -2
- package/templates/skills/ark-upgrade.md +9 -0
|
@@ -51,7 +51,14 @@ function shellSegments(text) {
|
|
|
51
51
|
index++;
|
|
52
52
|
continue;
|
|
53
53
|
}
|
|
54
|
-
if (
|
|
54
|
+
if (
|
|
55
|
+
char === '&' &&
|
|
56
|
+
(input[index - 1] === '>' || input[index - 1] === '<' || input[index + 1] === '>')
|
|
57
|
+
) {
|
|
58
|
+
current += char;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (char === ';' || char === '|' || char === '&' || char === '\n') {
|
|
55
62
|
push(char);
|
|
56
63
|
continue;
|
|
57
64
|
}
|
|
@@ -70,12 +77,18 @@ function analyzeCommands(commands, script = '') {
|
|
|
70
77
|
.filter((segment) => DIRECT_ARK.test(executableText(segment.text)))
|
|
71
78
|
.map((segment) => ({
|
|
72
79
|
text: segment.text,
|
|
73
|
-
enforcing:
|
|
80
|
+
enforcing:
|
|
81
|
+
segment.terminator !== '||' &&
|
|
82
|
+
segment.terminator !== '|' &&
|
|
83
|
+
segment.terminator !== '&',
|
|
74
84
|
}));
|
|
75
85
|
const found = [];
|
|
76
86
|
for (const segment of shellSegments(commands)) {
|
|
77
87
|
const executable = executableText(segment.text);
|
|
78
|
-
const outerEnforcing =
|
|
88
|
+
const outerEnforcing =
|
|
89
|
+
segment.terminator !== '||' &&
|
|
90
|
+
segment.terminator !== '|' &&
|
|
91
|
+
segment.terminator !== '&';
|
|
79
92
|
if (DIRECT_ARK.test(executable)) {
|
|
80
93
|
found.push({ text: segment.text, enforcing: outerEnforcing });
|
|
81
94
|
} else if (CHECK_SCRIPT.test(executable)) {
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
// Generated from hook-templates.source.mjs — run npm run generate:packaged-tooling.
|
|
2
|
-
import{execCommandParts as i,execRunner as s}from"../ark-shared.mjs";const c="arkgate-mcp";function l(
|
|
3
|
-
`}function p(
|
|
4
|
-
`}function
|
|
5
|
-
#
|
|
2
|
+
import{execCommandParts as i,execRunner as s}from"../ark-shared.mjs";const c="arkgate-mcp";function l(o){const r=s(o);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",command:`${r} ${c} --session-context --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit",hooks:[{type:"command",command:`${r} ${c} --hook --hook-repair --fail-on-new-smells --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
|
|
3
|
+
`}function p(o){const r=s(o);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${r} ${c} --session-context --root . --root-env CODEX_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"ApplyPatch|apply_patch|Write|Edit|MultiEdit",hooks:[{type:"command",timeout:30,command:`${r} ${c} --hook --hook-repair --fail-on-new-smells --root . --root-env CODEX_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
|
|
4
|
+
`}function f(o){const{command:r,args:e}=i(o,c,["--root",".","--config","ark.config.json"]),t=a=>a.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),n=e.map(a=>`"${t(a)}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Codex project scope).
|
|
5
|
+
# CONFIGURED ON DISK \u2014 RUNTIME NOT VERIFIED.
|
|
6
|
+
# Restart Codex, then call ark_identity with expectedRoot before trusting MCP verdicts.
|
|
6
7
|
[mcp_servers.ark]
|
|
7
8
|
command = "${t(r)}"
|
|
8
9
|
args = [${n}]
|
|
9
|
-
`}function
|
|
10
|
+
`}function g(o){const{command:r,args:e}=i(o,c,["--root",".","--config","ark.config.json"]),t=e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Grok Build project scope).
|
|
10
11
|
# Restart Grok (or /mcps \u2192 refresh) after changes. Also loads repo-root .mcp.json.
|
|
11
12
|
[mcp_servers.ark]
|
|
12
13
|
command = "${r.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"
|
|
13
14
|
args = [${t}]
|
|
14
|
-
`}function u(
|
|
15
|
-
`}function k(
|
|
16
|
-
`}function d(
|
|
17
|
-
`}function
|
|
18
|
-
`}function
|
|
19
|
-
`}export{c as PREFERRED_MCP_BIN,k as antigravityHooks,l as claudeSettings,p as codexHooks,
|
|
15
|
+
`}function u(o){const r=s(o);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${r} ${c} --session-context --root . --root-env GROK_WORKSPACE_ROOT,CLAUDE_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit|write|search_replace",hooks:[{type:"command",timeout:30,command:`${r} ${c} --hook --hook-repair --fail-on-new-smells --root . --root-env GROK_WORKSPACE_ROOT,CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
|
|
16
|
+
`}function k(o){const r=s(o);return`${JSON.stringify({"ark-write-gate":{PreToolUse:[{matcher:"write_to_file|replace_file_content|multi_replace_file_content",hooks:[{type:"command",timeout:30,command:`${r} ${c} --hook --hook-repair --fail-on-new-smells --root . --config ark.config.json`}]}]}},null,2)}
|
|
17
|
+
`}function d(o){const{command:r,args:e}=i(o,c,["--root",".","--config","ark.config.json"]);return`${JSON.stringify({$schema:"https://opencode.ai/config.json",mcp:{ark:{type:"local",command:[r,...e],enabled:!0}}},null,2)}
|
|
18
|
+
`}function h(o,r){let e,t;try{e=o&&o.trim()?JSON.parse(o):{},t=JSON.parse(r)}catch{return null}if(!e||typeof e!="object"||Array.isArray(e)||!t||typeof t!="object"||Array.isArray(t))return null;const n=t["ark-write-gate"];if(!n||typeof n!="object")return null;const a={...e,"ark-write-gate":n};return`${JSON.stringify(a,null,2)}
|
|
19
|
+
`}function y(o,r){let e,t;try{e=o&&o.trim()?JSON.parse(o):{},t=JSON.parse(r)}catch{return null}if(!e||typeof e!="object"||Array.isArray(e))return null;const n={...e};!n.$schema&&t.$schema&&(n.$schema=t.$schema);const a=e.mcp&&typeof e.mcp=="object"&&!Array.isArray(e.mcp)?{...e.mcp}:{};return a.ark=t.mcp.ark,n.mcp=a,`${JSON.stringify(n,null,2)}
|
|
20
|
+
`}export{c as PREFERRED_MCP_BIN,k as antigravityHooks,l as claudeSettings,p as codexHooks,f as codexProjectConfig,u as grokHooks,g as grokProjectConfig,h as mergeAntigravityArkHook,y as mergeOpencodeArkMcp,d as opencodeProjectConfig};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export function renderEvolutionSection({
|
|
2
|
+
originSnapshot,
|
|
3
|
+
currentSnapshot,
|
|
4
|
+
originJustCreated,
|
|
5
|
+
esc,
|
|
6
|
+
formatDelta,
|
|
7
|
+
historyMax,
|
|
8
|
+
}) {
|
|
9
|
+
if (!currentSnapshot) return '';
|
|
10
|
+
if (originJustCreated || !originSnapshot) {
|
|
11
|
+
return `<div class="section card evolve">
|
|
12
|
+
<h2>Origin baseline captured</h2>
|
|
13
|
+
<p class="dim" style="margin:.2rem 0 0;font-size:.9rem">
|
|
14
|
+
This is the <b>first</b> architecture snapshot for this project
|
|
15
|
+
(<code>.ark/reports/origin.json</code> + <code>origin.html</code>).
|
|
16
|
+
Future reports will show deltas against this starting point so you can prove evolution.
|
|
17
|
+
</p>
|
|
18
|
+
</div>`;
|
|
19
|
+
}
|
|
20
|
+
const scoreComparable =
|
|
21
|
+
typeof originSnapshot.arkVersion === 'string' &&
|
|
22
|
+
originSnapshot.arkVersion.length > 0 &&
|
|
23
|
+
originSnapshot.arkVersion === currentSnapshot.arkVersion;
|
|
24
|
+
const rows = [
|
|
25
|
+
['Ark score', originSnapshot.score, currentSnapshot.score, '', scoreComparable],
|
|
26
|
+
['Governed %', originSnapshot.governedPercent, currentSnapshot.governedPercent, 'pp', true],
|
|
27
|
+
['Files in scope', originSnapshot.totalFiles, currentSnapshot.totalFiles, '', true],
|
|
28
|
+
['Classified files', originSnapshot.classifiedFiles, currentSnapshot.classifiedFiles, '', true],
|
|
29
|
+
['Active violations', originSnapshot.activeViolations, currentSnapshot.activeViolations, '', true],
|
|
30
|
+
['Value violations', originSnapshot.valueViolations, currentSnapshot.valueViolations, '', true],
|
|
31
|
+
['Type-only violations', originSnapshot.typeOnlyViolations, currentSnapshot.typeOnlyViolations, '', true],
|
|
32
|
+
['Layers', originSnapshot.layerCount, currentSnapshot.layerCount, '', true],
|
|
33
|
+
['Deny rules', originSnapshot.denyRules, currentSnapshot.denyRules, '', true],
|
|
34
|
+
['Gates configured', originSnapshot.gatesOn, currentSnapshot.gatesOn, '', true],
|
|
35
|
+
];
|
|
36
|
+
const originDate = (originSnapshot.generatedAt || '').slice(0, 10) || 'origin';
|
|
37
|
+
const nowDate = (currentSnapshot.generatedAt || '').slice(0, 10) || 'now';
|
|
38
|
+
const tr = rows
|
|
39
|
+
.map(([label, from, to, unit, comparable]) => {
|
|
40
|
+
const d =
|
|
41
|
+
comparable && typeof from === 'number' && typeof to === 'number'
|
|
42
|
+
? to - from
|
|
43
|
+
: null;
|
|
44
|
+
const good =
|
|
45
|
+
label.includes('violation') || label.includes('Violation')
|
|
46
|
+
? d != null && d <= 0
|
|
47
|
+
: label.includes('Governed') ||
|
|
48
|
+
label.includes('score') ||
|
|
49
|
+
label.includes('Classified') ||
|
|
50
|
+
label.includes('Gates')
|
|
51
|
+
? d != null && d >= 0
|
|
52
|
+
: null;
|
|
53
|
+
const cls =
|
|
54
|
+
d == null || d === 0 ? 'flat' : good === true ? 'up' : good === false ? 'down' : 'flat';
|
|
55
|
+
const delta =
|
|
56
|
+
d == null
|
|
57
|
+
? '—'
|
|
58
|
+
: unit === 'pp'
|
|
59
|
+
? formatDelta(Math.round(d * 10) / 10, { suffix: ' pp' })
|
|
60
|
+
: formatDelta(d);
|
|
61
|
+
return `<tr>
|
|
62
|
+
<td>${esc(label)}</td>
|
|
63
|
+
<td class="num">${from ?? '—'}</td>
|
|
64
|
+
<td class="num">${to ?? '—'}</td>
|
|
65
|
+
<td class="num delta ${cls}">${esc(delta)}</td>
|
|
66
|
+
</tr>`;
|
|
67
|
+
})
|
|
68
|
+
.join('\n');
|
|
69
|
+
const originLayers = originSnapshot.layerFiles || {};
|
|
70
|
+
const currentLayers = currentSnapshot.layerFiles || {};
|
|
71
|
+
const layerKeys = [
|
|
72
|
+
...new Set([...Object.keys(originLayers), ...Object.keys(currentLayers)]),
|
|
73
|
+
].sort();
|
|
74
|
+
const layerTr = layerKeys
|
|
75
|
+
.map((name) => {
|
|
76
|
+
const from = originLayers[name] || 0;
|
|
77
|
+
const to = currentLayers[name] || 0;
|
|
78
|
+
const d = to - from;
|
|
79
|
+
const cls = d === 0 ? 'flat' : d > 0 ? 'up' : 'down';
|
|
80
|
+
return `<tr>
|
|
81
|
+
<td class="ln">${esc(name)}</td>
|
|
82
|
+
<td class="num">${from}</td>
|
|
83
|
+
<td class="num">${to}</td>
|
|
84
|
+
<td class="num delta ${cls}">${esc(formatDelta(d))}</td>
|
|
85
|
+
</tr>`;
|
|
86
|
+
})
|
|
87
|
+
.join('\n');
|
|
88
|
+
const scoreNote = scoreComparable
|
|
89
|
+
? ''
|
|
90
|
+
: `<p class="dim" style="margin:-.35rem 0 .75rem;font-size:.88rem">
|
|
91
|
+
Ark score is not comparable across Ark versions
|
|
92
|
+
(<code>${esc(originSnapshot.arkVersion ?? 'unknown')}</code> →
|
|
93
|
+
<code>${esc(currentSnapshot.arkVersion ?? 'unknown')}</code>); its Δ is shown as —.
|
|
94
|
+
Raw coverage, files, violations, layers, rules, and gate metrics remain visible.
|
|
95
|
+
</p>`;
|
|
96
|
+
return `<div class="section card evolve">
|
|
97
|
+
<h2>Evolution vs origin</h2>
|
|
98
|
+
<p class="dim" style="margin:.15rem 0 .75rem;font-size:.88rem">
|
|
99
|
+
Origin snapshot <code>${esc(originDate)}</code> → this report <code>${esc(nowDate)}</code>
|
|
100
|
+
· frozen at <code>.ark/reports/origin.*</code> · reopen origin HTML anytime for the starting picture.
|
|
101
|
+
</p>
|
|
102
|
+
${scoreNote}
|
|
103
|
+
<table class="layers">
|
|
104
|
+
<tr><th>Metric</th><th>Origin</th><th>Now</th><th>Δ</th></tr>
|
|
105
|
+
${tr}
|
|
106
|
+
</table>
|
|
107
|
+
<h3>Files per layer</h3>
|
|
108
|
+
<table class="layers">
|
|
109
|
+
<tr><th>Layer</th><th>Origin</th><th>Now</th><th>Δ</th></tr>
|
|
110
|
+
${layerTr || '<tr><td colspan="4" class="dim">No layer file data in snapshots.</td></tr>'}
|
|
111
|
+
</table>
|
|
112
|
+
<p class="legend">Green Δ = improvement for that metric (↑ coverage/score/gates, ↓ violations). Score Δ is comparable only within the same Ark version. History JSON under <code>.ark/reports/history/</code> (last ${historyMax}).</p>
|
|
113
|
+
</div>`;
|
|
114
|
+
}
|
package/bin/lib/html-report.mjs
CHANGED
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
} from './html-report-depth.mjs';
|
|
22
22
|
import { FIX_HINTS } from './violations.mjs';
|
|
23
23
|
import { capabilityBadgesFor, renderAdvisorySections } from './html-report-advisories.mjs';
|
|
24
|
+
import { renderEvolutionSection } from './html-report-evolution.mjs';
|
|
24
25
|
import { arkGitignoreAppendDecision } from './ark-gitignore.mjs';
|
|
26
|
+
import { captureGitSnapshot } from './report-snapshot-context.mjs';
|
|
25
27
|
|
|
26
28
|
export { arkGitignoreAppendDecision, gitignoreCoversArkState, gitignoreHasArkNegationException } from './ark-gitignore.mjs';
|
|
27
29
|
|
|
@@ -161,6 +163,7 @@ export function buildReportSnapshot({
|
|
|
161
163
|
return path.basename(root);
|
|
162
164
|
}
|
|
163
165
|
})(),
|
|
166
|
+
git: captureGitSnapshot(root),
|
|
164
167
|
ok: Boolean(ok),
|
|
165
168
|
mode: mode ?? null,
|
|
166
169
|
score: score ?? null,
|
|
@@ -1228,95 +1231,14 @@ export function renderHtmlReport({
|
|
|
1228
1231
|
<div class="gates">${enforcementRows}</div>
|
|
1229
1232
|
</div>
|
|
1230
1233
|
|
|
1231
|
-
${(
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
(<code>.ark/reports/origin.json</code> + <code>origin.html</code>).
|
|
1240
|
-
Future reports will show deltas against this starting point so you can prove evolution.
|
|
1241
|
-
</p>
|
|
1242
|
-
</div>`;
|
|
1243
|
-
}
|
|
1244
|
-
const rows = [
|
|
1245
|
-
['Ark score', originSnapshot.score, currentSnapshot.score, ''],
|
|
1246
|
-
['Governed %', originSnapshot.governedPercent, currentSnapshot.governedPercent, 'pp'],
|
|
1247
|
-
['Files in scope', originSnapshot.totalFiles, currentSnapshot.totalFiles, ''],
|
|
1248
|
-
['Classified files', originSnapshot.classifiedFiles, currentSnapshot.classifiedFiles, ''],
|
|
1249
|
-
['Active violations', originSnapshot.activeViolations, currentSnapshot.activeViolations, ''],
|
|
1250
|
-
['Value violations', originSnapshot.valueViolations, currentSnapshot.valueViolations, ''],
|
|
1251
|
-
['Type-only violations', originSnapshot.typeOnlyViolations, currentSnapshot.typeOnlyViolations, ''],
|
|
1252
|
-
['Layers', originSnapshot.layerCount, currentSnapshot.layerCount, ''],
|
|
1253
|
-
['Deny rules', originSnapshot.denyRules, currentSnapshot.denyRules, ''],
|
|
1254
|
-
['Gates configured', originSnapshot.gatesOn, currentSnapshot.gatesOn, ''],
|
|
1255
|
-
];
|
|
1256
|
-
const originDate = (originSnapshot.generatedAt || '').slice(0, 10) || 'origin';
|
|
1257
|
-
const nowDate = (currentSnapshot.generatedAt || '').slice(0, 10) || 'now';
|
|
1258
|
-
const tr = rows
|
|
1259
|
-
.map(([label, from, to, unit]) => {
|
|
1260
|
-
const d =
|
|
1261
|
-
typeof from === 'number' && typeof to === 'number' ? to - from : null;
|
|
1262
|
-
const good =
|
|
1263
|
-
label.includes('violation') || label.includes('Violation')
|
|
1264
|
-
? d != null && d <= 0
|
|
1265
|
-
: label.includes('Governed') || label.includes('score') || label.includes('Classified') || label.includes('Gates')
|
|
1266
|
-
? d != null && d >= 0
|
|
1267
|
-
: null;
|
|
1268
|
-
const cls =
|
|
1269
|
-
d == null || d === 0 ? 'flat' : good === true ? 'up' : good === false ? 'down' : 'flat';
|
|
1270
|
-
const delta =
|
|
1271
|
-
d == null
|
|
1272
|
-
? '—'
|
|
1273
|
-
: unit === 'pp'
|
|
1274
|
-
? formatDelta(Math.round(d * 10) / 10, { suffix: ' pp' })
|
|
1275
|
-
: formatDelta(d);
|
|
1276
|
-
return `<tr>
|
|
1277
|
-
<td>${esc(label)}</td>
|
|
1278
|
-
<td class="num">${from ?? '—'}</td>
|
|
1279
|
-
<td class="num">${to ?? '—'}</td>
|
|
1280
|
-
<td class="num delta ${cls}">${esc(delta)}</td>
|
|
1281
|
-
</tr>`;
|
|
1282
|
-
})
|
|
1283
|
-
.join('\n');
|
|
1284
|
-
// Layer file deltas
|
|
1285
|
-
const originLayers = originSnapshot.layerFiles || {};
|
|
1286
|
-
const currentLayers = currentSnapshot.layerFiles || {};
|
|
1287
|
-
const layerKeys = [...new Set([...Object.keys(originLayers), ...Object.keys(currentLayers)])].sort();
|
|
1288
|
-
const layerTr = layerKeys
|
|
1289
|
-
.map((name) => {
|
|
1290
|
-
const from = originLayers[name] || 0;
|
|
1291
|
-
const to = currentLayers[name] || 0;
|
|
1292
|
-
const d = to - from;
|
|
1293
|
-
const cls = d === 0 ? 'flat' : d > 0 ? 'up' : 'down';
|
|
1294
|
-
return `<tr>
|
|
1295
|
-
<td class="ln">${esc(name)}</td>
|
|
1296
|
-
<td class="num">${from}</td>
|
|
1297
|
-
<td class="num">${to}</td>
|
|
1298
|
-
<td class="num delta ${cls}">${esc(formatDelta(d))}</td>
|
|
1299
|
-
</tr>`;
|
|
1300
|
-
})
|
|
1301
|
-
.join('\n');
|
|
1302
|
-
return `<div class="section card evolve">
|
|
1303
|
-
<h2>Evolution vs origin</h2>
|
|
1304
|
-
<p class="dim" style="margin:.15rem 0 .75rem;font-size:.88rem">
|
|
1305
|
-
Origin snapshot <code>${esc(originDate)}</code> → this report <code>${esc(nowDate)}</code>
|
|
1306
|
-
· frozen at <code>.ark/reports/origin.*</code> · reopen origin HTML anytime for the starting picture.
|
|
1307
|
-
</p>
|
|
1308
|
-
<table class="layers">
|
|
1309
|
-
<tr><th>Metric</th><th>Origin</th><th>Now</th><th>Δ</th></tr>
|
|
1310
|
-
${tr}
|
|
1311
|
-
</table>
|
|
1312
|
-
<h3>Files per layer</h3>
|
|
1313
|
-
<table class="layers">
|
|
1314
|
-
<tr><th>Layer</th><th>Origin</th><th>Now</th><th>Δ</th></tr>
|
|
1315
|
-
${layerTr || '<tr><td colspan="4" class="dim">No layer file data in snapshots.</td></tr>'}
|
|
1316
|
-
</table>
|
|
1317
|
-
<p class="legend">Green Δ = improvement for that metric (↑ coverage/score/gates, ↓ violations). History JSON under <code>.ark/reports/history/</code> (last ${ARK_REPORT_HISTORY_MAX}).</p>
|
|
1318
|
-
</div>`;
|
|
1319
|
-
})()}
|
|
1234
|
+
${renderEvolutionSection({
|
|
1235
|
+
originSnapshot,
|
|
1236
|
+
currentSnapshot,
|
|
1237
|
+
originJustCreated,
|
|
1238
|
+
esc,
|
|
1239
|
+
formatDelta,
|
|
1240
|
+
historyMax: ARK_REPORT_HISTORY_MAX,
|
|
1241
|
+
})}
|
|
1320
1242
|
|
|
1321
1243
|
<div class="section card senior">
|
|
1322
1244
|
<h2>Senior diagnostics</h2>
|
|
@@ -35,32 +35,55 @@ export function readTsconfigAliases(ts, root) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function canonicalPathWithMissingTail(value) {
|
|
39
|
+
const resolved = path.resolve(value);
|
|
40
|
+
let current = resolved;
|
|
41
|
+
const tail = [];
|
|
42
|
+
while (true) {
|
|
43
|
+
try {
|
|
44
|
+
return path.join(fs.realpathSync(current), ...tail.reverse());
|
|
45
|
+
} catch {
|
|
46
|
+
const parent = path.dirname(current);
|
|
47
|
+
if (parent === current) return resolved;
|
|
48
|
+
tail.push(path.basename(current));
|
|
49
|
+
current = parent;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
/**
|
|
39
55
|
* Resolve an import specifier to a repo-relative path.
|
|
40
56
|
* Relative + tsconfig-aliased only; bare packages → undefined.
|
|
41
57
|
*/
|
|
42
58
|
export function resolveSpecifierToRel(specifier, fromFilePath, root, tsAliases) {
|
|
59
|
+
const canonicalRoot = canonicalPathWithMissingTail(root);
|
|
43
60
|
let abs;
|
|
44
61
|
if (specifier.startsWith('./') || specifier.startsWith('../')) {
|
|
45
62
|
if (!fromFilePath) return undefined;
|
|
46
|
-
const fromAbs =
|
|
47
|
-
? fromFilePath
|
|
48
|
-
|
|
49
|
-
abs = path.resolve(path.dirname(fromAbs), specifier);
|
|
63
|
+
const fromAbs = canonicalPathWithMissingTail(
|
|
64
|
+
path.isAbsolute(fromFilePath) ? fromFilePath : path.resolve(root, fromFilePath)
|
|
65
|
+
);
|
|
66
|
+
abs = canonicalPathWithMissingTail(path.resolve(path.dirname(fromAbs), specifier));
|
|
50
67
|
} else {
|
|
51
68
|
const alias = tsAliases.aliases.find((a) => specifier.startsWith(a.from));
|
|
52
69
|
if (!alias) return undefined;
|
|
53
|
-
abs =
|
|
70
|
+
abs = canonicalPathWithMissingTail(
|
|
71
|
+
path.resolve(tsAliases.baseUrl, `${alias.to}${specifier.slice(alias.from.length)}`)
|
|
72
|
+
);
|
|
54
73
|
}
|
|
55
|
-
const
|
|
56
|
-
|
|
74
|
+
const relative = path.relative(canonicalRoot, abs);
|
|
75
|
+
if (path.isAbsolute(relative) || relative.startsWith('..')) return undefined;
|
|
76
|
+
return relative.split(path.sep).join('/');
|
|
57
77
|
}
|
|
58
78
|
|
|
59
79
|
function filePathToRel(filePath, root) {
|
|
60
80
|
if (!filePath || typeof filePath !== 'string') return undefined;
|
|
61
|
-
const abs =
|
|
62
|
-
|
|
63
|
-
|
|
81
|
+
const abs = canonicalPathWithMissingTail(
|
|
82
|
+
path.isAbsolute(filePath) ? filePath : path.resolve(root, filePath)
|
|
83
|
+
);
|
|
84
|
+
const relative = path.relative(canonicalPathWithMissingTail(root), abs);
|
|
85
|
+
if (path.isAbsolute(relative) || relative.startsWith('..')) return undefined;
|
|
86
|
+
return relative.split(path.sep).join('/');
|
|
64
87
|
}
|
|
65
88
|
|
|
66
89
|
function classifyProbe(root, rel, layers) {
|
|
@@ -130,4 +153,3 @@ export function createImportTargetResolver(ts, root, config) {
|
|
|
130
153
|
return undefined;
|
|
131
154
|
};
|
|
132
155
|
}
|
|
133
|
-
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { arkCommand } from '../ark-shared.mjs';
|
|
4
|
+
import { codexProjectMcpIsValid } from './codex-home.mjs';
|
|
5
|
+
import { codexRuntimeActivation } from './enforcement-state.mjs';
|
|
6
|
+
|
|
7
|
+
export function inspectCodexInstallActivation(root, enabled) {
|
|
8
|
+
let configuredOnDisk = false;
|
|
9
|
+
if (enabled) {
|
|
10
|
+
try {
|
|
11
|
+
configuredOnDisk = codexProjectMcpIsValid(
|
|
12
|
+
fs.readFileSync(path.join(root, '.codex', 'config.toml'), 'utf8'),
|
|
13
|
+
root
|
|
14
|
+
);
|
|
15
|
+
} catch {
|
|
16
|
+
// Missing/unreadable configuration remains explicitly unverified.
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
codexProjectConfigured: configuredOnDisk,
|
|
21
|
+
runtimeActivation: codexRuntimeActivation({
|
|
22
|
+
configuredOnDisk,
|
|
23
|
+
restartRequired: configuredOnDisk,
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function reportPartialInstall({
|
|
29
|
+
root,
|
|
30
|
+
tools,
|
|
31
|
+
results,
|
|
32
|
+
homeResults,
|
|
33
|
+
earlyWritten,
|
|
34
|
+
codexMcp,
|
|
35
|
+
runtimeActivation,
|
|
36
|
+
}) {
|
|
37
|
+
const failed = [...results, ...homeResults]
|
|
38
|
+
.filter((result) => result.status === 'failed')
|
|
39
|
+
.map((result) => ({
|
|
40
|
+
target: result.relativePath ?? '(unknown template)',
|
|
41
|
+
reason: 'write failed',
|
|
42
|
+
}));
|
|
43
|
+
if (codexMcp?.status === 'failed') {
|
|
44
|
+
failed.push({
|
|
45
|
+
target: codexMcp.file,
|
|
46
|
+
reason: codexMcp.message ?? 'Codex MCP registration failed',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (failed.length === 0) return false;
|
|
50
|
+
|
|
51
|
+
const written = new Set([
|
|
52
|
+
...earlyWritten,
|
|
53
|
+
...[...results, ...homeResults]
|
|
54
|
+
.filter((result) => result.status === 'written' || result.status === 'merged')
|
|
55
|
+
.map((result) => result.relativePath),
|
|
56
|
+
]);
|
|
57
|
+
console.error('\nINSTALL PARTIAL — some artifacts were written; activation is not complete.');
|
|
58
|
+
console.error('Written:');
|
|
59
|
+
if (written.size === 0) console.error(' - (none)');
|
|
60
|
+
for (const relativePath of written) console.error(` - ${relativePath}`);
|
|
61
|
+
console.error('Failed:');
|
|
62
|
+
for (const failure of failed) console.error(` - ${failure.target}: ${failure.reason}`);
|
|
63
|
+
console.error('Recovery:');
|
|
64
|
+
console.error(
|
|
65
|
+
` - Fix the listed path or permission error, then re-run ${arkCommand(root, 'ark-check', `--install-agent-gates${tools.size > 0 ? ` --tools ${[...tools].join(',')}` : ''}`)}.`
|
|
66
|
+
);
|
|
67
|
+
console.error(' - Existing customized files were preserved; no destructive rollback was attempted.');
|
|
68
|
+
if (tools.has('codex')) {
|
|
69
|
+
console.error(` - Runtime activation: ${JSON.stringify(runtimeActivation)}`);
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function printCodexActivationHandoff(root, configuredOnDisk, runtimeActivation) {
|
|
75
|
+
console.log(
|
|
76
|
+
configuredOnDisk
|
|
77
|
+
? ' CODEX MCP CONFIGURED — RUNTIME NOT VERIFIED'
|
|
78
|
+
: ' CODEX MCP CONFIGURATION UNRESOLVED — RUNTIME NOT VERIFIED'
|
|
79
|
+
);
|
|
80
|
+
console.log(` Runtime activation: ${JSON.stringify(runtimeActivation)}`);
|
|
81
|
+
console.log(
|
|
82
|
+
configuredOnDisk
|
|
83
|
+
? ` Restart Codex, then call ark_identity with expectedRoot "${path.resolve(root)}".`
|
|
84
|
+
: ' Repair `.codex/config.toml`, then restart Codex and call ark_identity.'
|
|
85
|
+
);
|
|
86
|
+
console.log(' Do not trust MCP verdicts before the project identity matches.');
|
|
87
|
+
}
|