gsd-code-first 1.0.0
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.
- package/LICENSE +21 -0
- package/README.ja-JP.md +834 -0
- package/README.ko-KR.md +823 -0
- package/README.md +937 -0
- package/README.pt-BR.md +452 -0
- package/README.zh-CN.md +800 -0
- package/agents/gsd-advisor-researcher.md +104 -0
- package/agents/gsd-annotator.md +148 -0
- package/agents/gsd-arc-executor.md +537 -0
- package/agents/gsd-arc-planner.md +374 -0
- package/agents/gsd-assumptions-analyzer.md +105 -0
- package/agents/gsd-code-planner.md +155 -0
- package/agents/gsd-codebase-mapper.md +770 -0
- package/agents/gsd-debugger.md +1373 -0
- package/agents/gsd-executor.md +509 -0
- package/agents/gsd-integration-checker.md +443 -0
- package/agents/gsd-nyquist-auditor.md +176 -0
- package/agents/gsd-phase-researcher.md +698 -0
- package/agents/gsd-plan-checker.md +773 -0
- package/agents/gsd-planner.md +1354 -0
- package/agents/gsd-project-researcher.md +654 -0
- package/agents/gsd-prototyper.md +161 -0
- package/agents/gsd-research-synthesizer.md +247 -0
- package/agents/gsd-roadmapper.md +679 -0
- package/agents/gsd-ui-auditor.md +439 -0
- package/agents/gsd-ui-checker.md +300 -0
- package/agents/gsd-ui-researcher.md +357 -0
- package/agents/gsd-user-profiler.md +171 -0
- package/agents/gsd-verifier.md +700 -0
- package/bin/install.js +5009 -0
- package/commands/gsd/add-backlog.md +76 -0
- package/commands/gsd/add-phase.md +43 -0
- package/commands/gsd/add-tests.md +41 -0
- package/commands/gsd/add-todo.md +47 -0
- package/commands/gsd/annotate.md +54 -0
- package/commands/gsd/audit-milestone.md +36 -0
- package/commands/gsd/audit-uat.md +24 -0
- package/commands/gsd/autonomous.md +41 -0
- package/commands/gsd/check-todos.md +45 -0
- package/commands/gsd/cleanup.md +18 -0
- package/commands/gsd/complete-milestone.md +136 -0
- package/commands/gsd/debug.md +173 -0
- package/commands/gsd/deep-plan.md +52 -0
- package/commands/gsd/discuss-phase.md +64 -0
- package/commands/gsd/do.md +30 -0
- package/commands/gsd/execute-phase.md +59 -0
- package/commands/gsd/extract-plan.md +35 -0
- package/commands/gsd/fast.md +30 -0
- package/commands/gsd/forensics.md +56 -0
- package/commands/gsd/health.md +22 -0
- package/commands/gsd/help.md +22 -0
- package/commands/gsd/insert-phase.md +32 -0
- package/commands/gsd/iterate.md +124 -0
- package/commands/gsd/join-discord.md +18 -0
- package/commands/gsd/list-phase-assumptions.md +46 -0
- package/commands/gsd/list-workspaces.md +19 -0
- package/commands/gsd/manager.md +39 -0
- package/commands/gsd/map-codebase.md +71 -0
- package/commands/gsd/milestone-summary.md +51 -0
- package/commands/gsd/new-milestone.md +44 -0
- package/commands/gsd/new-project.md +42 -0
- package/commands/gsd/new-workspace.md +44 -0
- package/commands/gsd/next.md +24 -0
- package/commands/gsd/note.md +34 -0
- package/commands/gsd/pause-work.md +38 -0
- package/commands/gsd/plan-milestone-gaps.md +34 -0
- package/commands/gsd/plan-phase.md +47 -0
- package/commands/gsd/plant-seed.md +28 -0
- package/commands/gsd/pr-branch.md +25 -0
- package/commands/gsd/profile-user.md +46 -0
- package/commands/gsd/progress.md +24 -0
- package/commands/gsd/prototype.md +56 -0
- package/commands/gsd/quick.md +47 -0
- package/commands/gsd/reapply-patches.md +123 -0
- package/commands/gsd/remove-phase.md +31 -0
- package/commands/gsd/remove-workspace.md +26 -0
- package/commands/gsd/research-phase.md +195 -0
- package/commands/gsd/resume-work.md +40 -0
- package/commands/gsd/review-backlog.md +61 -0
- package/commands/gsd/review.md +37 -0
- package/commands/gsd/session-report.md +19 -0
- package/commands/gsd/set-mode.md +41 -0
- package/commands/gsd/set-profile.md +12 -0
- package/commands/gsd/settings.md +36 -0
- package/commands/gsd/ship.md +23 -0
- package/commands/gsd/stats.md +18 -0
- package/commands/gsd/thread.md +127 -0
- package/commands/gsd/ui-phase.md +34 -0
- package/commands/gsd/ui-review.md +32 -0
- package/commands/gsd/update.md +37 -0
- package/commands/gsd/validate-phase.md +35 -0
- package/commands/gsd/verify-work.md +38 -0
- package/commands/gsd/workstreams.md +63 -0
- package/get-shit-done/bin/gsd-tools.cjs +946 -0
- package/get-shit-done/bin/lib/arc-scanner.cjs +341 -0
- package/get-shit-done/bin/lib/commands.cjs +959 -0
- package/get-shit-done/bin/lib/config.cjs +466 -0
- package/get-shit-done/bin/lib/core.cjs +1230 -0
- package/get-shit-done/bin/lib/frontmatter.cjs +336 -0
- package/get-shit-done/bin/lib/init.cjs +1442 -0
- package/get-shit-done/bin/lib/milestone.cjs +252 -0
- package/get-shit-done/bin/lib/model-profiles.cjs +68 -0
- package/get-shit-done/bin/lib/phase.cjs +888 -0
- package/get-shit-done/bin/lib/profile-output.cjs +952 -0
- package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
- package/get-shit-done/bin/lib/roadmap.cjs +329 -0
- package/get-shit-done/bin/lib/security.cjs +382 -0
- package/get-shit-done/bin/lib/state.cjs +1031 -0
- package/get-shit-done/bin/lib/template.cjs +222 -0
- package/get-shit-done/bin/lib/uat.cjs +282 -0
- package/get-shit-done/bin/lib/verify.cjs +888 -0
- package/get-shit-done/bin/lib/workstream.cjs +491 -0
- package/get-shit-done/commands/gsd/workstreams.md +63 -0
- package/get-shit-done/references/arc-standard.md +315 -0
- package/get-shit-done/references/checkpoints.md +778 -0
- package/get-shit-done/references/continuation-format.md +249 -0
- package/get-shit-done/references/decimal-phase-calculation.md +64 -0
- package/get-shit-done/references/git-integration.md +295 -0
- package/get-shit-done/references/git-planning-commit.md +38 -0
- package/get-shit-done/references/model-profile-resolution.md +36 -0
- package/get-shit-done/references/model-profiles.md +139 -0
- package/get-shit-done/references/phase-argument-parsing.md +61 -0
- package/get-shit-done/references/planning-config.md +202 -0
- package/get-shit-done/references/questioning.md +162 -0
- package/get-shit-done/references/tdd.md +263 -0
- package/get-shit-done/references/ui-brand.md +160 -0
- package/get-shit-done/references/user-profiling.md +681 -0
- package/get-shit-done/references/verification-patterns.md +612 -0
- package/get-shit-done/references/workstream-flag.md +58 -0
- package/get-shit-done/templates/DEBUG.md +164 -0
- package/get-shit-done/templates/UAT.md +265 -0
- package/get-shit-done/templates/UI-SPEC.md +100 -0
- package/get-shit-done/templates/VALIDATION.md +76 -0
- package/get-shit-done/templates/claude-md.md +122 -0
- package/get-shit-done/templates/codebase/architecture.md +255 -0
- package/get-shit-done/templates/codebase/concerns.md +310 -0
- package/get-shit-done/templates/codebase/conventions.md +307 -0
- package/get-shit-done/templates/codebase/integrations.md +280 -0
- package/get-shit-done/templates/codebase/stack.md +186 -0
- package/get-shit-done/templates/codebase/structure.md +285 -0
- package/get-shit-done/templates/codebase/testing.md +480 -0
- package/get-shit-done/templates/config.json +44 -0
- package/get-shit-done/templates/context.md +352 -0
- package/get-shit-done/templates/continue-here.md +78 -0
- package/get-shit-done/templates/copilot-instructions.md +7 -0
- package/get-shit-done/templates/debug-subagent-prompt.md +91 -0
- package/get-shit-done/templates/dev-preferences.md +21 -0
- package/get-shit-done/templates/discovery.md +146 -0
- package/get-shit-done/templates/discussion-log.md +63 -0
- package/get-shit-done/templates/milestone-archive.md +123 -0
- package/get-shit-done/templates/milestone.md +115 -0
- package/get-shit-done/templates/phase-prompt.md +610 -0
- package/get-shit-done/templates/planner-subagent-prompt.md +117 -0
- package/get-shit-done/templates/project.md +186 -0
- package/get-shit-done/templates/requirements.md +231 -0
- package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
- package/get-shit-done/templates/research-project/FEATURES.md +147 -0
- package/get-shit-done/templates/research-project/PITFALLS.md +200 -0
- package/get-shit-done/templates/research-project/STACK.md +120 -0
- package/get-shit-done/templates/research-project/SUMMARY.md +170 -0
- package/get-shit-done/templates/research.md +552 -0
- package/get-shit-done/templates/retrospective.md +54 -0
- package/get-shit-done/templates/roadmap.md +202 -0
- package/get-shit-done/templates/state.md +176 -0
- package/get-shit-done/templates/summary-complex.md +59 -0
- package/get-shit-done/templates/summary-minimal.md +41 -0
- package/get-shit-done/templates/summary-standard.md +48 -0
- package/get-shit-done/templates/summary.md +248 -0
- package/get-shit-done/templates/user-profile.md +146 -0
- package/get-shit-done/templates/user-setup.md +311 -0
- package/get-shit-done/templates/verification-report.md +322 -0
- package/get-shit-done/workflows/add-phase.md +112 -0
- package/get-shit-done/workflows/add-tests.md +351 -0
- package/get-shit-done/workflows/add-todo.md +158 -0
- package/get-shit-done/workflows/audit-milestone.md +340 -0
- package/get-shit-done/workflows/audit-uat.md +109 -0
- package/get-shit-done/workflows/autonomous.md +891 -0
- package/get-shit-done/workflows/check-todos.md +177 -0
- package/get-shit-done/workflows/cleanup.md +152 -0
- package/get-shit-done/workflows/complete-milestone.md +767 -0
- package/get-shit-done/workflows/diagnose-issues.md +231 -0
- package/get-shit-done/workflows/discovery-phase.md +289 -0
- package/get-shit-done/workflows/discuss-phase-assumptions.md +653 -0
- package/get-shit-done/workflows/discuss-phase.md +1049 -0
- package/get-shit-done/workflows/do.md +104 -0
- package/get-shit-done/workflows/execute-phase.md +846 -0
- package/get-shit-done/workflows/execute-plan.md +514 -0
- package/get-shit-done/workflows/fast.md +105 -0
- package/get-shit-done/workflows/forensics.md +265 -0
- package/get-shit-done/workflows/health.md +181 -0
- package/get-shit-done/workflows/help.md +634 -0
- package/get-shit-done/workflows/insert-phase.md +130 -0
- package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
- package/get-shit-done/workflows/list-workspaces.md +56 -0
- package/get-shit-done/workflows/manager.md +362 -0
- package/get-shit-done/workflows/map-codebase.md +377 -0
- package/get-shit-done/workflows/milestone-summary.md +223 -0
- package/get-shit-done/workflows/new-milestone.md +486 -0
- package/get-shit-done/workflows/new-project.md +1250 -0
- package/get-shit-done/workflows/new-workspace.md +237 -0
- package/get-shit-done/workflows/next.md +97 -0
- package/get-shit-done/workflows/node-repair.md +92 -0
- package/get-shit-done/workflows/note.md +156 -0
- package/get-shit-done/workflows/pause-work.md +176 -0
- package/get-shit-done/workflows/plan-milestone-gaps.md +273 -0
- package/get-shit-done/workflows/plan-phase.md +859 -0
- package/get-shit-done/workflows/plant-seed.md +169 -0
- package/get-shit-done/workflows/pr-branch.md +129 -0
- package/get-shit-done/workflows/profile-user.md +450 -0
- package/get-shit-done/workflows/progress.md +507 -0
- package/get-shit-done/workflows/quick.md +757 -0
- package/get-shit-done/workflows/remove-phase.md +155 -0
- package/get-shit-done/workflows/remove-workspace.md +90 -0
- package/get-shit-done/workflows/research-phase.md +82 -0
- package/get-shit-done/workflows/resume-project.md +326 -0
- package/get-shit-done/workflows/review.md +228 -0
- package/get-shit-done/workflows/session-report.md +146 -0
- package/get-shit-done/workflows/settings.md +283 -0
- package/get-shit-done/workflows/ship.md +228 -0
- package/get-shit-done/workflows/stats.md +60 -0
- package/get-shit-done/workflows/transition.md +671 -0
- package/get-shit-done/workflows/ui-phase.md +302 -0
- package/get-shit-done/workflows/ui-review.md +165 -0
- package/get-shit-done/workflows/update.md +323 -0
- package/get-shit-done/workflows/validate-phase.md +174 -0
- package/get-shit-done/workflows/verify-phase.md +254 -0
- package/get-shit-done/workflows/verify-work.md +637 -0
- package/hooks/dist/gsd-check-update.js +114 -0
- package/hooks/dist/gsd-context-monitor.js +156 -0
- package/hooks/dist/gsd-prompt-guard.js +96 -0
- package/hooks/dist/gsd-statusline.js +119 -0
- package/hooks/dist/gsd-workflow-guard.js +94 -0
- package/package.json +52 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +82 -0
- package/scripts/prompt-injection-scan.sh +198 -0
- package/scripts/run-tests.cjs +29 -0
- package/scripts/secret-scan.sh +227 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* arc-scanner — Regex-based ARC annotation tag scanner
|
|
3
|
+
*
|
|
4
|
+
* Extracts @gsd-tags from source files anchored to comment tokens,
|
|
5
|
+
* preventing false positives from strings, URLs, and template literals.
|
|
6
|
+
*
|
|
7
|
+
* Requirements: SCAN-01, SCAN-02, SCAN-03, SCAN-04
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { output, error } = require('./core.cjs');
|
|
15
|
+
|
|
16
|
+
// COPY THIS REGEX VERBATIM — do not modify
|
|
17
|
+
// Anchors to: leading whitespace + comment token + optional space + @gsd-<type>[(metadata)] description
|
|
18
|
+
const TAG_LINE_RE = /^[ \t]*(?:\/\/+|\/\*+|\*+|#+|--+|"{3}|'{3})[ \t]*@gsd-(\w+)(?:\(([^)]*)\))?[ \t]*(.*?)[ \t]*$/gm;
|
|
19
|
+
|
|
20
|
+
const DEFAULT_EXCLUDES = ['node_modules', 'dist', 'build', '.planning', '.git'];
|
|
21
|
+
|
|
22
|
+
const VALID_TAG_TYPES = new Set([
|
|
23
|
+
'context', 'decision', 'todo', 'constraint', 'pattern', 'ref', 'risk', 'api'
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parse the parenthesized metadata block.
|
|
28
|
+
* e.g. "phase:2, priority:high" → { phase: '2', priority: 'high' }
|
|
29
|
+
*
|
|
30
|
+
* @param {string|undefined} raw - Captured metadata string or undefined
|
|
31
|
+
* @returns {Object} Key-value pairs, all values as strings
|
|
32
|
+
*/
|
|
33
|
+
function parseMetadata(raw) {
|
|
34
|
+
if (!raw || !raw.trim()) return {};
|
|
35
|
+
const result = {};
|
|
36
|
+
const parts = raw.split(/,\s*/);
|
|
37
|
+
for (const part of parts) {
|
|
38
|
+
const colonIdx = part.indexOf(':');
|
|
39
|
+
if (colonIdx === -1) continue;
|
|
40
|
+
const key = part.slice(0, colonIdx).trim();
|
|
41
|
+
const value = part.slice(colonIdx + 1).trim();
|
|
42
|
+
if (key) result[key] = value;
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Scan a single file for @gsd-tags anchored to comment tokens.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} filePath - Absolute or relative path to the file
|
|
51
|
+
* @returns {Array<Object>} Array of tag objects
|
|
52
|
+
*/
|
|
53
|
+
function scanFile(filePath) {
|
|
54
|
+
let content;
|
|
55
|
+
try {
|
|
56
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Create a fresh regex instance per call to avoid lastIndex state issues with /gm flag
|
|
62
|
+
const re = new RegExp(TAG_LINE_RE.source, 'gm');
|
|
63
|
+
const tags = [];
|
|
64
|
+
|
|
65
|
+
for (const match of content.matchAll(re)) {
|
|
66
|
+
const type = match[1];
|
|
67
|
+
|
|
68
|
+
// Only include known ARC tag types
|
|
69
|
+
if (!VALID_TAG_TYPES.has(type)) continue;
|
|
70
|
+
|
|
71
|
+
// Compute 1-based line number by counting newlines before match position
|
|
72
|
+
const line = (content.slice(0, match.index).match(/\n/g) || []).length + 1;
|
|
73
|
+
|
|
74
|
+
tags.push({
|
|
75
|
+
type,
|
|
76
|
+
file: filePath,
|
|
77
|
+
line,
|
|
78
|
+
metadata: parseMetadata(match[2]),
|
|
79
|
+
description: (match[3] || '').trim(),
|
|
80
|
+
raw: match[0].trim(),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return tags;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Recursively walk a directory, scanning all files for @gsd-tags.
|
|
89
|
+
* Respects DEFAULT_EXCLUDES and optional .gsdignore at dirPath root.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} dirPath - Root directory to scan
|
|
92
|
+
* @param {Object} [options] - Optional filters
|
|
93
|
+
* @param {string} [options.phaseFilter] - Only return tags where metadata.phase === phaseFilter
|
|
94
|
+
* @param {string} [options.typeFilter] - Only return tags where type === typeFilter
|
|
95
|
+
* @param {string[]} [options.excludes] - Additional directory/file names to skip
|
|
96
|
+
* @returns {Array<Object>} All matching tags across all files
|
|
97
|
+
*/
|
|
98
|
+
function scanDirectory(dirPath, options) {
|
|
99
|
+
options = options || {};
|
|
100
|
+
|
|
101
|
+
// Load .gsdignore patterns from project root (simple line matching)
|
|
102
|
+
const gsdIgnorePath = path.join(dirPath, '.gsdignore');
|
|
103
|
+
const gsdIgnorePatterns = [];
|
|
104
|
+
try {
|
|
105
|
+
if (fs.existsSync(gsdIgnorePath)) {
|
|
106
|
+
const lines = fs.readFileSync(gsdIgnorePath, 'utf-8').split('\n');
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
const trimmed = line.trim();
|
|
109
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
110
|
+
gsdIgnorePatterns.push(trimmed);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// Ignore errors reading .gsdignore
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Internal recursive walker.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} dir - Current directory being walked
|
|
122
|
+
* @returns {Array<Object>} Tags found under dir
|
|
123
|
+
*/
|
|
124
|
+
function walk(dir) {
|
|
125
|
+
let entries;
|
|
126
|
+
try {
|
|
127
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
128
|
+
} catch {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const allTags = [];
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
const name = entry.name;
|
|
135
|
+
|
|
136
|
+
// Skip DEFAULT_EXCLUDES directories
|
|
137
|
+
if (DEFAULT_EXCLUDES.includes(name)) continue;
|
|
138
|
+
|
|
139
|
+
// Skip user-provided excludes
|
|
140
|
+
if (options.excludes && options.excludes.includes(name)) continue;
|
|
141
|
+
|
|
142
|
+
// Skip .gsdignore patterns
|
|
143
|
+
if (gsdIgnorePatterns.some(pat => name === pat || name.startsWith(pat))) continue;
|
|
144
|
+
|
|
145
|
+
const fullPath = path.join(dir, name);
|
|
146
|
+
|
|
147
|
+
if (entry.isDirectory()) {
|
|
148
|
+
allTags.push(...walk(fullPath));
|
|
149
|
+
} else if (entry.isFile()) {
|
|
150
|
+
allTags.push(...scanFile(fullPath));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return allTags;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let tags = walk(dirPath);
|
|
158
|
+
|
|
159
|
+
// Apply phase filter
|
|
160
|
+
if (options.phaseFilter !== undefined && options.phaseFilter !== null) {
|
|
161
|
+
const phaseStr = String(options.phaseFilter);
|
|
162
|
+
tags = tags.filter(tag => tag.metadata.phase === phaseStr);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Apply type filter
|
|
166
|
+
if (options.typeFilter) {
|
|
167
|
+
tags = tags.filter(tag => tag.type === options.typeFilter);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return tags;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Format tags as a JSON string.
|
|
175
|
+
*
|
|
176
|
+
* @param {Array<Object>} tags - Array of tag objects
|
|
177
|
+
* @returns {string} Pretty-printed JSON string
|
|
178
|
+
*/
|
|
179
|
+
function formatAsJson(tags) {
|
|
180
|
+
return JSON.stringify(tags, null, 2);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Format tags as a CODE-INVENTORY.md Markdown document.
|
|
185
|
+
*
|
|
186
|
+
* Structure:
|
|
187
|
+
* - Header with metadata
|
|
188
|
+
* - ## Summary Statistics (table of tag type counts)
|
|
189
|
+
* - ## Tags by Type (H3 per type, H4 per file, table of line/metadata/description)
|
|
190
|
+
* - ## Phase Reference Index (table of phase/count/files)
|
|
191
|
+
*
|
|
192
|
+
* @param {Array<Object>} tags - Array of tag objects
|
|
193
|
+
* @param {string} [projectName] - Project name for the header
|
|
194
|
+
* @returns {string} CODE-INVENTORY.md Markdown string
|
|
195
|
+
*/
|
|
196
|
+
function formatAsMarkdown(tags, projectName) {
|
|
197
|
+
const now = new Date().toISOString();
|
|
198
|
+
const name = projectName || 'Unknown Project';
|
|
199
|
+
|
|
200
|
+
// Gather unique files
|
|
201
|
+
const fileSet = new Set(tags.map(t => t.file));
|
|
202
|
+
const totalFiles = fileSet.size;
|
|
203
|
+
const totalTags = tags.length;
|
|
204
|
+
|
|
205
|
+
// Build type → tags map (preserve ARC order)
|
|
206
|
+
const TYPE_ORDER = ['context', 'decision', 'todo', 'constraint', 'pattern', 'ref', 'risk', 'api'];
|
|
207
|
+
const byType = {};
|
|
208
|
+
for (const type of TYPE_ORDER) {
|
|
209
|
+
byType[type] = [];
|
|
210
|
+
}
|
|
211
|
+
for (const tag of tags) {
|
|
212
|
+
if (byType[tag.type]) {
|
|
213
|
+
byType[tag.type].push(tag);
|
|
214
|
+
} else {
|
|
215
|
+
byType[tag.type] = [tag];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── Header ────────────────────────────────────────────────────────────────
|
|
220
|
+
const lines = [
|
|
221
|
+
`# CODE-INVENTORY.md`,
|
|
222
|
+
``,
|
|
223
|
+
`**Generated:** ${now}`,
|
|
224
|
+
`**Project:** ${name}`,
|
|
225
|
+
`**Schema version:** 1.0`,
|
|
226
|
+
`**Tags found:** ${totalTags} across ${totalFiles} file${totalFiles !== 1 ? 's' : ''}`,
|
|
227
|
+
``,
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
// ── Summary Statistics ────────────────────────────────────────────────────
|
|
231
|
+
lines.push(`## Summary Statistics`, ``);
|
|
232
|
+
lines.push(`| Tag Type | Count |`);
|
|
233
|
+
lines.push(`|----------|-------|`);
|
|
234
|
+
for (const type of TYPE_ORDER) {
|
|
235
|
+
const count = byType[type] ? byType[type].length : 0;
|
|
236
|
+
if (count > 0) {
|
|
237
|
+
lines.push(`| @gsd-${type} | ${count} |`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
lines.push(``);
|
|
241
|
+
|
|
242
|
+
// ── Tags by Type ──────────────────────────────────────────────────────────
|
|
243
|
+
lines.push(`## Tags by Type`, ``);
|
|
244
|
+
|
|
245
|
+
for (const type of TYPE_ORDER) {
|
|
246
|
+
const typeTags = byType[type];
|
|
247
|
+
if (!typeTags || typeTags.length === 0) continue;
|
|
248
|
+
|
|
249
|
+
lines.push(`### @gsd-${type}`, ``);
|
|
250
|
+
|
|
251
|
+
// Group by file
|
|
252
|
+
const byFile = {};
|
|
253
|
+
for (const tag of typeTags) {
|
|
254
|
+
if (!byFile[tag.file]) byFile[tag.file] = [];
|
|
255
|
+
byFile[tag.file].push(tag);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const [filePath, fileTags] of Object.entries(byFile)) {
|
|
259
|
+
lines.push(`#### ${filePath}`, ``);
|
|
260
|
+
lines.push(`| Line | Metadata | Description |`);
|
|
261
|
+
lines.push(`|------|----------|-------------|`);
|
|
262
|
+
for (const tag of fileTags) {
|
|
263
|
+
const metaStr = Object.keys(tag.metadata).length > 0
|
|
264
|
+
? Object.entries(tag.metadata).map(([k, v]) => `${k}:${v}`).join(', ')
|
|
265
|
+
: '—';
|
|
266
|
+
lines.push(`| ${tag.line} | ${metaStr} | ${tag.description || '—'} |`);
|
|
267
|
+
}
|
|
268
|
+
lines.push(``);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── Phase Reference Index ─────────────────────────────────────────────────
|
|
273
|
+
lines.push(`## Phase Reference Index`, ``);
|
|
274
|
+
lines.push(`| Phase | Tag Count | Files |`);
|
|
275
|
+
lines.push(`|-------|-----------|-------|`);
|
|
276
|
+
|
|
277
|
+
// Group by phase value
|
|
278
|
+
const byPhase = {};
|
|
279
|
+
for (const tag of tags) {
|
|
280
|
+
const phase = tag.metadata.phase || '(untagged)';
|
|
281
|
+
if (!byPhase[phase]) byPhase[phase] = { count: 0, files: new Set() };
|
|
282
|
+
byPhase[phase].count++;
|
|
283
|
+
byPhase[phase].files.add(tag.file);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Sort: numbered phases first, then (untagged)
|
|
287
|
+
const phases = Object.keys(byPhase).sort((a, b) => {
|
|
288
|
+
if (a === '(untagged)') return 1;
|
|
289
|
+
if (b === '(untagged)') return -1;
|
|
290
|
+
return Number(a) - Number(b);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
for (const phase of phases) {
|
|
294
|
+
const { count, files } = byPhase[phase];
|
|
295
|
+
lines.push(`| ${phase} | ${count} | ${[...files].join(', ')} |`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
lines.push(``);
|
|
299
|
+
|
|
300
|
+
return lines.join('\n');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* CLI entry point: scan a target path, format, and write output.
|
|
305
|
+
* Called by gsd-tools.cjs case 'extract-tags'.
|
|
306
|
+
*
|
|
307
|
+
* @param {string} cwd - Current working directory
|
|
308
|
+
* @param {string} targetPath - Path to scan (file or directory)
|
|
309
|
+
* @param {Object} [opts] - Options
|
|
310
|
+
* @param {string} [opts.phaseFilter] - Phase filter
|
|
311
|
+
* @param {string} [opts.typeFilter] - Type filter
|
|
312
|
+
* @param {string} [opts.format] - 'json' (default) or 'md'
|
|
313
|
+
* @param {string} [opts.outputFile] - Write to file instead of stdout
|
|
314
|
+
*/
|
|
315
|
+
function cmdExtractTags(cwd, targetPath, opts) {
|
|
316
|
+
opts = opts || {};
|
|
317
|
+
const format = opts.format || 'json';
|
|
318
|
+
const resolvedPath = path.resolve(cwd, targetPath || cwd);
|
|
319
|
+
|
|
320
|
+
const tags = scanDirectory(resolvedPath, {
|
|
321
|
+
phaseFilter: opts.phaseFilter,
|
|
322
|
+
typeFilter: opts.typeFilter,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
let result;
|
|
326
|
+
if (format === 'md') {
|
|
327
|
+
result = formatAsMarkdown(tags, opts.projectName);
|
|
328
|
+
} else {
|
|
329
|
+
result = formatAsJson(tags);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (opts.outputFile) {
|
|
333
|
+
const outDir = path.dirname(opts.outputFile);
|
|
334
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
335
|
+
fs.writeFileSync(opts.outputFile, result, 'utf-8');
|
|
336
|
+
} else {
|
|
337
|
+
process.stdout.write(result + '\n');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
module.exports = { scanFile, scanDirectory, formatAsJson, formatAsMarkdown, cmdExtractTags };
|