Includes: CLAUDE.md, settings.json, agents, commands, rules, skills, hooks, contexts, evals, get-shit-done, plugin configs (installed list and marketplace sources). Excludes credentials, runtime caches, telemetry, session data, and plugin binary cache.
723 lines
29 KiB
JavaScript
723 lines
29 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* GSD Tools — CLI utility for GSD workflow operations
|
|
*
|
|
* Replaces repetitive inline bash patterns across ~50 GSD command/workflow/agent files.
|
|
* Centralizes: config parsing, model resolution, phase lookup, git commits, summary verification.
|
|
*
|
|
* Usage: node gsd-tools.cjs <command> [args] [--raw]
|
|
*
|
|
* Atomic Commands:
|
|
* state load Load project config + state
|
|
* state json Output STATE.md frontmatter as JSON
|
|
* state update <field> <value> Update a STATE.md field
|
|
* state get [section] Get STATE.md content or section
|
|
* state patch --field val ... Batch update STATE.md fields
|
|
* state begin-phase --phase N --name S --plans C Update STATE.md for new phase start
|
|
* state signal-waiting --type T --question Q --options "A|B" --phase P Write WAITING.json signal
|
|
* state signal-resume Remove WAITING.json signal
|
|
* resolve-model <agent-type> Get model for agent based on profile
|
|
* find-phase <phase> Find phase directory by number
|
|
* commit <message> [--files f1 f2] Commit planning docs
|
|
* verify-summary <path> Verify a SUMMARY.md file
|
|
* generate-slug <text> Convert text to URL-safe slug
|
|
* current-timestamp [format] Get timestamp (full|date|filename)
|
|
* list-todos [area] Count and enumerate pending todos
|
|
* verify-path-exists <path> Check file/directory existence
|
|
* config-ensure-section Initialize .planning/config.json
|
|
* history-digest Aggregate all SUMMARY.md data
|
|
* summary-extract <path> [--fields] Extract structured data from SUMMARY.md
|
|
* state-snapshot Structured parse of STATE.md
|
|
* phase-plan-index <phase> Index plans with waves and status
|
|
* websearch <query> Search web via Brave API (if configured)
|
|
* [--limit N] [--freshness day|week|month]
|
|
*
|
|
* Phase Operations:
|
|
* phase next-decimal <phase> Calculate next decimal phase number
|
|
* phase add <description> Append new phase to roadmap + create dir
|
|
* phase insert <after> <description> Insert decimal phase after existing
|
|
* phase remove <phase> [--force] Remove phase, renumber all subsequent
|
|
* phase complete <phase> Mark phase done, update state + roadmap
|
|
*
|
|
* Roadmap Operations:
|
|
* roadmap get-phase <phase> Extract phase section from ROADMAP.md
|
|
* roadmap analyze Full roadmap parse with disk status
|
|
* roadmap update-plan-progress <N> Update progress table row from disk (PLAN vs SUMMARY counts)
|
|
*
|
|
* Requirements Operations:
|
|
* requirements mark-complete <ids> Mark requirement IDs as complete in REQUIREMENTS.md
|
|
* Accepts: REQ-01,REQ-02 or REQ-01 REQ-02 or [REQ-01, REQ-02]
|
|
*
|
|
* Milestone Operations:
|
|
* milestone complete <version> Archive milestone, create MILESTONES.md
|
|
* [--name <name>]
|
|
* [--archive-phases] Move phase dirs to milestones/vX.Y-phases/
|
|
*
|
|
* Validation:
|
|
* validate consistency Check phase numbering, disk/roadmap sync
|
|
* validate health [--repair] Check .planning/ integrity, optionally repair
|
|
*
|
|
* Progress:
|
|
* progress [json|table|bar] Render progress in various formats
|
|
*
|
|
* Todos:
|
|
* todo complete <filename> Move todo from pending to completed
|
|
*
|
|
* Scaffolding:
|
|
* scaffold context --phase <N> Create CONTEXT.md template
|
|
* scaffold uat --phase <N> Create UAT.md template
|
|
* scaffold verification --phase <N> Create VERIFICATION.md template
|
|
* scaffold phase-dir --phase <N> Create phase directory
|
|
* --name <name>
|
|
*
|
|
* Frontmatter CRUD:
|
|
* frontmatter get <file> [--field k] Extract frontmatter as JSON
|
|
* frontmatter set <file> --field k Update single frontmatter field
|
|
* --value jsonVal
|
|
* frontmatter merge <file> Merge JSON into frontmatter
|
|
* --data '{json}'
|
|
* frontmatter validate <file> Validate required fields
|
|
* --schema plan|summary|verification
|
|
*
|
|
* Verification Suite:
|
|
* verify plan-structure <file> Check PLAN.md structure + tasks
|
|
* verify phase-completeness <phase> Check all plans have summaries
|
|
* verify references <file> Check @-refs + paths resolve
|
|
* verify commits <h1> [h2] ... Batch verify commit hashes
|
|
* verify artifacts <plan-file> Check must_haves.artifacts
|
|
* verify key-links <plan-file> Check must_haves.key_links
|
|
*
|
|
* Template Fill:
|
|
* template fill summary --phase N Create pre-filled SUMMARY.md
|
|
* [--plan M] [--name "..."]
|
|
* [--fields '{json}']
|
|
* template fill plan --phase N Create pre-filled PLAN.md
|
|
* [--plan M] [--type execute|tdd]
|
|
* [--wave N] [--fields '{json}']
|
|
* template fill verification Create pre-filled VERIFICATION.md
|
|
* --phase N [--fields '{json}']
|
|
*
|
|
* State Progression:
|
|
* state advance-plan Increment plan counter
|
|
* state record-metric --phase N Record execution metrics
|
|
* --plan M --duration Xmin
|
|
* [--tasks N] [--files N]
|
|
* state update-progress Recalculate progress bar
|
|
* state add-decision --summary "..." Add decision to STATE.md
|
|
* [--phase N] [--rationale "..."]
|
|
* [--summary-file path] [--rationale-file path]
|
|
* state add-blocker --text "..." Add blocker
|
|
* [--text-file path]
|
|
* state resolve-blocker --text "..." Remove blocker
|
|
* state record-session Update session continuity
|
|
* --stopped-at "..."
|
|
* [--resume-file path]
|
|
*
|
|
* Compound Commands (workflow-specific initialization):
|
|
* init execute-phase <phase> All context for execute-phase workflow
|
|
* init plan-phase <phase> All context for plan-phase workflow
|
|
* init new-project All context for new-project workflow
|
|
* init new-milestone All context for new-milestone workflow
|
|
* init quick <description> All context for quick workflow
|
|
* init resume All context for resume-project workflow
|
|
* init verify-work <phase> All context for verify-work workflow
|
|
* init phase-op <phase> Generic phase operation context
|
|
* init todos [area] All context for todo workflows
|
|
* init milestone-op All context for milestone operations
|
|
* init map-codebase All context for map-codebase workflow
|
|
* init progress All context for progress workflow
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { error } = require('./lib/core.cjs');
|
|
const state = require('./lib/state.cjs');
|
|
const phase = require('./lib/phase.cjs');
|
|
const roadmap = require('./lib/roadmap.cjs');
|
|
const verify = require('./lib/verify.cjs');
|
|
const config = require('./lib/config.cjs');
|
|
const template = require('./lib/template.cjs');
|
|
const milestone = require('./lib/milestone.cjs');
|
|
const commands = require('./lib/commands.cjs');
|
|
const init = require('./lib/init.cjs');
|
|
const frontmatter = require('./lib/frontmatter.cjs');
|
|
const profilePipeline = require('./lib/profile-pipeline.cjs');
|
|
const profileOutput = require('./lib/profile-output.cjs');
|
|
|
|
// ─── CLI Router ───────────────────────────────────────────────────────────────
|
|
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
|
|
// Optional cwd override for sandboxed subagents running outside project root.
|
|
let cwd = process.cwd();
|
|
const cwdEqArg = args.find(arg => arg.startsWith('--cwd='));
|
|
const cwdIdx = args.indexOf('--cwd');
|
|
if (cwdEqArg) {
|
|
const value = cwdEqArg.slice('--cwd='.length).trim();
|
|
if (!value) error('Missing value for --cwd');
|
|
args.splice(args.indexOf(cwdEqArg), 1);
|
|
cwd = path.resolve(value);
|
|
} else if (cwdIdx !== -1) {
|
|
const value = args[cwdIdx + 1];
|
|
if (!value || value.startsWith('--')) error('Missing value for --cwd');
|
|
args.splice(cwdIdx, 2);
|
|
cwd = path.resolve(value);
|
|
}
|
|
|
|
if (!fs.existsSync(cwd) || !fs.statSync(cwd).isDirectory()) {
|
|
error(`Invalid --cwd: ${cwd}`);
|
|
}
|
|
|
|
const rawIndex = args.indexOf('--raw');
|
|
const raw = rawIndex !== -1;
|
|
if (rawIndex !== -1) args.splice(rawIndex, 1);
|
|
|
|
const command = args[0];
|
|
|
|
if (!command) {
|
|
error('Usage: gsd-tools <command> [args] [--raw] [--cwd <path>]\nCommands: state, resolve-model, find-phase, commit, verify-summary, verify, frontmatter, template, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, init');
|
|
}
|
|
|
|
switch (command) {
|
|
case 'state': {
|
|
const subcommand = args[1];
|
|
if (subcommand === 'json') {
|
|
state.cmdStateJson(cwd, raw);
|
|
} else if (subcommand === 'update') {
|
|
state.cmdStateUpdate(cwd, args[2], args[3]);
|
|
} else if (subcommand === 'get') {
|
|
state.cmdStateGet(cwd, args[2], raw);
|
|
} else if (subcommand === 'patch') {
|
|
const patches = {};
|
|
for (let i = 2; i < args.length; i += 2) {
|
|
const key = args[i].replace(/^--/, '');
|
|
const value = args[i + 1];
|
|
if (key && value !== undefined) {
|
|
patches[key] = value;
|
|
}
|
|
}
|
|
state.cmdStatePatch(cwd, patches, raw);
|
|
} else if (subcommand === 'advance-plan') {
|
|
state.cmdStateAdvancePlan(cwd, raw);
|
|
} else if (subcommand === 'record-metric') {
|
|
const phaseIdx = args.indexOf('--phase');
|
|
const planIdx = args.indexOf('--plan');
|
|
const durationIdx = args.indexOf('--duration');
|
|
const tasksIdx = args.indexOf('--tasks');
|
|
const filesIdx = args.indexOf('--files');
|
|
state.cmdStateRecordMetric(cwd, {
|
|
phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
|
|
plan: planIdx !== -1 ? args[planIdx + 1] : null,
|
|
duration: durationIdx !== -1 ? args[durationIdx + 1] : null,
|
|
tasks: tasksIdx !== -1 ? args[tasksIdx + 1] : null,
|
|
files: filesIdx !== -1 ? args[filesIdx + 1] : null,
|
|
}, raw);
|
|
} else if (subcommand === 'update-progress') {
|
|
state.cmdStateUpdateProgress(cwd, raw);
|
|
} else if (subcommand === 'add-decision') {
|
|
const phaseIdx = args.indexOf('--phase');
|
|
const summaryIdx = args.indexOf('--summary');
|
|
const summaryFileIdx = args.indexOf('--summary-file');
|
|
const rationaleIdx = args.indexOf('--rationale');
|
|
const rationaleFileIdx = args.indexOf('--rationale-file');
|
|
state.cmdStateAddDecision(cwd, {
|
|
phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
|
|
summary: summaryIdx !== -1 ? args[summaryIdx + 1] : null,
|
|
summary_file: summaryFileIdx !== -1 ? args[summaryFileIdx + 1] : null,
|
|
rationale: rationaleIdx !== -1 ? args[rationaleIdx + 1] : '',
|
|
rationale_file: rationaleFileIdx !== -1 ? args[rationaleFileIdx + 1] : null,
|
|
}, raw);
|
|
} else if (subcommand === 'add-blocker') {
|
|
const textIdx = args.indexOf('--text');
|
|
const textFileIdx = args.indexOf('--text-file');
|
|
state.cmdStateAddBlocker(cwd, {
|
|
text: textIdx !== -1 ? args[textIdx + 1] : null,
|
|
text_file: textFileIdx !== -1 ? args[textFileIdx + 1] : null,
|
|
}, raw);
|
|
} else if (subcommand === 'resolve-blocker') {
|
|
const textIdx = args.indexOf('--text');
|
|
state.cmdStateResolveBlocker(cwd, textIdx !== -1 ? args[textIdx + 1] : null, raw);
|
|
} else if (subcommand === 'record-session') {
|
|
const stoppedIdx = args.indexOf('--stopped-at');
|
|
const resumeIdx = args.indexOf('--resume-file');
|
|
state.cmdStateRecordSession(cwd, {
|
|
stopped_at: stoppedIdx !== -1 ? args[stoppedIdx + 1] : null,
|
|
resume_file: resumeIdx !== -1 ? args[resumeIdx + 1] : 'None',
|
|
}, raw);
|
|
} else if (subcommand === 'begin-phase') {
|
|
const phaseIdx = args.indexOf('--phase');
|
|
const nameIdx = args.indexOf('--name');
|
|
const plansIdx = args.indexOf('--plans');
|
|
state.cmdStateBeginPhase(
|
|
cwd,
|
|
phaseIdx !== -1 ? args[phaseIdx + 1] : null,
|
|
nameIdx !== -1 ? args[nameIdx + 1] : null,
|
|
plansIdx !== -1 ? parseInt(args[plansIdx + 1], 10) : null,
|
|
raw
|
|
);
|
|
} else if (subcommand === 'signal-waiting') {
|
|
const typeIdx = args.indexOf('--type');
|
|
const qIdx = args.indexOf('--question');
|
|
const optIdx = args.indexOf('--options');
|
|
const phaseIdx = args.indexOf('--phase');
|
|
state.cmdSignalWaiting(
|
|
cwd,
|
|
typeIdx !== -1 ? args[typeIdx + 1] : null,
|
|
qIdx !== -1 ? args[qIdx + 1] : null,
|
|
optIdx !== -1 ? args[optIdx + 1] : null,
|
|
phaseIdx !== -1 ? args[phaseIdx + 1] : null,
|
|
raw
|
|
);
|
|
} else if (subcommand === 'signal-resume') {
|
|
state.cmdSignalResume(cwd, raw);
|
|
} else {
|
|
state.cmdStateLoad(cwd, raw);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'resolve-model': {
|
|
commands.cmdResolveModel(cwd, args[1], raw);
|
|
break;
|
|
}
|
|
|
|
case 'find-phase': {
|
|
phase.cmdFindPhase(cwd, args[1], raw);
|
|
break;
|
|
}
|
|
|
|
case 'commit': {
|
|
const amend = args.includes('--amend');
|
|
const filesIndex = args.indexOf('--files');
|
|
// Collect all positional args between command name and first flag,
|
|
// then join them — handles both quoted ("multi word msg") and
|
|
// unquoted (multi word msg) invocations from different shells
|
|
const endIndex = filesIndex !== -1 ? filesIndex : args.length;
|
|
const messageArgs = args.slice(1, endIndex).filter(a => !a.startsWith('--'));
|
|
const message = messageArgs.join(' ') || undefined;
|
|
const files = filesIndex !== -1 ? args.slice(filesIndex + 1).filter(a => !a.startsWith('--')) : [];
|
|
commands.cmdCommit(cwd, message, files, raw, amend);
|
|
break;
|
|
}
|
|
|
|
case 'verify-summary': {
|
|
const summaryPath = args[1];
|
|
const countIndex = args.indexOf('--check-count');
|
|
const checkCount = countIndex !== -1 ? parseInt(args[countIndex + 1], 10) : 2;
|
|
verify.cmdVerifySummary(cwd, summaryPath, checkCount, raw);
|
|
break;
|
|
}
|
|
|
|
case 'template': {
|
|
const subcommand = args[1];
|
|
if (subcommand === 'select') {
|
|
template.cmdTemplateSelect(cwd, args[2], raw);
|
|
} else if (subcommand === 'fill') {
|
|
const templateType = args[2];
|
|
const phaseIdx = args.indexOf('--phase');
|
|
const planIdx = args.indexOf('--plan');
|
|
const nameIdx = args.indexOf('--name');
|
|
const typeIdx = args.indexOf('--type');
|
|
const waveIdx = args.indexOf('--wave');
|
|
const fieldsIdx = args.indexOf('--fields');
|
|
template.cmdTemplateFill(cwd, templateType, {
|
|
phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
|
|
plan: planIdx !== -1 ? args[planIdx + 1] : null,
|
|
name: nameIdx !== -1 ? args[nameIdx + 1] : null,
|
|
type: typeIdx !== -1 ? args[typeIdx + 1] : 'execute',
|
|
wave: waveIdx !== -1 ? args[waveIdx + 1] : '1',
|
|
fields: fieldsIdx !== -1 ? JSON.parse(args[fieldsIdx + 1]) : {},
|
|
}, raw);
|
|
} else {
|
|
error('Unknown template subcommand. Available: select, fill');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'frontmatter': {
|
|
const subcommand = args[1];
|
|
const file = args[2];
|
|
if (subcommand === 'get') {
|
|
const fieldIdx = args.indexOf('--field');
|
|
frontmatter.cmdFrontmatterGet(cwd, file, fieldIdx !== -1 ? args[fieldIdx + 1] : null, raw);
|
|
} else if (subcommand === 'set') {
|
|
const fieldIdx = args.indexOf('--field');
|
|
const valueIdx = args.indexOf('--value');
|
|
frontmatter.cmdFrontmatterSet(cwd, file, fieldIdx !== -1 ? args[fieldIdx + 1] : null, valueIdx !== -1 ? args[valueIdx + 1] : undefined, raw);
|
|
} else if (subcommand === 'merge') {
|
|
const dataIdx = args.indexOf('--data');
|
|
frontmatter.cmdFrontmatterMerge(cwd, file, dataIdx !== -1 ? args[dataIdx + 1] : null, raw);
|
|
} else if (subcommand === 'validate') {
|
|
const schemaIdx = args.indexOf('--schema');
|
|
frontmatter.cmdFrontmatterValidate(cwd, file, schemaIdx !== -1 ? args[schemaIdx + 1] : null, raw);
|
|
} else {
|
|
error('Unknown frontmatter subcommand. Available: get, set, merge, validate');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'verify': {
|
|
const subcommand = args[1];
|
|
if (subcommand === 'plan-structure') {
|
|
verify.cmdVerifyPlanStructure(cwd, args[2], raw);
|
|
} else if (subcommand === 'phase-completeness') {
|
|
verify.cmdVerifyPhaseCompleteness(cwd, args[2], raw);
|
|
} else if (subcommand === 'references') {
|
|
verify.cmdVerifyReferences(cwd, args[2], raw);
|
|
} else if (subcommand === 'commits') {
|
|
verify.cmdVerifyCommits(cwd, args.slice(2), raw);
|
|
} else if (subcommand === 'artifacts') {
|
|
verify.cmdVerifyArtifacts(cwd, args[2], raw);
|
|
} else if (subcommand === 'key-links') {
|
|
verify.cmdVerifyKeyLinks(cwd, args[2], raw);
|
|
} else {
|
|
error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'generate-slug': {
|
|
commands.cmdGenerateSlug(args[1], raw);
|
|
break;
|
|
}
|
|
|
|
case 'current-timestamp': {
|
|
commands.cmdCurrentTimestamp(args[1] || 'full', raw);
|
|
break;
|
|
}
|
|
|
|
case 'list-todos': {
|
|
commands.cmdListTodos(cwd, args[1], raw);
|
|
break;
|
|
}
|
|
|
|
case 'verify-path-exists': {
|
|
commands.cmdVerifyPathExists(cwd, args[1], raw);
|
|
break;
|
|
}
|
|
|
|
case 'config-ensure-section': {
|
|
config.cmdConfigEnsureSection(cwd, raw);
|
|
break;
|
|
}
|
|
|
|
case 'config-set': {
|
|
config.cmdConfigSet(cwd, args[1], args[2], raw);
|
|
break;
|
|
}
|
|
|
|
case "config-set-model-profile": {
|
|
config.cmdConfigSetModelProfile(cwd, args[1], raw);
|
|
break;
|
|
}
|
|
|
|
case 'config-get': {
|
|
config.cmdConfigGet(cwd, args[1], raw);
|
|
break;
|
|
}
|
|
|
|
case 'history-digest': {
|
|
commands.cmdHistoryDigest(cwd, raw);
|
|
break;
|
|
}
|
|
|
|
case 'phases': {
|
|
const subcommand = args[1];
|
|
if (subcommand === 'list') {
|
|
const typeIndex = args.indexOf('--type');
|
|
const phaseIndex = args.indexOf('--phase');
|
|
const options = {
|
|
type: typeIndex !== -1 ? args[typeIndex + 1] : null,
|
|
phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
|
|
includeArchived: args.includes('--include-archived'),
|
|
};
|
|
phase.cmdPhasesList(cwd, options, raw);
|
|
} else {
|
|
error('Unknown phases subcommand. Available: list');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'roadmap': {
|
|
const subcommand = args[1];
|
|
if (subcommand === 'get-phase') {
|
|
roadmap.cmdRoadmapGetPhase(cwd, args[2], raw);
|
|
} else if (subcommand === 'analyze') {
|
|
roadmap.cmdRoadmapAnalyze(cwd, raw);
|
|
} else if (subcommand === 'update-plan-progress') {
|
|
roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
|
|
} else {
|
|
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'requirements': {
|
|
const subcommand = args[1];
|
|
if (subcommand === 'mark-complete') {
|
|
milestone.cmdRequirementsMarkComplete(cwd, args.slice(2), raw);
|
|
} else {
|
|
error('Unknown requirements subcommand. Available: mark-complete');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'phase': {
|
|
const subcommand = args[1];
|
|
if (subcommand === 'next-decimal') {
|
|
phase.cmdPhaseNextDecimal(cwd, args[2], raw);
|
|
} else if (subcommand === 'add') {
|
|
phase.cmdPhaseAdd(cwd, args.slice(2).join(' '), raw);
|
|
} else if (subcommand === 'insert') {
|
|
phase.cmdPhaseInsert(cwd, args[2], args.slice(3).join(' '), raw);
|
|
} else if (subcommand === 'remove') {
|
|
const forceFlag = args.includes('--force');
|
|
phase.cmdPhaseRemove(cwd, args[2], { force: forceFlag }, raw);
|
|
} else if (subcommand === 'complete') {
|
|
phase.cmdPhaseComplete(cwd, args[2], raw);
|
|
} else {
|
|
error('Unknown phase subcommand. Available: next-decimal, add, insert, remove, complete');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'milestone': {
|
|
const subcommand = args[1];
|
|
if (subcommand === 'complete') {
|
|
const nameIndex = args.indexOf('--name');
|
|
const archivePhases = args.includes('--archive-phases');
|
|
// Collect --name value (everything after --name until next flag or end)
|
|
let milestoneName = null;
|
|
if (nameIndex !== -1) {
|
|
const nameArgs = [];
|
|
for (let i = nameIndex + 1; i < args.length; i++) {
|
|
if (args[i].startsWith('--')) break;
|
|
nameArgs.push(args[i]);
|
|
}
|
|
milestoneName = nameArgs.join(' ') || null;
|
|
}
|
|
milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases }, raw);
|
|
} else {
|
|
error('Unknown milestone subcommand. Available: complete');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'validate': {
|
|
const subcommand = args[1];
|
|
if (subcommand === 'consistency') {
|
|
verify.cmdValidateConsistency(cwd, raw);
|
|
} else if (subcommand === 'health') {
|
|
const repairFlag = args.includes('--repair');
|
|
verify.cmdValidateHealth(cwd, { repair: repairFlag }, raw);
|
|
} else {
|
|
error('Unknown validate subcommand. Available: consistency, health');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'progress': {
|
|
const subcommand = args[1] || 'json';
|
|
commands.cmdProgressRender(cwd, subcommand, raw);
|
|
break;
|
|
}
|
|
|
|
case 'stats': {
|
|
const subcommand = args[1] || 'json';
|
|
commands.cmdStats(cwd, subcommand, raw);
|
|
break;
|
|
}
|
|
|
|
case 'todo': {
|
|
const subcommand = args[1];
|
|
if (subcommand === 'complete') {
|
|
commands.cmdTodoComplete(cwd, args[2], raw);
|
|
} else {
|
|
error('Unknown todo subcommand. Available: complete');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'scaffold': {
|
|
const scaffoldType = args[1];
|
|
const phaseIndex = args.indexOf('--phase');
|
|
const nameIndex = args.indexOf('--name');
|
|
const scaffoldOptions = {
|
|
phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
|
|
name: nameIndex !== -1 ? args.slice(nameIndex + 1).join(' ') : null,
|
|
};
|
|
commands.cmdScaffold(cwd, scaffoldType, scaffoldOptions, raw);
|
|
break;
|
|
}
|
|
|
|
case 'init': {
|
|
const workflow = args[1];
|
|
switch (workflow) {
|
|
case 'execute-phase':
|
|
init.cmdInitExecutePhase(cwd, args[2], raw);
|
|
break;
|
|
case 'plan-phase':
|
|
init.cmdInitPlanPhase(cwd, args[2], raw);
|
|
break;
|
|
case 'new-project':
|
|
init.cmdInitNewProject(cwd, raw);
|
|
break;
|
|
case 'new-milestone':
|
|
init.cmdInitNewMilestone(cwd, raw);
|
|
break;
|
|
case 'quick':
|
|
init.cmdInitQuick(cwd, args.slice(2).join(' '), raw);
|
|
break;
|
|
case 'resume':
|
|
init.cmdInitResume(cwd, raw);
|
|
break;
|
|
case 'verify-work':
|
|
init.cmdInitVerifyWork(cwd, args[2], raw);
|
|
break;
|
|
case 'phase-op':
|
|
init.cmdInitPhaseOp(cwd, args[2], raw);
|
|
break;
|
|
case 'todos':
|
|
init.cmdInitTodos(cwd, args[2], raw);
|
|
break;
|
|
case 'milestone-op':
|
|
init.cmdInitMilestoneOp(cwd, raw);
|
|
break;
|
|
case 'map-codebase':
|
|
init.cmdInitMapCodebase(cwd, raw);
|
|
break;
|
|
case 'progress':
|
|
init.cmdInitProgress(cwd, raw);
|
|
break;
|
|
default:
|
|
error(`Unknown init workflow: ${workflow}\nAvailable: execute-phase, plan-phase, new-project, new-milestone, quick, resume, verify-work, phase-op, todos, milestone-op, map-codebase, progress`);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'phase-plan-index': {
|
|
phase.cmdPhasePlanIndex(cwd, args[1], raw);
|
|
break;
|
|
}
|
|
|
|
case 'state-snapshot': {
|
|
state.cmdStateSnapshot(cwd, raw);
|
|
break;
|
|
}
|
|
|
|
case 'summary-extract': {
|
|
const summaryPath = args[1];
|
|
const fieldsIndex = args.indexOf('--fields');
|
|
const fields = fieldsIndex !== -1 ? args[fieldsIndex + 1].split(',') : null;
|
|
commands.cmdSummaryExtract(cwd, summaryPath, fields, raw);
|
|
break;
|
|
}
|
|
|
|
case 'websearch': {
|
|
const query = args[1];
|
|
const limitIdx = args.indexOf('--limit');
|
|
const freshnessIdx = args.indexOf('--freshness');
|
|
await commands.cmdWebsearch(query, {
|
|
limit: limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 10,
|
|
freshness: freshnessIdx !== -1 ? args[freshnessIdx + 1] : null,
|
|
}, raw);
|
|
break;
|
|
}
|
|
|
|
// ─── Profiling Pipeline ────────────────────────────────────────────────
|
|
|
|
case 'scan-sessions': {
|
|
const pathIdx = args.indexOf('--path');
|
|
const sessionsPath = pathIdx !== -1 ? args[pathIdx + 1] : null;
|
|
const verboseFlag = args.includes('--verbose');
|
|
const jsonFlag = args.includes('--json');
|
|
await profilePipeline.cmdScanSessions(sessionsPath, { verbose: verboseFlag, json: jsonFlag }, raw);
|
|
break;
|
|
}
|
|
|
|
case 'extract-messages': {
|
|
const sessionIdx = args.indexOf('--session');
|
|
const sessionId = sessionIdx !== -1 ? args[sessionIdx + 1] : null;
|
|
const limitIdx = args.indexOf('--limit');
|
|
const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : null;
|
|
const pathIdx = args.indexOf('--path');
|
|
const sessionsPath = pathIdx !== -1 ? args[pathIdx + 1] : null;
|
|
const projectArg = args[1];
|
|
if (!projectArg || projectArg.startsWith('--')) {
|
|
error('Usage: gsd-tools extract-messages <project> [--session <id>] [--limit N] [--path <dir>]\nRun scan-sessions first to see available projects.');
|
|
}
|
|
await profilePipeline.cmdExtractMessages(projectArg, { sessionId, limit }, raw, sessionsPath);
|
|
break;
|
|
}
|
|
|
|
case 'profile-sample': {
|
|
const pathIdx = args.indexOf('--path');
|
|
const sessionsPath = pathIdx !== -1 ? args[pathIdx + 1] : null;
|
|
const limitIdx = args.indexOf('--limit');
|
|
const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 150;
|
|
const maxPerIdx = args.indexOf('--max-per-project');
|
|
const maxPerProject = maxPerIdx !== -1 ? parseInt(args[maxPerIdx + 1], 10) : null;
|
|
const maxCharsIdx = args.indexOf('--max-chars');
|
|
const maxChars = maxCharsIdx !== -1 ? parseInt(args[maxCharsIdx + 1], 10) : 500;
|
|
await profilePipeline.cmdProfileSample(sessionsPath, { limit, maxPerProject, maxChars }, raw);
|
|
break;
|
|
}
|
|
|
|
// ─── Profile Output ──────────────────────────────────────────────────
|
|
|
|
case 'write-profile': {
|
|
const inputIdx = args.indexOf('--input');
|
|
const inputPath = inputIdx !== -1 ? args[inputIdx + 1] : null;
|
|
if (!inputPath) error('--input <analysis-json-path> is required');
|
|
const outputIdx = args.indexOf('--output');
|
|
const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
|
|
profileOutput.cmdWriteProfile(cwd, { input: inputPath, output: outputPath }, raw);
|
|
break;
|
|
}
|
|
|
|
case 'profile-questionnaire': {
|
|
const answersIdx = args.indexOf('--answers');
|
|
const answers = answersIdx !== -1 ? args[answersIdx + 1] : null;
|
|
profileOutput.cmdProfileQuestionnaire({ answers }, raw);
|
|
break;
|
|
}
|
|
|
|
case 'generate-dev-preferences': {
|
|
const analysisIdx = args.indexOf('--analysis');
|
|
const analysisPath = analysisIdx !== -1 ? args[analysisIdx + 1] : null;
|
|
const outputIdx = args.indexOf('--output');
|
|
const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
|
|
const stackIdx = args.indexOf('--stack');
|
|
const stack = stackIdx !== -1 ? args[stackIdx + 1] : null;
|
|
profileOutput.cmdGenerateDevPreferences(cwd, { analysis: analysisPath, output: outputPath, stack }, raw);
|
|
break;
|
|
}
|
|
|
|
case 'generate-claude-profile': {
|
|
const analysisIdx = args.indexOf('--analysis');
|
|
const analysisPath = analysisIdx !== -1 ? args[analysisIdx + 1] : null;
|
|
const outputIdx = args.indexOf('--output');
|
|
const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
|
|
const globalFlag = args.includes('--global');
|
|
profileOutput.cmdGenerateClaudeProfile(cwd, { analysis: analysisPath, output: outputPath, global: globalFlag }, raw);
|
|
break;
|
|
}
|
|
|
|
case 'generate-claude-md': {
|
|
const outputIdx = args.indexOf('--output');
|
|
const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
|
|
const autoFlag = args.includes('--auto');
|
|
const forceFlag = args.includes('--force');
|
|
profileOutput.cmdGenerateClaudeMd(cwd, { output: outputPath, auto: autoFlag, force: forceFlag }, raw);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
error(`Unknown command: ${command}`);
|
|
}
|
|
}
|
|
|
|
main();
|