@orkestrel/scaffold 0.0.39 → 0.0.41
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/bin/main.js +8 -1
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +2 -2
- package/dist/host/agents/orchestration.md +33 -2
- package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +16 -16
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +3 -3
- package/dist/host/agents/skills/orkestrel-align-packages/references/integration.md +1 -1
- package/dist/host/agents/skills/orkestrel-build-application/SKILL.md +7 -2
- package/dist/host/claude/agents/builder.md +2 -2
- package/dist/host/claude/agents/orkestrel.md +48 -48
- package/dist/host/claude/rules/application.md +6 -4
- package/dist/host/claude/rules/architecture.md +2 -0
- package/dist/host/claude/rules/documentation.md +6 -0
- package/dist/host/claude/rules/tests.md +11 -1
- package/dist/host/claude/rules/typescript.md +15 -1
- package/dist/host/claude/rules/workspace.md +43 -13
- package/dist/host/claude/rules/writing.md +125 -0
- package/dist/host/claude/skills/enterprise-bootstrap/SKILL.md +10 -1
- package/dist/host/claude/skills/orkestrel-align-packages/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-build-application/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-harden-package/SKILL.md +1 -1
- package/dist/host/configs/policy.ts +185 -0
- package/dist/host/dotfiles/oxlintrc.json +13 -1
- package/dist/host/dotfiles/prettierignore +3 -0
- package/dist/host/guides/scaffold.md +13 -7
- package/dist/host/manifest.json +11 -7
- package/dist/host/tests/config.test.ts +195 -4
- package/dist/host/tests/policy.test.ts +82 -0
- package/dist/host/tests/setupPolicy.ts +863 -21
- package/dist/src/core/index.cjs +68 -6
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +11 -9
- package/dist/src/core/index.d.ts +11 -9
- package/dist/src/core/index.js +68 -6
- package/dist/src/core/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/host/agents/skills/orkestrel-build-application/references/application.md +0 -129
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// P1: Every checked population must exist and be non-empty; absence fails instead of passing vacuously.
|
|
2
2
|
// P2: Required items are checked strictly; extra items are ignored before their shape is read.
|
|
3
3
|
|
|
4
|
+
import { spawnSync } from 'node:child_process'
|
|
4
5
|
import {
|
|
5
6
|
existsSync,
|
|
6
7
|
globSync,
|
|
@@ -14,10 +15,12 @@ import {
|
|
|
14
15
|
import { dirname, join, resolve } from 'node:path'
|
|
15
16
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
16
17
|
import { build, loadConfigFromFile } from 'vite'
|
|
17
|
-
import {
|
|
18
|
+
import { RuleTester } from 'oxlint/plugins-dev'
|
|
18
19
|
import * as configHelpers from '../configs/helpers.js'
|
|
20
|
+
import { MOCKING_RULE, PRIVACY_RULE } from '../configs/policy.js'
|
|
19
21
|
import configuration, { resolveWorkspacePath } from '../vite.config.js'
|
|
20
22
|
import tsconfig from '../tsconfig.json' with { type: 'json' }
|
|
23
|
+
import { createPolicyScratch, inspectPolicyConfiguration } from './setupPolicy.js'
|
|
21
24
|
import { describe, expect, it } from 'vitest'
|
|
22
25
|
|
|
23
26
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
@@ -452,6 +455,194 @@ describe('root configuration', () => {
|
|
|
452
455
|
hasService && publishes,
|
|
453
456
|
)
|
|
454
457
|
})
|
|
458
|
+
|
|
459
|
+
it('keeps policy rules active across every linted workspace path', () => {
|
|
460
|
+
const parsed: unknown = JSON.parse(readFileSync(resolve(root, '.oxlintrc.json'), 'utf8'))
|
|
461
|
+
expect(inspectPolicyConfiguration(parsed)).toEqual([])
|
|
462
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
463
|
+
throw new Error('The Oxlint configuration is not a record')
|
|
464
|
+
}
|
|
465
|
+
const controlled = structuredClone(parsed)
|
|
466
|
+
const overrides: unknown = Object.getOwnPropertyDescriptor(controlled, 'overrides')?.value
|
|
467
|
+
if (!Array.isArray(overrides)) throw new Error('The Oxlint configuration has no overrides')
|
|
468
|
+
Object.defineProperty(controlled, 'overrides', {
|
|
469
|
+
value: overrides.concat({
|
|
470
|
+
files: ['src/**'],
|
|
471
|
+
rules: { 'policy/no-mocking': 'off' },
|
|
472
|
+
}),
|
|
473
|
+
enumerable: true,
|
|
474
|
+
configurable: true,
|
|
475
|
+
writable: true,
|
|
476
|
+
})
|
|
477
|
+
expect(inspectPolicyConfiguration(controlled)).toEqual([
|
|
478
|
+
'overrides must not configure policy/no-mocking',
|
|
479
|
+
])
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
it('omits the audit-confirmed dead policy type exports', () => {
|
|
483
|
+
const source = readFileSync(resolve(root, 'configs/policy.ts'), 'utf8')
|
|
484
|
+
expect(source).not.toMatch(/\bPolicy(?:Call|ClassMember)\b/u)
|
|
485
|
+
})
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
describe('policy plugin', () => {
|
|
489
|
+
RuleTester.describe = describe
|
|
490
|
+
RuleTester.it = it
|
|
491
|
+
|
|
492
|
+
const tester = new RuleTester({ languageOptions: { parserOptions: { lang: 'ts' } } })
|
|
493
|
+
tester.run('no-mocking', MOCKING_RULE, {
|
|
494
|
+
valid: [
|
|
495
|
+
{ name: 'accepts recorders', code: 'createRecorder()' },
|
|
496
|
+
{ name: 'accepts non-framework members', code: "registry.mock('./x')" },
|
|
497
|
+
{ name: 'accepts unlisted framework members', code: 'vi.clearAllMocks()' },
|
|
498
|
+
],
|
|
499
|
+
invalid: [
|
|
500
|
+
{
|
|
501
|
+
name: 'rejects module mocking [membership: named vi and jest module APIs]',
|
|
502
|
+
code: "vi.mock('./x')",
|
|
503
|
+
errors: [{ messageId: 'mock' }],
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: 'rejects computed module mocking [membership: named vi and jest module APIs]',
|
|
507
|
+
code: `vi['mock']('./x')`,
|
|
508
|
+
errors: [{ messageId: 'mock' }],
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: 'rejects template module mocking [membership: named vi and jest module APIs]',
|
|
512
|
+
code: `vi[\`mock\`]('./x')`,
|
|
513
|
+
errors: [{ messageId: 'mock' }],
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: 'rejects spy factories [membership: named vi and jest spy APIs]',
|
|
517
|
+
code: 'jest.fn()',
|
|
518
|
+
errors: [{ messageId: 'spy' }],
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: 'rejects fake clocks [membership: named vi and jest clock APIs]',
|
|
522
|
+
code: 'vi.useFakeTimers()',
|
|
523
|
+
errors: [{ messageId: 'clock' }],
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
name: 'rejects environment stubs [membership: named vi and jest stub APIs]',
|
|
527
|
+
code: "vi.stubEnv('A', '1')",
|
|
528
|
+
errors: [{ messageId: 'stub' }],
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
tester.run('no-keyword-privacy', PRIVACY_RULE, {
|
|
534
|
+
valid: [
|
|
535
|
+
{ name: 'accepts runtime-private fields', code: 'class Example { #value = 1 }' },
|
|
536
|
+
{
|
|
537
|
+
name: 'accepts unannotated members',
|
|
538
|
+
code: 'class Example { value = 1; read() { return this.value } }',
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
invalid: [
|
|
542
|
+
{
|
|
543
|
+
name: 'rejects private properties [membership: keyword-annotated class members]',
|
|
544
|
+
code: 'class Example { private value = 1 }',
|
|
545
|
+
errors: [{ messageId: 'keyword' }],
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: 'rejects private methods [membership: keyword-annotated class members]',
|
|
549
|
+
code: 'class Example { private read() { return 1 } }',
|
|
550
|
+
errors: [{ messageId: 'keyword' }],
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: 'rejects protected properties [membership: keyword-annotated class members]',
|
|
554
|
+
code: 'class Example { protected value = 1 }',
|
|
555
|
+
errors: [{ messageId: 'keyword' }],
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
name: 'rejects protected methods [membership: keyword-annotated class members]',
|
|
559
|
+
code: 'class Example { protected read() { return 1 } }',
|
|
560
|
+
errors: [{ messageId: 'keyword' }],
|
|
561
|
+
},
|
|
562
|
+
],
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
it('loads every configured policy rule through the real binary', () => {
|
|
566
|
+
const scratch = createPolicyScratch({ prefix: 'orkestrel-config-policy-' })
|
|
567
|
+
try {
|
|
568
|
+
scratch.write(
|
|
569
|
+
'violations/fixture.ts',
|
|
570
|
+
[
|
|
571
|
+
"vi.mock('./x')",
|
|
572
|
+
'class PrivateMember { private value = 1 }',
|
|
573
|
+
'class ParameterMember { constructor(readonly value: string) {} }',
|
|
574
|
+
'class PublicMember { public value = 1 }',
|
|
575
|
+
'void PrivateMember',
|
|
576
|
+
'void ParameterMember',
|
|
577
|
+
'void PublicMember',
|
|
578
|
+
].join('\n'),
|
|
579
|
+
)
|
|
580
|
+
scratch.write(
|
|
581
|
+
'clean/fixture.ts',
|
|
582
|
+
[
|
|
583
|
+
'class CleanMember {',
|
|
584
|
+
'\t#value = 1',
|
|
585
|
+
'\tvalue(): number { return this.#value }',
|
|
586
|
+
'}',
|
|
587
|
+
'void CleanMember',
|
|
588
|
+
].join('\n'),
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
const binary = resolve(root, 'node_modules/.bin/oxlint')
|
|
592
|
+
const config = resolve(root, '.oxlintrc.json')
|
|
593
|
+
const violations = spawnSync(
|
|
594
|
+
binary,
|
|
595
|
+
['--config', config, '--format', 'json', resolve(scratch.path, 'violations')],
|
|
596
|
+
{ cwd: root, encoding: 'utf8', timeout: 15_000 },
|
|
597
|
+
)
|
|
598
|
+
const clean = spawnSync(
|
|
599
|
+
binary,
|
|
600
|
+
['--config', config, '--format', 'json', resolve(scratch.path, 'clean')],
|
|
601
|
+
{ cwd: root, encoding: 'utf8', timeout: 15_000 },
|
|
602
|
+
)
|
|
603
|
+
const reports: string[][] = []
|
|
604
|
+
for (const result of [violations, clean]) {
|
|
605
|
+
if (result.error !== undefined) throw result.error
|
|
606
|
+
const report: unknown = JSON.parse(result.stdout)
|
|
607
|
+
if (typeof report !== 'object' || report === null) {
|
|
608
|
+
throw new Error('Oxlint returned no JSON report')
|
|
609
|
+
}
|
|
610
|
+
const diagnostics: unknown = Object.getOwnPropertyDescriptor(report, 'diagnostics')?.value
|
|
611
|
+
if (!Array.isArray(diagnostics)) throw new Error('Oxlint returned no diagnostic list')
|
|
612
|
+
const codes: string[] = []
|
|
613
|
+
for (const diagnostic of diagnostics) {
|
|
614
|
+
if (typeof diagnostic !== 'object' || diagnostic === null) {
|
|
615
|
+
throw new Error('Oxlint returned a malformed diagnostic')
|
|
616
|
+
}
|
|
617
|
+
const code: unknown = Object.getOwnPropertyDescriptor(diagnostic, 'code')?.value
|
|
618
|
+
if (typeof code !== 'string') {
|
|
619
|
+
throw new Error('Oxlint returned a diagnostic without a rule id')
|
|
620
|
+
}
|
|
621
|
+
codes.push(code)
|
|
622
|
+
}
|
|
623
|
+
reports.push(codes)
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const violationCodes = reports[0]
|
|
627
|
+
const cleanCodes = reports[1]
|
|
628
|
+
if (violationCodes === undefined || cleanCodes === undefined) {
|
|
629
|
+
throw new Error('Oxlint returned no fixture reports')
|
|
630
|
+
}
|
|
631
|
+
expect(violations.status).toBe(1)
|
|
632
|
+
for (const rule of [
|
|
633
|
+
'policy(no-mocking)',
|
|
634
|
+
'policy(no-keyword-privacy)',
|
|
635
|
+
'typescript(parameter-properties)',
|
|
636
|
+
'typescript(explicit-member-accessibility)',
|
|
637
|
+
]) {
|
|
638
|
+
expect(violationCodes).toContain(rule)
|
|
639
|
+
}
|
|
640
|
+
expect(clean.status).toBe(0)
|
|
641
|
+
expect(cleanCodes).toHaveLength(0)
|
|
642
|
+
} finally {
|
|
643
|
+
scratch.destroy()
|
|
644
|
+
}
|
|
645
|
+
})
|
|
455
646
|
})
|
|
456
647
|
|
|
457
648
|
describe('configuration helpers', () => {
|
|
@@ -490,7 +681,7 @@ describe('configuration helpers', () => {
|
|
|
490
681
|
})
|
|
491
682
|
|
|
492
683
|
it('resolves contained workspace paths and refuses a real outside sibling', () => {
|
|
493
|
-
const scratch =
|
|
684
|
+
const scratch = createPolicyScratch({ prefix: 'orkestrel-config-outside-' })
|
|
494
685
|
try {
|
|
495
686
|
const outside = scratch.path
|
|
496
687
|
const importer = resolve(root, 'tests/config.test.ts')
|
|
@@ -513,7 +704,7 @@ describe('configuration helpers', () => {
|
|
|
513
704
|
})
|
|
514
705
|
|
|
515
706
|
it('reads bounded files and resolves package roots from real manifests', () => {
|
|
516
|
-
const scratch =
|
|
707
|
+
const scratch = createPolicyScratch({ prefix: 'orkestrel-config-package-' })
|
|
517
708
|
try {
|
|
518
709
|
const workspace = scratch.path
|
|
519
710
|
const packageRoot = resolve(workspace, 'node_modules/@sample/package')
|
|
@@ -551,7 +742,7 @@ describe('configuration helpers', () => {
|
|
|
551
742
|
})
|
|
552
743
|
|
|
553
744
|
it('classifies module boundaries and extracts static asset sources', async () => {
|
|
554
|
-
const scratch =
|
|
745
|
+
const scratch = createPolicyScratch({ prefix: 'orkestrel-config-assets-' })
|
|
555
746
|
try {
|
|
556
747
|
const workspace = scratch.path
|
|
557
748
|
const source = resolve(workspace, 'entry.ts')
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import {
|
|
3
|
+
BRIDGE_POLICY_CONTROLS,
|
|
4
|
+
createPolicyScratch,
|
|
3
5
|
FUNCTION_SOURCE_FILES,
|
|
4
6
|
GENERIC_POLICY_SOURCES,
|
|
5
7
|
inspectPolicyControl,
|
|
@@ -7,15 +9,36 @@ import {
|
|
|
7
9
|
inspectPolicySources,
|
|
8
10
|
inspectPolicyWorkspace,
|
|
9
11
|
inspectSkillFamily,
|
|
12
|
+
inspectSkillBridges,
|
|
13
|
+
matchesSkillTrigger,
|
|
14
|
+
parseSkillFrontmatter,
|
|
10
15
|
POLICY_CONTROLS,
|
|
16
|
+
POLICY_SUPPRESSION_DIRECTIVE,
|
|
11
17
|
readSkillFamily,
|
|
12
18
|
SKILL_POLICY_APOSTROPHE,
|
|
19
|
+
SKILL_POLICY_BACKTICKED,
|
|
13
20
|
SKILL_POLICY_CONTROLS,
|
|
14
21
|
SKILL_POLICY_EXCLUSION,
|
|
22
|
+
SKILL_POLICY_FOLDED,
|
|
23
|
+
SKILL_POLICY_PARAGRAPHS,
|
|
15
24
|
stemToPolicyCandidates,
|
|
16
25
|
testToPolicyStem,
|
|
17
26
|
} from './setupPolicy.js'
|
|
18
27
|
|
|
28
|
+
describe('policy scratch', () => {
|
|
29
|
+
it('contains every write within its root', () => {
|
|
30
|
+
const scratch = createPolicyScratch({ prefix: 'orkestrel-policy-containment-' })
|
|
31
|
+
try {
|
|
32
|
+
expect(() => scratch.write('inside/fixture.ts', '')).not.toThrow()
|
|
33
|
+
expect(() => scratch.write('../escape', '')).toThrow(
|
|
34
|
+
'Scratch target must stay within its root',
|
|
35
|
+
)
|
|
36
|
+
} finally {
|
|
37
|
+
scratch.destroy()
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
19
42
|
describe('fleet policy register', () => {
|
|
20
43
|
it('keeps handlers in the function set and routes out', () => {
|
|
21
44
|
expect(FUNCTION_SOURCE_FILES).toContain('handlers.ts')
|
|
@@ -285,6 +308,22 @@ describe('policy population controls', () => {
|
|
|
285
308
|
}),
|
|
286
309
|
).toEqual([])
|
|
287
310
|
})
|
|
311
|
+
|
|
312
|
+
it('excludes documentation from the suppression population', () => {
|
|
313
|
+
expect(
|
|
314
|
+
inspectPolicyControl({
|
|
315
|
+
label: 'excludes documentation from the suppression population',
|
|
316
|
+
membership: 'files outside source, test, config, and script code',
|
|
317
|
+
rule: 'suppression',
|
|
318
|
+
files: [
|
|
319
|
+
{
|
|
320
|
+
path: 'guides/sample.md',
|
|
321
|
+
content: `<!-- ${POLICY_SUPPRESSION_DIRECTIVE} -->\n`,
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
}),
|
|
325
|
+
).toEqual([])
|
|
326
|
+
})
|
|
288
327
|
})
|
|
289
328
|
|
|
290
329
|
describe('instrument negative controls', () => {
|
|
@@ -307,11 +346,20 @@ describe('skill family policy', () => {
|
|
|
307
346
|
expect(inspectSkillFamily(process.cwd())).toEqual([])
|
|
308
347
|
})
|
|
309
348
|
|
|
349
|
+
it('parses a folded description containing a colon as exactly two frontmatter keys', () => {
|
|
350
|
+
const skill = SKILL_POLICY_FOLDED.files.find((file) => file.path.endsWith('/SKILL.md'))
|
|
351
|
+
const frontmatter = parseSkillFrontmatter(skill?.content ?? '')
|
|
352
|
+
expect(frontmatter?.keys).toEqual(['name', 'description'])
|
|
353
|
+
expect(frontmatter?.name).toBe('sample')
|
|
354
|
+
expect(frontmatter?.description).toBe('Use this skill when a continuation contains: a colon.')
|
|
355
|
+
})
|
|
356
|
+
|
|
310
357
|
for (const control of SKILL_POLICY_CONTROLS) {
|
|
311
358
|
it(`${control.label} [membership: ${control.membership}]`, () => {
|
|
312
359
|
const violations = inspectPolicyControl(control)
|
|
313
360
|
expect(violations).toHaveLength(1)
|
|
314
361
|
expect(violations[0]?.rule).toBe(control.rule)
|
|
362
|
+
expect(control.message === undefined || violations[0]?.message === control.message).toBe(true)
|
|
315
363
|
})
|
|
316
364
|
}
|
|
317
365
|
|
|
@@ -319,11 +367,45 @@ describe('skill family policy', () => {
|
|
|
319
367
|
expect(inspectPolicyControl(SKILL_POLICY_APOSTROPHE)).toEqual([])
|
|
320
368
|
})
|
|
321
369
|
|
|
370
|
+
it(`${SKILL_POLICY_FOLDED.label} [membership: ${SKILL_POLICY_FOLDED.membership}]`, () => {
|
|
371
|
+
expect(inspectPolicyControl(SKILL_POLICY_FOLDED)).toEqual([])
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it(`${SKILL_POLICY_BACKTICKED.label} [membership: ${SKILL_POLICY_BACKTICKED.membership}]`, () => {
|
|
375
|
+
expect(inspectPolicyControl(SKILL_POLICY_BACKTICKED)).toEqual([])
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
it('parses a folded description containing two paragraphs', () => {
|
|
379
|
+
const skill = SKILL_POLICY_PARAGRAPHS.files.find((file) => file.path.endsWith('/SKILL.md'))
|
|
380
|
+
const frontmatter = parseSkillFrontmatter(skill?.content ?? '')
|
|
381
|
+
expect(frontmatter?.keys).toEqual(['name', 'description'])
|
|
382
|
+
expect(frontmatter?.description).toBe(
|
|
383
|
+
'First paragraph.\nUse `--app` when a policy fixture needs it.',
|
|
384
|
+
)
|
|
385
|
+
expect(matchesSkillTrigger(frontmatter?.description ?? '')).toBe(true)
|
|
386
|
+
expect(inspectPolicyControl(SKILL_POLICY_PARAGRAPHS)).toEqual([])
|
|
387
|
+
})
|
|
388
|
+
|
|
322
389
|
it(`${SKILL_POLICY_EXCLUSION.label} [membership: ${SKILL_POLICY_EXCLUSION.membership}]`, () => {
|
|
323
390
|
expect(inspectPolicyControl(SKILL_POLICY_EXCLUSION)).toEqual([])
|
|
324
391
|
})
|
|
325
392
|
})
|
|
326
393
|
|
|
394
|
+
describe('skill bridge policy', () => {
|
|
395
|
+
it('matches every real provider bridge to its canonical skill', () => {
|
|
396
|
+
expect(inspectSkillBridges(process.cwd())).toEqual([])
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
for (const control of BRIDGE_POLICY_CONTROLS) {
|
|
400
|
+
it(`${control.label} [membership: ${control.membership}]`, () => {
|
|
401
|
+
const violations = inspectPolicyControl(control)
|
|
402
|
+
expect(violations).toHaveLength(1)
|
|
403
|
+
expect(violations[0]?.rule).toBe(control.rule)
|
|
404
|
+
expect(control.message === undefined || violations[0]?.message === control.message).toBe(true)
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
})
|
|
408
|
+
|
|
327
409
|
describe('repository policy', () => {
|
|
328
410
|
it('enforces placement and mirrors over the real workspace', () => {
|
|
329
411
|
expect(inspectPolicyWorkspace(process.cwd())).toEqual([])
|