Spaces:
Runtime error
Runtime error
Aryan Mishra
feat: initial commit - Multilingual ABSA project setup with 6 phases, 36 requirements
a6b96c2 | ; | |
| /** | |
| * Docs β Commands for the docs-update workflow | |
| * | |
| * Provides `cmdDocsInit` which returns project signals, existing doc inventory | |
| * with GSD marker detection, doc tooling detection, monorepo awareness, and | |
| * model resolution. Used by Phase 2 to route doc generation appropriately. | |
| * | |
| * ADR-457 build-at-publish: the hand-written bin/lib/docs.cjs collapsed | |
| * to a TypeScript source of truth. Behaviour is preserved byte-for-behaviour | |
| * from the prior hand-written .cjs; only strict types are added. | |
| */ | |
| var __importDefault = (this && this.__importDefault) || function (mod) { | |
| return (mod && mod.__esModule) ? mod : { "default": mod }; | |
| }; | |
| const node_fs_1 = __importDefault(require("node:fs")); | |
| const node_path_1 = __importDefault(require("node:path")); | |
| // eslint-disable-next-line @typescript-eslint/no-require-imports | |
| const io = require("./io.cjs"); | |
| const { output } = io; | |
| // eslint-disable-next-line @typescript-eslint/no-require-imports | |
| const configLoader = require("./config-loader.cjs"); | |
| const { loadConfig } = configLoader; | |
| // eslint-disable-next-line @typescript-eslint/no-require-imports | |
| const modelResolver = require("./model-resolver.cjs"); | |
| const { resolveModelInternal } = modelResolver; | |
| // eslint-disable-next-line @typescript-eslint/no-require-imports | |
| const coreUtils = require("./core-utils.cjs"); | |
| const { pathExistsInternal, toPosixPath } = coreUtils; | |
| // eslint-disable-next-line @typescript-eslint/no-require-imports | |
| const agentInstallCheck = require("./agent-install-check.cjs"); | |
| const { checkAgentsInstalled } = agentInstallCheck; | |
| const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs"); | |
| // βββ Constants ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const GSD_MARKER = '<!-- generated-by: gsd-doc-writer -->'; | |
| const SKIP_DIRS = new Set([ | |
| 'node_modules', '.git', '.planning', '.claude', '__pycache__', | |
| 'target', 'dist', 'build', '.next', '.nuxt', 'coverage', | |
| '.vscode', '.idea', | |
| ]); | |
| // βββ Private helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Check whether a file begins with the GSD doc writer marker. | |
| * Reads the first 500 bytes only β avoids loading large files. | |
| */ | |
| function hasGsdMarker(filePath) { | |
| try { | |
| const buf = Buffer.alloc(500); | |
| const fd = node_fs_1.default.openSync(filePath, 'r'); | |
| const bytesRead = node_fs_1.default.readSync(fd, buf, 0, 500, 0); | |
| node_fs_1.default.closeSync(fd); | |
| return buf.slice(0, bytesRead).toString('utf-8').includes(GSD_MARKER); | |
| } | |
| catch { | |
| return false; | |
| } | |
| } | |
| /** | |
| * Recursively scan the project root (immediate .md files) and docs/ directory | |
| * (up to 4 levels deep) for Markdown files, excluding dirs in SKIP_DIRS. | |
| */ | |
| function scanExistingDocs(cwd) { | |
| const MAX_DEPTH = 4; | |
| const results = []; | |
| function walkDir(dir, depth) { | |
| if (depth > MAX_DEPTH) | |
| return; | |
| try { | |
| const entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true }); | |
| for (const entry of entries) { | |
| if (SKIP_DIRS.has(entry.name)) | |
| continue; | |
| const abs = node_path_1.default.join(dir, entry.name); | |
| if (entry.isDirectory()) { | |
| walkDir(abs, depth + 1); | |
| } | |
| else if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) { | |
| const rel = toPosixPath(node_path_1.default.relative(cwd, abs)); | |
| results.push({ path: rel, has_gsd_marker: hasGsdMarker(abs) }); | |
| } | |
| } | |
| } | |
| catch { /* directory may not exist β best-effort */ } | |
| } | |
| // Scan root-level .md files (non-recursive) | |
| try { | |
| const entries = node_fs_1.default.readdirSync(cwd, { withFileTypes: true }); | |
| for (const entry of entries) { | |
| if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) { | |
| const abs = node_path_1.default.join(cwd, entry.name); | |
| const rel = toPosixPath(node_path_1.default.relative(cwd, abs)); | |
| results.push({ path: rel, has_gsd_marker: hasGsdMarker(abs) }); | |
| } | |
| } | |
| } | |
| catch { /* best-effort */ } | |
| // Recursively scan docs/ directory | |
| const docsDir = node_path_1.default.join(cwd, 'docs'); | |
| walkDir(docsDir, 1); | |
| // Fallback: if docs/ does not exist, try documentation/ or doc/ | |
| try { | |
| node_fs_1.default.statSync(docsDir); | |
| } | |
| catch { | |
| const alternatives = ['documentation', 'doc']; | |
| for (const alt of alternatives) { | |
| const altDir = node_path_1.default.join(cwd, alt); | |
| try { | |
| const stat = node_fs_1.default.statSync(altDir); | |
| if (stat.isDirectory()) { | |
| walkDir(altDir, 1); | |
| break; | |
| } | |
| } | |
| catch { /* not present */ } | |
| } | |
| } | |
| return results.sort((a, b) => a.path.localeCompare(b.path)); | |
| } | |
| /** | |
| * Detect project type signals from the filesystem and package.json. | |
| * All checks are best-effort and never throw. | |
| */ | |
| function detectProjectType(cwd) { | |
| const exists = (rel) => { | |
| try { | |
| return pathExistsInternal(cwd, rel); | |
| } | |
| catch { | |
| return false; | |
| } | |
| }; | |
| // Read package.json once β used by has_cli_bin, is_monorepo, has_tests checks. | |
| const pkgRaw = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(cwd, 'package.json')); | |
| let pkg = null; | |
| if (pkgRaw) { | |
| try { | |
| pkg = JSON.parse(pkgRaw); | |
| } | |
| catch { /* invalid JSON */ } | |
| } | |
| // has_cli_bin: package.json has a `bin` field | |
| const binField = pkg?.['bin']; | |
| const has_cli_bin = !!(binField && (typeof binField === 'string' || | |
| (typeof binField === 'object' && Object.keys(binField).length > 0))); | |
| // is_monorepo: pnpm-workspace.yaml, lerna.json, or package.json workspaces | |
| let is_monorepo = exists('pnpm-workspace.yaml') || exists('lerna.json'); | |
| if (!is_monorepo && pkg) { | |
| is_monorepo = Array.isArray(pkg['workspaces']) && pkg['workspaces'].length > 0; | |
| } | |
| // has_tests: common test directories or test frameworks in devDependencies | |
| let has_tests = exists('test') || exists('tests') || exists('__tests__') || exists('spec'); | |
| if (!has_tests && pkg) { | |
| const devDeps = Object.keys(pkg['devDependencies'] || {}); | |
| has_tests = devDeps.some(d => ['vitest', 'jest', 'mocha', 'jasmine', 'ava'].includes(d)); | |
| } | |
| // has_deploy_config: various deployment config files | |
| const deployFiles = [ | |
| 'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', | |
| 'fly.toml', 'render.yaml', 'vercel.json', 'netlify.toml', 'railway.json', | |
| '.github/workflows/deploy.yml', '.github/workflows/deploy.yaml', | |
| ]; | |
| const has_deploy_config = deployFiles.some(f => exists(f)); | |
| return { | |
| has_package_json: exists('package.json'), | |
| has_api_routes: (exists('src/app/api') || exists('routes') || exists('src/routes') || | |
| exists('api') || exists('server')), | |
| has_cli_bin, | |
| is_open_source: exists('LICENSE') || exists('LICENSE.md'), | |
| has_deploy_config, | |
| is_monorepo, | |
| has_tests, | |
| }; | |
| } | |
| /** | |
| * Detect known documentation tooling in the project. | |
| */ | |
| function detectDocTooling(cwd) { | |
| const exists = (rel) => { | |
| try { | |
| return pathExistsInternal(cwd, rel); | |
| } | |
| catch { | |
| return false; | |
| } | |
| }; | |
| return { | |
| docusaurus: exists('docusaurus.config.js') || exists('docusaurus.config.ts'), | |
| vitepress: (exists('.vitepress/config.js') || | |
| exists('.vitepress/config.ts') || | |
| exists('.vitepress/config.mts')), | |
| mkdocs: exists('mkdocs.yml'), | |
| storybook: exists('.storybook'), | |
| }; | |
| } | |
| /** | |
| * Extract monorepo workspace globs from pnpm-workspace.yaml, package.json | |
| * workspaces, or lerna.json. | |
| */ | |
| function detectMonorepoWorkspaces(cwd) { | |
| // pnpm-workspace.yaml | |
| const pnpmRaw = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(cwd, 'pnpm-workspace.yaml')); | |
| if (pnpmRaw) { | |
| const workspaces = []; | |
| for (const line of pnpmRaw.split('\n')) { | |
| const m = line.match(/^\s*-\s+['"]?(.+?)['"]?\s*$/); | |
| if (m) | |
| workspaces.push(m[1].trim()); | |
| } | |
| if (workspaces.length > 0) | |
| return workspaces; | |
| } | |
| // package.json workspaces | |
| const pkgRaw = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(cwd, 'package.json')); | |
| if (pkgRaw) { | |
| try { | |
| const pkg = JSON.parse(pkgRaw); | |
| if (Array.isArray(pkg['workspaces']) && pkg['workspaces'].length > 0) { | |
| return pkg['workspaces']; | |
| } | |
| } | |
| catch { /* invalid JSON */ } | |
| } | |
| // lerna.json | |
| const lernaRaw = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(cwd, 'lerna.json')); | |
| if (lernaRaw) { | |
| try { | |
| const lerna = JSON.parse(lernaRaw); | |
| if (Array.isArray(lerna['packages']) && lerna['packages'].length > 0) { | |
| return lerna['packages']; | |
| } | |
| } | |
| catch { /* invalid JSON */ } | |
| } | |
| return []; | |
| } | |
| // βββ Public commands ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Return JSON context for the docs-update workflow: project signals, existing | |
| * doc inventory, doc tooling detection, monorepo workspaces, and model | |
| * resolution. Follows the cmdInitMapCodebase pattern. | |
| * | |
| * @example | |
| * node gsd-tools.cjs docs-init --raw | |
| */ | |
| function cmdDocsInit(cwd, raw) { | |
| const config = loadConfig(cwd); | |
| const result = { | |
| doc_writer_model: resolveModelInternal(cwd, 'gsd-doc-writer'), | |
| commit_docs: config.commit_docs, | |
| existing_docs: scanExistingDocs(cwd), | |
| project_type: detectProjectType(cwd), | |
| doc_tooling: detectDocTooling(cwd), | |
| monorepo_workspaces: detectMonorepoWorkspaces(cwd), | |
| planning_exists: pathExistsInternal(cwd, '.planning'), | |
| }; | |
| // Inject project_root and agent installation status (mirrors withProjectRoot in init.cjs) | |
| result['project_root'] = cwd; | |
| const agentStatus = checkAgentsInstalled(); | |
| result['agents_installed'] = agentStatus.agents_installed; | |
| result['missing_agents'] = agentStatus.missing_agents; | |
| output(result, raw, undefined); | |
| } | |
| module.exports = { cmdDocsInit }; | |