arkgate 4.8.13 → 4.8.15
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 +219 -4
- package/README.md +33 -22
- package/SECURITY.md +5 -3
- package/bin/ark-check-runtime.mjs +25 -57
- package/bin/ark-mcp-runtime.mjs +111 -2
- package/bin/ark-shared.mjs +140 -11
- package/bin/ark.mjs +7 -25
- package/bin/lib/adr-presence.mjs +97 -0
- package/bin/lib/agent-projection-formatters.mjs +2 -0
- package/bin/lib/agent-skills-package.mjs +241 -8
- package/bin/lib/analysis-completeness.mjs +38 -2
- package/bin/lib/analysis-engine.mjs +6 -6
- package/bin/lib/architecture-scan.mjs +33 -5
- package/bin/lib/ark-order-doctor.mjs +7 -1
- package/bin/lib/ark-order-report.mjs +2 -1
- package/bin/lib/ark-run-doctor.mjs +6 -0
- package/bin/lib/ark-run-report.mjs +6 -2
- package/bin/lib/arkrules-sensors.mjs +81 -0
- package/bin/lib/baseline-key.mjs +4 -1
- package/bin/lib/check-args.mjs +52 -2
- package/bin/lib/ci-and-commands.mjs +7 -2
- package/bin/lib/config-contract.mjs +64 -1
- package/bin/lib/design-smells.mjs +21 -1
- package/bin/lib/diagnostic-catalog.mjs +5 -3
- package/bin/lib/doctor-advisories.mjs +109 -17
- package/bin/lib/doctor-human.mjs +20 -6
- package/bin/lib/doctor-next-actions.mjs +14 -0
- package/bin/lib/doctor-plan.mjs +32 -2
- package/bin/lib/enforcement-honesty.mjs +47 -2
- package/bin/lib/first-run-help.mjs +19 -3
- package/bin/lib/gate-files.mjs +40 -3
- package/bin/lib/host-support-matrix.mjs +9 -1
- package/bin/lib/html-report-depth.mjs +2 -0
- package/bin/lib/html-report.mjs +13 -2
- package/bin/lib/import-resolve.mjs +74 -13
- package/bin/lib/improvement-compass-doctor.mjs +6 -1
- package/bin/lib/improvement-compass-map.mjs +3 -1
- package/bin/lib/install-migrate.mjs +23 -0
- package/bin/lib/layer-description.mjs +90 -8
- package/bin/lib/mcp-hook-payload.mjs +57 -1
- package/bin/lib/no-domain-frontend.mjs +91 -0
- package/bin/lib/presets.mjs +3 -4
- package/bin/lib/product-copy.mjs +19 -0
- package/bin/lib/projected-governed-coverage.mjs +114 -0
- package/bin/lib/recommend-cli.mjs +54 -0
- package/bin/lib/remediation.mjs +6 -2
- package/bin/lib/resolved-candidate-facts.mjs +225 -100
- package/bin/lib/rules-under-contract.mjs +117 -2
- package/bin/lib/scan-files.mjs +39 -0
- package/bin/lib/snippet-analysis.mjs +14 -8
- package/bin/lib/start-preview.mjs +8 -2
- package/bin/lib/states-transitions-presence.mjs +212 -0
- package/bin/lib/status-command.mjs +2 -0
- package/bin/lib/status-transition-catalog.mjs +410 -0
- package/bin/lib/team-parliament-io.mjs +10 -0
- package/bin/lib/upgrade-whats-new.mjs +3 -3
- package/bin/lib/violations.mjs +43 -1
- package/bin/lib/write-path-capabilities.mjs +20 -5
- package/bin/lib/write-path-detect.mjs +27 -2
- package/dist/{configTypes-j7so8B4O.d.ts → configTypes-Dt3DpVbd.d.ts} +19 -0
- package/dist/{diagnosticCatalog-DA565Lja.d.ts → diagnosticCatalog-BEg85XlE.d.ts} +3 -3
- package/dist/eslint/index.cjs +4 -4
- package/dist/eslint/index.d.ts +1 -1
- package/dist/eslint/index.js +4 -4
- package/dist/index.cjs +31 -31
- package/dist/index.d.ts +126 -19
- package/dist/index.js +31 -31
- package/dist/nestjs/index.cjs +5 -5
- package/dist/nestjs/index.d.ts +3 -3
- package/dist/nestjs/index.js +5 -5
- package/dist/runtime/index.cjs +15 -15
- package/dist/runtime/index.d.ts +6 -6
- package/dist/runtime/index.js +15 -15
- package/dist/{types-Djbs3KjE.d.ts → types-CN9tVMPz.d.ts} +3 -1
- package/dist/{types-tGhZUiGX.d.ts → types-TBiv0WHL.d.ts} +1 -1
- package/docs/README.md +8 -6
- package/docs/agent-guide.md +35 -17
- package/docs/ai-gates.md +13 -4
- package/docs/arkorder.md +11 -4
- package/docs/brownfield-adoption.md +4 -1
- package/docs/configuration.md +63 -14
- package/docs/develop.md +4 -1
- package/docs/diagnostics.md +30 -10
- package/docs/enthusiast/README.md +6 -1
- package/docs/enthusiast/how-to-agent-gates.md +5 -0
- package/docs/enthusiast/how-to-gallery-starter.md +2 -1
- package/docs/enthusiast/how-to-pick-shape.md +1 -1
- package/docs/package-surface.md +10 -6
- package/docs/product-voice.md +32 -6
- package/docs/threat-model.md +2 -2
- package/docs/typescript-support.md +3 -3
- package/docs/use.md +23 -11
- package/package.json +1 -1
- package/schemas/ark.config.schema.json +23 -2
- package/server.json +2 -2
- package/templates/agent-skills/README.md +7 -4
- package/templates/agent-skills/ark-adopt/SKILL.md +91 -6
- package/templates/agent-skills/ark-architect/SKILL.md +5 -18
- package/templates/agent-skills/ark-autopilot/SKILL.md +14 -6
- package/templates/agent-skills/ark-contract/SKILL.md +9 -20
- package/templates/agent-skills/ark-coverage/SKILL.md +13 -9
- package/templates/agent-skills/ark-explain/SKILL.md +11 -5
- package/templates/agent-skills/ark-explore/SKILL.md +39 -5
- package/templates/agent-skills/ark-fix/SKILL.md +15 -20
- package/templates/agent-skills/ark-loop/SKILL.md +14 -20
- package/templates/agent-skills/ark-order/SKILL.md +200 -0
- package/templates/agent-skills/ark-place/SKILL.md +49 -11
- package/templates/agent-skills/ark-runtime/SKILL.md +21 -6
- package/templates/agent-skills/ark-think/SKILL.md +24 -126
- package/templates/agent-skills/ark-upgrade/SKILL.md +14 -3
- package/templates/arkrules/DomainModel.json +14 -1
- package/templates/skills/ark-adopt.md +91 -6
- package/templates/skills/ark-architect.md +5 -18
- package/templates/skills/ark-autopilot.md +14 -6
- package/templates/skills/ark-contract.md +9 -20
- package/templates/skills/ark-coverage.md +13 -9
- package/templates/skills/ark-explain.md +11 -5
- package/templates/skills/ark-explore.md +39 -5
- package/templates/skills/ark-fix.md +15 -20
- package/templates/skills/ark-loop.md +14 -20
- package/templates/skills/ark-order.md +200 -0
- package/templates/skills/ark-place.md +49 -11
- package/templates/skills/ark-runtime.md +21 -6
- package/templates/skills/ark-think.md +24 -126
- package/templates/skills/ark-upgrade.md +14 -3
- package/templates/tests/ark-adoption-gaps.test.ts +5 -4
|
@@ -19,7 +19,74 @@ export const FLAT_SKILL_TEMPLATES_RELATIVE_ROOT = 'templates/skills';
|
|
|
19
19
|
/** Required entry filename inside each skill directory (Agent Skills standard). */
|
|
20
20
|
export const AGENT_SKILL_ENTRY_FILENAME = 'SKILL.md';
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* North-star filter for first-class doors. Not a score. Not enforcement.
|
|
23
|
+
* Skills speak these so an agent picks the right sibling.
|
|
24
|
+
*/
|
|
25
|
+
export const ARK_SKILL_NORTH_STAR = Object.freeze(['Contener', 'Guiar', 'Ordenar']);
|
|
26
|
+
/**
|
|
27
|
+
* First-class doors. Each must have crisp when / not when / handoff.
|
|
28
|
+
* Sorted alphabetically.
|
|
29
|
+
*/
|
|
30
|
+
export const ARK_FIRST_CLASS_SKILL_NAMES = Object.freeze([
|
|
31
|
+
'ark-adopt',
|
|
32
|
+
'ark-autopilot',
|
|
33
|
+
'ark-coverage',
|
|
34
|
+
'ark-explain',
|
|
35
|
+
'ark-explore',
|
|
36
|
+
'ark-order',
|
|
37
|
+
'ark-place',
|
|
38
|
+
'ark-runtime',
|
|
39
|
+
'ark-upgrade',
|
|
40
|
+
]);
|
|
41
|
+
/**
|
|
42
|
+
* One-release redirect stubs. Muscle memory / old docs still resolve.
|
|
43
|
+
* Values are first-class doors. Capability surface must stay zero-loss.
|
|
44
|
+
*/
|
|
45
|
+
export const ARK_SKILL_STUB_REDIRECTS = Object.freeze({
|
|
46
|
+
'ark-architect': 'ark-adopt',
|
|
47
|
+
'ark-contract': 'ark-adopt',
|
|
48
|
+
'ark-fix': 'ark-autopilot',
|
|
49
|
+
'ark-loop': 'ark-autopilot',
|
|
50
|
+
'ark-think': 'ark-explore',
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* Product surface → first-class doors that exercise it.
|
|
54
|
+
* Standing check: every surface has at least one first-class door whose
|
|
55
|
+
* body mentions that surface. Table keys must match
|
|
56
|
+
* {@link ARK_SKILL_REQUIRED_SURFACES}.
|
|
57
|
+
*/
|
|
58
|
+
export const ARK_SKILL_CAPACITY = Object.freeze({
|
|
59
|
+
Layers: ['ark-adopt', 'ark-place', 'ark-explore', 'ark-autopilot', 'ark-coverage', 'ark-explain'],
|
|
60
|
+
ArkRules: ['ark-adopt', 'ark-explore', 'ark-autopilot'],
|
|
61
|
+
ArkRun: ['ark-adopt', 'ark-runtime', 'ark-place', 'ark-autopilot'],
|
|
62
|
+
ArkOrder: ['ark-adopt', 'ark-order', 'ark-place', 'ark-autopilot'],
|
|
63
|
+
Contener: ['ark-adopt', 'ark-place', 'ark-upgrade'],
|
|
64
|
+
Guiar: ['ark-explore', 'ark-autopilot', 'ark-explain', 'ark-coverage', 'ark-runtime'],
|
|
65
|
+
Ordenar: ['ark-order'],
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Closed product + north-star surfaces the skill *set* must exercise.
|
|
69
|
+
* Independent of {@link ARK_SKILL_CAPACITY} so deleting a table key fails closed.
|
|
70
|
+
*/
|
|
71
|
+
export const ARK_SKILL_REQUIRED_SURFACES = Object.freeze([
|
|
72
|
+
'Layers',
|
|
73
|
+
'ArkRules',
|
|
74
|
+
'ArkRun',
|
|
75
|
+
'ArkOrder',
|
|
76
|
+
'Contener',
|
|
77
|
+
'Guiar',
|
|
78
|
+
'Ordenar',
|
|
79
|
+
]);
|
|
80
|
+
/** Named first-class door that must stay on these surfaces (no leftover substitute). */
|
|
81
|
+
export const ARK_SKILL_CAPACITY_DEDICATED_DOORS = Object.freeze({
|
|
82
|
+
ArkRun: 'ark-runtime',
|
|
83
|
+
ArkOrder: 'ark-order',
|
|
84
|
+
Ordenar: 'ark-order',
|
|
85
|
+
});
|
|
86
|
+
const NORTH_STAR_PHRASE = 'Contener · Guiar · Ordenar';
|
|
87
|
+
const FORBIDDEN_ORDER_FREEZE = 'Do not invent `/ark-order`';
|
|
88
|
+
/**
|
|
89
|
+
* Closed shipped catalog (first-class + one-release stubs).
|
|
23
90
|
* Sorted alphabetically for deterministic inventory diffs.
|
|
24
91
|
*/
|
|
25
92
|
export const ARK_SKILL_NAMES = Object.freeze([
|
|
@@ -32,12 +99,13 @@ export const ARK_SKILL_NAMES = Object.freeze([
|
|
|
32
99
|
'ark-explore',
|
|
33
100
|
'ark-fix',
|
|
34
101
|
'ark-loop',
|
|
102
|
+
'ark-order',
|
|
35
103
|
'ark-place',
|
|
36
104
|
'ark-runtime',
|
|
37
105
|
'ark-think',
|
|
38
106
|
'ark-upgrade',
|
|
39
107
|
]);
|
|
40
|
-
/** Count of
|
|
108
|
+
/** Count of shipped skill names (first-class + stubs). */
|
|
41
109
|
export const ARK_SKILL_NAME_COUNT = ARK_SKILL_NAMES.length;
|
|
42
110
|
const AGENT_SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
43
111
|
/**
|
|
@@ -51,10 +119,20 @@ export function isValidAgentSkillName(name) {
|
|
|
51
119
|
return false;
|
|
52
120
|
return AGENT_SKILL_NAME_PATTERN.test(name);
|
|
53
121
|
}
|
|
54
|
-
/** True when `name` is
|
|
122
|
+
/** True when `name` is in the closed shipped catalog. */
|
|
55
123
|
export function isArkSkillName(name) {
|
|
56
124
|
return ARK_SKILL_NAMES.includes(name);
|
|
57
125
|
}
|
|
126
|
+
/** True when `name` is a first-class door (not a redirect stub). */
|
|
127
|
+
export function isFirstClassArkSkillName(name) {
|
|
128
|
+
return ARK_FIRST_CLASS_SKILL_NAMES.includes(name);
|
|
129
|
+
}
|
|
130
|
+
/** First-class door a leftover name redirects to, or null. */
|
|
131
|
+
export function arkSkillStubRedirect(name) {
|
|
132
|
+
if (!(name in ARK_SKILL_STUB_REDIRECTS))
|
|
133
|
+
return null;
|
|
134
|
+
return ARK_SKILL_STUB_REDIRECTS[name];
|
|
135
|
+
}
|
|
58
136
|
/**
|
|
59
137
|
* Parse a skill markdown document with optional YAML frontmatter.
|
|
60
138
|
* Supports the simple `key: value` / `key: "quoted"` form used by Ark templates
|
|
@@ -135,7 +213,7 @@ export function validateAgentSkillDocument(input) {
|
|
|
135
213
|
else if (requireArk && !isArkSkillName(name)) {
|
|
136
214
|
issues.push({
|
|
137
215
|
code: 'UNKNOWN_SKILL_NAME',
|
|
138
|
-
message: `Skill name "${name}" is not in the
|
|
216
|
+
message: `Skill name "${name}" is not in the closed Ark skill catalog (ARK_SKILL_NAMES).`,
|
|
139
217
|
skillName: name,
|
|
140
218
|
});
|
|
141
219
|
}
|
|
@@ -163,7 +241,7 @@ export function validateAgentSkillDocument(input) {
|
|
|
163
241
|
return issues;
|
|
164
242
|
}
|
|
165
243
|
/**
|
|
166
|
-
* Validate a full Agent Skills package inventory against the
|
|
244
|
+
* Validate a full Agent Skills package inventory against the closed catalog.
|
|
167
245
|
* Detects missing, extra, duplicate, invalid, and (when supplied) flat-template drift.
|
|
168
246
|
*/
|
|
169
247
|
export function validateAgentSkillsPackage(entries) {
|
|
@@ -201,7 +279,7 @@ export function validateAgentSkillsPackage(entries) {
|
|
|
201
279
|
if (!seen.has(expected)) {
|
|
202
280
|
issues.push({
|
|
203
281
|
code: 'MISSING_SKILL',
|
|
204
|
-
message: `Missing
|
|
282
|
+
message: `Missing catalog skill "${expected}" from Agent Skills package.`,
|
|
205
283
|
skillName: expected,
|
|
206
284
|
});
|
|
207
285
|
}
|
|
@@ -212,14 +290,14 @@ export function validateAgentSkillsPackage(entries) {
|
|
|
212
290
|
if (!issues.some((i) => i.code === 'UNKNOWN_SKILL_NAME' && i.skillName === name)) {
|
|
213
291
|
issues.push({
|
|
214
292
|
code: 'EXTRA_SKILL',
|
|
215
|
-
message: `Extra skill "${name}" is not in the
|
|
293
|
+
message: `Extra skill "${name}" is not in the closed Ark skill catalog.`,
|
|
216
294
|
skillName: name,
|
|
217
295
|
});
|
|
218
296
|
}
|
|
219
297
|
else {
|
|
220
298
|
issues.push({
|
|
221
299
|
code: 'EXTRA_SKILL',
|
|
222
|
-
message: `Extra skill "${name}" is not in the
|
|
300
|
+
message: `Extra skill "${name}" is not in the closed Ark skill catalog.`,
|
|
223
301
|
skillName: name,
|
|
224
302
|
});
|
|
225
303
|
}
|
|
@@ -234,6 +312,161 @@ export function validateAgentSkillsPackage(entries) {
|
|
|
234
312
|
presentCount: names.length,
|
|
235
313
|
};
|
|
236
314
|
}
|
|
315
|
+
function pushCapacityIssue(issues, issue) {
|
|
316
|
+
issues.push(issue);
|
|
317
|
+
}
|
|
318
|
+
function skillMentionsSurface(body, surface) {
|
|
319
|
+
return body.includes(surface);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Observe skill bodies (and optional hubs) against the closed product surfaces.
|
|
323
|
+
* Inventory/layout stay on {@link validateAgentSkillsPackage}. This is the
|
|
324
|
+
* fail-closed tooth so a plane cannot silently drop out of the skill set.
|
|
325
|
+
*/
|
|
326
|
+
export function validateSkillProductCapacity(input) {
|
|
327
|
+
const issues = [];
|
|
328
|
+
const skills = input.skills ?? {};
|
|
329
|
+
const tableKeys = Object.keys(ARK_SKILL_CAPACITY);
|
|
330
|
+
for (const surface of ARK_SKILL_REQUIRED_SURFACES) {
|
|
331
|
+
if (!tableKeys.includes(surface)) {
|
|
332
|
+
pushCapacityIssue(issues, {
|
|
333
|
+
code: 'CAPACITY_SURFACE_MISSING',
|
|
334
|
+
surface,
|
|
335
|
+
message: `The capacity table dropped ${surface}. Skills must keep covering ` +
|
|
336
|
+
`Layers, ArkRules, ArkRun, ArkOrder plus Contener · Guiar · Ordenar. ` +
|
|
337
|
+
`Put ${surface} back on ARK_SKILL_CAPACITY.`,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
for (const key of tableKeys) {
|
|
342
|
+
if (!ARK_SKILL_REQUIRED_SURFACES.includes(key)) {
|
|
343
|
+
pushCapacityIssue(issues, {
|
|
344
|
+
code: 'CAPACITY_SURFACE_UNMAPPED',
|
|
345
|
+
surface: key,
|
|
346
|
+
message: `The capacity table lists ${key}, which is not a product surface. ` +
|
|
347
|
+
`Keep the table to Layers, ArkRules, ArkRun, ArkOrder and Contener · Guiar · Ordenar.`,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
for (const surface of ARK_SKILL_REQUIRED_SURFACES) {
|
|
352
|
+
const doors = ARK_SKILL_CAPACITY[surface];
|
|
353
|
+
if (!doors || doors.length === 0)
|
|
354
|
+
continue;
|
|
355
|
+
for (const door of doors) {
|
|
356
|
+
if (!isFirstClassArkSkillName(door)) {
|
|
357
|
+
pushCapacityIssue(issues, {
|
|
358
|
+
code: 'CAPACITY_DOOR_NOT_FIRST_CLASS',
|
|
359
|
+
surface,
|
|
360
|
+
skillName: door,
|
|
361
|
+
message: `/${door} is listed for ${surface} but is not a first-class door. ` +
|
|
362
|
+
`Leftover names are shortcuts. Point ${surface} at a first-class skill.`,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const covering = doors.filter((door) => {
|
|
367
|
+
const body = skills[door];
|
|
368
|
+
return typeof body === 'string' && skillMentionsSurface(body, surface);
|
|
369
|
+
});
|
|
370
|
+
if (covering.length === 0) {
|
|
371
|
+
const hint = surface === 'ArkOrder' || surface === 'Ordenar'
|
|
372
|
+
? ' — usually /ark-order'
|
|
373
|
+
: surface === 'ArkRun'
|
|
374
|
+
? ' — usually /ark-runtime'
|
|
375
|
+
: '';
|
|
376
|
+
pushCapacityIssue(issues, {
|
|
377
|
+
code: 'CAPACITY_BODY_GAP',
|
|
378
|
+
surface,
|
|
379
|
+
message: `Skills no longer cover ${surface}. The product has four parts ` +
|
|
380
|
+
`(Layers, ArkRules, ArkRun, ArkOrder) plus Contener · Guiar · Ordenar. ` +
|
|
381
|
+
`Put ${surface} back in a first-class skill${hint} so agents still know when to use it.`,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
for (const [surface, door] of Object.entries(ARK_SKILL_CAPACITY_DEDICATED_DOORS)) {
|
|
386
|
+
const doors = ARK_SKILL_CAPACITY[surface];
|
|
387
|
+
if (!doors?.includes(door) || !isFirstClassArkSkillName(door)) {
|
|
388
|
+
pushCapacityIssue(issues, {
|
|
389
|
+
code: 'CAPACITY_DEDICATED_DOOR',
|
|
390
|
+
surface,
|
|
391
|
+
skillName: door,
|
|
392
|
+
message: `/${door} is the first-class door for ${surface}. Keep it on that surface. ` +
|
|
393
|
+
`Leftover names like /ark-think are shortcuts, not a replacement.`,
|
|
394
|
+
});
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
const body = skills[door];
|
|
398
|
+
if (typeof body !== 'string' || !skillMentionsSurface(body, surface)) {
|
|
399
|
+
pushCapacityIssue(issues, {
|
|
400
|
+
code: 'CAPACITY_DEDICATED_DOOR',
|
|
401
|
+
surface,
|
|
402
|
+
skillName: door,
|
|
403
|
+
message: `/${door} no longer talks about ${surface}. That door is how agents reach ` +
|
|
404
|
+
`this part of the product. Put ${surface} back in the skill body.`,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
for (const name of ARK_FIRST_CLASS_SKILL_NAMES) {
|
|
409
|
+
const body = skills[name];
|
|
410
|
+
if (typeof body !== 'string') {
|
|
411
|
+
pushCapacityIssue(issues, {
|
|
412
|
+
code: 'CAPACITY_ROUTING_GAP',
|
|
413
|
+
skillName: name,
|
|
414
|
+
message: `/${name} is a first-class door but has no skill body in this check. ` +
|
|
415
|
+
`Ship the template so agents know when to use it.`,
|
|
416
|
+
});
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const hasNorthStar = body.includes(NORTH_STAR_PHRASE);
|
|
420
|
+
const hasWhen = /When \/ not when|\*\*When:\*\*/.test(body);
|
|
421
|
+
const hasNotWhen = /Not when|Do \*\*not\*\* use|Prefer instead/.test(body);
|
|
422
|
+
const hasHandoff = /Handoff|hand off|`\/ark-/i.test(body);
|
|
423
|
+
if (!hasNorthStar || !hasWhen || !hasNotWhen || !hasHandoff) {
|
|
424
|
+
pushCapacityIssue(issues, {
|
|
425
|
+
code: 'CAPACITY_ROUTING_GAP',
|
|
426
|
+
skillName: name,
|
|
427
|
+
message: `/${name} is missing when / not when / handoff or Contener · Guiar · Ordenar. ` +
|
|
428
|
+
`First-class skills must say when to use them and which sibling to call next.`,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
if (body.includes(FORBIDDEN_ORDER_FREEZE)) {
|
|
432
|
+
pushCapacityIssue(issues, {
|
|
433
|
+
code: 'CAPACITY_ROUTING_GAP',
|
|
434
|
+
skillName: name,
|
|
435
|
+
message: `/${name} still says not to invent /ark-order. That freeze is outdated — ` +
|
|
436
|
+
`/ark-order is a first-class door.`,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const hubs = input.hubs ?? {};
|
|
441
|
+
for (const [hub, text] of Object.entries(hubs)) {
|
|
442
|
+
const body = String(text ?? '');
|
|
443
|
+
if (!body.includes('/ark-order')) {
|
|
444
|
+
pushCapacityIssue(issues, {
|
|
445
|
+
code: 'CAPACITY_HUB_GAP',
|
|
446
|
+
hub,
|
|
447
|
+
message: `${hub} no longer names /ark-order. Host instructions must stay at 100% of ` +
|
|
448
|
+
`the product. Add /ark-order back, or you left the order plane behind.`,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
if (!body.includes(NORTH_STAR_PHRASE)) {
|
|
452
|
+
pushCapacityIssue(issues, {
|
|
453
|
+
code: 'CAPACITY_HUB_GAP',
|
|
454
|
+
hub,
|
|
455
|
+
message: `${hub} no longer says Contener · Guiar · Ordenar. Keep that filter in the ` +
|
|
456
|
+
`living docs so agents pick the right sibling.`,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (body.includes(FORBIDDEN_ORDER_FREEZE)) {
|
|
460
|
+
pushCapacityIssue(issues, {
|
|
461
|
+
code: 'CAPACITY_HUB_GAP',
|
|
462
|
+
hub,
|
|
463
|
+
message: `${hub} still says not to invent /ark-order. That freeze is outdated — ` +
|
|
464
|
+
`/ark-order is a first-class door.`,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return { ok: issues.length === 0, issues };
|
|
469
|
+
}
|
|
237
470
|
/**
|
|
238
471
|
* Visible package stamp at the start of Agent Skills `description`.
|
|
239
472
|
* Hosts show `description` in the picker; `arkVersion:` in YAML is invisible there.
|
|
@@ -60,8 +60,15 @@ export const EMPTY_ANALYSIS_RULE_ID = 'ANALYSIS_COVERS_NO_FILES';
|
|
|
60
60
|
* `countUngovernedSourceFiles` in scan-files.mjs. Feeding it a count that honours
|
|
61
61
|
* `config.exclude` reopens the false green through `exclude: ["**"]`.
|
|
62
62
|
*
|
|
63
|
+
* `classifiedFileCount` is optional. When omitted, included files are treated as
|
|
64
|
+
* classified (the historical meaning of `governedFileCount`). When provided and
|
|
65
|
+
* zero while include still matched files, this is the same vacuous green: import
|
|
66
|
+
* rules cannot run on unclassified source. Omit the count when `layers` is empty
|
|
67
|
+
* so `CONFIG_NO_LAYERS` stays the next step. Partial unclassified stays a warning.
|
|
68
|
+
*
|
|
63
69
|
* @param {{
|
|
64
70
|
* governedFileCount?: number,
|
|
71
|
+
* classifiedFileCount?: number,
|
|
65
72
|
* ungovernedSourceCount?: number,
|
|
66
73
|
* ungovernedSourceCap?: number,
|
|
67
74
|
* root?: string,
|
|
@@ -72,8 +79,29 @@ export const EMPTY_ANALYSIS_RULE_ID = 'ANALYSIS_COVERS_NO_FILES';
|
|
|
72
79
|
* @returns {{ ruleId: string, message: string, nextAction: string } | null}
|
|
73
80
|
*/
|
|
74
81
|
export function emptyAnalysisRefusal(input = {}) {
|
|
75
|
-
const
|
|
76
|
-
if (!Number.isFinite(
|
|
82
|
+
const included = Number(input.governedFileCount);
|
|
83
|
+
if (!Number.isFinite(included)) return null;
|
|
84
|
+
|
|
85
|
+
const classifiedRaw = input.classifiedFileCount;
|
|
86
|
+
const classifiedProvided =
|
|
87
|
+
classifiedRaw !== undefined && classifiedRaw !== null && Number.isFinite(Number(classifiedRaw));
|
|
88
|
+
const classified = classifiedProvided ? Number(classifiedRaw) : included;
|
|
89
|
+
|
|
90
|
+
if (included > 0 && classified === 0) {
|
|
91
|
+
const root = String(input.root ?? '');
|
|
92
|
+
const configPath = String(input.configPath ?? '');
|
|
93
|
+
const message =
|
|
94
|
+
`Analysis covered 0 files: ${included} included file(s) exist under ${root} but none ` +
|
|
95
|
+
`matched a layer pattern in ${configPath}. Every rule is vacuously satisfied on an empty ` +
|
|
96
|
+
'set, so a pass here would certify nothing.';
|
|
97
|
+
const nextAction =
|
|
98
|
+
`Extend layer patterns or narrow include in ${configPath} so every included file has a ` +
|
|
99
|
+
'layer. `npx arkgate-check --root . --coverage` lists unclassified files, and `/ark-place` ' +
|
|
100
|
+
'picks a folder. `--plan` and `--doctor` report this without refusing.';
|
|
101
|
+
return { ruleId: EMPTY_ANALYSIS_RULE_ID, message, nextAction };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (included !== 0) return null;
|
|
77
105
|
|
|
78
106
|
const ungoverned = Math.max(0, Number(input.ungovernedSourceCount) || 0);
|
|
79
107
|
const cap = Math.max(0, Number(input.ungovernedSourceCap) || 0);
|
|
@@ -112,3 +140,11 @@ export function emptyAnalysisRefusal(input = {}) {
|
|
|
112
140
|
|
|
113
141
|
return { ruleId: EMPTY_ANALYSIS_RULE_ID, message, nextAction };
|
|
114
142
|
}
|
|
143
|
+
|
|
144
|
+
/** Path-only classified count. `undefined` when no layers — caller keeps the include-only meaning. */
|
|
145
|
+
export function classifiedCountFromFiles(files, layers, layerForFile, root) {
|
|
146
|
+
if (!Array.isArray(layers) || layers.length === 0 || typeof layerForFile !== 'function') {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
return files.filter((abs) => layerForFile(root, abs, layers)).length;
|
|
150
|
+
}
|