@orkestrel/scaffold 0.0.35 → 0.0.36
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/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +146 -194
- package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +29 -22
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +31 -4
- package/dist/host/agents/skills/enterprise-bootstrap/references/frontend-design.md +103 -33
- package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +1 -1
- package/dist/host/agents/skills/orkestrel-human-journey/SKILL.md +124 -0
- package/dist/host/agents/skills/orkestrel-human-journey/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/orkestrel-human-journey/references/captures.md +74 -0
- package/dist/host/agents/skills/orkestrel-human-journey/references/layer.md +131 -0
- package/dist/host/claude/rules/documentation.md +4 -1
- package/dist/host/claude/skills/orkestrel-human-journey/SKILL.md +10 -0
- package/dist/host/claude/skills/orkestrel-polish-surface/SKILL.md +1 -1
- package/dist/host/manifest.json +30 -1
- package/dist/host/tests/policy.test.ts +33 -0
- package/dist/host/tests/setupPolicy.ts +319 -2
- package/dist/src/core/index.cjs +1 -1
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.js +1 -1
- package/dist/src/core/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orkestrel-human-journey
|
|
3
|
+
description: Prove a browser application the way a person uses it — real keystrokes, clicks, and Tab/Enter against only what is visible and reachable — and generate the capture portfolio from those same journeys. Use when accepting a UI build, proving an application end to end, deciding whether a surface is reachable by keyboard alone, proving what a screen refuses as well as what it does, auditing whether the interface speaks the user's vocabulary rather than the engine's, producing the screenshots a design review judges, or whenever the only evidence a screen works is a test that drove it through JavaScript instead of through the interface.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Load the canonical workflow
|
|
7
|
+
|
|
8
|
+
Read `.agents/skills/orkestrel-human-journey/SKILL.md` completely, then read every reference it requires. Follow that canonical workflow before acting.
|
|
9
|
+
|
|
10
|
+
This bridge contains no independent process. `AGENTS.md`, applicable rules, the canonical skill, and the governing guide/spec remain authoritative in that order.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: orkestrel-polish-surface
|
|
3
|
-
description: Drive a rendered or externally driven surface to shipped quality through capture-evidence verdict rounds. Use
|
|
3
|
+
description: Drive a rendered or externally driven surface to shipped quality through capture-evidence verdict rounds. Use when asked to polish an interface, bring a rendered surface to enterprise grade, judge what actually renders rather than what the source claims, reconcile design, state-truth, and inventory findings into fix units, or converge repeated review rounds on captured proof. Run one round for a narrow request; run the full campaign for a polish or production-readiness request.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Load the canonical workflow
|
package/dist/host/manifest.json
CHANGED
|
@@ -155,6 +155,26 @@
|
|
|
155
155
|
"destination": ".agents/skills/orkestrel-harden-package/references/research.md",
|
|
156
156
|
"executable": false
|
|
157
157
|
},
|
|
158
|
+
{
|
|
159
|
+
"storage": "agents/skills/orkestrel-human-journey/SKILL.md",
|
|
160
|
+
"destination": ".agents/skills/orkestrel-human-journey/SKILL.md",
|
|
161
|
+
"executable": false
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"storage": "agents/skills/orkestrel-human-journey/agents/openai.yaml",
|
|
165
|
+
"destination": ".agents/skills/orkestrel-human-journey/agents/openai.yaml",
|
|
166
|
+
"executable": false
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"storage": "agents/skills/orkestrel-human-journey/references/captures.md",
|
|
170
|
+
"destination": ".agents/skills/orkestrel-human-journey/references/captures.md",
|
|
171
|
+
"executable": false
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"storage": "agents/skills/orkestrel-human-journey/references/layer.md",
|
|
175
|
+
"destination": ".agents/skills/orkestrel-human-journey/references/layer.md",
|
|
176
|
+
"executable": false
|
|
177
|
+
},
|
|
158
178
|
{
|
|
159
179
|
"storage": "agents/skills/orkestrel-polish-surface/SKILL.md",
|
|
160
180
|
"destination": ".agents/skills/orkestrel-polish-surface/SKILL.md",
|
|
@@ -330,6 +350,11 @@
|
|
|
330
350
|
"destination": ".claude/skills/orkestrel-harden-package/SKILL.md",
|
|
331
351
|
"executable": false
|
|
332
352
|
},
|
|
353
|
+
{
|
|
354
|
+
"storage": "claude/skills/orkestrel-human-journey/SKILL.md",
|
|
355
|
+
"destination": ".claude/skills/orkestrel-human-journey/SKILL.md",
|
|
356
|
+
"executable": false
|
|
357
|
+
},
|
|
333
358
|
{
|
|
334
359
|
"storage": "claude/skills/orkestrel-polish-surface/SKILL.md",
|
|
335
360
|
"destination": ".claude/skills/orkestrel-polish-surface/SKILL.md",
|
|
@@ -526,6 +551,9 @@
|
|
|
526
551
|
".agents/skills/orkestrel-harden-package",
|
|
527
552
|
".agents/skills/orkestrel-harden-package/agents",
|
|
528
553
|
".agents/skills/orkestrel-harden-package/references",
|
|
554
|
+
".agents/skills/orkestrel-human-journey",
|
|
555
|
+
".agents/skills/orkestrel-human-journey/agents",
|
|
556
|
+
".agents/skills/orkestrel-human-journey/references",
|
|
529
557
|
".agents/skills/orkestrel-polish-surface",
|
|
530
558
|
".agents/skills/orkestrel-polish-surface/agents",
|
|
531
559
|
".agents/skills/orkestrel-polish-surface/references",
|
|
@@ -538,9 +566,10 @@
|
|
|
538
566
|
".claude/skills/orkestrel-debrief",
|
|
539
567
|
".claude/skills/orkestrel-falsify",
|
|
540
568
|
".claude/skills/orkestrel-harden-package",
|
|
569
|
+
".claude/skills/orkestrel-human-journey",
|
|
541
570
|
".claude/skills/orkestrel-polish-surface",
|
|
542
571
|
".codex/agents",
|
|
543
572
|
".cursor/rules"
|
|
544
573
|
],
|
|
545
|
-
"digest": "
|
|
574
|
+
"digest": "d2b056bb83ece5f6e49cb3acd27fd917241a51a3e287791eaf8d807cd56fb4c7"
|
|
546
575
|
}
|
|
@@ -6,7 +6,12 @@ import {
|
|
|
6
6
|
inspectPolicyMirrorPaths,
|
|
7
7
|
inspectPolicySources,
|
|
8
8
|
inspectPolicyWorkspace,
|
|
9
|
+
inspectSkillFamily,
|
|
9
10
|
POLICY_CONTROLS,
|
|
11
|
+
readSkillFamily,
|
|
12
|
+
SKILL_POLICY_APOSTROPHE,
|
|
13
|
+
SKILL_POLICY_CONTROLS,
|
|
14
|
+
SKILL_POLICY_EXCLUSION,
|
|
10
15
|
stemToPolicyCandidates,
|
|
11
16
|
testToPolicyStem,
|
|
12
17
|
} from './setupPolicy.js'
|
|
@@ -291,6 +296,34 @@ describe('instrument negative controls', () => {
|
|
|
291
296
|
}
|
|
292
297
|
})
|
|
293
298
|
|
|
299
|
+
describe('skill family policy', () => {
|
|
300
|
+
it('discovers a non-empty family containing orkestrel-falsify', () => {
|
|
301
|
+
const family = readSkillFamily(process.cwd())
|
|
302
|
+
expect(family.length).toBeGreaterThan(0)
|
|
303
|
+
expect(family).toContain('orkestrel-falsify')
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('requires every discovered skill file, metadata token, and reference', () => {
|
|
307
|
+
expect(inspectSkillFamily(process.cwd())).toEqual([])
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
for (const control of SKILL_POLICY_CONTROLS) {
|
|
311
|
+
it(`${control.label} [membership: ${control.membership}]`, () => {
|
|
312
|
+
const violations = inspectPolicyControl(control)
|
|
313
|
+
expect(violations).toHaveLength(1)
|
|
314
|
+
expect(violations[0]?.rule).toBe(control.rule)
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
it(`${SKILL_POLICY_APOSTROPHE.label} [membership: ${SKILL_POLICY_APOSTROPHE.membership}]`, () => {
|
|
319
|
+
expect(inspectPolicyControl(SKILL_POLICY_APOSTROPHE)).toEqual([])
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it(`${SKILL_POLICY_EXCLUSION.label} [membership: ${SKILL_POLICY_EXCLUSION.membership}]`, () => {
|
|
323
|
+
expect(inspectPolicyControl(SKILL_POLICY_EXCLUSION)).toEqual([])
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
|
|
294
327
|
describe('repository policy', () => {
|
|
295
328
|
it('enforces placement and mirrors over the real workspace', () => {
|
|
296
329
|
expect(inspectPolicyWorkspace(process.cwd())).toEqual([])
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
globSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from 'node:fs'
|
|
2
10
|
import { tmpdir } from 'node:os'
|
|
3
11
|
import { basename, dirname, join } from 'node:path'
|
|
4
12
|
import * as ts from 'typescript'
|
|
@@ -14,6 +22,7 @@ export type PolicyRule =
|
|
|
14
22
|
| 'function'
|
|
15
23
|
| 'mirror'
|
|
16
24
|
| 'parser'
|
|
25
|
+
| 'skill'
|
|
17
26
|
| 'type'
|
|
18
27
|
|
|
19
28
|
/** One TypeScript source supplied to the placement instrument. */
|
|
@@ -38,6 +47,21 @@ export interface PolicyControl {
|
|
|
38
47
|
readonly files: readonly PolicySource[]
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
/** The directory whose immediate child directories form the complete skill family. */
|
|
51
|
+
export const SKILL_FAMILY_ROOT = '.agents/skills'
|
|
52
|
+
|
|
53
|
+
/** Minimal valid skill text for physical family controls. */
|
|
54
|
+
export const SKILL_POLICY_TEXT = '# Skill\n'
|
|
55
|
+
|
|
56
|
+
/** Skill text naming one reference for physical family controls. */
|
|
57
|
+
export const SKILL_REFERENCE_TEXT = '# Skill\n\nRead references/example.md.\n'
|
|
58
|
+
|
|
59
|
+
/** Canonical skill metadata whose three values each carry YAML's escaped apostrophe. */
|
|
60
|
+
export const SKILL_APOSTROPHE_METADATA =
|
|
61
|
+
"interface:\n display_name: 'Owner''s Fixture'\n" +
|
|
62
|
+
" short_description: 'Exercise the family''s apostrophe rule'\n" +
|
|
63
|
+
" default_prompt: 'Use $sample for this fixture''s value.'\n"
|
|
64
|
+
|
|
41
65
|
/** Every centralized module named by the architecture kind table. */
|
|
42
66
|
export const CENTRAL_SOURCE_FILES: readonly string[] = Object.freeze([
|
|
43
67
|
'cloners.ts',
|
|
@@ -707,6 +731,206 @@ export function inspectPolicyMirrors(root: string): readonly PolicyViolation[] {
|
|
|
707
731
|
return inspectPolicyMirrorPaths(tests, modules)
|
|
708
732
|
}
|
|
709
733
|
|
|
734
|
+
/**
|
|
735
|
+
* Resolve an exact-case directory beneath a physical root.
|
|
736
|
+
*
|
|
737
|
+
* @param root - The physical directory from which resolution starts.
|
|
738
|
+
* @param path - The relative directory path to resolve.
|
|
739
|
+
* @returns The resolved directory, or `undefined` when a segment is absent or not a directory.
|
|
740
|
+
*/
|
|
741
|
+
export function resolvePolicyDirectory(root: string, path: string): string | undefined {
|
|
742
|
+
const normalized = normalizePolicyPath(path)
|
|
743
|
+
if (normalized === '' || normalized === '.') return root
|
|
744
|
+
let current = root
|
|
745
|
+
for (const segment of normalized.split('/')) {
|
|
746
|
+
const entry = readdirSync(current, { withFileTypes: true }).find(
|
|
747
|
+
(candidate) => candidate.name === segment && candidate.isDirectory(),
|
|
748
|
+
)
|
|
749
|
+
if (entry === undefined) return undefined
|
|
750
|
+
current = join(current, entry.name)
|
|
751
|
+
}
|
|
752
|
+
return current
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Whether an exact-case path resolves to a regular file beneath a physical root.
|
|
757
|
+
*
|
|
758
|
+
* @param root - The physical directory from which resolution starts.
|
|
759
|
+
* @param path - The relative file path to inspect.
|
|
760
|
+
* @returns `true` only when every directory and the regular file match exact case.
|
|
761
|
+
*/
|
|
762
|
+
export function isPolicyFile(root: string, path: string): boolean {
|
|
763
|
+
const normalized = normalizePolicyPath(path)
|
|
764
|
+
const directory = resolvePolicyDirectory(root, dirname(normalized))
|
|
765
|
+
if (directory === undefined) return false
|
|
766
|
+
const name = basename(normalized)
|
|
767
|
+
return readdirSync(directory, { withFileTypes: true }).some(
|
|
768
|
+
(entry) => entry.name === name && entry.isFile(),
|
|
769
|
+
)
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Discover the skill family from immediate directories in the workspace tree.
|
|
774
|
+
*
|
|
775
|
+
* @param root - The workspace root to inspect.
|
|
776
|
+
* @returns The sorted directory names that belong to the skill family.
|
|
777
|
+
*/
|
|
778
|
+
export function readSkillFamily(root: string): readonly string[] {
|
|
779
|
+
const directory = resolvePolicyDirectory(root, SKILL_FAMILY_ROOT)
|
|
780
|
+
if (directory === undefined) return []
|
|
781
|
+
return readdirSync(directory, { withFileTypes: true })
|
|
782
|
+
.filter((entry) => entry.isDirectory())
|
|
783
|
+
.map((entry) => entry.name)
|
|
784
|
+
.sort()
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Create metadata in the canonical four-line skill interface shape.
|
|
789
|
+
*
|
|
790
|
+
* @param name - The skill token the default prompt invokes.
|
|
791
|
+
* @returns Canonical skill interface metadata ending in one newline.
|
|
792
|
+
*/
|
|
793
|
+
export function createSkillMetadata(name: string): string {
|
|
794
|
+
return (
|
|
795
|
+
[
|
|
796
|
+
'interface:',
|
|
797
|
+
" display_name: 'Fixture Skill'",
|
|
798
|
+
" short_description: 'Exercise the skill family policy'",
|
|
799
|
+
` default_prompt: 'Use $${name} for this fixture.'`,
|
|
800
|
+
].join('\n') + '\n'
|
|
801
|
+
)
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Parse the default prompt from the canonical four-line skill interface shape.
|
|
806
|
+
*
|
|
807
|
+
* Each value is a non-empty single-quoted scalar in which `''` carries an apostrophe.
|
|
808
|
+
*
|
|
809
|
+
* @param content - The raw agents/openai.yaml text.
|
|
810
|
+
* @returns The default prompt scalar as written, or `undefined` when any structural rule fails.
|
|
811
|
+
*/
|
|
812
|
+
export function parseSkillPrompt(content: string): string | undefined {
|
|
813
|
+
const normalized = content.replaceAll('\r\n', '\n')
|
|
814
|
+
const lines = (normalized.endsWith('\n') ? normalized.slice(0, -1) : normalized).split('\n')
|
|
815
|
+
if (lines.length !== 4 || lines[0] !== 'interface:') return undefined
|
|
816
|
+
const display = lines[1]?.match(/^ display_name: '((?:[^']|'')+)'$/u)
|
|
817
|
+
const description = lines[2]?.match(/^ short_description: '((?:[^']|'')+)'$/u)
|
|
818
|
+
const prompt = lines[3]?.match(/^ default_prompt: '((?:[^']|'')+)'$/u)
|
|
819
|
+
if (display?.[1] === undefined || description?.[1] === undefined || prompt?.[1] === undefined) {
|
|
820
|
+
return undefined
|
|
821
|
+
}
|
|
822
|
+
return prompt[1]
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Test whether a default prompt names one skill's token in complete form.
|
|
827
|
+
*
|
|
828
|
+
* A skill directory name is lowercase letters and hyphens, so a match followed by either continues
|
|
829
|
+
* a longer name and names a different skill.
|
|
830
|
+
*
|
|
831
|
+
* @param prompt - The default prompt scalar as written.
|
|
832
|
+
* @param name - The discovered skill directory name.
|
|
833
|
+
* @returns True when the prompt carries `$name` as a complete token.
|
|
834
|
+
*/
|
|
835
|
+
export function matchesSkillToken(prompt: string, name: string): boolean {
|
|
836
|
+
const token = `$${name}`
|
|
837
|
+
for (let index = prompt.indexOf(token); index !== -1; index = prompt.indexOf(token, index + 1)) {
|
|
838
|
+
const next = prompt.charAt(index + token.length)
|
|
839
|
+
if (next === '' || !/[a-z-]/u.test(next)) return true
|
|
840
|
+
}
|
|
841
|
+
return false
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Extract the direct Markdown reference paths named in one skill document.
|
|
846
|
+
*
|
|
847
|
+
* @param content - The raw SKILL.md text.
|
|
848
|
+
* @returns Each distinct references/name.md token in sorted order.
|
|
849
|
+
*/
|
|
850
|
+
export function extractSkillReferences(content: string): readonly string[] {
|
|
851
|
+
const references = new Set<string>()
|
|
852
|
+
// This raw-text scan includes fenced examples. Over-matching safely requires the named file.
|
|
853
|
+
for (const match of content.matchAll(/references\/[A-Za-z0-9._-]+\.md/gu)) {
|
|
854
|
+
const reference = match[0]
|
|
855
|
+
if (reference !== undefined) references.add(reference)
|
|
856
|
+
}
|
|
857
|
+
return [...references].sort()
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Inspect one discovered skill's required files, metadata, token, and references.
|
|
862
|
+
*
|
|
863
|
+
* @param root - The workspace root to inspect.
|
|
864
|
+
* @param name - The discovered skill directory name.
|
|
865
|
+
* @returns Every skill-family violation in invariant order.
|
|
866
|
+
*/
|
|
867
|
+
export function inspectSkill(root: string, name: string): readonly PolicyViolation[] {
|
|
868
|
+
const base = `${SKILL_FAMILY_ROOT}/${name}`
|
|
869
|
+
const skill = `${base}/SKILL.md`
|
|
870
|
+
const metadata = `${base}/agents/openai.yaml`
|
|
871
|
+
const violations: PolicyViolation[] = []
|
|
872
|
+
if (!isPolicyFile(root, skill)) {
|
|
873
|
+
violations.push(
|
|
874
|
+
createPolicyViolation('skill', skill, 'skill requires an exact-case regular SKILL.md'),
|
|
875
|
+
)
|
|
876
|
+
}
|
|
877
|
+
if (!isPolicyFile(root, metadata)) {
|
|
878
|
+
violations.push(
|
|
879
|
+
createPolicyViolation(
|
|
880
|
+
'skill',
|
|
881
|
+
metadata,
|
|
882
|
+
'skill requires an exact-case regular agents/openai.yaml',
|
|
883
|
+
),
|
|
884
|
+
)
|
|
885
|
+
} else {
|
|
886
|
+
const prompt = parseSkillPrompt(readFileSync(join(root, metadata), 'utf8'))
|
|
887
|
+
if (prompt === undefined) {
|
|
888
|
+
violations.push(
|
|
889
|
+
createPolicyViolation(
|
|
890
|
+
'skill',
|
|
891
|
+
metadata,
|
|
892
|
+
'agents/openai.yaml matches the canonical four-line interface schema',
|
|
893
|
+
),
|
|
894
|
+
)
|
|
895
|
+
} else if (!matchesSkillToken(prompt, name)) {
|
|
896
|
+
violations.push(
|
|
897
|
+
createPolicyViolation(
|
|
898
|
+
'skill',
|
|
899
|
+
metadata,
|
|
900
|
+
`agents/openai.yaml default_prompt contains the complete token $${name}`,
|
|
901
|
+
),
|
|
902
|
+
)
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
if (isPolicyFile(root, skill)) {
|
|
906
|
+
for (const reference of extractSkillReferences(readFileSync(join(root, skill), 'utf8'))) {
|
|
907
|
+
const path = `${base}/${reference}`
|
|
908
|
+
if (!isPolicyFile(root, path)) {
|
|
909
|
+
violations.push(
|
|
910
|
+
createPolicyViolation(
|
|
911
|
+
'skill',
|
|
912
|
+
path,
|
|
913
|
+
`SKILL.md reference resolves to an exact-case regular file: ${reference}`,
|
|
914
|
+
),
|
|
915
|
+
)
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return violations
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Inspect every immediate member of the discovered skill family.
|
|
924
|
+
*
|
|
925
|
+
* @param root - The workspace root to inspect.
|
|
926
|
+
* @returns Every skill-family violation in directory and invariant order.
|
|
927
|
+
*/
|
|
928
|
+
export function inspectSkillFamily(root: string): readonly PolicyViolation[] {
|
|
929
|
+
const violations: PolicyViolation[] = []
|
|
930
|
+
for (const name of readSkillFamily(root)) violations.push(...inspectSkill(root, name))
|
|
931
|
+
return violations
|
|
932
|
+
}
|
|
933
|
+
|
|
710
934
|
/**
|
|
711
935
|
* Inspect source placement and test mirrors across one workspace.
|
|
712
936
|
*
|
|
@@ -720,6 +944,9 @@ export function inspectPolicyWorkspace(root: string): readonly PolicyViolation[]
|
|
|
720
944
|
/**
|
|
721
945
|
* Write a control to a real temporary workspace and run the production sweep over it.
|
|
722
946
|
*
|
|
947
|
+
* The control's rule selects the sweep: `skill` inspects the family, every other rule inspects
|
|
948
|
+
* source placement and mirrors.
|
|
949
|
+
*
|
|
723
950
|
* @param control - The physical fixture and expected rule boundary.
|
|
724
951
|
* @returns Every violation reported through the production workspace route.
|
|
725
952
|
*/
|
|
@@ -731,7 +958,7 @@ export function inspectPolicyControl(control: PolicyControl): readonly PolicyVio
|
|
|
731
958
|
mkdirSync(dirname(path), { recursive: true })
|
|
732
959
|
writeFileSync(path, file.content, 'utf8')
|
|
733
960
|
}
|
|
734
|
-
return inspectPolicyWorkspace(root)
|
|
961
|
+
return control.rule === 'skill' ? inspectSkillFamily(root) : inspectPolicyWorkspace(root)
|
|
735
962
|
} finally {
|
|
736
963
|
rmSync(root, { recursive: true, force: true })
|
|
737
964
|
}
|
|
@@ -963,6 +1190,96 @@ export const POLICY_CONTROLS: readonly PolicyControl[] = Object.freeze([
|
|
|
963
1190
|
},
|
|
964
1191
|
])
|
|
965
1192
|
|
|
1193
|
+
/** Physical in-family controls for every skill-family assertion class. */
|
|
1194
|
+
export const SKILL_POLICY_CONTROLS: readonly PolicyControl[] = Object.freeze([
|
|
1195
|
+
{
|
|
1196
|
+
label: 'rejects a missing exact-case SKILL.md',
|
|
1197
|
+
membership: 'immediate directories beneath .agents/skills',
|
|
1198
|
+
rule: 'skill',
|
|
1199
|
+
files: [
|
|
1200
|
+
{ path: '.agents/skills/sample/skill.md', content: SKILL_POLICY_TEXT },
|
|
1201
|
+
{ path: '.agents/skills/sample/agents/openai.yaml', content: createSkillMetadata('sample') },
|
|
1202
|
+
],
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
label: 'rejects a missing exact-case agents/openai.yaml',
|
|
1206
|
+
membership: 'immediate directories beneath .agents/skills',
|
|
1207
|
+
rule: 'skill',
|
|
1208
|
+
files: [
|
|
1209
|
+
{ path: '.agents/skills/sample/SKILL.md', content: SKILL_POLICY_TEXT },
|
|
1210
|
+
{ path: '.agents/skills/sample/agents/OpenAI.yaml', content: createSkillMetadata('sample') },
|
|
1211
|
+
],
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
label: 'rejects a non-regular SKILL.md',
|
|
1215
|
+
membership: 'immediate directories beneath .agents/skills',
|
|
1216
|
+
rule: 'skill',
|
|
1217
|
+
files: [
|
|
1218
|
+
{ path: '.agents/skills/sample/SKILL.md/child.txt', content: '' },
|
|
1219
|
+
{ path: '.agents/skills/sample/agents/openai.yaml', content: createSkillMetadata('sample') },
|
|
1220
|
+
],
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
label: 'rejects malformed agents/openai.yaml metadata',
|
|
1224
|
+
membership: 'immediate directories beneath .agents/skills',
|
|
1225
|
+
rule: 'skill',
|
|
1226
|
+
files: [
|
|
1227
|
+
{ path: '.agents/skills/sample/SKILL.md', content: SKILL_POLICY_TEXT },
|
|
1228
|
+
{ path: '.agents/skills/sample/agents/openai.yaml', content: 'interface: {}\n' },
|
|
1229
|
+
],
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
label: 'rejects a default prompt with the wrong skill token',
|
|
1233
|
+
membership: 'immediate directories beneath .agents/skills',
|
|
1234
|
+
rule: 'skill',
|
|
1235
|
+
files: [
|
|
1236
|
+
{ path: '.agents/skills/sample/SKILL.md', content: SKILL_POLICY_TEXT },
|
|
1237
|
+
{ path: '.agents/skills/sample/agents/openai.yaml', content: createSkillMetadata('other') },
|
|
1238
|
+
],
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
label: 'rejects a default prompt whose token extends the skill name',
|
|
1242
|
+
membership: 'immediate directories beneath .agents/skills',
|
|
1243
|
+
rule: 'skill',
|
|
1244
|
+
files: [
|
|
1245
|
+
{ path: '.agents/skills/sample/SKILL.md', content: SKILL_POLICY_TEXT },
|
|
1246
|
+
{
|
|
1247
|
+
path: '.agents/skills/sample/agents/openai.yaml',
|
|
1248
|
+
content: createSkillMetadata('samplex'),
|
|
1249
|
+
},
|
|
1250
|
+
],
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
label: 'rejects a dangling exact-case SKILL.md reference',
|
|
1254
|
+
membership: 'immediate directories beneath .agents/skills',
|
|
1255
|
+
rule: 'skill',
|
|
1256
|
+
files: [
|
|
1257
|
+
{ path: '.agents/skills/sample/SKILL.md', content: SKILL_REFERENCE_TEXT },
|
|
1258
|
+
{ path: '.agents/skills/sample/agents/openai.yaml', content: createSkillMetadata('sample') },
|
|
1259
|
+
{ path: '.agents/skills/sample/references/Example.md', content: '# Example\n' },
|
|
1260
|
+
],
|
|
1261
|
+
},
|
|
1262
|
+
])
|
|
1263
|
+
|
|
1264
|
+
/** An in-family skill whose metadata values carry escaped apostrophes, proving they parse. */
|
|
1265
|
+
export const SKILL_POLICY_APOSTROPHE: PolicyControl = Object.freeze({
|
|
1266
|
+
label: 'accepts escaped apostrophes in agents/openai.yaml values',
|
|
1267
|
+
membership: 'immediate directories beneath .agents/skills',
|
|
1268
|
+
rule: 'skill',
|
|
1269
|
+
files: [
|
|
1270
|
+
{ path: '.agents/skills/sample/SKILL.md', content: SKILL_POLICY_TEXT },
|
|
1271
|
+
{ path: '.agents/skills/sample/agents/openai.yaml', content: SKILL_APOSTROPHE_METADATA },
|
|
1272
|
+
],
|
|
1273
|
+
})
|
|
1274
|
+
|
|
1275
|
+
/** A bridge skill outside the discovered family, used to prove the membership boundary. */
|
|
1276
|
+
export const SKILL_POLICY_EXCLUSION: PolicyControl = Object.freeze({
|
|
1277
|
+
label: 'excludes .claude/skills from the skill family',
|
|
1278
|
+
membership: 'directories outside .agents/skills',
|
|
1279
|
+
rule: 'skill',
|
|
1280
|
+
files: [{ path: '.claude/skills/bridge/SKILL.md', content: SKILL_POLICY_TEXT }],
|
|
1281
|
+
})
|
|
1282
|
+
|
|
966
1283
|
/** A differently shaped workspace with app, browser, and worker environments but no core. */
|
|
967
1284
|
export const GENERIC_POLICY_SOURCES: readonly PolicySource[] = Object.freeze([
|
|
968
1285
|
{
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -305,7 +305,7 @@ var DEFAULT_ENGINES = `>=${MINIMUM_NODE_VERSION}`;
|
|
|
305
305
|
/** The tooling versions scaffold and every generated workspace share. */
|
|
306
306
|
var BASE_DEV_DEPENDENCIES = Object.freeze({
|
|
307
307
|
"@orkestrel/guide": "^0.0.11",
|
|
308
|
-
"@orkestrel/scaffold": "^0.0.
|
|
308
|
+
"@orkestrel/scaffold": "^0.0.36",
|
|
309
309
|
"@orkestrel/test": "^0.0.3",
|
|
310
310
|
"@types/node": "^26.2.0",
|
|
311
311
|
oxfmt: "^0.62.0",
|