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
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* Pure CLI helper (bin/lib/arkrules-sensors.mjs). Zero Node I/O.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { findClassDeclarations, scanClassMembers } from './class-source-scan.mjs';
|
|
12
|
+
import { sourceHasPersistenceWrite, sourceHasTaggedSqlWrite, sourceImportsPersistenceDriverText, } from './persistence-write-hint.mjs';
|
|
11
13
|
/** Keep in lockstep with arkRulesTypes.ARK_RULE_TIER2_SENSOR_IDS (self-contained for CLI gen). */
|
|
12
14
|
const ARK_RULE_TIER2_SENSOR_IDS = ['no-anemic-model'];
|
|
13
15
|
/**
|
|
@@ -30,11 +32,10 @@ export const DOMAIN_EVENTS_PUSH_RE = new RegExp(`\\bthis\\.${EVENTS_ARRAY_PROP}\
|
|
|
30
32
|
const EVENTS_ARRAY_RESET_RE = new RegExp(`^this\\.${EVENTS_ARRAY_PROP}\\s*=\\s*\\[\\s*\\]`);
|
|
31
33
|
const ANY_THIS_EMPTY_ARRAY_RE = /^this\.[A-Za-z_][A-Za-z0-9_]*\s*=\s*\[\s*\]/;
|
|
32
34
|
const THIS_FIELD_ASSIGNMENT_RE = /\bthis\.[A-Za-z_][A-Za-z0-9_]*\s*=(?!=)/g;
|
|
33
|
-
const SHAPE_TRUNCATED_UNTIL = 'truncatedUntil';
|
|
34
35
|
export function expectedDomainInvariantWordsPhrase() {
|
|
35
36
|
return `${DOMAIN_INVARIANT_WORDS.join(', ')}, or events-array .push(`;
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
function referencesGuardOrPublish(source) {
|
|
38
39
|
return DOMAIN_INVARIANT_WORD_RE.test(source) || DOMAIN_EVENTS_PUSH_RE.test(source);
|
|
39
40
|
}
|
|
40
41
|
export function isIdiomaticEventsReset(source, assignIndex, methodName) {
|
|
@@ -59,18 +60,10 @@ function methodAssignsThis(methodName, methodBody) {
|
|
|
59
60
|
return false;
|
|
60
61
|
}
|
|
61
62
|
function attachShapeTruncation(shape, truncatedUntil) {
|
|
62
|
-
|
|
63
|
-
return shape;
|
|
64
|
-
Object.defineProperty(shape, SHAPE_TRUNCATED_UNTIL, {
|
|
65
|
-
value: truncatedUntil,
|
|
66
|
-
enumerable: false,
|
|
67
|
-
configurable: true,
|
|
68
|
-
});
|
|
69
|
-
return shape;
|
|
63
|
+
return truncatedUntil == null ? shape : { ...shape, truncatedUntil };
|
|
70
64
|
}
|
|
71
65
|
function shapeTruncatedUntil(shape) {
|
|
72
|
-
|
|
73
|
-
return typeof value === 'number' ? value : undefined;
|
|
66
|
+
return typeof shape.truncatedUntil === 'number' ? shape.truncatedUntil : undefined;
|
|
74
67
|
}
|
|
75
68
|
function shapeTruncationSuffix(shape) {
|
|
76
69
|
const until = shapeTruncatedUntil(shape);
|
|
@@ -188,9 +181,22 @@ function shapesForRule(rule, shapes, layerForFile) {
|
|
|
188
181
|
return true;
|
|
189
182
|
});
|
|
190
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* A shape the scanner could not walk to its end (unbalanced header/body) is
|
|
186
|
+
* reported, never passed in silence: later members may be invisible.
|
|
187
|
+
*/
|
|
188
|
+
function truncatedShapeViolation(rule, shape, what) {
|
|
189
|
+
const until = shapeTruncatedUntil(shape);
|
|
190
|
+
if (until == null)
|
|
191
|
+
return undefined;
|
|
192
|
+
return baseViolation(rule, shape.file, `Exported class ${shape.className} shape analysed until character ${until}; later ${what} may be invisible (sensor ${rule.sensor}).`);
|
|
193
|
+
}
|
|
191
194
|
function evaluateAggregatePrivateState(rule, shapes, layerForFile) {
|
|
192
195
|
const out = [];
|
|
193
196
|
for (const shape of shapesForRule(rule, shapes, layerForFile)) {
|
|
197
|
+
const truncated = truncatedShapeViolation(rule, shape, 'members');
|
|
198
|
+
if (truncated)
|
|
199
|
+
out.push(truncated);
|
|
194
200
|
// P1-L — require real mutability (setters or non-readonly public fields).
|
|
195
201
|
// Readonly public props on intentional value/entity shapes are false-positive-prone.
|
|
196
202
|
if (shape.hasPublicSetters || shape.hasPublicMutableFields) {
|
|
@@ -202,6 +208,9 @@ function evaluateAggregatePrivateState(rule, shapes, layerForFile) {
|
|
|
202
208
|
function evaluateAlwaysValidFactory(rule, shapes, layerForFile) {
|
|
203
209
|
const out = [];
|
|
204
210
|
for (const shape of shapesForRule(rule, shapes, layerForFile)) {
|
|
211
|
+
const truncated = truncatedShapeViolation(rule, shape, 'constructors and factories');
|
|
212
|
+
if (truncated)
|
|
213
|
+
out.push(truncated);
|
|
205
214
|
if (shape.hasPublicConstructor && !shape.hasStaticFactory) {
|
|
206
215
|
// P1-L — prefer false negatives on intentional DDD / DI aggregates: a public
|
|
207
216
|
// constructor alone is weak evidence. Only fire when mutable public surface
|
|
@@ -221,9 +230,9 @@ function evaluateDomainEventOnMutation(rule, shapes, layerForFile) {
|
|
|
221
230
|
const expected = expectedDomainInvariantWordsPhrase();
|
|
222
231
|
for (const shape of shapesForRule(rule, shapes, layerForFile)) {
|
|
223
232
|
const truncation = shapeTruncationSuffix(shape);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
233
|
+
const truncated = truncatedShapeViolation(rule, shape, 'methods');
|
|
234
|
+
if (truncated)
|
|
235
|
+
out.push(truncated);
|
|
227
236
|
for (const method of shape.mutatingMethods) {
|
|
228
237
|
if (!method.referencesGuardOrPublish) {
|
|
229
238
|
out.push(baseViolation(rule, shape.file, `Mutating method ${shape.className}.${method.name} does not reference ${expected} (sensor domain-event-on-mutation).${truncation}`));
|
|
@@ -459,24 +468,11 @@ export function collectEmptyInvariantCatalogFindings(input) {
|
|
|
459
468
|
},
|
|
460
469
|
];
|
|
461
470
|
}
|
|
462
|
-
|
|
463
|
-
const IO_IMPORT_HINT_RE = /\bfrom\s+['"](?:@?prisma\/client|@supabase\/|drizzle-orm(?:\/[^'"]+)?|postgres(?:\/[^'"]+)?|typeorm|knex|mongodb|pg|mysql2|mongoose|better-sqlite3|ioredis|redis|kysely|sequelize)['"]|require\(\s*['"](?:@?prisma\/client|pg|postgres(?:\/[^'"]+)?|drizzle-orm(?:\/[^'"]+)?|knex|typeorm|mongoose)/;
|
|
464
|
-
/**
|
|
465
|
-
* Path-alias / local db module (`@/lib/db`) without resolving tsconfig.
|
|
466
|
-
* Keep in lockstep with arkOrderFacts.
|
|
467
|
-
*/
|
|
468
|
-
const IO_ALIAS_IMPORT_RE = /\bfrom\s+['"](?:@\/|~\/)?(?:[\w.-]+\/)*(?:db|database|prisma|drizzle)(?:\.[cm]?[jt]sx?)?['"]|require\(\s*['"](?:@\/|~\/)?(?:[\w.-]+\/)*(?:db|database|prisma|drizzle)/;
|
|
469
|
-
/**
|
|
470
|
-
* Write tokens that skip the aggregate when paired with a persistence driver import.
|
|
471
|
-
* Callee must be db|tx|client|prisma|drizzle (PrismaClient included); not repo.update(.
|
|
472
|
-
* Keep in lockstep with arkOrderFacts.
|
|
473
|
-
*/
|
|
474
|
-
const PERSISTENCE_WRITE_HINT_RE = /\b(?:db|tx|client|prisma(?:Client)?|drizzle)\b(?:\s*\.\s*[A-Za-z_]\w*)*\s*\.\s*(?:insert(?:One|Many)?|update(?:One|Many)?|upsert|delete(?:One|Many)?|createMany|create|replaceOne|findOneAnd(?:Update|Delete|Replace))\s*\(|\bINSERT\s+INTO\b|\bUPDATE\s+[A-Za-z_][\w.]*\s+SET\b|\bDELETE\s+FROM\b/i;
|
|
475
|
-
export function isPersistenceDriverLayer(layer) {
|
|
471
|
+
function isPersistenceDriverLayer(layer) {
|
|
476
472
|
return layer === 'PersistenceAdapters';
|
|
477
473
|
}
|
|
478
|
-
|
|
479
|
-
if (
|
|
474
|
+
function sourceImportsPersistenceDriver(content, resolvedImports) {
|
|
475
|
+
if (sourceImportsPersistenceDriverText(content))
|
|
480
476
|
return true;
|
|
481
477
|
if (!resolvedImports)
|
|
482
478
|
return false;
|
|
@@ -487,7 +483,7 @@ export function sourceImportsPersistenceDriver(content, resolvedImports) {
|
|
|
487
483
|
if (!specifier)
|
|
488
484
|
continue;
|
|
489
485
|
const synthetic = `from '${specifier}'`;
|
|
490
|
-
if (
|
|
486
|
+
if (sourceImportsPersistenceDriverText(synthetic))
|
|
491
487
|
return true;
|
|
492
488
|
}
|
|
493
489
|
return false;
|
|
@@ -506,7 +502,9 @@ export function deriveArkRuleFileHints(_file, content, resolvedImports) {
|
|
|
506
502
|
if (!content)
|
|
507
503
|
return null;
|
|
508
504
|
const hasIo = sourceImportsPersistenceDriver(content, resolvedImports);
|
|
509
|
-
|
|
505
|
+
// A tagged-template SQL write head is evidence on its own (the use case composed
|
|
506
|
+
// the statement). ORM verbs and untagged SQL still need a driver import.
|
|
507
|
+
const persistenceWrite = (hasIo && sourceHasPersistenceWrite(content)) || sourceHasTaggedSqlWrite(content);
|
|
510
508
|
// Orchestration/adapter heuristics need a longer window; writes still fire on short probes.
|
|
511
509
|
if (content.length < 40) {
|
|
512
510
|
return persistenceWrite ? { persistenceWrite: true } : null;
|
|
@@ -549,213 +547,18 @@ export function buildArkRuleFileHints(fileContents, resolvedImportsByFile) {
|
|
|
549
547
|
}
|
|
550
548
|
return out;
|
|
551
549
|
}
|
|
552
|
-
const MEMBER_MODIFIERS = new Set([
|
|
553
|
-
'public',
|
|
554
|
-
'private',
|
|
555
|
-
'protected',
|
|
556
|
-
'static',
|
|
557
|
-
'async',
|
|
558
|
-
'readonly',
|
|
559
|
-
'abstract',
|
|
560
|
-
'override',
|
|
561
|
-
'declare',
|
|
562
|
-
'get',
|
|
563
|
-
'set',
|
|
564
|
-
]);
|
|
565
550
|
const CONTROL_FLOW_METHOD_NAMES = new Set(['if', 'match', 'when']);
|
|
566
|
-
function skipStringOrComment(src, index) {
|
|
567
|
-
const ch = src[index];
|
|
568
|
-
if (ch === '/' && src[index + 1] === '/') {
|
|
569
|
-
const nl = src.indexOf('\n', index);
|
|
570
|
-
return nl === -1 ? src.length : nl;
|
|
571
|
-
}
|
|
572
|
-
if (ch === '/' && src[index + 1] === '*') {
|
|
573
|
-
const end = src.indexOf('*/', index + 2);
|
|
574
|
-
return end === -1 ? src.length : end + 2;
|
|
575
|
-
}
|
|
576
|
-
if (ch === "'" || ch === '"' || ch === '`') {
|
|
577
|
-
let j = index + 1;
|
|
578
|
-
while (j < src.length) {
|
|
579
|
-
if (src[j] === '\\') {
|
|
580
|
-
j += 2;
|
|
581
|
-
continue;
|
|
582
|
-
}
|
|
583
|
-
if (src[j] === ch)
|
|
584
|
-
return j + 1;
|
|
585
|
-
j += 1;
|
|
586
|
-
}
|
|
587
|
-
return src.length;
|
|
588
|
-
}
|
|
589
|
-
return index;
|
|
590
|
-
}
|
|
591
|
-
function skipWsAndComments(src, index) {
|
|
592
|
-
let i = index;
|
|
593
|
-
while (i < src.length) {
|
|
594
|
-
if (/\s/.test(src[i])) {
|
|
595
|
-
i += 1;
|
|
596
|
-
continue;
|
|
597
|
-
}
|
|
598
|
-
if (src[i] === '/' && (src[i + 1] === '/' || src[i + 1] === '*')) {
|
|
599
|
-
i = skipStringOrComment(src, i);
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
break;
|
|
603
|
-
}
|
|
604
|
-
return i;
|
|
605
|
-
}
|
|
606
|
-
function readIdent(src, index) {
|
|
607
|
-
const ch = src[index];
|
|
608
|
-
if (!ch || !/[A-Za-z_]/.test(ch))
|
|
609
|
-
return null;
|
|
610
|
-
let j = index + 1;
|
|
611
|
-
while (j < src.length && /[A-Za-z0-9_]/.test(src[j]))
|
|
612
|
-
j += 1;
|
|
613
|
-
return { ident: src.slice(index, j), end: j };
|
|
614
|
-
}
|
|
615
|
-
function skipBalanced(src, openIndex, openCh, closeCh) {
|
|
616
|
-
if (src[openIndex] !== openCh)
|
|
617
|
-
return null;
|
|
618
|
-
let depth = 1;
|
|
619
|
-
let i = openIndex + 1;
|
|
620
|
-
while (i < src.length && depth > 0) {
|
|
621
|
-
const skipped = skipStringOrComment(src, i);
|
|
622
|
-
if (skipped !== i) {
|
|
623
|
-
i = skipped;
|
|
624
|
-
continue;
|
|
625
|
-
}
|
|
626
|
-
const ch = src[i];
|
|
627
|
-
if (ch === openCh)
|
|
628
|
-
depth += 1;
|
|
629
|
-
else if (ch === closeCh)
|
|
630
|
-
depth -= 1;
|
|
631
|
-
i += 1;
|
|
632
|
-
}
|
|
633
|
-
return depth === 0 ? i : null;
|
|
634
|
-
}
|
|
635
|
-
function scanClassMembers(body) {
|
|
636
|
-
const members = [];
|
|
637
|
-
let i = 0;
|
|
638
|
-
let truncatedAt;
|
|
639
|
-
while (i < body.length) {
|
|
640
|
-
i = skipWsAndComments(body, i);
|
|
641
|
-
if (i >= body.length)
|
|
642
|
-
break;
|
|
643
|
-
if (body[i] === ';') {
|
|
644
|
-
i += 1;
|
|
645
|
-
continue;
|
|
646
|
-
}
|
|
647
|
-
const modifiers = [];
|
|
648
|
-
let cursor = i;
|
|
649
|
-
while (true) {
|
|
650
|
-
const tok = readIdent(body, cursor);
|
|
651
|
-
if (!tok || !MEMBER_MODIFIERS.has(tok.ident))
|
|
652
|
-
break;
|
|
653
|
-
modifiers.push(tok.ident);
|
|
654
|
-
cursor = skipWsAndComments(body, tok.end);
|
|
655
|
-
}
|
|
656
|
-
const nameTok = readIdent(body, cursor);
|
|
657
|
-
if (!nameTok) {
|
|
658
|
-
i += 1;
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
661
|
-
cursor = skipWsAndComments(body, nameTok.end);
|
|
662
|
-
if (body[cursor] === '<') {
|
|
663
|
-
const afterGeneric = skipBalanced(body, cursor, '<', '>');
|
|
664
|
-
if (afterGeneric == null) {
|
|
665
|
-
truncatedAt = body.length;
|
|
666
|
-
break;
|
|
667
|
-
}
|
|
668
|
-
cursor = skipWsAndComments(body, afterGeneric);
|
|
669
|
-
}
|
|
670
|
-
if (body[cursor] === '(') {
|
|
671
|
-
const afterParen = skipBalanced(body, cursor, '(', ')');
|
|
672
|
-
if (afterParen == null) {
|
|
673
|
-
truncatedAt = body.length;
|
|
674
|
-
break;
|
|
675
|
-
}
|
|
676
|
-
cursor = skipWsAndComments(body, afterParen);
|
|
677
|
-
if (body[cursor] === ':') {
|
|
678
|
-
cursor += 1;
|
|
679
|
-
while (cursor < body.length && body[cursor] !== '{' && body[cursor] !== ';') {
|
|
680
|
-
const skipped = skipStringOrComment(body, cursor);
|
|
681
|
-
if (skipped !== cursor) {
|
|
682
|
-
cursor = skipped;
|
|
683
|
-
continue;
|
|
684
|
-
}
|
|
685
|
-
cursor += 1;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
if (body[cursor] === '{') {
|
|
689
|
-
const afterBrace = skipBalanced(body, cursor, '{', '}');
|
|
690
|
-
if (afterBrace == null) {
|
|
691
|
-
truncatedAt = body.length;
|
|
692
|
-
break;
|
|
693
|
-
}
|
|
694
|
-
members.push({
|
|
695
|
-
name: nameTok.ident,
|
|
696
|
-
modifiers,
|
|
697
|
-
kind: 'method',
|
|
698
|
-
body: body.slice(cursor + 1, afterBrace - 1),
|
|
699
|
-
});
|
|
700
|
-
i = afterBrace;
|
|
701
|
-
continue;
|
|
702
|
-
}
|
|
703
|
-
if (body[cursor] === ';') {
|
|
704
|
-
i = cursor + 1;
|
|
705
|
-
continue;
|
|
706
|
-
}
|
|
707
|
-
i = cursor + 1;
|
|
708
|
-
continue;
|
|
709
|
-
}
|
|
710
|
-
let depthBrace = 0;
|
|
711
|
-
let depthParen = 0;
|
|
712
|
-
let depthBracket = 0;
|
|
713
|
-
while (cursor < body.length) {
|
|
714
|
-
const skipped = skipStringOrComment(body, cursor);
|
|
715
|
-
if (skipped !== cursor) {
|
|
716
|
-
cursor = skipped;
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
719
|
-
const ch = body[cursor];
|
|
720
|
-
if (ch === '{')
|
|
721
|
-
depthBrace += 1;
|
|
722
|
-
else if (ch === '}') {
|
|
723
|
-
if (depthBrace === 0)
|
|
724
|
-
break;
|
|
725
|
-
depthBrace -= 1;
|
|
726
|
-
}
|
|
727
|
-
else if (ch === '(')
|
|
728
|
-
depthParen += 1;
|
|
729
|
-
else if (ch === ')')
|
|
730
|
-
depthParen -= 1;
|
|
731
|
-
else if (ch === '[')
|
|
732
|
-
depthBracket += 1;
|
|
733
|
-
else if (ch === ']')
|
|
734
|
-
depthBracket -= 1;
|
|
735
|
-
else if (ch === ';' &&
|
|
736
|
-
depthBrace === 0 &&
|
|
737
|
-
depthParen === 0 &&
|
|
738
|
-
depthBracket === 0) {
|
|
739
|
-
cursor += 1;
|
|
740
|
-
break;
|
|
741
|
-
}
|
|
742
|
-
cursor += 1;
|
|
743
|
-
}
|
|
744
|
-
members.push({
|
|
745
|
-
name: nameTok.ident,
|
|
746
|
-
modifiers,
|
|
747
|
-
kind: 'field',
|
|
748
|
-
body: '',
|
|
749
|
-
});
|
|
750
|
-
i = cursor;
|
|
751
|
-
}
|
|
752
|
-
return { members, truncatedAt };
|
|
753
|
-
}
|
|
754
551
|
/**
|
|
755
552
|
* Lightweight class-shape extraction from TypeScript source text (no compiler).
|
|
756
553
|
* Conservative: prefers false negatives over false positives for mutability.
|
|
757
554
|
* Tooling may replace with TypeScript-API facts; sensors consume the same shape.
|
|
758
555
|
*
|
|
556
|
+
* The header and body are walked by the shared tokenizer (classSourceScan):
|
|
557
|
+
* type parameters (`class Box<T>`), generic `extends` / `implements` clauses, and
|
|
558
|
+
* braces inside strings, comments, or template literals are handled. A class whose
|
|
559
|
+
* header or body cannot be walked is still reported, with a truncation mark, so
|
|
560
|
+
* enforced sensors say so instead of passing in silence.
|
|
561
|
+
*
|
|
759
562
|
* Limitation (AR05/AR06): only `export class` / `export abstract class` forms.
|
|
760
563
|
* `export default class`, re-exported classes, and non-exported aggregates are
|
|
761
564
|
* invisible — enforced structure sensors stay silent (false negative). Silence
|
|
@@ -763,25 +566,25 @@ function scanClassMembers(body) {
|
|
|
763
566
|
*/
|
|
764
567
|
export function extractClassShapesFromSource(file, content) {
|
|
765
568
|
const shapes = [];
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
i += 1;
|
|
569
|
+
for (const decl of findClassDeclarations(content, { exportedOnly: true })) {
|
|
570
|
+
const className = decl.name;
|
|
571
|
+
if (decl.bodyStart == null) {
|
|
572
|
+
// Header seen, body not derivable: an unshaped class, never a silent pass.
|
|
573
|
+
shapes.push(attachShapeTruncation({
|
|
574
|
+
file,
|
|
575
|
+
className,
|
|
576
|
+
exported: true,
|
|
577
|
+
hasPublicMutableFields: false,
|
|
578
|
+
hasPublicSetters: false,
|
|
579
|
+
hasPublicConstructor: false,
|
|
580
|
+
hasStaticFactory: false,
|
|
581
|
+
mutatingMethods: [],
|
|
582
|
+
}, decl.start));
|
|
583
|
+
continue;
|
|
782
584
|
}
|
|
783
|
-
const
|
|
784
|
-
const classUnclosed =
|
|
585
|
+
const start = decl.bodyStart;
|
|
586
|
+
const classUnclosed = decl.bodyEnd == null;
|
|
587
|
+
const body = content.slice(start, decl.bodyEnd ?? content.length);
|
|
785
588
|
const scanned = scanClassMembers(body);
|
|
786
589
|
const truncatedUntil = classUnclosed
|
|
787
590
|
? content.length
|
package/bin/lib/check-args.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ark-check CLI flag parsing.
|
|
3
3
|
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { discoverLocalBaseRef, normalizePolicyBaseRef } from './policy-delta-io.mjs';
|
|
6
7
|
|
|
@@ -23,7 +24,26 @@ export const LOCAL_STRICT_MERGE_MESSAGE =
|
|
|
23
24
|
'--local cannot be combined with --strict-merge: CI stays the full fail-closed check. Use --local (or ARK_CHECK_LOCAL=1) only on the local / pre-push path.';
|
|
24
25
|
|
|
25
26
|
export const LOCAL_TREE_MODE_MESSAGE =
|
|
26
|
-
'--local cannot be combined with report modes that need the whole tree (--doctor, --coverage, --plan, --report, --promote). Use --local --base <ref> for the cheap local check.';
|
|
27
|
+
'--local cannot be combined with report modes that need the whole tree (--doctor, --coverage, --plan, --report, --promote, --probe-invariants) or with --update-baseline. Use --local --base <ref> for the cheap local check.';
|
|
28
|
+
|
|
29
|
+
const PROBE_RUNNERS = ['vitest', 'jest', 'node'];
|
|
30
|
+
|
|
31
|
+
export const UPDATE_BASELINE_SCOPE_MESSAGE =
|
|
32
|
+
'--update-baseline cannot be combined with a changed-files scope (--changed, --local, or a --persona that implies --changed): the baseline is a whole-tree record, and a changed-files scan would overwrite it with only that subset and delete every other frozen key. Drop the scope flag (ARK_CHECK_LOCAL=1 is ignored for a baseline update) and re-run.';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Baseline file that suppresses frozen findings for this run, or null.
|
|
36
|
+
* Explicit `--baseline` always wins. `--report` (without `--against`) applies the committed
|
|
37
|
+
* `.ark-baseline.json` by default, the same file `--doctor` reads, so report, `ark status`
|
|
38
|
+
* and doctor agree on what is active vs frozen.
|
|
39
|
+
*/
|
|
40
|
+
export function effectiveBaselineName(args, root, exists = fs.existsSync) {
|
|
41
|
+
if (args?.baseline) return args.baseline;
|
|
42
|
+
// A merge verdict (--strict / --strict-merge / --contract-diff) never inherits the
|
|
43
|
+
// committed freeze implicitly: adding --report must not flip a fail-closed run to pass.
|
|
44
|
+
if (!args?.report || args.against || args.strictMerge || args.contractDiff) return null;
|
|
45
|
+
return exists(path.join(root, '.ark-baseline.json')) ? '.ark-baseline.json' : null;
|
|
46
|
+
}
|
|
27
47
|
|
|
28
48
|
export function localCheckEnvelope(args, root) {
|
|
29
49
|
if (!args?.local) return {};
|
|
@@ -48,6 +68,8 @@ export function applyLocalCheckMode(args, env = process.env) {
|
|
|
48
68
|
args.plan && '--plan',
|
|
49
69
|
args.report && '--report',
|
|
50
70
|
args.promote && '--promote',
|
|
71
|
+
args.probeInvariants && '--probe-invariants',
|
|
72
|
+
args.updateBaseline && '--update-baseline',
|
|
51
73
|
].filter(Boolean);
|
|
52
74
|
if (args.strictMerge) {
|
|
53
75
|
if (explicit) throw new Error(LOCAL_STRICT_MERGE_MESSAGE);
|
|
@@ -182,6 +204,24 @@ export function parseArgs(argv, env = process.env) {
|
|
|
182
204
|
args.promote = next && !next.startsWith('-') ? argv[++i] : true;
|
|
183
205
|
}
|
|
184
206
|
}
|
|
207
|
+
else if (arg === '--probe-invariants' || arg.startsWith('--probe-invariants=')) {
|
|
208
|
+
// Same shape as --promote: bare probes every declared invariant; a value
|
|
209
|
+
// narrows to one id (`=<id>` also carries an id that begins with '-').
|
|
210
|
+
const eq = arg.indexOf('=');
|
|
211
|
+
if (eq > 0) {
|
|
212
|
+
args.probeInvariants = arg.slice(eq + 1) || true;
|
|
213
|
+
} else {
|
|
214
|
+
const next = argv[i + 1];
|
|
215
|
+
args.probeInvariants = next && !next.startsWith('-') ? argv[++i] : true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (arg === '--runner') {
|
|
219
|
+
const value = requireValue(arg, i++).trim().toLowerCase();
|
|
220
|
+
if (!PROBE_RUNNERS.includes(value)) {
|
|
221
|
+
throw new Error(`--runner must be one of ${PROBE_RUNNERS.join(', ')} (got ${JSON.stringify(value)}).`);
|
|
222
|
+
}
|
|
223
|
+
args.runner = value;
|
|
224
|
+
}
|
|
185
225
|
else if (arg === '--apply') args.apply = true;
|
|
186
226
|
else if (arg === '--recommend') args.recommend = true;
|
|
187
227
|
else if (arg === '--write-plan') args.writePlan = true;
|
|
@@ -288,5 +328,49 @@ export function parseArgs(argv, env = process.env) {
|
|
|
288
328
|
);
|
|
289
329
|
}
|
|
290
330
|
}
|
|
331
|
+
assertProbeInvariantsAlone(args);
|
|
332
|
+
if (args.updateBaseline && (args.changed || args.local)) {
|
|
333
|
+
throw new Error(UPDATE_BASELINE_SCOPE_MESSAGE);
|
|
334
|
+
}
|
|
291
335
|
return applyLocalCheckMode(args, env);
|
|
292
336
|
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* ADR 0039 D1: the probe runs the project's own tests in a temporary copy, so
|
|
340
|
+
* it is never part of a check, the merge gate, status, or a changed-files scope.
|
|
341
|
+
* Every pairing that would put it on one of those paths is refused, and a
|
|
342
|
+
* report mode that answers first would make it silently do nothing.
|
|
343
|
+
*/
|
|
344
|
+
function assertProbeInvariantsAlone(args) {
|
|
345
|
+
if (args.runner && !args.probeInvariants) {
|
|
346
|
+
throw new Error('--runner applies to --probe-invariants. Run `arkgate-check --probe-invariants --runner <id>`.');
|
|
347
|
+
}
|
|
348
|
+
if (!args.probeInvariants) return;
|
|
349
|
+
const gatePaths = [
|
|
350
|
+
args.local && '--local',
|
|
351
|
+
args.changed && '--changed',
|
|
352
|
+
args.strictMerge && '--strict-merge',
|
|
353
|
+
args.updateBaseline && '--update-baseline',
|
|
354
|
+
args.doctor && '--doctor',
|
|
355
|
+
].filter(Boolean);
|
|
356
|
+
if (gatePaths.length > 0) {
|
|
357
|
+
throw new Error(
|
|
358
|
+
`--probe-invariants cannot be combined with ${gatePaths.join(', ')}: the probe runs your tests in a temporary copy and is never part of a check, the merge gate, or status. Run it on its own.`
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
const shadowing = [
|
|
362
|
+
args.promote && '--promote',
|
|
363
|
+
args.sensors && '--sensors',
|
|
364
|
+
args.pathDrift && '--path-drift',
|
|
365
|
+
args.coverage && '--coverage',
|
|
366
|
+
args.plan && '--plan',
|
|
367
|
+
args.report && '--report',
|
|
368
|
+
args.rulesInventory && '--rules-inventory',
|
|
369
|
+
args.recommend && '--recommend',
|
|
370
|
+
].filter(Boolean);
|
|
371
|
+
if (shadowing.length > 0) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
`--probe-invariants cannot be combined with ${shadowing.join(', ')}: one of them would answer first and the other would silently do nothing. Run them separately.`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
@@ -16,22 +16,12 @@ import {
|
|
|
16
16
|
buildAgentProjectionBlock,
|
|
17
17
|
} from './agent-projection.mjs';
|
|
18
18
|
import { getDiagnosticCatalogEntry } from './diagnostic-catalog.mjs';
|
|
19
|
-
import {
|
|
19
|
+
import { addPackageJsonPropertyPreservingFormat } from './field-install.mjs';
|
|
20
20
|
import { renderHostSupportMatrixMarkdown } from './host-support-matrix.mjs';
|
|
21
21
|
import { PREFERRED_MCP_BIN } from './hook-templates.mjs';
|
|
22
22
|
import { hasCheckArchitectureScript, readPackageJson } from './gate-files.mjs';
|
|
23
23
|
import { arkPackageVersion } from './skill-install.mjs';
|
|
24
|
-
|
|
25
|
-
// Field-install helpers re-exported for callers that import from this module.
|
|
26
|
-
export {
|
|
27
|
-
ensureBaselineFlagInCheckCommand,
|
|
28
|
-
syncBaselineIntoCheckSurfaces,
|
|
29
|
-
pinArkgateDevDependency,
|
|
30
|
-
IO_DIR_SEGMENTS,
|
|
31
|
-
detectContractFalseGreenRisk,
|
|
32
|
-
FALSE_GREEN_GAP_ID,
|
|
33
|
-
falseGreenAdoptionGap,
|
|
34
|
-
} from './field-install.mjs';
|
|
24
|
+
import { arkgateIsProjectDependency, pinnedArkgateRunner } from './package-manager.mjs';
|
|
35
25
|
|
|
36
26
|
export function checkArgsForRoot(root, { requireGates = false } = {}) {
|
|
37
27
|
const baselineFlag = fs.existsSync(path.join(root, '.ark-baseline.json'))
|
|
@@ -41,8 +31,18 @@ export function checkArgsForRoot(root, { requireGates = false } = {}) {
|
|
|
41
31
|
return `--root . --config ark.config.json ${profile}${baselineFlag}`;
|
|
42
32
|
}
|
|
43
33
|
|
|
44
|
-
|
|
45
34
|
export function packageManager(root) {
|
|
35
|
+
const pm = localPackageManager(root);
|
|
36
|
+
if (arkgateIsProjectDependency(root)) return pm;
|
|
37
|
+
// arkgate is not pinned: the local runner would ask npm for a package named `ark-check`
|
|
38
|
+
// (404) or run a stale global, so name the exact arkgate that generated this workflow.
|
|
39
|
+
const run = `${pinnedArkgateRunner()} ark-check ${checkArgsForRoot(root, { requireGates: true })}`;
|
|
40
|
+
// No package.json: nothing to install, and setup-node's cache needs a lockfile.
|
|
41
|
+
if (!readPackageJson(root)) return { cache: null, setup: [], install: null, run };
|
|
42
|
+
return { ...pm, run };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function localPackageManager(root) {
|
|
46
46
|
// CI always require-gates; baseline follows checkArgsForRoot.
|
|
47
47
|
const checkArgs = checkArgsForRoot(root, { requireGates: true });
|
|
48
48
|
// Same detection as every emitted command (execRunner): honors the packageManager field and
|
|
@@ -102,65 +102,13 @@ export function checkArchitectureScriptSnippet(root) {
|
|
|
102
102
|
* @param {string} scriptValue
|
|
103
103
|
*/
|
|
104
104
|
function addPackageScriptPreservingFormat(source, scriptName, scriptValue) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (scriptsMatch) {
|
|
114
|
-
const open = source.indexOf('{', scriptsMatch.index);
|
|
115
|
-
let depth = 0;
|
|
116
|
-
let quoted = false;
|
|
117
|
-
let escaped = false;
|
|
118
|
-
let close = -1;
|
|
119
|
-
for (let index = open; index < source.length; index += 1) {
|
|
120
|
-
const char = source[index];
|
|
121
|
-
if (quoted) {
|
|
122
|
-
if (escaped) escaped = false;
|
|
123
|
-
else if (char === '\\') escaped = true;
|
|
124
|
-
else if (char === '"') quoted = false;
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
if (char === '"') quoted = true;
|
|
128
|
-
else if (char === '{') depth += 1;
|
|
129
|
-
else if (char === '}' && --depth === 0) {
|
|
130
|
-
close = index;
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
if (close === -1) throw new Error('Unbalanced package.json scripts object');
|
|
135
|
-
const body = source.slice(open + 1, close);
|
|
136
|
-
if (!multiline) {
|
|
137
|
-
const addition = body.trim() ? `,${key}:${encoded}` : `${key}:${encoded}`;
|
|
138
|
-
return `${source.slice(0, close)}${addition}${source.slice(close)}`;
|
|
139
|
-
}
|
|
140
|
-
const beforeClose = source.slice(0, close);
|
|
141
|
-
const trailing = beforeClose.match(/\s*$/)?.[0] ?? '';
|
|
142
|
-
const contentEnd = close - trailing.length;
|
|
143
|
-
const closingIndent = trailing.slice(trailing.lastIndexOf('\n') + 1);
|
|
144
|
-
const propertyIndent = `${closingIndent}${indentUnit}`;
|
|
145
|
-
const addition = body.trim()
|
|
146
|
-
? `,${eol}${propertyIndent}${key}: ${encoded}`
|
|
147
|
-
: `${propertyIndent}${key}: ${encoded}`;
|
|
148
|
-
return `${source.slice(0, contentEnd)}${addition}${eol}${closingIndent}${source.slice(close)}`;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const rootClose = source.lastIndexOf('}');
|
|
152
|
-
if (rootClose === -1) throw new Error('Unbalanced package.json object');
|
|
153
|
-
const rootBody = source.slice(0, rootClose);
|
|
154
|
-
if (!multiline) {
|
|
155
|
-
const separator = rootBody.trim().endsWith('{') ? '' : ',';
|
|
156
|
-
return `${rootBody}${separator}"scripts":{${key}:${encoded}}${source.slice(rootClose)}`;
|
|
157
|
-
}
|
|
158
|
-
const trailing = rootBody.match(/\s*$/)?.[0] ?? '';
|
|
159
|
-
const contentEnd = rootClose - trailing.length;
|
|
160
|
-
const rootClosingIndent = trailing.slice(trailing.lastIndexOf('\n') + 1);
|
|
161
|
-
const separator = source.slice(0, contentEnd).trimEnd().endsWith('{') ? '' : ',';
|
|
162
|
-
const addition = `${separator}${eol}${rootPropertyIndent}"scripts": {${eol}${rootPropertyIndent}${indentUnit}${key}: ${encoded}${eol}${rootPropertyIndent}}`;
|
|
163
|
-
return `${source.slice(0, contentEnd)}${addition}${eol}${rootClosingIndent}${source.slice(rootClose)}`;
|
|
105
|
+
return addPackageJsonPropertyPreservingFormat(
|
|
106
|
+
source,
|
|
107
|
+
'scripts',
|
|
108
|
+
scriptName,
|
|
109
|
+
scriptValue,
|
|
110
|
+
'package.json scripts'
|
|
111
|
+
);
|
|
164
112
|
}
|
|
165
113
|
|
|
166
114
|
/**
|
|
@@ -243,10 +191,11 @@ ${rows}`;
|
|
|
243
191
|
* Load project layers for AGENTS generation. Returns null when config is absent/invalid
|
|
244
192
|
* so callers fall back to the stock 11-layer table.
|
|
245
193
|
* @param {string} root
|
|
194
|
+
* @param {string} [configPath] contract file (defaults to `<root>/ark.config.json`)
|
|
246
195
|
*/
|
|
247
|
-
export function loadConfigLayersForAgents(root) {
|
|
196
|
+
export function loadConfigLayersForAgents(root, configPath = path.join(root, 'ark.config.json')) {
|
|
248
197
|
try {
|
|
249
|
-
const cfgPath =
|
|
198
|
+
const cfgPath = configPath;
|
|
250
199
|
if (!fs.existsSync(cfgPath)) return null;
|
|
251
200
|
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
252
201
|
if (!Array.isArray(cfg?.layers) || cfg.layers.length === 0) return null;
|
|
@@ -638,10 +587,7 @@ ${setupSteps ? `${setupSteps}\n` : ''} - name: Setup Node
|
|
|
638
587
|
uses: actions/setup-node@v4
|
|
639
588
|
with:
|
|
640
589
|
${nodeSetup}
|
|
641
|
-
cache: ${pm.cache}
|
|
642
|
-
- name: Install dependencies
|
|
643
|
-
run: ${pm.install}
|
|
644
|
-
${qualityBlock ? `${qualityBlock}\n` : ''} - name: Ark architecture check
|
|
590
|
+
${pm.cache ? ` cache: ${pm.cache}\n` : ''}${pm.install ? ` - name: Install dependencies\n run: ${pm.install}\n` : ''}${qualityBlock ? `${qualityBlock}\n` : ''} - name: Ark architecture check
|
|
645
591
|
env:
|
|
646
592
|
ARK_POLICY_BASE_REF: \${{ github.event.pull_request.base.sha || github.event.before }}
|
|
647
593
|
run: |
|