Initial publish v0.1.0: standalone workflow core (corpus + examples + guards)
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, "..")
|
||||
const MANIFEST_PATH = path.join(ROOT, "core", "CORE-MANIFEST.json")
|
||||
const CORE_ROOT = path.join(ROOT, "core")
|
||||
|
||||
export const HARD_PATTERNS: RegExp[] = [
|
||||
/\bins24\b/,
|
||||
/dev\.eightarms\.net/,
|
||||
/10\.\d+\.\d+\.\d+/,
|
||||
/192\.168\.\d+\.\d+/,
|
||||
/172\.(1[6-9]|2\d|3[01])\.\d+\.\d+/,
|
||||
/~\/\.config\/octopus/,
|
||||
]
|
||||
|
||||
export const SOFT_PATTERNS: RegExp[] = [
|
||||
/\.octopus\//,
|
||||
/examples\//,
|
||||
/\/data\/git/,
|
||||
/\bins24\b/,
|
||||
/dev\.eightarms\.net/,
|
||||
/gitea_wiki__/,
|
||||
/gitea_issue_comment__/,
|
||||
/gitea_column__/,
|
||||
/gitea_project__/,
|
||||
/packages\/octopus/,
|
||||
/~\/\.config\/octopus/,
|
||||
]
|
||||
|
||||
export const INSTANCE_PATTERNS: RegExp[] = SOFT_PATTERNS
|
||||
|
||||
export function stripCodeSpans(line: string): string {
|
||||
return line.replace(/`[^`]*`/g, "")
|
||||
}
|
||||
|
||||
export function stripFencedBlocks(lines: string[]): string[] {
|
||||
let inFence = false
|
||||
return lines.filter((line) => {
|
||||
if (line.trimStart().startsWith("```")) {
|
||||
inFence = !inFence
|
||||
return false
|
||||
}
|
||||
return !inFence
|
||||
})
|
||||
}
|
||||
|
||||
function scanFile(relFile: string, patterns: RegExp[]): string[] {
|
||||
const file = path.join(ROOT, relFile)
|
||||
const lines = stripFencedBlocks(fs.readFileSync(file, "utf8").split("\n"))
|
||||
const violations: string[] = []
|
||||
lines.forEach((line) => {
|
||||
const effective = stripCodeSpans(line)
|
||||
for (const pattern of patterns) {
|
||||
if (pattern.test(effective)) {
|
||||
violations.push(`${relFile}: ${pattern.source}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
return violations
|
||||
}
|
||||
|
||||
export function findInstanceViolations(relFile: string): string[] {
|
||||
return scanFile(relFile, INSTANCE_PATTERNS)
|
||||
}
|
||||
|
||||
export function findHardViolations(relFile: string): string[] {
|
||||
return scanFile(relFile, HARD_PATTERNS)
|
||||
}
|
||||
|
||||
export function scanContent(relFile: string, content: string, patterns: RegExp[]): string[] {
|
||||
const lines = stripFencedBlocks(content.split("\n"))
|
||||
const violations: string[] = []
|
||||
lines.forEach((line) => {
|
||||
const effective = stripCodeSpans(line)
|
||||
for (const pattern of patterns) {
|
||||
if (pattern.test(effective)) {
|
||||
violations.push(`${relFile}: ${pattern.source}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
return violations
|
||||
}
|
||||
|
||||
function findPatternsFor(relFile: string): RegExp[] {
|
||||
if (relFile.startsWith("core/adapters/")) return HARD_PATTERNS
|
||||
if (relFile === "core/README.md" || relFile === "core/MIGRATION.md")
|
||||
return [...HARD_PATTERNS, ...SOFT_PATTERNS.filter((p) => p.source !== "examples\\/")]
|
||||
return [...HARD_PATTERNS, ...SOFT_PATTERNS]
|
||||
}
|
||||
|
||||
interface Mapping {
|
||||
core: string
|
||||
dogfood: string | null
|
||||
sync?: string
|
||||
deferHard?: boolean
|
||||
}
|
||||
|
||||
function fail(message: string): never {
|
||||
console.error(`check-core-cohesion: ${message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function listMarkdownFiles(dir: string, base = ""): string[] {
|
||||
const out: string[] = []
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const rel = base ? `${base}/${entry.name}` : entry.name
|
||||
if (entry.isDirectory()) out.push(...listMarkdownFiles(path.join(dir, entry.name), rel))
|
||||
else if (entry.name.endsWith(".md") || entry.name.endsWith(".yaml")) out.push(rel)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
const STRICT = process.argv.includes("--strict")
|
||||
|
||||
if (!fs.existsSync(MANIFEST_PATH)) fail(`manifest not found: ${MANIFEST_PATH}`)
|
||||
let mappings: Mapping[]
|
||||
try {
|
||||
mappings = (JSON.parse(fs.readFileSync(MANIFEST_PATH, "utf8")) as { mappings: Mapping[] }).mappings
|
||||
} catch (e) {
|
||||
fail(`manifest is not valid JSON: ${(e as Error).message}`)
|
||||
}
|
||||
if (!Array.isArray(mappings) || mappings.length === 0) fail("manifest has no mappings")
|
||||
|
||||
const missing: string[] = []
|
||||
for (const m of mappings) {
|
||||
if (!fs.existsSync(path.join(ROOT, m.core))) missing.push(m.core)
|
||||
}
|
||||
if (missing.length > 0) {
|
||||
console.error("check-core-cohesion: manifest core path(s) missing:")
|
||||
for (const p of missing) console.error(` ${p}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const violations: string[] = []
|
||||
const exempt = new Set<string>()
|
||||
for (const m of mappings) {
|
||||
if (m.sync !== "verbatim" && m.sync !== "verbatimDir") continue
|
||||
if (m.sync === "verbatimDir") {
|
||||
const dir = m.core.endsWith("/") ? m.core : `${m.core}/`
|
||||
for (const f of listDir(dir)) exempt.add(`${dir}${f}`)
|
||||
} else {
|
||||
exempt.add(m.core)
|
||||
}
|
||||
}
|
||||
const mdFiles = listMarkdownFiles(CORE_ROOT).map((f) => `core/${f}`)
|
||||
const scanned = mdFiles.filter((rel) => !exempt.has(rel) && !exempt.has(rel.replace(/^core\//, "")))
|
||||
for (const rel of scanned) {
|
||||
violations.push(...scanFile(rel, findPatternsFor(rel)))
|
||||
}
|
||||
|
||||
if (STRICT) {
|
||||
let deferred = 0
|
||||
let strictScanned = 0
|
||||
for (const m of mappings) {
|
||||
if (m.sync !== "verbatim") continue
|
||||
if (m.deferHard) {
|
||||
deferred++
|
||||
continue
|
||||
}
|
||||
strictScanned++
|
||||
violations.push(...scanFile(m.core, INSTANCE_PATTERNS))
|
||||
}
|
||||
console.log(`check-core-cohesion: strict verbatim scan: ${strictScanned} scanned, ${deferred} deferred (deferHard)`)
|
||||
}
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error("check-core-cohesion: C-1 violation(s):")
|
||||
for (const v of violations) console.error(` ${v}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(
|
||||
`check-core-cohesion: ${mappings.length} mappings present, ${scanned.length}/${mdFiles.length} core markdown files scanned, 0 violations`,
|
||||
)
|
||||
}
|
||||
|
||||
export function listCorpus(relDir: string, base = ""): string[] {
|
||||
const out: string[] = []
|
||||
for (const entry of fs.readdirSync(path.join(ROOT, relDir), { withFileTypes: true })) {
|
||||
const rel = base ? `${base}/${entry.name}` : entry.name
|
||||
if (entry.isDirectory()) out.push(...listCorpus(path.join(relDir, entry.name), rel))
|
||||
else if (entry.name.endsWith(".md") || entry.name.endsWith(".yaml")) out.push(rel)
|
||||
}
|
||||
return out.sort()
|
||||
}
|
||||
|
||||
export function listDir(relDir: string, base = ""): string[] {
|
||||
const out: string[] = []
|
||||
for (const entry of fs.readdirSync(path.join(ROOT, relDir), { withFileTypes: true })) {
|
||||
const rel = base ? `${base}/${entry.name}` : entry.name
|
||||
if (entry.isDirectory()) out.push(...listDir(path.join(relDir, entry.name), rel))
|
||||
else if (entry.name !== ".gitkeep") out.push(rel)
|
||||
}
|
||||
return out.sort()
|
||||
}
|
||||
|
||||
export function collectVerbatimDirFiles(m: Mapping): { side: string; files: string[] } | null {
|
||||
if (m.sync !== "verbatimDir") return null
|
||||
const rel = m.core.endsWith("/") ? m.core : `${m.core}/`
|
||||
return { side: rel, files: listDir(rel) }
|
||||
}
|
||||
Reference in New Issue
Block a user