@orkestrel/scaffold 0.0.67 → 0.0.69

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.
Files changed (74) hide show
  1. package/dist/bin/main.js +67 -44
  2. package/dist/bin/main.js.map +1 -1
  3. package/dist/host/agents/templates/brief.md +9 -0
  4. package/dist/host/claude/agents/orkestrel.md +4 -4
  5. package/dist/host/claude/rules/names.md +15 -0
  6. package/dist/host/claude/rules/tests.md +33 -4
  7. package/dist/host/claude/rules/workspace.md +14 -2
  8. package/dist/host/dotfiles/prettierignore +3 -0
  9. package/dist/host/guides/README.md +65 -0
  10. package/dist/host/guides/abort.md +169 -0
  11. package/dist/host/guides/agent.md +1567 -0
  12. package/dist/host/guides/brief.md +1266 -0
  13. package/dist/host/guides/browser.md +2200 -0
  14. package/dist/host/guides/budget.md +196 -0
  15. package/dist/host/guides/codec.md +519 -0
  16. package/dist/host/guides/console.md +785 -0
  17. package/dist/host/guides/contract.md +1193 -0
  18. package/dist/host/guides/csv.md +541 -0
  19. package/dist/host/guides/database.md +2518 -0
  20. package/dist/host/guides/emitter.md +233 -0
  21. package/dist/host/guides/form.md +1791 -0
  22. package/dist/host/guides/html.md +717 -0
  23. package/dist/host/guides/indexeddb.md +505 -0
  24. package/dist/host/guides/interpret.md +1029 -0
  25. package/dist/host/guides/lsp.md +515 -0
  26. package/dist/host/guides/markdown.md +964 -0
  27. package/dist/host/guides/mcp.md +5554 -0
  28. package/dist/host/guides/middleware.md +927 -0
  29. package/dist/host/guides/msg.md +440 -0
  30. package/dist/host/guides/ndjson.md +120 -0
  31. package/dist/host/guides/ollama.md +380 -0
  32. package/dist/host/guides/pool.md +280 -0
  33. package/dist/host/guides/probe.md +1210 -0
  34. package/dist/host/guides/process.md +1620 -0
  35. package/dist/host/guides/program.md +1110 -0
  36. package/dist/host/guides/qualifier.md +854 -0
  37. package/dist/host/guides/queue.md +370 -0
  38. package/dist/host/guides/rater.md +330 -0
  39. package/dist/host/guides/reason.md +1122 -0
  40. package/dist/host/guides/relation.md +373 -0
  41. package/dist/host/guides/router.md +753 -0
  42. package/dist/host/guides/scaffold.md +192 -31
  43. package/dist/host/guides/sea.md +383 -0
  44. package/dist/host/guides/server.md +752 -0
  45. package/dist/host/guides/sqlite.md +330 -0
  46. package/dist/host/guides/sse.md +187 -0
  47. package/dist/host/guides/supervisor.md +4890 -0
  48. package/dist/host/guides/table.md +1556 -0
  49. package/dist/host/guides/template.md +280 -0
  50. package/dist/host/guides/terminal.md +1145 -0
  51. package/dist/host/guides/test.md +2969 -0
  52. package/dist/host/guides/timeout.md +252 -0
  53. package/dist/host/guides/tool.md +507 -0
  54. package/dist/host/guides/toolbox.md +1038 -0
  55. package/dist/host/guides/websocket.md +282 -0
  56. package/dist/host/guides/worker.md +615 -0
  57. package/dist/host/guides/workflow.md +1507 -0
  58. package/dist/host/guides/workspace.md +595 -0
  59. package/dist/host/manifest.json +1218 -10
  60. package/dist/host/tests/policy.test.ts +279 -2
  61. package/dist/host/tests/setupPolicy.ts +445 -6
  62. package/dist/src/core/index.cjs +38 -16
  63. package/dist/src/core/index.cjs.map +1 -1
  64. package/dist/src/core/index.d.cts +33 -9
  65. package/dist/src/core/index.d.ts +33 -9
  66. package/dist/src/core/index.js +37 -17
  67. package/dist/src/core/index.js.map +1 -1
  68. package/dist/src/server/index.cjs +1750 -1567
  69. package/dist/src/server/index.cjs.map +1 -1
  70. package/dist/src/server/index.d.cts +106 -24
  71. package/dist/src/server/index.d.ts +106 -24
  72. package/dist/src/server/index.js +1751 -1570
  73. package/dist/src/server/index.js.map +1 -1
  74. package/package.json +3 -3
