arkgate 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +26 -16
- package/SECURITY.md +9 -8
- package/bin/ark-check.mjs +605 -53
- package/bin/ark-shared.mjs +70 -3
- package/bin/ark.mjs +20 -5
- 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/docs/agent-guide.md +11 -5
- package/docs/ai-gates.md +11 -7
- package/docs/brownfield-adoption.md +14 -13
- package/docs/demos/03-copilot-autopilot.md +5 -3
- package/docs/enthusiast/README.md +4 -3
- package/docs/enthusiast/how-to-agent-gates.md +14 -6
- package/docs/enthusiast/reference-commands.md +23 -8
- package/docs/migrate-from-ark-runtime-kernel.md +18 -0
- package/docs/typescript-support.md +142 -0
- package/package.json +12 -3
- package/server.json +2 -2
- package/templates/skills/ark-autopilot.md +6 -4
- package/templates/skills/ark-explain.md +7 -2
- package/templates/skills/ark-fix.md +16 -12
- package/templates/skills/ark-loop.md +14 -4
- package/templates/skills/ark-upgrade.md +26 -1
- package/templates/tests/ark-adoption-gaps.test.ts +68 -0
- package/tests/fixtures/ts-consumer/ark.config.json +11 -0
- package/tests/fixtures/ts-consumer/src/app/types.ts +1 -0
- package/tests/fixtures/ts-consumer/src/domain/bad.ts +1 -0
- package/tests/fixtures/ts-consumer/src/domain/ok.ts +1 -0
- package/tests/fixtures/ts-consumer/src/domain/user.ts +1 -0
- package/tests/fixtures/ts-consumer/tsconfig.json +16 -0
package/bin/ark-shared.mjs
CHANGED
|
@@ -542,20 +542,33 @@ export function looksLikeIntent(value) {
|
|
|
542
542
|
*
|
|
543
543
|
* Deliberately biased toward 'judgment': a false 'mechanical-safe' that auto-lands a bad edit
|
|
544
544
|
* is the failure mode that sinks trust. Only statically-provable type-surface fixes earn 'auto':
|
|
545
|
-
* (1)
|
|
546
|
-
* (2)
|
|
545
|
+
* (1) whole source file is pure type-surface + type-only edge → relocate the file
|
|
546
|
+
* (2) import/export already marked type-only → move/re-export the type
|
|
547
|
+
* (3) static value-syntax import of a pure type-only *target* module → import type
|
|
547
548
|
* Pure function of one violation object so CLI, MCP, and apply-loop classify identically.
|
|
548
|
-
* Returns { class, confidence, rationale }.
|
|
549
|
+
* Returns { class, confidence, rationale, remediationKind? }.
|
|
549
550
|
*/
|
|
550
551
|
export const REMEDIATION_CLASSES = ['mechanical-safe', 'judgment', 'deferred'];
|
|
551
552
|
|
|
552
553
|
export function classifyRemediation(violation) {
|
|
553
554
|
const ruleId = violation?.ruleId;
|
|
554
555
|
if (ruleId === 'LAYER_IMPORT_VIOLATION') {
|
|
556
|
+
// Pure type-only *source file* with a type-only edge: relocating the whole file is
|
|
557
|
+
// behavior-preserving (no runtime body). Distinct from a single import-type move.
|
|
558
|
+
if (violation.typeOnly && violation.sourcePureTypeModule) {
|
|
559
|
+
return {
|
|
560
|
+
class: 'mechanical-safe',
|
|
561
|
+
confidence: 0.88,
|
|
562
|
+
remediationKind: 'pure-type-file-relocate',
|
|
563
|
+
rationale:
|
|
564
|
+
'Whole source file is type-only surface (no runtime statements) with a type-only cross-layer edge: relocate the file to the owning layer (or extract the type there). Behavior-preserving; gate verifies.',
|
|
565
|
+
};
|
|
566
|
+
}
|
|
555
567
|
if (violation.typeOnly) {
|
|
556
568
|
return {
|
|
557
569
|
class: 'mechanical-safe',
|
|
558
570
|
confidence: 0.9,
|
|
571
|
+
remediationKind: 'type-only-import-move',
|
|
559
572
|
rationale:
|
|
560
573
|
'Type-only import (erased at runtime): move the type to the layer that owns it and re-export for back-compat. Behavior-preserving, and the gate verifies it.',
|
|
561
574
|
};
|
|
@@ -576,6 +589,7 @@ export function classifyRemediation(violation) {
|
|
|
576
589
|
return {
|
|
577
590
|
class: 'mechanical-safe',
|
|
578
591
|
confidence: 0.85,
|
|
592
|
+
remediationKind: 'import-type-from-pure-type-module',
|
|
579
593
|
rationale:
|
|
580
594
|
'Static import targets a pure type-only module: convert to `import type` (erased at runtime) and place the type in a shared/owning layer. No runtime coupling; gate verifies.',
|
|
581
595
|
};
|
|
@@ -616,6 +630,59 @@ export function classifyRemediation(violation) {
|
|
|
616
630
|
};
|
|
617
631
|
}
|
|
618
632
|
|
|
633
|
+
/**
|
|
634
|
+
* Normalize a required/imported TypeScript module for ark-check's host.
|
|
635
|
+
* TS 5/6 expose `sys` on the root export. Early TS 7 / some ESM interop shapes
|
|
636
|
+
* may nest under `.default` or omit `sys` — those are unusable for resolve/scan
|
|
637
|
+
* and must fall through to a JS-API-compatible TypeScript (Ark's own or 5/6).
|
|
638
|
+
*
|
|
639
|
+
* @param {unknown} mod
|
|
640
|
+
* @returns {object | null} usable typescript namespace, or null
|
|
641
|
+
*/
|
|
642
|
+
export function usableTypescript(mod) {
|
|
643
|
+
if (!mod || typeof mod !== 'object') return null;
|
|
644
|
+
// Prefer root; if root has no sys but default does (CJS/ESM interop), use default.
|
|
645
|
+
const candidates = [mod];
|
|
646
|
+
if (mod.default && typeof mod.default === 'object') candidates.push(mod.default);
|
|
647
|
+
for (const ts of candidates) {
|
|
648
|
+
if (
|
|
649
|
+
ts &&
|
|
650
|
+
typeof ts === 'object' &&
|
|
651
|
+
ts.sys &&
|
|
652
|
+
typeof ts.sys.fileExists === 'function' &&
|
|
653
|
+
typeof ts.createSourceFile === 'function' &&
|
|
654
|
+
typeof ts.resolveModuleName === 'function'
|
|
655
|
+
) {
|
|
656
|
+
return ts;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Human-readable reason a typescript package module is unusable for the gate.
|
|
664
|
+
* @param {unknown} mod
|
|
665
|
+
*/
|
|
666
|
+
export function typescriptUsabilityHint(mod) {
|
|
667
|
+
if (!mod) return 'module is null/undefined';
|
|
668
|
+
const ts = mod.default && mod.sys == null ? mod.default : mod;
|
|
669
|
+
if (!ts || typeof ts !== 'object') return 'not an object export';
|
|
670
|
+
// TS 7.0.x main export is only { version, versionMajorMinor }; classic JS host is not there.
|
|
671
|
+
if (
|
|
672
|
+
typeof ts.version === 'string' &&
|
|
673
|
+
!ts.sys &&
|
|
674
|
+
typeof ts.createSourceFile !== 'function' &&
|
|
675
|
+
typeof ts.resolveModuleName !== 'function'
|
|
676
|
+
) {
|
|
677
|
+
return `version-only export (${ts.version}) — TypeScript 7 main entry no longer ships the classic JS host (sys/AST/resolve); gate falls back to a JS-API TypeScript`;
|
|
678
|
+
}
|
|
679
|
+
if (!ts.sys) return 'missing ts.sys (common with early TypeScript 7 native builds without a full JS host)';
|
|
680
|
+
if (typeof ts.sys.fileExists !== 'function') return 'ts.sys.fileExists is not a function';
|
|
681
|
+
if (typeof ts.createSourceFile !== 'function') return 'missing createSourceFile (AST API)';
|
|
682
|
+
if (typeof ts.resolveModuleName !== 'function') return 'missing resolveModuleName';
|
|
683
|
+
return 'unknown shape incompatibility';
|
|
684
|
+
}
|
|
685
|
+
|
|
619
686
|
/** The three package managers Ark emits commands for. */
|
|
620
687
|
const LOCKFILES = { pnpm: 'pnpm-lock.yaml', yarn: 'yarn.lock', npm: 'package-lock.json' };
|
|
621
688
|
|
package/bin/ark.mjs
CHANGED
|
@@ -123,11 +123,12 @@ async function upgrade(args) {
|
|
|
123
123
|
let status = runArkCheck(['--root', root, '--install-agent-gates'], { cwd: root });
|
|
124
124
|
if (status !== 0) return status;
|
|
125
125
|
|
|
126
|
-
// Codex loads slash-command prompts from
|
|
127
|
-
// when a Codex home exists
|
|
128
|
-
// (e.g.
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
// Codex loads slash-command prompts from $CODEX_HOME/prompts, not the repo — refresh those
|
|
127
|
+
// when a Codex home exists. --force rewrites temp/upgrade MCP roots to this project + arkgate-mcp.
|
|
128
|
+
// Non-fatal: a permission error (e.g. sandbox) shouldn't fail the whole upgrade.
|
|
129
|
+
const codexHomeBase = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
130
|
+
if (fs.existsSync(codexHomeBase)) {
|
|
131
|
+
console.log(`\n Refreshing Codex home (${codexHomeBase})…`);
|
|
131
132
|
runArkCheck(
|
|
132
133
|
['--root', root, '--install-agent-gates', '--skills-only', '--codex-home', '--force'],
|
|
133
134
|
{ cwd: root }
|
|
@@ -279,6 +280,10 @@ async function init(args) {
|
|
|
279
280
|
if (archetype) {
|
|
280
281
|
console.log(`Shape: ${archetype}. Plan: ${arkCommand(root, 'ark-check', '--recommend')}`);
|
|
281
282
|
}
|
|
283
|
+
console.log(
|
|
284
|
+
`Freeze day-one architecture snapshot: ${arkCommand(root, 'ark-check', '--report ark-report.html')} (writes .ark/reports/origin.* once).`
|
|
285
|
+
);
|
|
286
|
+
console.log(`Adoption health: ${arkCommand(root, 'ark-check', '--doctor')}`);
|
|
282
287
|
return 0;
|
|
283
288
|
} finally {
|
|
284
289
|
rl?.close();
|
|
@@ -433,7 +438,17 @@ async function start(args) {
|
|
|
433
438
|
}
|
|
434
439
|
console.log(` • Re-run the plan anytime: ${arkCommand(root, 'ark-check', '--plan')}`);
|
|
435
440
|
console.log(` • Full project check: ${arkCommand(root, 'ark-check', '--root . --config ark.config.json --strict-config')}`);
|
|
441
|
+
console.log(` • Adoption health: ${arkCommand(root, 'ark-check', '--doctor')}`);
|
|
436
442
|
console.log(` • Update Ark later: ${arkCommand(root, 'ark', 'upgrade')}`);
|
|
443
|
+
if (fs.existsSync(path.join(root, '.ark-baseline.json'))) {
|
|
444
|
+
console.log(
|
|
445
|
+
' • Baseline file present — keep empty for ratchet-from-clean, or freeze debt with --update-baseline.'
|
|
446
|
+
);
|
|
447
|
+
} else {
|
|
448
|
+
console.log(
|
|
449
|
+
' • No baseline yet (fine on clean trees). Adopting dirty code? freeze with --update-baseline.'
|
|
450
|
+
);
|
|
451
|
+
}
|
|
437
452
|
|
|
438
453
|
// 6) First architecture report — freezes an origin snapshot under .ark/reports/
|
|
439
454
|
// so later --report runs can show evolution. Idempotent: origin is written only once.
|