7 nye moduler:
- lib/util/result.mjs — Result-shape m/ ok/fail/combine helpers
- lib/util/frontmatter.mjs — håndruller YAML-frontmatter-parser (subset, zero deps)
- lib/parsers/plan-schema.mjs — v1.7 step-regex + forbidden-heading-deteksjon (Fase/Phase/Stage/Steg)
- lib/parsers/manifest-yaml.mjs — per-step Manifest YAML-ekstraksjon m/ regex-validering
- lib/parsers/project-discovery.mjs — finn brief/research/architecture/plan/progress i prosjektmappe
- lib/parsers/arg-parser.mjs — $ARGUMENTS for alle 4 commands m/ flag-schema
- lib/parsers/bash-normalize.mjs — løftet fra hooks/scripts/pre-bash-executor.mjs
6 test-filer (66 tester totalt) — alle grønn:
- frontmatter (CRLF/BOM, scalars, lister, indent-rejection)
- plan-schema (positive Step-form, negative Fase/Phase/Stage/Steg, numbering, slicing)
- manifest-yaml (extraction, parsing, regex-validering, missing-key detection)
- project-discovery (sortert research, architecture-detection, phase-requirements)
- arg-parser (boolean/valued/multi-value flags, kvotert positional, ukjente flag)
- bash-normalize (\${x}/\\\\evasion, ANSI-stripping, full canonicalize-pipeline)
Forbereder Wave 2 (validators) og Spor 1-wiring inn i commands.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
89 lines
3 KiB
JavaScript
89 lines
3 KiB
JavaScript
// lib/parsers/project-discovery.mjs
|
|
// Discover ultra-suite artifacts inside a project directory.
|
|
//
|
|
// Layout (post-v3.0.0 project-directory contract):
|
|
// .claude/projects/<YYYY-MM-DD>-<slug>/
|
|
// brief.md
|
|
// research/<NN>-<slug>.md (sorted by filename)
|
|
// architecture/overview.md (opt-in, owned by separate ultra-cc-architect plugin)
|
|
// plan.md
|
|
// progress.json
|
|
|
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
/**
|
|
* @typedef {{
|
|
* projectDir: string,
|
|
* brief: string|null,
|
|
* research: string[],
|
|
* architecture: { overview: string|null, gaps: string|null, looseFiles: string[] },
|
|
* plan: string|null,
|
|
* progress: string|null,
|
|
* }} ProjectArtifacts
|
|
*/
|
|
|
|
/** @returns {ProjectArtifacts} */
|
|
export function discoverProject(projectDir) {
|
|
const out = {
|
|
projectDir,
|
|
brief: null,
|
|
research: [],
|
|
architecture: { overview: null, gaps: null, looseFiles: [] },
|
|
plan: null,
|
|
progress: null,
|
|
};
|
|
|
|
if (!projectDir || !existsSync(projectDir) || !statSync(projectDir).isDirectory()) {
|
|
return out;
|
|
}
|
|
|
|
const briefPath = join(projectDir, 'brief.md');
|
|
if (existsSync(briefPath) && statSync(briefPath).isFile()) out.brief = briefPath;
|
|
|
|
const planPath = join(projectDir, 'plan.md');
|
|
if (existsSync(planPath) && statSync(planPath).isFile()) out.plan = planPath;
|
|
|
|
const progressPath = join(projectDir, 'progress.json');
|
|
if (existsSync(progressPath) && statSync(progressPath).isFile()) out.progress = progressPath;
|
|
|
|
const researchDir = join(projectDir, 'research');
|
|
if (existsSync(researchDir) && statSync(researchDir).isDirectory()) {
|
|
out.research = readdirSync(researchDir)
|
|
.filter(f => f.endsWith('.md'))
|
|
.sort()
|
|
.map(f => join(researchDir, f));
|
|
}
|
|
|
|
const archDir = join(projectDir, 'architecture');
|
|
if (existsSync(archDir) && statSync(archDir).isDirectory()) {
|
|
const overviewPath = join(archDir, 'overview.md');
|
|
const gapsPath = join(archDir, 'gaps.md');
|
|
if (existsSync(overviewPath)) out.architecture.overview = overviewPath;
|
|
if (existsSync(gapsPath)) out.architecture.gaps = gapsPath;
|
|
const all = readdirSync(archDir).filter(f => f.endsWith('.md'));
|
|
out.architecture.looseFiles = all
|
|
.filter(f => f !== 'overview.md' && f !== 'gaps.md')
|
|
.map(f => join(archDir, f));
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Validate that artifact set is consistent for a given pipeline phase.
|
|
* Phase = 'brief' | 'research' | 'plan' | 'execute'.
|
|
*/
|
|
export function checkPhaseRequirements(artifacts, phase) {
|
|
const errors = [];
|
|
if (phase === 'research' && !artifacts.brief) {
|
|
errors.push({ code: 'PROJECT_NO_BRIEF', message: 'research phase requires brief.md' });
|
|
}
|
|
if (phase === 'plan' && !artifacts.brief) {
|
|
errors.push({ code: 'PROJECT_NO_BRIEF', message: 'plan phase requires brief.md' });
|
|
}
|
|
if (phase === 'execute' && !artifacts.plan) {
|
|
errors.push({ code: 'PROJECT_NO_PLAN', message: 'execute phase requires plan.md' });
|
|
}
|
|
return { valid: errors.length === 0, errors, warnings: [], parsed: artifacts };
|
|
}
|