@@ -1,4 +1,12 @@
1
1
  import {
2
+ createGuide,
3
+ extractSourceLines,
4
+ hasCanonicalSegments,
5
+ resolveLink,
6
+ } from '@orkestrel/guide'
7
+ import { HOST_PATHS } from '@orkestrel/scaffold'
8
+ import {
9
+ existsSync,
2
10
  globSync,
3
11
  mkdirSync,
4
12
  mkdtempSync,
@@ -10,6 +18,7 @@ import {
10
18
  import { tmpdir } from 'node:os'
11
19
  import { basename, dirname, join, matchesGlob, relative as relativePath, resolve } from 'node:path'
12
20
  import { fileURLToPath } from 'node:url'
21
+ import { parseSync } from 'vite'
13
22
  import { stripPolicyCode, textToPolicyHits } from '../configs/policy.js'
14
23
 
15
24
  /** Names a rule the fleet sweep decides from workspace text and paths. */
@@ -20,6 +29,7 @@ export type PolicyRule =
20
29
  | 'prose'
21
30
  | 'rules'
22
31
  | 'skill'
32
+ | 'surface'
23
33
  | 'suppression'
24
34
 
25
35
  /** Describes one workspace file a physical control writes. */
@@ -36,6 +46,107 @@ export interface PolicyViolation {
36
46
  readonly message: string
37
47
  }
38
48
 
49
+ /** Describes one reachable declaration and its physical source location. */
50
+ export interface PolicySurfaceDeclaration {
51
+ readonly name: string
52
+ readonly path: string
53
+ readonly line: number
54
+ }
55
+
56
+ /** Groups reachable declarations with refusals of incomplete barrel evidence. */
57
+ export interface PolicySurfacePopulation {
58
+ readonly declarations: readonly PolicySurfaceDeclaration[]
59
+ readonly violations: readonly PolicyViolation[]
60
+ }
61
+
62
+ /** Reports one file's reachable declarations, or the violation reading it raised. */
63
+ export interface PolicyDeclarationRead {
64
+ readonly declarations: readonly PolicySurfaceDeclaration[]
65
+ readonly violation?: PolicyViolation
66
+ }
67
+
68
+ /** Names the installed host root containing the fleet's reference guides. */
69
+ export const POLICY_SURFACE_HOST = 'node_modules/@orkestrel/scaffold/dist/host'
70
+
71
+ /** Names the staged catalog's storage path beneath the installed host. */
72
+ export const POLICY_SURFACE_CATALOG = 'claude/agents/orkestrel.md'
73
+
74
+ /** Supplies export forms that must participate in the fleet comparison. */
75
+ export const POLICY_SURFACE_EXPORT_CASES = Object.freeze([
76
+ { label: 'namespace', text: 'export namespace waitForCondition { export const value = 1 }' },
77
+ { label: 'const declarators', text: 'export const other = 0, waitForCondition = 1' },
78
+ { label: 'let declarators', text: 'export let other = 0, waitForCondition = 1' },
79
+ { label: 'enum', text: 'export enum waitForCondition { Ready }' },
80
+ { label: 'class', text: ' export class waitForCondition {}' },
81
+ { label: 'interface', text: ' export interface waitForCondition {}' },
82
+ { label: 'type', text: ' export type waitForCondition = string' },
83
+ { label: 'function', text: ' export function waitForCondition() {}' },
84
+ {
85
+ label: 'function overload',
86
+ text:
87
+ 'export function waitForCondition(value: string): void\n' +
88
+ 'export function waitForCondition(value: number): void\n' +
89
+ 'export function waitForCondition() {}',
90
+ },
91
+ { label: 're-export list', text: "export { other as waitForCondition } from './helpers.js'" },
92
+ { label: 'local export list', text: 'const other = 1; export { other as waitForCondition }' },
93
+ ])
94
+
95
+ /** Matches the complete physical barrel rows the parser accepts. */
96
+ export const POLICY_SURFACE_BARREL_PATTERN =
97
+ /^\s*export\s+\*\s+from\s+(?:'(\.\.?\/[^']+\.js)'|"(\.\.?\/[^"]+\.js)")\s*;?\s*$/u
98
+
99
+ /**
100
+ * Creates a guide whose surface claims the supplied fixture names.
101
+ *
102
+ * @param names - The bare names the guide claims.
103
+ * @returns The fixture guide text.
104
+ */
105
+ export function createPolicySurfaceGuide(names: readonly string[]): string {
106
+ return [
107
+ '# Fixture',
108
+ '',
109
+ '## Surface',
110
+ '',
111
+ '| Name | Kind | Summary |',
112
+ '| ---- | ---- | ------- |',
113
+ ...names.map((name) => `| \`${name}\` | function | Declares a fixture name. |`),
114
+ '',
115
+ ].join('\n')
116
+ }
117
+
118
+ /**
119
+ * Writes a complete hosted reference population for a physical policy control.
120
+ *
121
+ * @param scratch - The owned workspace receiving the hosted references.
122
+ * @returns Nothing.
123
+ */
124
+ export function writePolicySurfaceHost(scratch: PolicyScratchInterface): void {
125
+ scratch.write(
126
+ `${POLICY_SURFACE_HOST}/${POLICY_SURFACE_CATALOG}`,
127
+ createPolicyCatalog(['other', 'sample']),
128
+ )
129
+ scratch.write(`${POLICY_SURFACE_HOST}/guides/other.md`, createPolicySurfaceGuide(['readShared']))
130
+ scratch.write(`${POLICY_SURFACE_HOST}/guides/sample.md`, createPolicySurfaceGuide([]))
131
+ }
132
+
133
+ /**
134
+ * Creates a scratch target with a complete installed guide population.
135
+ *
136
+ * @returns The owned scratch workspace.
137
+ */
138
+ export function createPolicySurfaceFixture(): PolicyScratchInterface {
139
+ const scratch = createPolicyScratch({ prefix: 'orkestrel-policy-surface-' })
140
+ try {
141
+ writePolicySurfaceHost(scratch)
142
+ scratch.write('package.json', '{"name":"@orkestrel/sample"}\n')
143
+ return scratch
144
+ } catch (error) {
145
+ scratch.destroy()
146
+ throw error
147
+ }
148
+ }
149
+
39
150
  /** Describes one physical negative control, including the population boundary it attacks. */
40
151
  export interface PolicyControl {
41
152
  readonly label: string
@@ -1410,13 +1521,15 @@ export function readPolicyPackage(root: string): string | undefined {
1410
1521
  * there rather than failing.
1411
1522
  *
1412
1523
  * @param root - The workspace root to read.
1524
+ * @param path - The catalog's path within that root. Default: the target catalog path.
1413
1525
  * @returns Each catalog row's package short name, its scope removed, in table order.
1414
1526
  */
1415
- export function readPolicyCatalog(root: string): readonly string[] {
1416
- if (!isPolicyFile(root, POLICY_CATALOG_FILE)) return []
1417
- const lines = readFileSync(join(root, POLICY_CATALOG_FILE), 'utf8')
1418
- .replaceAll('\r\n', '\n')
1419
- .split('\n')
1527
+ export function readPolicyCatalog(
1528
+ root: string,
1529
+ path: string = POLICY_CATALOG_FILE,
1530
+ ): readonly string[] {
1531
+ if (!isPolicyFile(root, path)) return []
1532
+ const lines = readFileSync(join(root, path), 'utf8').replaceAll('\r\n', '\n').split('\n')
1420
1533
  const heading = lines.indexOf(POLICY_CATALOG_HEADING)
1421
1534
  if (heading === -1) return []
1422
1535
  const names: string[] = []
@@ -1565,11 +1678,335 @@ export function inspectPolicyPortability(root: string): readonly PolicyViolation
1565
1678
  ]
1566
1679
  }
1567
1680
 
1681
+ /**
1682
+ * Locates parsed exports at their physical declaration lines.
1683
+ *
1684
+ * @param path - The declaring workspace-relative path.
1685
+ * @param text - The declaration source text.
1686
+ * @param root - The workspace root used to resolve relative star exports, when supplied.
1687
+ * @param ancestors - The source paths already visited along this star-export branch.
1688
+ * @returns The exported names with normalized paths and declaration lines.
1689
+ * @throws An `Error` when syntax, an export form, or a star target cannot be read.
1690
+ */
1691
+ export function readPolicyDeclarations(
1692
+ path: string,
1693
+ text: string,
1694
+ root?: string,
1695
+ ancestors: readonly string[] = [],
1696
+ ): readonly PolicySurfaceDeclaration[] {
1697
+ path = normalizePolicyPath(path)
1698
+ if (ancestors.includes(path)) return []
1699
+ const source = parseSync(path, text)
1700
+ if (source.errors.length > 0) throw new Error(`export syntax is unreadable at ${path}`)
1701
+ const declarations: PolicySurfaceDeclaration[] = []
1702
+ for (const statement of source.program.body) {
1703
+ const line = text.slice(0, statement.start).split(/\r\n|\n/u).length
1704
+ if (statement.type === 'ExportAllDeclaration') {
1705
+ if (statement.exported !== null) {
1706
+ const name =
1707
+ statement.exported.type === 'Identifier'
1708
+ ? statement.exported.name
1709
+ : statement.exported.value
1710
+ if (name === 'default') throw new Error(`default export is unsupported at ${path}:${line}`)
1711
+ declarations.push({ name, path, line })
1712
+ continue
1713
+ }
1714
+ const target = statement.source.value
1715
+ const resolved = resolveLink(
1716
+ path,
1717
+ target.endsWith('.js') ? `${target.slice(0, -3)}.ts` : target,
1718
+ )
1719
+ if (
1720
+ root === undefined ||
1721
+ !target.startsWith('.') ||
1722
+ !hasCanonicalSegments(resolved) ||
1723
+ !isPolicyFile(root, resolved)
1724
+ ) {
1725
+ throw new Error(`star export target is unreadable at ${path}:${line}: ${target}`)
1726
+ }
1727
+ declarations.push(
1728
+ ...readPolicyDeclarations(resolved, readFileSync(join(root, resolved), 'utf8'), root, [
1729
+ ...ancestors,
1730
+ path,
1731
+ ]),
1732
+ )
1733
+ continue
1734
+ }
1735
+ if (statement.type === 'ExportNamedDeclaration') {
1736
+ for (const specifier of statement.specifiers) {
1737
+ const name =
1738
+ specifier.exported.type === 'Identifier'
1739
+ ? specifier.exported.name
1740
+ : specifier.exported.value
1741
+ if (name === 'default') throw new Error(`default export is unsupported at ${path}:${line}`)
1742
+ declarations.push({
1743
+ name,
1744
+ path,
1745
+ line: text.slice(0, specifier.start).split(/\r\n|\n/u).length,
1746
+ })
1747
+ }
1748
+ const declaration = statement.declaration
1749
+ if (declaration === null) continue
1750
+ if (declaration.type === 'VariableDeclaration') {
1751
+ for (const variable of declaration.declarations) {
1752
+ if (variable.id.type !== 'Identifier')
1753
+ throw new Error(`export binding is unsupported at ${path}:${line}`)
1754
+ declarations.push({
1755
+ name: variable.id.name,
1756
+ path,
1757
+ line: text.slice(0, variable.id.start).split(/\r\n|\n/u).length,
1758
+ })
1759
+ }
1760
+ continue
1761
+ }
1762
+ if (
1763
+ declaration.type === 'FunctionDeclaration' ||
1764
+ declaration.type === 'TSDeclareFunction' ||
1765
+ declaration.type === 'ClassDeclaration' ||
1766
+ declaration.type === 'TSInterfaceDeclaration' ||
1767
+ declaration.type === 'TSTypeAliasDeclaration' ||
1768
+ declaration.type === 'TSEnumDeclaration' ||
1769
+ declaration.type === 'TSModuleDeclaration'
1770
+ ) {
1771
+ if (declaration.id?.type !== 'Identifier')
1772
+ throw new Error(`export name is unreadable at ${path}:${line}`)
1773
+ declarations.push({ name: declaration.id.name, path, line })
1774
+ continue
1775
+ }
1776
+ throw new Error(`export declaration is unsupported at ${path}:${line}: ${declaration.type}`)
1777
+ }
1778
+ if (
1779
+ statement.type === 'ExportDefaultDeclaration' ||
1780
+ statement.type === 'TSExportAssignment' ||
1781
+ statement.type === 'TSNamespaceExportDeclaration'
1782
+ ) {
1783
+ throw new Error(`export statement is unsupported at ${path}:${line}: ${statement.type}`)
1784
+ }
1785
+ }
1786
+ return declarations
1787
+ }
1788
+
1789
+ /**
1790
+ * Reads one file's declarations, or converts the reader's throw into a surface violation.
1791
+ *
1792
+ * @param root - The workspace root used to resolve relative star exports.
1793
+ * @param path - The workspace-relative path being read.
1794
+ * @param text - The declaration source text.
1795
+ * @returns The read declarations, or the violation the reader raised.
1796
+ */
1797
+ export function collectPolicyDeclarations(
1798
+ root: string,
1799
+ path: string,
1800
+ text: string,
1801
+ ): PolicyDeclarationRead {
1802
+ try {
1803
+ return { declarations: readPolicyDeclarations(path, text, root) }
1804
+ } catch (error) {
1805
+ return {
1806
+ declarations: [],
1807
+ violation: createPolicyViolation(
1808
+ 'surface',
1809
+ path,
1810
+ `surface population incomplete: ${error instanceof Error ? error.message : String(error)}`,
1811
+ ),
1812
+ }
1813
+ }
1814
+ }
1815
+
1816
+ /**
1817
+ * Reads reachable source declarations and refuses unread barrel statements or targets.
1818
+ *
1819
+ * @param root - The workspace root to inspect.
1820
+ * @returns The parsed declarations and incomplete-population violations.
1821
+ */
1822
+ export function readPolicySurface(root: string): PolicySurfacePopulation {
1823
+ const files: Record<string, string> = {}
1824
+ for (const path of globSync('src/**/*.ts', { cwd: root }).map(normalizePolicyPath).sort()) {
1825
+ files[path] = readFileSync(join(root, path), 'utf8')
1826
+ }
1827
+ const barrels = Object.keys(files).filter((path) => path.endsWith('/index.ts'))
1828
+ const targets = new Set<string>()
1829
+ const violations: PolicyViolation[] = []
1830
+ for (const path of barrels) {
1831
+ const text = files[path]
1832
+ if (text === undefined) continue
1833
+ const source = parseSync(path, text)
1834
+ const lines = extractSourceLines(text)
1835
+ if (source.errors.length > 0) {
1836
+ violations.push(
1837
+ createPolicyViolation(
1838
+ 'surface',
1839
+ path,
1840
+ 'surface population incomplete: barrel syntax is unreadable',
1841
+ ),
1842
+ )
1843
+ continue
1844
+ }
1845
+ for (const statement of source.program.body) {
1846
+ const start = text.slice(0, statement.start).split(/\r\n|\n/u).length - 1
1847
+ const end = text.slice(0, statement.end).split(/\r\n|\n/u).length - 1
1848
+ const row = lines[start]?.code.match(POLICY_SURFACE_BARREL_PATTERN)
1849
+ const target = row?.[1] ?? row?.[2]
1850
+ if (
1851
+ statement.type !== 'ExportAllDeclaration' ||
1852
+ statement.exportKind !== 'value' ||
1853
+ statement.exported !== null ||
1854
+ start !== end ||
1855
+ target === undefined
1856
+ ) {
1857
+ violations.push(
1858
+ createPolicyViolation(
1859
+ 'surface',
1860
+ path,
1861
+ 'surface population incomplete: barrel requires a relative .js star export on one line',
1862
+ start + 1,
1863
+ ),
1864
+ )
1865
+ continue
1866
+ }
1867
+ const resolved = resolveLink(path, `${target.slice(0, -3)}.ts`)
1868
+ if (
1869
+ !hasCanonicalSegments(resolved) ||
1870
+ !resolved.startsWith('src/') ||
1871
+ files[resolved] === undefined
1872
+ ) {
1873
+ violations.push(
1874
+ createPolicyViolation(
1875
+ 'surface',
1876
+ path,
1877
+ `surface population incomplete: barrel target is unreadable: ${target}`,
1878
+ start + 1,
1879
+ ),
1880
+ )
1881
+ continue
1882
+ }
1883
+ if (!resolved.endsWith('/index.ts')) targets.add(resolved)
1884
+ }
1885
+ }
1886
+ const declarations: PolicySurfaceDeclaration[] = []
1887
+ for (const path of targets) {
1888
+ const text = files[path]
1889
+ if (text === undefined) continue
1890
+ const read = collectPolicyDeclarations(root, path, text)
1891
+ declarations.push(...read.declarations)
1892
+ if (read.violation !== undefined) violations.push(read.violation)
1893
+ }
1894
+ return { declarations, violations }
1895
+ }
1896
+
1897
+ /**
1898
+ * Inspects source and target-owned setup names against the hosted fleet guides.
1899
+ *
1900
+ * @remarks
1901
+ * Source names claimed by the target's own hosted guide are grandfathered. Setup names have no
1902
+ * grandfather. An absent installed host falls back to checkout guides only with catalog coverage.
1903
+ * Missing comparison evidence and unread barrel statements produce surface violations.
1904
+ *
1905
+ * @param root - The workspace root to inspect.
1906
+ * @returns Surface violations sorted by path, declaration line, name, and owner.
1907
+ */
1908
+ export function inspectPolicySurface(root: string): readonly PolicyViolation[] {
1909
+ const installed = join(root, POLICY_SURFACE_HOST)
1910
+ const own = readPolicyPackage(root)
1911
+ const hosted = own !== 'scaffold' && existsSync(installed)
1912
+ const host = hosted ? installed : root
1913
+ const catalog = hosted ? POLICY_SURFACE_CATALOG : POLICY_CATALOG_FILE
1914
+ const directory = hosted ? `${POLICY_SURFACE_HOST}/guides` : 'guides'
1915
+ const names = new Set([...readPolicyCatalog(host, catalog), ...readPolicyCatalog(root)])
1916
+ if (!existsSync(join(host, 'guides')) || names.size === 0) {
1917
+ return [
1918
+ createPolicyViolation(
1919
+ 'surface',
1920
+ directory,
1921
+ 'surface evidence missing: hosted guides and a populated catalog are required',
1922
+ ),
1923
+ ]
1924
+ }
1925
+ const violations: PolicyViolation[] = []
1926
+ for (const name of names) {
1927
+ if (!isPolicyFile(host, `guides/${name}.md`)) {
1928
+ violations.push(
1929
+ createPolicyViolation(
1930
+ 'surface',
1931
+ `${directory}/${name}.md`,
1932
+ `surface evidence missing: catalog package ${name} has no hosted guide`,
1933
+ ),
1934
+ )
1935
+ }
1936
+ }
1937
+ const grandfather = new Set<string>()
1938
+ const owners = new Map<string, Set<string>>()
1939
+ for (const path of globSync('guides/*.md', { cwd: host }).map(normalizePolicyPath).sort()) {
1940
+ const owner = basename(path, '.md')
1941
+ if (owner === POLICY_GUIDE_MAP) continue
1942
+ const guide = createGuide(readFileSync(join(host, path), 'utf8'))
1943
+ if (!guide.sections().includes('Surface')) {
1944
+ violations.push(
1945
+ createPolicyViolation(
1946
+ 'surface',
1947
+ `${directory}/${owner}.md`,
1948
+ 'surface evidence missing: hosted guide has no Surface section',
1949
+ ),
1950
+ )
1951
+ continue
1952
+ }
1953
+ for (const symbol of guide.surface()) {
1954
+ if (owner === own) {
1955
+ grandfather.add(symbol.name)
1956
+ continue
1957
+ }
1958
+ const claimed = owners.get(symbol.name) ?? new Set<string>()
1959
+ claimed.add(owner)
1960
+ owners.set(symbol.name, claimed)
1961
+ }
1962
+ }
1963
+ const population = readPolicySurface(root)
1964
+ violations.push(...population.violations)
1965
+ const declarations = population.declarations.filter(
1966
+ (declaration) => !grandfather.has(declaration.name),
1967
+ )
1968
+ for (const path of globSync('tests/setup*.ts', { cwd: root }).map(normalizePolicyPath).sort()) {
1969
+ if (
1970
+ path.endsWith('.test.ts') ||
1971
+ HOST_PATHS.some(
1972
+ (vendored) =>
1973
+ path === normalizePolicyPath(vendored) ||
1974
+ path.startsWith(`${normalizePolicyPath(vendored)}/`),
1975
+ )
1976
+ )
1977
+ continue
1978
+ const read = collectPolicyDeclarations(root, path, readFileSync(join(root, path), 'utf8'))
1979
+ declarations.push(...read.declarations)
1980
+ if (read.violation !== undefined) violations.push(read.violation)
1981
+ }
1982
+ const seen = new Set<string>()
1983
+ for (const declaration of declarations) {
1984
+ for (const owner of owners.get(declaration.name) ?? []) {
1985
+ const key = `${declaration.path}\n${declaration.name}\n${owner}`
1986
+ if (seen.has(key)) continue
1987
+ seen.add(key)
1988
+ violations.push(
1989
+ createPolicyViolation(
1990
+ 'surface',
1991
+ declaration.path,
1992
+ `surface name belongs to one package: ${declaration.name} (${owner})`,
1993
+ declaration.line,
1994
+ ),
1995
+ )
1996
+ }
1997
+ }
1998
+ return violations.sort((first, second) => {
1999
+ if (first.path !== second.path) return first.path < second.path ? -1 : 1
2000
+ if (first.line !== second.line) return (first.line ?? 0) - (second.line ?? 0)
2001
+ return first.message === second.message ? 0 : first.message < second.message ? -1 : 1
2002
+ })
2003
+ }
2004
+
1568
2005
  /**
1569
2006
  * Inspects every policy rule across one workspace.
1570
2007
  *
1571
2008
  * @param root - The workspace root to inspect.
1572
- * @returns Every mirror, suppression, skill, bridge, portability, and prose violation.
2009
+ * @returns Every mirror, suppression, skill, bridge, portability, prose, and surface violation.
1573
2010
  */
1574
2011
  export function inspectPolicyWorkspace(root: string): readonly PolicyViolation[] {
1575
2012
  return [
@@ -1579,6 +2016,7 @@ export function inspectPolicyWorkspace(root: string): readonly PolicyViolation[]
1579
2016
  ...inspectSkillBridges(root),
1580
2017
  ...inspectPolicyPortability(root),
1581
2018
  ...inspectPolicyProse(root),
2019
+ ...inspectPolicySurface(root),
1582
2020
  ]
1583
2021
  }
1584
2022
 
@@ -1594,6 +2032,7 @@ export function inspectPolicyWorkspace(root: string): readonly PolicyViolation[]
1594
2032
  export function inspectPolicyControl(control: PolicyControl): readonly PolicyViolation[] {
1595
2033
  const scratch = createPolicyScratch({ prefix: 'orkestrel-policy-' })
1596
2034
  try {
2035
+ writePolicySurfaceHost(scratch)
1597
2036
  for (const file of control.files) {
1598
2037
  scratch.write(file.path, file.content)
1599
2038
  }
@@ -4,7 +4,7 @@ let _orkestrel_template = require("@orkestrel/template");
4
4
  let _orkestrel_emitter = require("@orkestrel/emitter");
5
5
  var package_default = {
6
6
  name: "@orkestrel/scaffold",
7
- version: "0.0.67",
7
+ version: "0.0.69",
8
8
  description: "Scaffold workspaces with commands: new, audit, repair, catalog, and overwrite.",
9
9
  keywords: [
10
10
  "audit",
@@ -84,7 +84,7 @@ var package_default = {
84
84
  "build:src:core": "vite build --config configs/src/vite.core.config.ts && npm run copy dist/src/core/index.d.ts dist/src/core/index.d.cts",
85
85
  "build:src:server": "vite build --config configs/src/vite.server.config.ts && npm run copy dist/src/server/index.d.ts dist/src/server/index.d.cts",
86
86
  "build:src:bin": "vite build --config configs/src/vite.bin.config.ts",
87
- "build:host": "node -e \"import('./dist/src/server/index.js').then((m)=>{const n=m.stageHost(process.cwd(),'dist/host').length;console.log('build-host: staged '+n+' file(s) into dist/host')})\"",
87
+ "build:host": "node -e \"import('./dist/src/server/index.js').then((m)=>{const n=m.stageHost(process.cwd(),'dist/host',{report: (message) => console.error(message)}).length;console.log('build-host: staged '+n+' file(s) into dist/host')})\"",
88
88
  "build:inventory": "node -e \"import('./dist/src/server/index.js').then((m)=>{const p=process.argv[1]??'host.json',n=m.stageInventory(process.cwd(),p).entries.length;console.log('build-inventory: staged '+n+' file(s) into '+p)})\"",
89
89
  "prepack": "npm run build",
90
90
  "prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test && npm run test:distribution -- --mode release"
@@ -99,7 +99,7 @@ var package_default = {
99
99
  },
100
100
  devDependencies: {
101
101
  "@microsoft/api-extractor": "^7.59.1",
102
- "@orkestrel/guide": "^0.0.18",
102
+ "@orkestrel/guide": "^0.0.19",
103
103
  "@orkestrel/html": "^0.0.9",
104
104
  "@orkestrel/probe": "^0.0.14",
105
105
  "@orkestrel/test": "^0.0.14",
@@ -214,12 +214,11 @@ var BIN_ENTRY_PATH = "src/bin/main.ts";
214
214
  * of the paths it selects: the licence, the harness permission file, the
215
215
  * session-start hooks, the shared policy
216
216
  * register, the shared policy proof, the shared policy plugin, the shared
217
- * configuration leaf and its proof, the byte-identical root dotfiles, and the
218
- * guide mirrors a generated workspace starts from. A directory entry vendors
219
- * everything beneath it.
217
+ * configuration leaf and its proof, and the byte-identical root dotfiles.
218
+ * A directory entry vendors everything beneath it.
220
219
  *
221
- * A plan carries the subset its target selects, which is why the list is a
222
- * candidate set rather than a plan: a workspace never mirrors its own guide.
220
+ * A plan carries the subset its target selects. The seed guide mirrors are
221
+ * claimed separately by `blueprintToHostArtifacts` through {@link SEED_GUIDE_PATHS}.
223
222
  *
224
223
  * Neither the instruction canon nor the bench and MCP wiring is here. A target reads
225
224
  * its rules, its skills, its agent roles, its bench configuration, and its MCP
@@ -243,9 +242,7 @@ var HOST_PATHS = Object.freeze([
243
242
  ".oxfmtrc.json",
244
243
  ".oxlintrc.json",
245
244
  ".oxlintignore",
246
- ".prettierignore",
247
- "guides/guide.md",
248
- "guides/scaffold.md"
245
+ ".prettierignore"
249
246
  ]);
250
247
  /**
251
248
  * Lists the instruction-canon paths staged for reading rather than for a target, frozen.
@@ -263,8 +260,9 @@ var HOST_PATHS = Object.freeze([
263
260
  * the installed package. The `AGENTS.md` and `CLAUDE.md` pointers scaffold plans
264
261
  * are what name each location.
265
262
  *
266
- * The lists are disjoint by prefix in either direction: no member of either
267
- * equals or sits beneath a member of the other. Staging depends on that, because
263
+ * This list, {@link HOST_PATHS}, and {@link REFERENCE_PATHS} are disjoint by
264
+ * prefix in every direction: no member equals or sits beneath another list's
265
+ * member. Staging depends on that, because
268
266
  * the walk covers the union and a path it discovers twice claims one storage
269
267
  * name twice, which refuses the stage.
270
268
  *
@@ -294,6 +292,24 @@ var CANON_PATHS = Object.freeze([
294
292
  ".cursor/mcp.json",
295
293
  ".cursor/rules"
296
294
  ]);
295
+ /**
296
+ * Lists the reference paths staged for offline reading, frozen.
297
+ *
298
+ * @remarks
299
+ * A directory entry covers everything beneath it. Reference membership grants
300
+ * no target ownership and no instruction-canon membership. Keep this list,
301
+ * {@link HOST_PATHS}, and {@link CANON_PATHS} disjoint by prefix in every
302
+ * direction; duplicate discovery refuses the stage.
303
+ */
304
+ var REFERENCE_PATHS = Object.freeze(["guides"]);
305
+ /**
306
+ * Lists the guide paths a generated workspace starts with, frozen.
307
+ *
308
+ * @remarks
309
+ * `blueprintToHostArtifacts` claims these mirrors from {@link REFERENCE_PATHS}.
310
+ * `selectHostPaths` excludes the workspace's own guide from that claim.
311
+ */
312
+ var SEED_GUIDE_PATHS = Object.freeze(["guides/guide.md", "guides/scaffold.md"]);
297
313
  /** Names the repository-relative path where the committed vendored-file inventory is served. */
298
314
  var HOST_INVENTORY_PATH = "host.json";
299
315
  /**
@@ -3842,7 +3858,7 @@ function srcToRoot(src) {
3842
3858
  * @returns Every candidate except the workspace's own guide, in input order.
3843
3859
  *
3844
3860
  * @remarks
3845
- * `HOST_PATHS` is a candidate set rather than a plan, because a workspace never
3861
+ * The candidates include the seed guide mirrors beside `HOST_PATHS`. A workspace never
3846
3862
  * mirrors its own guide: that file is the workspace's own product, and vendoring
3847
3863
  * it would have the target overwrite its guide with the copy it published.
3848
3864
  *
@@ -5625,7 +5641,7 @@ function blueprintToOrchestrationArtifacts(blueprint) {
5625
5641
  *
5626
5642
  * @param blueprint - The workspace specification.
5627
5643
  * @returns One artifact per selected vendored path in `HOST_PATHS` order, then
5628
- * the catalog file.
5644
+ * the seed guide mirrors and the catalog file.
5629
5645
  *
5630
5646
  * @remarks
5631
5647
  * Every artifact is claimed by presence, which is the strongest claim a pure
@@ -5648,6 +5664,10 @@ function blueprintToOrchestrationArtifacts(blueprint) {
5648
5664
  * from the installed package, at the locations the `AGENTS.md` and `CLAUDE.md`
5649
5665
  * pointers {@link blueprintToDocumentArtifacts} emits name.
5650
5666
  *
5667
+ * The {@link SEED_GUIDE_PATHS} mirrors are claimed explicitly
5668
+ * from `REFERENCE_PATHS`. The target's own guide is excluded by
5669
+ * {@link selectHostPaths}; the remaining reference guides grant no target claim.
5670
+ *
5651
5671
  * @example
5652
5672
  * ```ts
5653
5673
  * import { blueprintToHostArtifacts, createBlueprint } from '@orkestrel/scaffold'
@@ -5659,7 +5679,7 @@ function blueprintToOrchestrationArtifacts(blueprint) {
5659
5679
  * ```
5660
5680
  */
5661
5681
  function blueprintToHostArtifacts(blueprint) {
5662
- return [...selectHostPaths(HOST_PATHS, blueprint.name), CATALOG_AGENT_PATH].map((path) => ({
5682
+ return [...selectHostPaths([...HOST_PATHS, ...SEED_GUIDE_PATHS], blueprint.name), CATALOG_AGENT_PATH].map((path) => ({
5663
5683
  path,
5664
5684
  group: inferGroup(path),
5665
5685
  ownership: "presence",
@@ -6989,7 +7009,9 @@ exports.ORCHESTRATION_PATH_NAMES = ORCHESTRATION_PATH_NAMES;
6989
7009
  exports.ORCHESTRATION_PATH_PREFIXES = ORCHESTRATION_PATH_PREFIXES;
6990
7010
  exports.ORKESTREL_RANGE_PATTERN = ORKESTREL_RANGE_PATTERN;
6991
7011
  exports.PRINT_WIDTH = PRINT_WIDTH;
7012
+ exports.REFERENCE_PATHS = REFERENCE_PATHS;
6992
7013
  exports.RELEASE_PROOF_COMMAND = RELEASE_PROOF_COMMAND;
7014
+ exports.SEED_GUIDE_PATHS = SEED_GUIDE_PATHS;
6993
7015
  exports.SERVICE_SCRIPT_PATH = SERVICE_SCRIPT_PATH;
6994
7016
  exports.SERVICE_SETUP_PATH = SERVICE_SETUP_PATH;
6995
7017
  exports.SERVICE_TEST_INCLUDE = SERVICE_TEST_INCLUDE;