depa-codument 0.4.1

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 (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +262 -0
  3. package/package.json +63 -0
  4. package/src/cli/commands/archive.ts +519 -0
  5. package/src/cli/commands/decisions.ts +123 -0
  6. package/src/cli/commands/engineering.ts +105 -0
  7. package/src/cli/commands/init.ts +54 -0
  8. package/src/cli/commands/list.ts +73 -0
  9. package/src/cli/commands/modeling.ts +105 -0
  10. package/src/cli/commands/show.ts +238 -0
  11. package/src/cli/commands/status.ts +140 -0
  12. package/src/cli/commands/upgrade-track.ts +385 -0
  13. package/src/cli/commands/upgrade-workspace.ts +138 -0
  14. package/src/cli/commands/validate.ts +330 -0
  15. package/src/cli/engineering/config.ts +68 -0
  16. package/src/cli/engineering/lint.ts +58 -0
  17. package/src/cli/engineering/merge.ts +172 -0
  18. package/src/cli/engineering/registry.ts +230 -0
  19. package/src/cli/engineering/schema.ts +126 -0
  20. package/src/cli/engineering/validate.ts +286 -0
  21. package/src/cli/index.ts +136 -0
  22. package/src/cli/modeling/config.ts +68 -0
  23. package/src/cli/modeling/lint.ts +58 -0
  24. package/src/cli/modeling/merge.ts +172 -0
  25. package/src/cli/modeling/registry.ts +229 -0
  26. package/src/cli/modeling/schema.ts +160 -0
  27. package/src/cli/modeling/validate.ts +282 -0
  28. package/src/cli/utils/index.ts +941 -0
  29. package/src/cli/utils/install.ts +291 -0
  30. package/src/cli/utils/spec-xml.ts +673 -0
  31. package/src/cli/utils/track-time.ts +75 -0
  32. package/src/cli/utils/vfs.ts +102 -0
  33. package/src/templates/codument/README.md +59 -0
  34. package/src/templates/codument/attractors/product.md +17 -0
  35. package/src/templates/codument/attractors/project.md +10 -0
  36. package/src/templates/codument/backlog/README.md +33 -0
  37. package/src/templates/codument/config/attractor-profiles.xml +31 -0
  38. package/src/templates/codument/config/engineering.xml +22 -0
  39. package/src/templates/codument/config/modeling.xml +22 -0
  40. package/src/templates/codument/config/operation-hooks.xml +55 -0
  41. package/src/templates/codument/memory/README.md +13 -0
  42. package/src/templates/codument/missions/README.md +125 -0
  43. package/src/templates/codument/sop/README.md +14 -0
  44. package/src/templates/codument/std/AGENTS.md +82 -0
  45. package/src/templates/codument/std/attractors/depa-attractor.md +572 -0
  46. package/src/templates/codument/std/attractors/knowledge-tiers.md +128 -0
  47. package/src/templates/codument/std/attractors/model-driven-docs.md +293 -0
  48. package/src/templates/codument/std/attractors/project-memory.md +48 -0
  49. package/src/templates/codument/std/docs-impl-fractal/index.md +110 -0
  50. package/src/templates/codument/std/docs-modeling-fractal/index.md +156 -0
  51. package/src/templates/codument/std/kernel-pointer.md +19 -0
  52. package/src/templates/codument/std/operations/README.md +30 -0
  53. package/src/templates/codument/std/operations/_operation-spec.md +41 -0
  54. package/src/templates/codument/std/operations/archive-mission.md +66 -0
  55. package/src/templates/codument/std/operations/archive-track.md +238 -0
  56. package/src/templates/codument/std/operations/artifact-sync.md +172 -0
  57. package/src/templates/codument/std/operations/discuss-phase.md +214 -0
  58. package/src/templates/codument/std/operations/discuss.md +87 -0
  59. package/src/templates/codument/std/operations/docs-bootstrap.md +148 -0
  60. package/src/templates/codument/std/operations/gap-loop.md +301 -0
  61. package/src/templates/codument/std/operations/impl-mission.md +167 -0
  62. package/src/templates/codument/std/operations/impl-quick.md +79 -0
  63. package/src/templates/codument/std/operations/impl-track.md +537 -0
  64. package/src/templates/codument/std/operations/migrate.md +337 -0
  65. package/src/templates/codument/std/operations/plan-mission.md +230 -0
  66. package/src/templates/codument/std/operations/plan-track-wave.md +231 -0
  67. package/src/templates/codument/std/operations/plan-track.md +579 -0
  68. package/src/templates/codument/std/operations/revise-track.md +136 -0
  69. package/src/templates/codument/std/operations/validate.md +339 -0
  70. package/src/templates/codument/std/operations/verify.md +184 -0
  71. package/src/templates/codument/std/root-agents.md +39 -0
  72. package/src/templates/codument/std/sop/questioning.md +98 -0
  73. package/src/templates/codument/std/sop/tdd.md +26 -0
  74. package/src/templates/codument/std/sop/validation.md +25 -0
  75. package/src/templates/codument/std/sop/wave-exec.md +42 -0
  76. package/src/templates/codument/std/sop/workflow.md +35 -0
  77. package/src/templates/codument/std/spec/behavior-delta.md +36 -0
  78. package/src/templates/codument/std/spec/behavior-registry.md +42 -0
  79. package/src/templates/codument/std/spec/engineering-delta.md +68 -0
  80. package/src/templates/codument/std/spec/engineering-node-schema.md +86 -0
  81. package/src/templates/codument/std/spec/engineering-registry.md +82 -0
  82. package/src/templates/codument/std/spec/flow-notation.md +93 -0
  83. package/src/templates/codument/std/spec/folder-manifest.md +99 -0
  84. package/src/templates/codument/std/spec/mission-xml-spec.md +249 -0
  85. package/src/templates/codument/std/spec/modeling-delta.md +85 -0
  86. package/src/templates/codument/std/spec/modeling-node-schema.md +183 -0
  87. package/src/templates/codument/std/spec/modeling-registry.md +49 -0
  88. package/src/templates/codument/std/spec/track-xml-spec.md +272 -0
  89. package/src/templates/codument/std/spec/xnl-format.md +301 -0
  90. package/src/templates/codument/workflows/README.md +15 -0
  91. package/src/templates/manifest.ts +177 -0
  92. package/src/templates/skills/README.md +38 -0
  93. package/src/templates/skills/codument-archive/SKILL.md +17 -0
  94. package/src/templates/skills/codument-archive-mission/SKILL.md +17 -0
  95. package/src/templates/skills/codument-archive-track/SKILL.md +17 -0
  96. package/src/templates/skills/codument-artifact-sync/SKILL.md +17 -0
  97. package/src/templates/skills/codument-code-quality-score/SKILL.md +67 -0
  98. package/src/templates/skills/codument-decision-tree/SKILL.md +40 -0
  99. package/src/templates/skills/codument-discuss/SKILL.md +17 -0
  100. package/src/templates/skills/codument-discuss-phase/SKILL.md +17 -0
  101. package/src/templates/skills/codument-docs-bootstrap/SKILL.md +17 -0
  102. package/src/templates/skills/codument-gap-loop/SKILL.md +17 -0
  103. package/src/templates/skills/codument-impl-mission/SKILL.md +17 -0
  104. package/src/templates/skills/codument-impl-quick/SKILL.md +17 -0
  105. package/src/templates/skills/codument-impl-track/SKILL.md +17 -0
  106. package/src/templates/skills/codument-implement/SKILL.md +14 -0
  107. package/src/templates/skills/codument-migrate/SKILL.md +17 -0
  108. package/src/templates/skills/codument-modeling-engineering-e2e/SKILL.md +74 -0
  109. package/src/templates/skills/codument-plan-mission/SKILL.md +17 -0
  110. package/src/templates/skills/codument-plan-track/SKILL.md +17 -0
  111. package/src/templates/skills/codument-plan-track-wave/SKILL.md +17 -0
  112. package/src/templates/skills/codument-revise-track/SKILL.md +17 -0
  113. package/src/templates/skills/codument-track/SKILL.md +14 -0
  114. package/src/templates/skills/codument-validate/SKILL.md +17 -0
  115. package/src/templates/skills/codument-verify/SKILL.md +17 -0
  116. package/src/types/text-assets.d.ts +9 -0
  117. package/src/version.ts +1 -0
@@ -0,0 +1,105 @@
1
+ import * as path from 'path';
2
+ import { parseOptions, CODUMENT_DIR } from '../utils';
3
+ import { lintEngineeringRegistry } from '../engineering/lint';
4
+ import { loadEngineeringConfig, engineeringEnabled } from '../engineering/config';
5
+ import { validateEngineeringTree, type ValidateFinding } from '../engineering/validate';
6
+
7
+ export async function engineeringCommand(args: string[]) {
8
+ const sub = args[0];
9
+ const rest = args.slice(1);
10
+ switch (sub) {
11
+ case 'lint':
12
+ return engineeringLint(rest);
13
+ case 'validate':
14
+ return engineeringValidate(rest);
15
+ default:
16
+ console.error(`Unknown engineering subcommand: ${sub ?? '(none)'}`);
17
+ console.log('Usage: codument engineering lint [dir] [--max-lines N] [--max-nodes N]');
18
+ console.log(' codument engineering validate [dir] [--deltas <track>]');
19
+ process.exit(1);
20
+ }
21
+ }
22
+
23
+ function engineeringValidate(args: string[]): void {
24
+ const { positional, options } = parseOptions(args);
25
+ const deltaTrack = typeof options.deltas === 'string' ? options.deltas : undefined;
26
+
27
+ let dir: string;
28
+ let mode: 'registry' | 'deltas';
29
+ // An explicit [dir] positional is a manual request: bypass the enabled gate.
30
+ let explicit = positional.length > 0;
31
+
32
+ if (deltaTrack) {
33
+ dir = path.join(CODUMENT_DIR, 'tracks', deltaTrack, 'engineering_deltas');
34
+ mode = 'deltas';
35
+ explicit = true;
36
+ } else if (positional[0]) {
37
+ dir = positional[0];
38
+ mode = 'registry';
39
+ } else {
40
+ dir = loadEngineeringConfig().registryDir;
41
+ mode = 'registry';
42
+ }
43
+
44
+ // Default registry mode is gated; explicit dir / --deltas bypass the gate.
45
+ if (!explicit && !engineeringEnabled()) {
46
+ console.log('engineering disabled,跳过 (set enabled="true" in codument/config/engineering.xml)');
47
+ return;
48
+ }
49
+
50
+ const findings = validateEngineeringTree(dir, { mode });
51
+ reportFindings(findings, dir);
52
+ }
53
+
54
+ function reportFindings(findings: ValidateFinding[], dir: string): void {
55
+ if (findings.length === 0) {
56
+ console.log(`✓ engineering validate: no issues in ${dir}`);
57
+ return;
58
+ }
59
+
60
+ // Group by file, preserving first-seen order.
61
+ const byFile = new Map<string, ValidateFinding[]>();
62
+ for (const f of findings) {
63
+ const list = byFile.get(f.file);
64
+ if (list) list.push(f);
65
+ else byFile.set(f.file, [f]);
66
+ }
67
+
68
+ console.log(`engineering validate: issues in ${dir}:`);
69
+ for (const [file, list] of byFile) {
70
+ console.log(`${file}:`);
71
+ for (const f of list) {
72
+ const where = f.line !== undefined ? ` (line ${f.line})` : '';
73
+ console.log(` [${f.layer}/${f.severity}] ${f.message}${where}`);
74
+ }
75
+ }
76
+
77
+ const errors = findings.filter((f) => f.severity === 'error').length;
78
+ const warnings = findings.filter((f) => f.severity === 'warning').length;
79
+ console.log(`${errors} error(s), ${warnings} warning(s)`);
80
+
81
+ if (errors > 0) process.exit(1);
82
+ }
83
+
84
+ function engineeringLint(args: string[]): void {
85
+ const { positional, options } = parseOptions(args);
86
+ const config = loadEngineeringConfig();
87
+ const dir = positional[0] ?? config.registryDir;
88
+ const thresholds = {
89
+ maxLines: options['max-lines'] ? Number(options['max-lines']) : config.thresholds.maxLines,
90
+ maxNodes: options['max-nodes'] ? Number(options['max-nodes']) : config.thresholds.maxNodes,
91
+ };
92
+
93
+ const findings = lintEngineeringRegistry(dir, thresholds);
94
+ if (findings.length === 0) {
95
+ console.log(`✓ engineering lint: no fractal-split candidates in ${dir}`);
96
+ return;
97
+ }
98
+
99
+ console.log(`engineering lint: ${findings.length} fractal-split candidate(s) in ${dir}:`);
100
+ for (const f of findings) {
101
+ console.log(
102
+ ` • ${f.file} — ${f.reasons.join(', ')} → consider splitting into a same-name folder (see folder-manifest.md)`,
103
+ );
104
+ }
105
+ }
@@ -0,0 +1,54 @@
1
+ import { parseOptions, codumentExists } from '../utils';
2
+ import {
3
+ installSkillTemplates,
4
+ installTemplates,
5
+ ensureCodumentGitignoreRules,
6
+ injectAgentsBlock,
7
+ parseAgents,
8
+ resolveSkillsTargets,
9
+ writeCliToolsConfig,
10
+ } from '../utils/install';
11
+
12
+ /**
13
+ * `codument init` — pure text copy of the embedded templates into the project.
14
+ *
15
+ * No interactive prompts, no template rendering, no per-agent prompt variants,
16
+ * no commands: just copies codument/** into the workspace and the skill shells
17
+ * into the agent skills directory, then injects the AGENTS.md managed block.
18
+ *
19
+ * Options:
20
+ * --agent <names> comma-separated target agent skills dirs (claude|codex|opencode|eidolon|codeflicker|sparrow; default claude)
21
+ * --skills-dir <path> explicit skills destination (overrides --agent as a single target)
22
+ * --force overwrite existing codument/** files (default: preserve existing)
23
+ */
24
+ export async function initCommand(args: string[]): Promise<void> {
25
+ const { options } = parseOptions(args);
26
+ const selectedTools = parseAgents(typeof options['agent'] === 'string' ? String(options['agent']) : undefined);
27
+ const targets = resolveSkillsTargets(options, selectedTools);
28
+ const [firstTarget, ...additionalTargets] = targets;
29
+ const force = options['force'] === true;
30
+
31
+ const result = installTemplates({ skillsDir: firstTarget.skillsDir, overwriteStd: false, force });
32
+ const skillResults = [{ ...firstTarget, skillsWritten: result.skillsWritten, skillsRemoved: result.skillsRemoved }];
33
+ for (const target of additionalTargets) {
34
+ skillResults.push({ ...target, ...installSkillTemplates(target.skillsDir) });
35
+ }
36
+ injectAgentsBlock();
37
+ const gitignoreRulesAdded = ensureCodumentGitignoreRules();
38
+
39
+ console.log('Codument initialized.');
40
+ console.log(` codument/ : ${result.workspaceWritten} written, ${result.workspaceSkipped} kept`);
41
+ for (const skillResult of skillResults) {
42
+ const removed = skillResult.skillsRemoved ? `, ${skillResult.skillsRemoved} deprecated removed` : '';
43
+ console.log(` skills : ${skillResult.skillsWritten} → ${skillResult.skillsDir} (agent: ${skillResult.agent}${removed})`);
44
+ }
45
+ writeCliToolsConfig(selectedTools);
46
+ console.log(' config/cli-tools.json: tools updated');
47
+ console.log(' AGENTS.md : managed block written');
48
+ if (gitignoreRulesAdded > 0) {
49
+ console.log(` .gitignore: ${gitignoreRulesAdded} codument rule(s) added`);
50
+ }
51
+ if (codumentExists()) {
52
+ console.log('\nNext: edit codument/attractors/{project,product}.md, then use the codument-plan-track skill.');
53
+ }
54
+ }
@@ -0,0 +1,73 @@
1
+ import { getTracks, getSpecs, parseOptions, formatStatus, codumentExists } from '../utils';
2
+
3
+ export async function listCommand(args: string[]) {
4
+ if (!codumentExists()) {
5
+ console.error('Codument is not initialized. Run codument init first.');
6
+ process.exit(1);
7
+ }
8
+
9
+ const { options } = parseOptions(args);
10
+ const showSpecs = options['specs'] === true;
11
+ const jsonOutput = options['json'] === true;
12
+
13
+ if (showSpecs) {
14
+ const specs = getSpecs();
15
+
16
+ if (jsonOutput) {
17
+ console.log(JSON.stringify(specs, null, 2));
18
+ return;
19
+ }
20
+
21
+ if (specs.length === 0) {
22
+ console.log('No specifications found.');
23
+ return;
24
+ }
25
+
26
+ console.log('\nSpecifications:\n');
27
+ console.log(' ID Format Requirements Scenarios');
28
+ console.log(' ' + '-'.repeat(72));
29
+
30
+ for (const spec of specs) {
31
+ const id = spec.id.padEnd(28);
32
+ const format = (spec.format || 'markdown').padEnd(12);
33
+ const reqs = String(spec.requirements).padStart(8);
34
+ const scenarios = String(spec.scenarios).padStart(10);
35
+ console.log(` ${id}${format}${reqs}${scenarios}`);
36
+ }
37
+
38
+ console.log(`\nTotal: ${specs.length} spec(s)\n`);
39
+ } else {
40
+ const tracks = getTracks();
41
+
42
+ if (jsonOutput) {
43
+ console.log(JSON.stringify(tracks, null, 2));
44
+ return;
45
+ }
46
+
47
+ if (tracks.length === 0) {
48
+ console.log('No active tracks found.');
49
+ return;
50
+ }
51
+
52
+ console.log('\nActive Tracks:\n');
53
+ console.log(' Status ID Type Progress');
54
+ console.log(' ' + '-'.repeat(68));
55
+
56
+ for (const track of tracks) {
57
+ const status = formatStatus(track.metadata.status);
58
+ const id = track.id.padEnd(32);
59
+ const type = track.metadata.type.padEnd(10);
60
+
61
+ let progress = '-';
62
+ if (track.taskSummary) {
63
+ const { completed, total_tasks } = track.taskSummary;
64
+ const pct = total_tasks > 0 ? Math.round((completed / total_tasks) * 100) : 0;
65
+ progress = `${completed}/${total_tasks} (${pct}%)`;
66
+ }
67
+
68
+ console.log(` ${status} ${id}${type}${progress}`);
69
+ }
70
+
71
+ console.log(`\nTotal: ${tracks.length} track(s)\n`);
72
+ }
73
+ }
@@ -0,0 +1,105 @@
1
+ import * as path from 'path';
2
+ import { parseOptions, CODUMENT_DIR } from '../utils';
3
+ import { lintModelingRegistry } from '../modeling/lint';
4
+ import { loadModelingConfig, modelingEnabled } from '../modeling/config';
5
+ import { validateModelingTree, type ValidateFinding } from '../modeling/validate';
6
+
7
+ export async function modelingCommand(args: string[]) {
8
+ const sub = args[0];
9
+ const rest = args.slice(1);
10
+ switch (sub) {
11
+ case 'lint':
12
+ return modelingLint(rest);
13
+ case 'validate':
14
+ return modelingValidate(rest);
15
+ default:
16
+ console.error(`Unknown modeling subcommand: ${sub ?? '(none)'}`);
17
+ console.log('Usage: codument modeling lint [dir] [--max-lines N] [--max-nodes N]');
18
+ console.log(' codument modeling validate [dir] [--deltas <track>]');
19
+ process.exit(1);
20
+ }
21
+ }
22
+
23
+ function modelingValidate(args: string[]): void {
24
+ const { positional, options } = parseOptions(args);
25
+ const deltaTrack = typeof options.deltas === 'string' ? options.deltas : undefined;
26
+
27
+ let dir: string;
28
+ let mode: 'registry' | 'deltas';
29
+ // An explicit [dir] positional is a manual request: bypass the enabled gate.
30
+ let explicit = positional.length > 0;
31
+
32
+ if (deltaTrack) {
33
+ dir = path.join(CODUMENT_DIR, 'tracks', deltaTrack, 'modeling_deltas');
34
+ mode = 'deltas';
35
+ explicit = true;
36
+ } else if (positional[0]) {
37
+ dir = positional[0];
38
+ mode = 'registry';
39
+ } else {
40
+ dir = loadModelingConfig().registryDir;
41
+ mode = 'registry';
42
+ }
43
+
44
+ // Default registry mode is gated; explicit dir / --deltas bypass the gate.
45
+ if (!explicit && !modelingEnabled()) {
46
+ console.log('modeling disabled,跳过 (set enabled="true" in codument/config/modeling.xml)');
47
+ return;
48
+ }
49
+
50
+ const findings = validateModelingTree(dir, { mode });
51
+ reportFindings(findings, dir);
52
+ }
53
+
54
+ function reportFindings(findings: ValidateFinding[], dir: string): void {
55
+ if (findings.length === 0) {
56
+ console.log(`✓ modeling validate: no issues in ${dir}`);
57
+ return;
58
+ }
59
+
60
+ // Group by file, preserving first-seen order.
61
+ const byFile = new Map<string, ValidateFinding[]>();
62
+ for (const f of findings) {
63
+ const list = byFile.get(f.file);
64
+ if (list) list.push(f);
65
+ else byFile.set(f.file, [f]);
66
+ }
67
+
68
+ console.log(`modeling validate: issues in ${dir}:`);
69
+ for (const [file, list] of byFile) {
70
+ console.log(`${file}:`);
71
+ for (const f of list) {
72
+ const where = f.line !== undefined ? ` (line ${f.line})` : '';
73
+ console.log(` [${f.layer}/${f.severity}] ${f.message}${where}`);
74
+ }
75
+ }
76
+
77
+ const errors = findings.filter((f) => f.severity === 'error').length;
78
+ const warnings = findings.filter((f) => f.severity === 'warning').length;
79
+ console.log(`${errors} error(s), ${warnings} warning(s)`);
80
+
81
+ if (errors > 0) process.exit(1);
82
+ }
83
+
84
+ function modelingLint(args: string[]): void {
85
+ const { positional, options } = parseOptions(args);
86
+ const config = loadModelingConfig();
87
+ const dir = positional[0] ?? config.registryDir;
88
+ const thresholds = {
89
+ maxLines: options['max-lines'] ? Number(options['max-lines']) : config.thresholds.maxLines,
90
+ maxNodes: options['max-nodes'] ? Number(options['max-nodes']) : config.thresholds.maxNodes,
91
+ };
92
+
93
+ const findings = lintModelingRegistry(dir, thresholds);
94
+ if (findings.length === 0) {
95
+ console.log(`✓ modeling lint: no fractal-split candidates in ${dir}`);
96
+ return;
97
+ }
98
+
99
+ console.log(`modeling lint: ${findings.length} fractal-split candidate(s) in ${dir}:`);
100
+ for (const f of findings) {
101
+ console.log(
102
+ ` • ${f.file} — ${f.reasons.join(', ')} → consider splitting into a same-name folder (see folder-manifest.md)`,
103
+ );
104
+ }
105
+ }
@@ -0,0 +1,238 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { getTrack, getSpecs, parseOptions, formatStatus, codumentExists, TRACKS_DIR, SPECS_DIR } from '../utils';
4
+ import { getSpecXmlStats, loadSpecXml } from '../utils/spec-xml';
5
+
6
+ export async function showCommand(args: string[]) {
7
+ if (!codumentExists()) {
8
+ console.error('Codument is not initialized. Run codument init first.');
9
+ process.exit(1);
10
+ }
11
+
12
+ const { positional, options } = parseOptions(args);
13
+ const itemId = positional[0];
14
+ const itemType = options['type'] as string | undefined;
15
+ const jsonOutput = options['json'] === true;
16
+
17
+ if (!itemId) {
18
+ console.error('Please specify a track or spec ID.');
19
+ console.log('Usage: codument show <id> [--type track|spec] [--json]');
20
+ process.exit(1);
21
+ }
22
+
23
+ // Try to determine type
24
+ let foundType: 'track' | 'spec' | null = null;
25
+
26
+ if (itemType === 'track' || itemType === 'spec') {
27
+ foundType = itemType;
28
+ } else {
29
+ // Auto-detect
30
+ const trackDir = path.join(TRACKS_DIR, itemId);
31
+ const specDir = path.join(SPECS_DIR, itemId);
32
+ const specXmlFile = path.join(SPECS_DIR, `${itemId}.xml`);
33
+
34
+ if (fs.existsSync(trackDir)) {
35
+ foundType = 'track';
36
+ } else if (fs.existsSync(specDir) || fs.existsSync(specXmlFile)) {
37
+ foundType = 'spec';
38
+ }
39
+ }
40
+
41
+ if (!foundType) {
42
+ console.error(`Item not found: ${itemId}`);
43
+ console.log('Use --type to specify whether this is a track or spec.');
44
+ process.exit(1);
45
+ }
46
+
47
+ if (foundType === 'track') {
48
+ showTrack(itemId, jsonOutput);
49
+ } else {
50
+ showSpec(itemId, jsonOutput);
51
+ }
52
+ }
53
+
54
+ function showTrack(trackId: string, jsonOutput: boolean) {
55
+ const track = getTrack(trackId);
56
+
57
+ if (!track) {
58
+ console.error(`Track not found: ${trackId}`);
59
+ process.exit(1);
60
+ }
61
+
62
+ if (jsonOutput) {
63
+ // Include file contents in JSON output
64
+ const trackDir = path.join(TRACKS_DIR, trackId);
65
+ const specDeltaFiles = collectTrackXmlSpecDeltas(trackDir);
66
+ const result: Record<string, unknown> = {
67
+ ...track,
68
+ files: {},
69
+ };
70
+
71
+ const files = ['proposal.md', 'track.xml', 'design.md', 'decisions.md'];
72
+ for (const file of files) {
73
+ const filePath = path.join(trackDir, file);
74
+ if (fs.existsSync(filePath)) {
75
+ (result.files as Record<string, string>)[file] = fs.readFileSync(filePath, 'utf-8');
76
+ }
77
+ }
78
+ for (const filePath of specDeltaFiles) {
79
+ const relativePath = path.relative(trackDir, filePath);
80
+ (result.files as Record<string, string>)[relativePath] = fs.readFileSync(filePath, 'utf-8');
81
+ }
82
+
83
+ console.log(JSON.stringify(result, null, 2));
84
+ return;
85
+ }
86
+
87
+ const trackDir = path.join(TRACKS_DIR, trackId);
88
+ const specDeltaFiles = collectTrackXmlSpecDeltas(trackDir);
89
+
90
+ console.log('\n' + '='.repeat(60));
91
+ console.log(`Track: ${trackId}`);
92
+ console.log('='.repeat(60));
93
+
94
+ console.log(`\nStatus: ${formatStatus(track.metadata.status)} ${track.metadata.status}`);
95
+ console.log(`Type: ${track.metadata.type}`);
96
+ console.log(`Description: ${track.metadata.description}`);
97
+ console.log(`Created: ${track.metadata.created_at}`);
98
+ console.log(`Updated: ${track.metadata.updated_at}`);
99
+
100
+ if (track.taskSummary) {
101
+ const s = track.taskSummary;
102
+ const pct = s.total_tasks > 0 ? Math.round((s.completed / s.total_tasks) * 100) : 0;
103
+ console.log(`\nProgress: ${s.completed}/${s.total_tasks} (${pct}%)`);
104
+ console.log(` Phases: ${s.total_phases}`);
105
+ console.log(` Completed: ${s.completed}`);
106
+ console.log(` In Progress: ${s.in_progress}`);
107
+ console.log(` Todo: ${s.todo}`);
108
+ console.log(` Blocked: ${s.blocked}`);
109
+ }
110
+
111
+ console.log('\nFiles:');
112
+ const files = ['proposal.md', 'track.xml', 'design.md', 'decisions.md'];
113
+ for (const file of files) {
114
+ const filePath = path.join(trackDir, file);
115
+ const exists = fs.existsSync(filePath);
116
+ const status = exists ? '✓' : '✗';
117
+ console.log(` ${status} ${file}`);
118
+ }
119
+ if (specDeltaFiles.length > 0) {
120
+ for (const filePath of specDeltaFiles) {
121
+ console.log(` ✓ ${path.relative(trackDir, filePath)}`);
122
+ }
123
+ } else {
124
+ console.log(` ✗ behavior_deltas/**/*.xml`);
125
+ }
126
+
127
+ console.log('');
128
+ }
129
+
130
+ function collectTrackXmlSpecDeltas(trackDir: string): string[] {
131
+ const results: string[] = [];
132
+ const roots = [
133
+ path.join(trackDir, 'behavior_deltas'),
134
+ path.join(trackDir, 'spec_deltas'),
135
+ path.join(trackDir, 'spec-deltas'),
136
+ ];
137
+
138
+ const visit = (dir: string) => {
139
+ if (!fs.existsSync(dir)) {
140
+ return;
141
+ }
142
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
143
+ const entryPath = path.join(dir, entry.name);
144
+ if (entry.isDirectory()) {
145
+ visit(entryPath);
146
+ } else if (entry.isFile() && entry.name.endsWith('.xml')) {
147
+ results.push(entryPath);
148
+ }
149
+ }
150
+ };
151
+
152
+ for (const root of roots) {
153
+ visit(root);
154
+ }
155
+ return results.sort();
156
+ }
157
+
158
+ function showSpec(specId: string, jsonOutput: boolean) {
159
+ const specDir = path.join(SPECS_DIR, specId);
160
+ const specPath = path.join(specDir, 'spec.md');
161
+ const specXmlFilePath = path.join(SPECS_DIR, `${specId}.xml`);
162
+ const specXmlIndexPath = path.join(specDir, 'index.xml');
163
+
164
+ const isXml = fs.existsSync(specXmlFilePath) || fs.existsSync(specXmlIndexPath);
165
+ if (!fs.existsSync(specPath) && !isXml) {
166
+ console.error(`Spec not found: ${specId}`);
167
+ process.exit(1);
168
+ }
169
+
170
+ if (isXml) {
171
+ const xmlEntryPath = fs.existsSync(specXmlFilePath) ? specXmlFilePath : specDir;
172
+ const xmlDisplayPath = fs.existsSync(specXmlFilePath) ? specXmlFilePath : specXmlIndexPath;
173
+ const content = fs.readFileSync(xmlDisplayPath, 'utf-8');
174
+ const root = loadSpecXml(xmlEntryPath);
175
+ const stats = getSpecXmlStats(root);
176
+
177
+ if (jsonOutput) {
178
+ console.log(JSON.stringify({
179
+ id: specId,
180
+ path: xmlDisplayPath,
181
+ format: 'xml',
182
+ requirements: stats.requirements,
183
+ scenarios: stats.scenarios,
184
+ content,
185
+ }, null, 2));
186
+ return;
187
+ }
188
+
189
+ console.log('\n' + '='.repeat(60));
190
+ console.log(`Spec: ${specId}`);
191
+ console.log('='.repeat(60));
192
+ console.log('\nFormat: XML');
193
+ console.log(`Requirements: ${stats.requirements}`);
194
+ console.log(`Scenarios: ${stats.scenarios}`);
195
+ console.log('\nFiles:');
196
+ console.log(` ✓ ${path.relative(specDir, xmlDisplayPath) || path.basename(xmlDisplayPath)}`);
197
+ console.log('');
198
+ return;
199
+ }
200
+
201
+ const content = fs.readFileSync(specPath, 'utf-8');
202
+ const requirements = content.match(/^### Requirement: (.+)$/gm) || [];
203
+ const scenarios = content.match(/^#### Scenario: (.+)$/gm) || [];
204
+
205
+ if (jsonOutput) {
206
+ console.log(JSON.stringify({
207
+ id: specId,
208
+ path: specPath,
209
+ format: 'markdown',
210
+ requirements: requirements.map(r => r.replace('### Requirement: ', '')),
211
+ scenarios: scenarios.map(s => s.replace('#### Scenario: ', '')),
212
+ content,
213
+ }, null, 2));
214
+ return;
215
+ }
216
+
217
+ console.log('\n' + '='.repeat(60));
218
+ console.log(`Spec: ${specId}`);
219
+ console.log('='.repeat(60));
220
+
221
+ console.log(`\nRequirements: ${requirements.length}`);
222
+ for (const req of requirements) {
223
+ console.log(` - ${req.replace('### Requirement: ', '')}`);
224
+ }
225
+
226
+ console.log(`\nScenarios: ${scenarios.length}`);
227
+
228
+ console.log('\nFiles:');
229
+ const files = ['spec.md', 'design.md'];
230
+ for (const file of files) {
231
+ const filePath = path.join(specDir, file);
232
+ const exists = fs.existsSync(filePath);
233
+ const status = exists ? '✓' : '✗';
234
+ console.log(` ${status} ${file}`);
235
+ }
236
+
237
+ console.log('');
238
+ }