@orkestrel/scaffold 0.0.21 → 0.0.23
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/scaffold.js +371 -140
- package/dist/bin/scaffold.js.map +1 -1
- package/dist/host/AGENTS.md +61 -42
- package/dist/host/CLAUDE.md +39 -408
- package/dist/host/agents/orchestration.md +454 -0
- package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +25 -7
- package/dist/host/agents/skills/orkestrel-align-packages/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-build-application/SKILL.md +42 -31
- package/dist/host/agents/skills/orkestrel-build-application/references/application.md +129 -0
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +3 -3
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +2 -2
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +167 -0
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +98 -0
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +148 -0
- package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +4 -2
- package/dist/host/agents/skills/orkestrel-harden-package/references/centralization.md +5 -5
- package/dist/host/agents/skills/orkestrel-harden-package/references/research.md +2 -0
- package/dist/host/agents/skills/orkestrel-polish-surface/SKILL.md +11 -6
- package/dist/host/claude/agents/analyst.md +59 -0
- package/dist/host/claude/agents/application.md +1 -1
- package/dist/host/claude/agents/builder.md +1 -1
- package/dist/host/claude/agents/checker.md +5 -1
- package/dist/host/claude/agents/codex.md +120 -94
- package/dist/host/claude/agents/grok.md +44 -32
- package/dist/host/claude/agents/implementer.md +1 -1
- package/dist/host/claude/agents/orkestrel.md +5 -5
- package/dist/host/claude/agents/planner.md +9 -3
- package/dist/host/claude/agents/researcher.md +6 -5
- package/dist/host/claude/agents/reviewer.md +12 -6
- package/dist/host/claude/agents/scout.md +7 -4
- package/dist/host/claude/agents/verifier.md +1 -1
- package/dist/host/claude/rules/application.md +17 -16
- package/dist/host/claude/rules/architecture.md +41 -22
- package/dist/host/claude/rules/documentation.md +2 -1
- package/dist/host/claude/rules/names.md +7 -6
- package/dist/host/claude/rules/quality.md +65 -18
- package/dist/host/claude/rules/styles.md +1 -1
- package/dist/host/claude/rules/tests.md +12 -6
- package/dist/host/claude/rules/typescript.md +1 -1
- package/dist/host/claude/rules/workspace.md +6 -3
- package/dist/host/claude/settings.json +2 -0
- package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +10 -0
- package/dist/host/codex/agents/analyst.toml +13 -4
- package/dist/host/codex/agents/checker.toml +1 -1
- package/dist/host/codex/agents/grok.toml +2 -1
- package/dist/host/codex/agents/opus.toml +1 -1
- package/dist/host/codex/agents/planner.toml +1 -1
- package/dist/host/codex/agents/researcher.toml +2 -2
- package/dist/host/codex/agents/reviewer.toml +1 -1
- package/dist/host/codex/agents/scout.toml +2 -2
- package/dist/host/codex/config.toml +23 -70
- package/dist/host/cursor/rules/orchestration.mdc +33 -0
- package/dist/host/guides/src/scaffold.md +339 -125
- package/dist/host/manifest.json +47 -1
- package/dist/host/tests/setupPolicy.ts +210 -3
- package/dist/src/core/index.cjs +477 -69
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +132 -43
- package/dist/src/core/index.d.ts +132 -43
- package/dist/src/core/index.js +474 -70
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +83 -33
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +43 -27
- package/dist/src/server/index.d.ts +43 -27
- package/dist/src/server/index.js +84 -35
- package/dist/src/server/index.js.map +1 -1
- package/package.json +4 -4
package/dist/host/manifest.json
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"entries": [
|
|
3
|
+
{
|
|
4
|
+
"storage": "agents/orchestration.md",
|
|
5
|
+
"destination": ".agents/orchestration.md",
|
|
6
|
+
"executable": false
|
|
7
|
+
},
|
|
3
8
|
{
|
|
4
9
|
"storage": "agents/skills/enterprise-bootstrap/SKILL.md",
|
|
5
10
|
"destination": ".agents/skills/enterprise-bootstrap/SKILL.md",
|
|
@@ -60,6 +65,11 @@
|
|
|
60
65
|
"destination": ".agents/skills/orkestrel-build-application/agents/openai.yaml",
|
|
61
66
|
"executable": false
|
|
62
67
|
},
|
|
68
|
+
{
|
|
69
|
+
"storage": "agents/skills/orkestrel-build-application/references/application.md",
|
|
70
|
+
"destination": ".agents/skills/orkestrel-build-application/references/application.md",
|
|
71
|
+
"executable": false
|
|
72
|
+
},
|
|
63
73
|
{
|
|
64
74
|
"storage": "agents/skills/orkestrel-debrief/SKILL.md",
|
|
65
75
|
"destination": ".agents/skills/orkestrel-debrief/SKILL.md",
|
|
@@ -80,6 +90,21 @@
|
|
|
80
90
|
"destination": ".agents/skills/orkestrel-debrief/references/instruction-audit.md",
|
|
81
91
|
"executable": false
|
|
82
92
|
},
|
|
93
|
+
{
|
|
94
|
+
"storage": "agents/skills/orkestrel-falsify/SKILL.md",
|
|
95
|
+
"destination": ".agents/skills/orkestrel-falsify/SKILL.md",
|
|
96
|
+
"executable": false
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"storage": "agents/skills/orkestrel-falsify/references/brief.md",
|
|
100
|
+
"destination": ".agents/skills/orkestrel-falsify/references/brief.md",
|
|
101
|
+
"executable": false
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"storage": "agents/skills/orkestrel-falsify/references/reconcile.md",
|
|
105
|
+
"destination": ".agents/skills/orkestrel-falsify/references/reconcile.md",
|
|
106
|
+
"executable": false
|
|
107
|
+
},
|
|
83
108
|
{
|
|
84
109
|
"storage": "agents/skills/orkestrel-harden-package/SKILL.md",
|
|
85
110
|
"destination": ".agents/skills/orkestrel-harden-package/SKILL.md",
|
|
@@ -125,6 +150,11 @@
|
|
|
125
150
|
"destination": ".agents/skills/orkestrel-polish-surface/references/capture-harness.md",
|
|
126
151
|
"executable": false
|
|
127
152
|
},
|
|
153
|
+
{
|
|
154
|
+
"storage": "claude/agents/analyst.md",
|
|
155
|
+
"destination": ".claude/agents/analyst.md",
|
|
156
|
+
"executable": false
|
|
157
|
+
},
|
|
128
158
|
{
|
|
129
159
|
"storage": "claude/agents/application.md",
|
|
130
160
|
"destination": ".claude/agents/application.md",
|
|
@@ -265,6 +295,11 @@
|
|
|
265
295
|
"destination": ".claude/skills/orkestrel-debrief/SKILL.md",
|
|
266
296
|
"executable": false
|
|
267
297
|
},
|
|
298
|
+
{
|
|
299
|
+
"storage": "claude/skills/orkestrel-falsify/SKILL.md",
|
|
300
|
+
"destination": ".claude/skills/orkestrel-falsify/SKILL.md",
|
|
301
|
+
"executable": false
|
|
302
|
+
},
|
|
268
303
|
{
|
|
269
304
|
"storage": "claude/skills/orkestrel-harden-package/SKILL.md",
|
|
270
305
|
"destination": ".claude/skills/orkestrel-harden-package/SKILL.md",
|
|
@@ -350,6 +385,11 @@
|
|
|
350
385
|
"destination": ".cursor/mcp.json",
|
|
351
386
|
"executable": false
|
|
352
387
|
},
|
|
388
|
+
{
|
|
389
|
+
"storage": "cursor/rules/orchestration.mdc",
|
|
390
|
+
"destination": ".cursor/rules/orchestration.mdc",
|
|
391
|
+
"executable": false
|
|
392
|
+
},
|
|
353
393
|
{
|
|
354
394
|
"storage": "dotfiles/editorconfig",
|
|
355
395
|
"destination": ".editorconfig",
|
|
@@ -452,9 +492,12 @@
|
|
|
452
492
|
".agents/skills/orkestrel-align-packages/references",
|
|
453
493
|
".agents/skills/orkestrel-build-application",
|
|
454
494
|
".agents/skills/orkestrel-build-application/agents",
|
|
495
|
+
".agents/skills/orkestrel-build-application/references",
|
|
455
496
|
".agents/skills/orkestrel-debrief",
|
|
456
497
|
".agents/skills/orkestrel-debrief/agents",
|
|
457
498
|
".agents/skills/orkestrel-debrief/references",
|
|
499
|
+
".agents/skills/orkestrel-falsify",
|
|
500
|
+
".agents/skills/orkestrel-falsify/references",
|
|
458
501
|
".agents/skills/orkestrel-harden-package",
|
|
459
502
|
".agents/skills/orkestrel-harden-package/agents",
|
|
460
503
|
".agents/skills/orkestrel-harden-package/references",
|
|
@@ -469,14 +512,17 @@
|
|
|
469
512
|
".claude/skills/orkestrel-align-packages",
|
|
470
513
|
".claude/skills/orkestrel-build-application",
|
|
471
514
|
".claude/skills/orkestrel-debrief",
|
|
515
|
+
".claude/skills/orkestrel-falsify",
|
|
472
516
|
".claude/skills/orkestrel-harden-package",
|
|
473
517
|
".claude/skills/orkestrel-polish-surface",
|
|
474
518
|
".codex",
|
|
475
519
|
".codex/agents",
|
|
476
520
|
".cursor",
|
|
521
|
+
".cursor/rules",
|
|
477
522
|
"guides",
|
|
478
523
|
"guides/src",
|
|
479
524
|
"scripts",
|
|
480
525
|
"tests"
|
|
481
|
-
]
|
|
526
|
+
],
|
|
527
|
+
"digest": "e4351919e4d34e3696ee3ce77e2cce555c20acc58a29649cd9f3c489f3d0bf2b"
|
|
482
528
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { globSync, readFileSync } from 'node:fs'
|
|
2
2
|
import { isBuiltin } from 'node:module'
|
|
3
|
-
import { basename, join } from 'node:path'
|
|
3
|
+
import { basename, extname, join } from 'node:path'
|
|
4
4
|
import * as ts from 'typescript'
|
|
5
5
|
|
|
6
6
|
/** Centralized source modules whose top-level declarations must all be exported. */
|
|
@@ -60,6 +60,9 @@ export const DATA_SOURCE_FILES: readonly string[] = Object.freeze([
|
|
|
60
60
|
'validators.ts',
|
|
61
61
|
])
|
|
62
62
|
|
|
63
|
+
/** Domain folders whose modules each export one named function rather than one class. */
|
|
64
|
+
export const FUNCTION_DOMAIN_FOLDERS: readonly string[] = Object.freeze(['app/browser/composables'])
|
|
65
|
+
|
|
63
66
|
/** Worker-only value globals that WebWorker typing must not expose to core implementations. */
|
|
64
67
|
export const WORKER_SCOPE_VALUE_GLOBALS: readonly string[] = Object.freeze([
|
|
65
68
|
'name',
|
|
@@ -109,6 +112,32 @@ export const CODING_SOURCE_EXTENSIONS: readonly string[] = Object.freeze([
|
|
|
109
112
|
/** Production-source glob derived from the complete inspected extension vocabulary. */
|
|
110
113
|
export const CODING_SOURCE_GLOB = `{app,src}/**/*.{${CODING_SOURCE_EXTENSIONS.join(',')}}`
|
|
111
114
|
|
|
115
|
+
/** Fleet-owned policy files that must stay free of any one package's architecture. */
|
|
116
|
+
export const POLICY_INFRASTRUCTURE_FILES: readonly string[] = Object.freeze([
|
|
117
|
+
'tests/policy.test.ts',
|
|
118
|
+
'tests/setupPolicy.ts',
|
|
119
|
+
])
|
|
120
|
+
|
|
121
|
+
/** Source environments whose prefixed paths fleet policy must not name in a literal. */
|
|
122
|
+
export const POLICY_SOURCE_ENVIRONMENTS: readonly string[] = Object.freeze([
|
|
123
|
+
'browser',
|
|
124
|
+
'core',
|
|
125
|
+
'server',
|
|
126
|
+
])
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The prefixed source-environment path fleet policy must not name in a literal.
|
|
130
|
+
*
|
|
131
|
+
* @remarks
|
|
132
|
+
* Built rather than written, so this file states no matching literal of its own
|
|
133
|
+
* and can never report itself. Policy names an environment without the source
|
|
134
|
+
* prefix when it must name one at all, which is why the prefix is the rule.
|
|
135
|
+
*/
|
|
136
|
+
export const POLICY_ENVIRONMENT_PATTERN: RegExp = new RegExp(
|
|
137
|
+
`src/(?:${POLICY_SOURCE_ENVIRONMENTS.join('|')})/`,
|
|
138
|
+
'u',
|
|
139
|
+
)
|
|
140
|
+
|
|
112
141
|
/** Virtual source text used while binding one policy-inspected module. */
|
|
113
142
|
export const POLICY_SOURCE_TEXTS: Map<string, string> = new Map()
|
|
114
143
|
|
|
@@ -139,6 +168,28 @@ export function isCodingSourcePath(path: string): boolean {
|
|
|
139
168
|
)
|
|
140
169
|
}
|
|
141
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Whether a path is an eligible function-domain module.
|
|
173
|
+
*
|
|
174
|
+
* @param path - The workspace-relative source path to inspect
|
|
175
|
+
* @returns `true` when the path is a direct module of a registered function domain
|
|
176
|
+
*/
|
|
177
|
+
export function isFunctionDomainPath(path: string): boolean {
|
|
178
|
+
const normalized = normalizePolicyPath(path)
|
|
179
|
+
const file = basename(normalized)
|
|
180
|
+
const separator = normalized.lastIndexOf('/')
|
|
181
|
+
const parent = separator < 0 ? '' : normalized.slice(0, separator)
|
|
182
|
+
return (
|
|
183
|
+
FUNCTION_DOMAIN_FOLDERS.includes(parent) &&
|
|
184
|
+
/^[a-z][A-Za-z0-9]*\.ts$/u.test(file) &&
|
|
185
|
+
file !== 'index.ts' &&
|
|
186
|
+
file !== 'main.ts' &&
|
|
187
|
+
!CENTRAL_SOURCE_FILES.includes(file) &&
|
|
188
|
+
!FUNCTION_SOURCE_FILES.includes(file) &&
|
|
189
|
+
!DATA_SOURCE_FILES.includes(file)
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
142
193
|
/** Whether a declaration carries an explicit export modifier. */
|
|
143
194
|
export function hasExportModifier(node: ts.Node): boolean {
|
|
144
195
|
return (
|
|
@@ -458,7 +509,16 @@ export function inspectCodingSource(
|
|
|
458
509
|
if (!normalizedPath.startsWith('app/browser/')) {
|
|
459
510
|
violations.push(`${normalizedPath} Vue components belong in app/browser`)
|
|
460
511
|
}
|
|
461
|
-
|
|
512
|
+
// A missing extractor is reported, never absorbed. Returning [] here would make
|
|
513
|
+
// every SFC's script block silently unchecked while the surrounding sweep still
|
|
514
|
+
// reported success — an instrument claiming a coverage it does not have.
|
|
515
|
+
if (vueScripts === undefined) {
|
|
516
|
+
violations.push(
|
|
517
|
+
`${normalizedPath} requires a Vue script extractor; its script blocks were not inspected`,
|
|
518
|
+
)
|
|
519
|
+
return violations
|
|
520
|
+
}
|
|
521
|
+
violations.push(...inspectVueCodingLaw(normalizedPath, vueScripts(normalizedPath, content)))
|
|
462
522
|
return violations
|
|
463
523
|
}
|
|
464
524
|
|
|
@@ -536,6 +596,34 @@ export function inspectCodingNode(
|
|
|
536
596
|
ts.forEachChild(node, (child) => inspectCodingNode(path, child, violations, checker))
|
|
537
597
|
}
|
|
538
598
|
|
|
599
|
+
/**
|
|
600
|
+
* Inspect one eligible function-domain module for its required declaration shape.
|
|
601
|
+
*
|
|
602
|
+
* @param path - The source path used in diagnostics
|
|
603
|
+
* @param source - The parsed source file to inspect
|
|
604
|
+
* @returns A shape violation when the module is not imports plus one matching named export
|
|
605
|
+
*/
|
|
606
|
+
export function inspectFunctionModule(path: string, source: ts.SourceFile): readonly string[] {
|
|
607
|
+
const file = basename(normalizePolicyPath(path))
|
|
608
|
+
const declarations = source.statements.filter(ts.isFunctionDeclaration)
|
|
609
|
+
const functions = declarations.filter((declaration) => declaration.body !== undefined)
|
|
610
|
+
const invalid = source.statements.filter(
|
|
611
|
+
(statement) => !ts.isImportDeclaration(statement) && !ts.isFunctionDeclaration(statement),
|
|
612
|
+
)
|
|
613
|
+
const declaration = functions[0]
|
|
614
|
+
if (
|
|
615
|
+
functions.length === 1 &&
|
|
616
|
+
invalid.length === 0 &&
|
|
617
|
+
declarations.every((candidate) => candidate.name?.text === file.slice(0, -3)) &&
|
|
618
|
+
declaration !== undefined &&
|
|
619
|
+
hasExportModifier(declaration) &&
|
|
620
|
+
!hasModifier(declaration, ts.SyntaxKind.DefaultKeyword)
|
|
621
|
+
) {
|
|
622
|
+
return []
|
|
623
|
+
}
|
|
624
|
+
return [`${path} declarations do not form one matching exported function implementation`]
|
|
625
|
+
}
|
|
626
|
+
|
|
539
627
|
/** Inspect one TypeScript source module for repository coding-law violations. */
|
|
540
628
|
export function inspectCodingLaw(path: string, content: string): readonly string[] {
|
|
541
629
|
const violations: string[] = []
|
|
@@ -544,6 +632,8 @@ export function inspectCodingLaw(path: string, content: string): readonly string
|
|
|
544
632
|
if (source === undefined) throw new Error(`Policy source was not bound at ${path}`)
|
|
545
633
|
const checker = program.getTypeChecker()
|
|
546
634
|
const file = basename(path)
|
|
635
|
+
const stem = basename(file, extname(file))
|
|
636
|
+
const functionModule = isFunctionDomainPath(path)
|
|
547
637
|
const placementExempt =
|
|
548
638
|
!CENTRAL_SOURCE_FILES.includes(file) &&
|
|
549
639
|
!FUNCTION_SOURCE_FILES.includes(file) &&
|
|
@@ -553,6 +643,9 @@ export function inspectCodingLaw(path: string, content: string): readonly string
|
|
|
553
643
|
if (/\.[cm]?jsx?$/u.test(path)) {
|
|
554
644
|
violations.push(`${path} production modules use TypeScript source extensions`)
|
|
555
645
|
}
|
|
646
|
+
if (FUNCTION_DOMAIN_FOLDERS.some((folder) => basename(folder) === stem)) {
|
|
647
|
+
violations.push(`${path} names a function domain, which belongs in a folder rather than a file`)
|
|
648
|
+
}
|
|
556
649
|
if (/@ts-(?:expect-error|ignore|nocheck)|eslint-disable|oxlint-disable/u.test(content)) {
|
|
557
650
|
violations.push(`${path} forbids suppression directives`)
|
|
558
651
|
}
|
|
@@ -598,6 +691,7 @@ export function inspectCodingLaw(path: string, content: string): readonly string
|
|
|
598
691
|
}
|
|
599
692
|
if (
|
|
600
693
|
!placementExempt &&
|
|
694
|
+
!functionModule &&
|
|
601
695
|
ts.isFunctionDeclaration(statement) &&
|
|
602
696
|
!FUNCTION_SOURCE_FILES.includes(file)
|
|
603
697
|
) {
|
|
@@ -605,6 +699,7 @@ export function inspectCodingLaw(path: string, content: string): readonly string
|
|
|
605
699
|
}
|
|
606
700
|
if (
|
|
607
701
|
!placementExempt &&
|
|
702
|
+
!functionModule &&
|
|
608
703
|
ts.isVariableStatement(statement) &&
|
|
609
704
|
!DATA_SOURCE_FILES.includes(file)
|
|
610
705
|
) {
|
|
@@ -646,6 +741,10 @@ export function inspectCodingLaw(path: string, content: string): readonly string
|
|
|
646
741
|
}
|
|
647
742
|
}
|
|
648
743
|
|
|
744
|
+
if (functionModule) {
|
|
745
|
+
violations.push(...inspectFunctionModule(path, source))
|
|
746
|
+
}
|
|
747
|
+
|
|
649
748
|
if (/^[A-Z][A-Za-z0-9]*\.ts$/u.test(file)) {
|
|
650
749
|
const classes = source.statements.filter(ts.isClassDeclaration)
|
|
651
750
|
const invalid = source.statements.filter(
|
|
@@ -673,7 +772,14 @@ export function inspectCodingLaw(path: string, content: string): readonly string
|
|
|
673
772
|
return violations
|
|
674
773
|
}
|
|
675
774
|
|
|
676
|
-
/**
|
|
775
|
+
/**
|
|
776
|
+
* Inspect every production source under one workspace.
|
|
777
|
+
*
|
|
778
|
+
* @remarks
|
|
779
|
+
* `vueScripts` is required whenever the workspace contains a `.vue` file: script
|
|
780
|
+
* blocks cannot be read without it, and omitting it is reported as a violation
|
|
781
|
+
* against each SFC rather than passing silently.
|
|
782
|
+
*/
|
|
677
783
|
export function inspectCodingWorkspace(
|
|
678
784
|
root: string,
|
|
679
785
|
vueScripts?: VueScriptExtractorInterface,
|
|
@@ -687,3 +793,104 @@ export function inspectCodingWorkspace(
|
|
|
687
793
|
}
|
|
688
794
|
return violations
|
|
689
795
|
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Read one workspace's declared package name.
|
|
799
|
+
*
|
|
800
|
+
* @param root - The workspace root holding the package manifest
|
|
801
|
+
* @returns The declared package name
|
|
802
|
+
*/
|
|
803
|
+
export function readPackageName(root: string): string {
|
|
804
|
+
const manifest: unknown = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
805
|
+
const name =
|
|
806
|
+
typeof manifest === 'object' && manifest !== null && 'name' in manifest
|
|
807
|
+
? manifest.name
|
|
808
|
+
: undefined
|
|
809
|
+
if (typeof name !== 'string') throw new Error(`Package manifest at ${root} declares no name`)
|
|
810
|
+
return name
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Derive the identifier prefixes fleet policy must not use from a package name.
|
|
815
|
+
*
|
|
816
|
+
* @param name - The declared package name, scoped or bare
|
|
817
|
+
* @returns The short name's upper-snake and Pascal spellings, deduped
|
|
818
|
+
*
|
|
819
|
+
* @example
|
|
820
|
+
* ```ts
|
|
821
|
+
* derivePolicyTokens('@orkestrel/my-router') // ['MY_ROUTER', 'MyRouter']
|
|
822
|
+
* ```
|
|
823
|
+
*/
|
|
824
|
+
export function derivePolicyTokens(name: string): readonly string[] {
|
|
825
|
+
const short = name.slice(name.lastIndexOf('/') + 1)
|
|
826
|
+
const words = short.split(/[^A-Za-z0-9]+/u).filter((word) => word.length > 0)
|
|
827
|
+
const upper = words.map((word) => word.toUpperCase()).join('_')
|
|
828
|
+
const pascal = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join('')
|
|
829
|
+
return [...new Set([upper, pascal])].filter((token) => token.length > 0)
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Add package-architecture violations while traversing one fleet policy source tree.
|
|
834
|
+
*
|
|
835
|
+
* @remarks
|
|
836
|
+
* An identifier carries a package's architecture when it is named for that package,
|
|
837
|
+
* so it must begin with the token. A word that merely holds the token somewhere
|
|
838
|
+
* inside it belongs to the fleet's own vocabulary and is left alone.
|
|
839
|
+
*/
|
|
840
|
+
export function inspectPolicyNode(
|
|
841
|
+
path: string,
|
|
842
|
+
node: ts.Node,
|
|
843
|
+
violations: string[],
|
|
844
|
+
tokens: readonly string[],
|
|
845
|
+
): void {
|
|
846
|
+
if (ts.isIdentifier(node)) {
|
|
847
|
+
const token = tokens.find((candidate) => node.text.startsWith(candidate))
|
|
848
|
+
if (token !== undefined) {
|
|
849
|
+
violations.push(`${path}:${formatPolicyPosition(node)} forbids the ${token} package token`)
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (
|
|
853
|
+
(ts.isStringLiteral(node) || ts.isTemplateLiteralToken(node)) &&
|
|
854
|
+
POLICY_ENVIRONMENT_PATTERN.test(node.text)
|
|
855
|
+
) {
|
|
856
|
+
violations.push(
|
|
857
|
+
`${path}:${formatPolicyPosition(node)} forbids a source-environment path literal`,
|
|
858
|
+
)
|
|
859
|
+
}
|
|
860
|
+
ts.forEachChild(node, (child) => inspectPolicyNode(path, child, violations, tokens))
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Inspect one fleet policy module for a single package's architecture.
|
|
865
|
+
*
|
|
866
|
+
* @param path - The workspace-relative source path used in diagnostics
|
|
867
|
+
* @param content - The TypeScript source text to inspect
|
|
868
|
+
* @param tokens - The package identifier tokens no identifier may begin with
|
|
869
|
+
* @returns Every violation, in source-position order
|
|
870
|
+
*/
|
|
871
|
+
export function inspectPolicyPurity(
|
|
872
|
+
path: string,
|
|
873
|
+
content: string,
|
|
874
|
+
tokens: readonly string[],
|
|
875
|
+
): readonly string[] {
|
|
876
|
+
const violations: string[] = []
|
|
877
|
+
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true)
|
|
878
|
+
inspectPolicyNode(path, source, violations, tokens)
|
|
879
|
+
return violations
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Inspect every fleet policy module under one workspace.
|
|
884
|
+
*
|
|
885
|
+
* @param root - The workspace root whose manifest names the consuming package
|
|
886
|
+
* @returns Every violation across the fleet policy files, in file and position order
|
|
887
|
+
*/
|
|
888
|
+
export function inspectPolicyWorkspace(root: string): readonly string[] {
|
|
889
|
+
const tokens = derivePolicyTokens(readPackageName(root))
|
|
890
|
+
const violations: string[] = []
|
|
891
|
+
for (const path of POLICY_INFRASTRUCTURE_FILES) {
|
|
892
|
+
const content = readFileSync(join(root, path), 'utf8')
|
|
893
|
+
violations.push(...inspectPolicyPurity(path, content, tokens))
|
|
894
|
+
}
|
|
895
|
+
return violations
|
|
896
|
+
}
|