gsdd-cli 0.18.5 → 0.19.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.
- package/LICENSE +21 -21
- package/README.md +610 -608
- package/agents/DISTILLATION.md +421 -421
- package/agents/README.md +62 -62
- package/agents/approach-explorer.md +370 -361
- package/agents/debugger.md +82 -82
- package/agents/executor.md +473 -394
- package/agents/integration-checker.md +318 -318
- package/agents/mapper.md +103 -103
- package/agents/planner.md +342 -313
- package/agents/researcher.md +84 -84
- package/agents/roadmapper.md +296 -296
- package/agents/synthesizer.md +236 -236
- package/agents/verifier.md +337 -337
- package/bin/adapters/agents.mjs +34 -34
- package/bin/adapters/claude.mjs +193 -191
- package/bin/adapters/codex.mjs +85 -85
- package/bin/adapters/index.mjs +20 -20
- package/bin/adapters/opencode.mjs +280 -278
- package/bin/gsdd.mjs +123 -116
- package/bin/lib/cli-utils.mjs +28 -28
- package/bin/lib/evidence-contract.mjs +325 -112
- package/bin/lib/file-ops.mjs +186 -144
- package/bin/lib/health-truth.mjs +196 -178
- package/bin/lib/health.mjs +246 -226
- package/bin/lib/init-flow.mjs +247 -231
- package/bin/lib/init-prompts.mjs +248 -247
- package/bin/lib/init-runtime.mjs +193 -190
- package/bin/lib/init.mjs +17 -17
- package/bin/lib/lifecycle-preflight.mjs +760 -326
- package/bin/lib/lifecycle-state.mjs +356 -267
- package/bin/lib/manifest.mjs +116 -114
- package/bin/lib/models.mjs +411 -411
- package/bin/lib/phase.mjs +365 -358
- package/bin/lib/plan-constants.mjs +35 -30
- package/bin/lib/provenance.mjs +109 -106
- package/bin/lib/rendering.mjs +119 -83
- package/bin/lib/runtime-freshness.mjs +214 -214
- package/bin/lib/session-fingerprint.mjs +91 -14
- package/bin/lib/templates.mjs +225 -224
- package/bin/lib/workspace-root.mjs +2 -1
- package/distilled/DESIGN.md +2461 -2323
- package/distilled/EVIDENCE-INDEX.md +418 -392
- package/distilled/README.md +196 -193
- package/distilled/SKILL.md +86 -85
- package/distilled/templates/agents.block.md +21 -21
- package/distilled/templates/agents.md +6 -6
- package/distilled/templates/approach.md +272 -232
- package/distilled/templates/auth-matrix.md +78 -78
- package/distilled/templates/brownfield-change/CHANGE.md +99 -0
- package/distilled/templates/brownfield-change/HANDOFF.md +38 -0
- package/distilled/templates/brownfield-change/VERIFICATION.md +56 -0
- package/distilled/templates/codebase/architecture.md +110 -110
- package/distilled/templates/codebase/concerns.md +95 -95
- package/distilled/templates/codebase/conventions.md +193 -193
- package/distilled/templates/codebase/stack.md +96 -96
- package/distilled/templates/delegates/approach-explorer.md +28 -25
- package/distilled/templates/delegates/mapper-arch.md +26 -26
- package/distilled/templates/delegates/mapper-concerns.md +27 -27
- package/distilled/templates/delegates/mapper-quality.md +28 -28
- package/distilled/templates/delegates/mapper-tech.md +25 -25
- package/distilled/templates/delegates/plan-checker.md +78 -68
- package/distilled/templates/delegates/researcher-architecture.md +30 -30
- package/distilled/templates/delegates/researcher-features.md +30 -30
- package/distilled/templates/delegates/researcher-pitfalls.md +30 -30
- package/distilled/templates/delegates/researcher-stack.md +30 -30
- package/distilled/templates/delegates/researcher-synthesizer.md +31 -31
- package/distilled/templates/research/architecture.md +57 -57
- package/distilled/templates/research/features.md +23 -23
- package/distilled/templates/research/pitfalls.md +46 -46
- package/distilled/templates/research/stack.md +45 -45
- package/distilled/templates/research/summary.md +67 -67
- package/distilled/templates/roadmap.md +74 -62
- package/distilled/templates/spec.md +110 -110
- package/distilled/workflows/audit-milestone.md +303 -271
- package/distilled/workflows/complete-milestone.md +349 -332
- package/distilled/workflows/execute.md +457 -450
- package/distilled/workflows/map-codebase.md +253 -253
- package/distilled/workflows/new-milestone.md +242 -238
- package/distilled/workflows/new-project.md +398 -398
- package/distilled/workflows/pause.md +160 -156
- package/distilled/workflows/plan-milestone-gaps.md +183 -183
- package/distilled/workflows/plan.md +454 -448
- package/distilled/workflows/progress.md +227 -223
- package/distilled/workflows/quick.md +351 -347
- package/distilled/workflows/resume.md +220 -212
- package/distilled/workflows/verify-work.md +260 -260
- package/distilled/workflows/verify.md +431 -429
- package/docs/BROWNFIELD-PROOF.md +95 -95
- package/docs/RUNTIME-SUPPORT.md +80 -69
- package/docs/USER-GUIDE.md +394 -386
- package/docs/VERIFICATION-DISCIPLINE.md +59 -59
- package/docs/claude/context-monitor.md +98 -98
- package/docs/proof/consumer-node-cli/README.md +37 -37
- package/docs/proof/consumer-node-cli/ROADMAP.md +14 -14
- package/docs/proof/consumer-node-cli/SPEC.md +17 -17
- package/docs/proof/consumer-node-cli/brief.md +9 -9
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-PLAN.md +34 -34
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-SUMMARY.md +10 -10
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-VERIFICATION.md +30 -30
- package/package.json +62 -61
package/bin/lib/health-truth.mjs
CHANGED
|
@@ -1,92 +1,95 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { evaluateLifecycleState } from './lifecycle-state.mjs';
|
|
4
|
-
import {
|
|
5
|
-
getRuntimeFreshnessRepairGuidance,
|
|
6
|
-
summarizeRuntimeFreshnessIssues,
|
|
7
|
-
} from './runtime-freshness.mjs';
|
|
8
|
-
import { checkDrift } from './session-fingerprint.mjs';
|
|
9
|
-
|
|
10
|
-
export const TRUTH_CHECK_IDS = ['W7', 'W8', 'W9', 'W10', 'W11', 'W12'];
|
|
11
|
-
|
|
12
|
-
export function runTruthChecks(planningDir, frameworkDir, actualCheckIds, options = {}) {
|
|
13
|
-
const warnings = [];
|
|
14
|
-
const designPath = join(frameworkDir, 'distilled', 'DESIGN.md');
|
|
15
|
-
const readmePath = join(frameworkDir, 'distilled', 'README.md');
|
|
16
|
-
const workflowsDir = join(frameworkDir, 'distilled', 'workflows');
|
|
17
|
-
const gapsPath = join(frameworkDir, '.internal-research', 'gaps.md');
|
|
18
|
-
const specPath = join(planningDir, 'SPEC.md');
|
|
19
|
-
const roadmapPath = join(planningDir, 'ROADMAP.md');
|
|
20
|
-
const lifecycle = evaluateLifecycleState({ planningDir });
|
|
21
|
-
|
|
22
|
-
if (existsSync(designPath)) {
|
|
23
|
-
const documentedIds = extractHealthTableIds(readFileSync(designPath, 'utf-8'));
|
|
24
|
-
const missingFromDesign = actualCheckIds.filter((id) => !documentedIds.includes(id));
|
|
25
|
-
const extraInDesign = documentedIds.filter((id) => !actualCheckIds.includes(id));
|
|
26
|
-
if (missingFromDesign.length > 0 || extraInDesign.length > 0) {
|
|
27
|
-
const parts = [];
|
|
28
|
-
if (missingFromDesign.length > 0) parts.push(`missing in DESIGN.md: ${missingFromDesign.join(', ')}`);
|
|
29
|
-
if (extraInDesign.length > 0) parts.push(`extra in DESIGN.md: ${extraInDesign.join(', ')}`);
|
|
30
|
-
warnings.push({
|
|
31
|
-
id: 'W7',
|
|
32
|
-
severity: 'WARN',
|
|
33
|
-
message: `DESIGN.md health check table is out of sync (${parts.join('; ')})`,
|
|
34
|
-
fix: 'Update distilled/DESIGN.md section 20 to match the implemented health checks',
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (existsSync(readmePath) && existsSync(workflowsDir)) {
|
|
40
|
-
const readme = readFileSync(readmePath, 'utf-8');
|
|
41
|
-
const workflowFiles = readdirSync(workflowsDir).filter((entry) => entry.endsWith('.md')).sort();
|
|
42
|
-
const statusEntries = extractReadmeStatusEntries(readme);
|
|
43
|
-
const treeEntries = extractReadmeWorkflowTreeEntries(readme);
|
|
44
|
-
const issues = [];
|
|
45
|
-
if (statusEntries.length !== workflowFiles.length) {
|
|
46
|
-
issues.push(`status table ${statusEntries.length} != workflows dir ${workflowFiles.length}`);
|
|
47
|
-
}
|
|
48
|
-
if (treeEntries.length !== workflowFiles.length) {
|
|
49
|
-
issues.push(`framework tree ${treeEntries.length} != workflows dir ${workflowFiles.length}`);
|
|
50
|
-
}
|
|
51
|
-
const missingStatus = workflowFiles.filter((name) => !statusEntries.includes(name));
|
|
52
|
-
const missingTree = workflowFiles.filter((name) => !treeEntries.includes(name));
|
|
53
|
-
if (missingStatus.length > 0) issues.push(`missing from status table: ${missingStatus.join(', ')}`);
|
|
54
|
-
if (missingTree.length > 0) issues.push(`missing from framework tree: ${missingTree.join(', ')}`);
|
|
55
|
-
if (issues.length > 0) {
|
|
56
|
-
warnings.push({
|
|
57
|
-
id: 'W8',
|
|
58
|
-
severity: 'WARN',
|
|
59
|
-
message: `distilled/README.md workflow inventory is out of sync (${issues.join('; ')})`,
|
|
60
|
-
fix: 'Update distilled/README.md workflow
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (existsSync(gapsPath)) {
|
|
66
|
-
const gapRefs = extractRepoLocalPaths(readFileSync(gapsPath, 'utf-8'));
|
|
67
|
-
const missingRefs = gapRefs.filter((ref) => !existsSync(join(frameworkDir, ref)));
|
|
68
|
-
if (missingRefs.length > 0) {
|
|
69
|
-
warnings.push({
|
|
70
|
-
id: 'W9',
|
|
71
|
-
severity: 'WARN',
|
|
72
|
-
message: `gaps.md references missing repo-local paths (${missingRefs.join(', ')})`,
|
|
73
|
-
fix: 'Annotate stale gap references as resolved or update them to current repo truth',
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (existsSync(
|
|
79
|
-
const mismatches =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { evaluateLifecycleState } from './lifecycle-state.mjs';
|
|
4
|
+
import {
|
|
5
|
+
getRuntimeFreshnessRepairGuidance,
|
|
6
|
+
summarizeRuntimeFreshnessIssues,
|
|
7
|
+
} from './runtime-freshness.mjs';
|
|
8
|
+
import { checkDrift } from './session-fingerprint.mjs';
|
|
9
|
+
|
|
10
|
+
export const TRUTH_CHECK_IDS = ['W7', 'W8', 'W9', 'W10', 'W11', 'W12'];
|
|
11
|
+
|
|
12
|
+
export function runTruthChecks(planningDir, frameworkDir, actualCheckIds, options = {}) {
|
|
13
|
+
const warnings = [];
|
|
14
|
+
const designPath = join(frameworkDir, 'distilled', 'DESIGN.md');
|
|
15
|
+
const readmePath = join(frameworkDir, 'distilled', 'README.md');
|
|
16
|
+
const workflowsDir = join(frameworkDir, 'distilled', 'workflows');
|
|
17
|
+
const gapsPath = join(frameworkDir, '.internal-research', 'gaps.md');
|
|
18
|
+
const specPath = join(planningDir, 'SPEC.md');
|
|
19
|
+
const roadmapPath = join(planningDir, 'ROADMAP.md');
|
|
20
|
+
const lifecycle = evaluateLifecycleState({ planningDir });
|
|
21
|
+
|
|
22
|
+
if (existsSync(designPath)) {
|
|
23
|
+
const documentedIds = extractHealthTableIds(readFileSync(designPath, 'utf-8'));
|
|
24
|
+
const missingFromDesign = actualCheckIds.filter((id) => !documentedIds.includes(id));
|
|
25
|
+
const extraInDesign = documentedIds.filter((id) => !actualCheckIds.includes(id));
|
|
26
|
+
if (missingFromDesign.length > 0 || extraInDesign.length > 0) {
|
|
27
|
+
const parts = [];
|
|
28
|
+
if (missingFromDesign.length > 0) parts.push(`missing in DESIGN.md: ${missingFromDesign.join(', ')}`);
|
|
29
|
+
if (extraInDesign.length > 0) parts.push(`extra in DESIGN.md: ${extraInDesign.join(', ')}`);
|
|
30
|
+
warnings.push({
|
|
31
|
+
id: 'W7',
|
|
32
|
+
severity: 'WARN',
|
|
33
|
+
message: `DESIGN.md health check table is out of sync (${parts.join('; ')})`,
|
|
34
|
+
fix: 'Update distilled/DESIGN.md section 20 to match the implemented health checks',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (existsSync(readmePath) && existsSync(workflowsDir)) {
|
|
40
|
+
const readme = readFileSync(readmePath, 'utf-8');
|
|
41
|
+
const workflowFiles = readdirSync(workflowsDir).filter((entry) => entry.endsWith('.md')).sort();
|
|
42
|
+
const statusEntries = extractReadmeStatusEntries(readme);
|
|
43
|
+
const treeEntries = extractReadmeWorkflowTreeEntries(readme);
|
|
44
|
+
const issues = [];
|
|
45
|
+
if (statusEntries.length !== workflowFiles.length) {
|
|
46
|
+
issues.push(`status table ${statusEntries.length} != workflows dir ${workflowFiles.length}`);
|
|
47
|
+
}
|
|
48
|
+
if (treeEntries.length !== workflowFiles.length) {
|
|
49
|
+
issues.push(`framework tree ${treeEntries.length} != workflows dir ${workflowFiles.length}`);
|
|
50
|
+
}
|
|
51
|
+
const missingStatus = workflowFiles.filter((name) => !statusEntries.includes(name));
|
|
52
|
+
const missingTree = workflowFiles.filter((name) => !treeEntries.includes(name));
|
|
53
|
+
if (missingStatus.length > 0) issues.push(`missing from status table: ${missingStatus.join(', ')}`);
|
|
54
|
+
if (missingTree.length > 0) issues.push(`missing from framework tree: ${missingTree.join(', ')}`);
|
|
55
|
+
if (issues.length > 0) {
|
|
56
|
+
warnings.push({
|
|
57
|
+
id: 'W8',
|
|
58
|
+
severity: 'WARN',
|
|
59
|
+
message: `distilled/README.md workflow surface/status inventory is out of sync (${issues.join('; ')})`,
|
|
60
|
+
fix: 'Update distilled/README.md workflow inventory table and framework file tree to match distilled/workflows/',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (existsSync(gapsPath)) {
|
|
66
|
+
const gapRefs = extractRepoLocalPaths(readFileSync(gapsPath, 'utf-8'));
|
|
67
|
+
const missingRefs = gapRefs.filter((ref) => !existsSync(join(frameworkDir, ref)));
|
|
68
|
+
if (missingRefs.length > 0) {
|
|
69
|
+
warnings.push({
|
|
70
|
+
id: 'W9',
|
|
71
|
+
severity: 'WARN',
|
|
72
|
+
message: `gaps.md references missing repo-local paths (${missingRefs.join(', ')})`,
|
|
73
|
+
fix: 'Annotate stale gap references as resolved or update them to current repo truth',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (existsSync(roadmapPath)) {
|
|
79
|
+
const mismatches = [
|
|
80
|
+
...(existsSync(specPath) ? lifecycle.requirementAlignment.mismatches : []),
|
|
81
|
+
...lifecycle.phaseStatusAlignment.mismatches,
|
|
82
|
+
];
|
|
83
|
+
if (mismatches.length > 0) {
|
|
84
|
+
warnings.push({
|
|
85
|
+
id: 'W10',
|
|
86
|
+
severity: 'WARN',
|
|
87
|
+
message: `ROADMAP/SPEC requirement status drift (${mismatches.join('; ')})`,
|
|
88
|
+
fix: 'Reconcile .planning/ROADMAP.md phase completion markers with .planning/SPEC.md requirement checkboxes',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
90
93
|
if (options.runtimeFreshnessReport?.issueCount > 0) {
|
|
91
94
|
warnings.push({
|
|
92
95
|
id: 'W11',
|
|
@@ -95,92 +98,107 @@ export function runTruthChecks(planningDir, frameworkDir, actualCheckIds, option
|
|
|
95
98
|
fix: getRuntimeFreshnessRepairGuidance(options.runtimeFreshnessReport),
|
|
96
99
|
});
|
|
97
100
|
}
|
|
98
|
-
|
|
99
|
-
const drift = checkDrift(planningDir);
|
|
100
|
-
if (drift.drifted) {
|
|
101
|
-
warnings.push({
|
|
102
|
-
id: 'W12',
|
|
103
|
-
severity: 'WARN',
|
|
104
|
-
message: `Planning state drifted since last recorded session (${drift.details.join('; ')})`,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return warnings;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function extractHealthTableIds(content) {
|
|
113
|
-
const section = extractSection(content, '## 20. Workspace Health Diagnostics', '## 21.');
|
|
114
|
-
if (!section) return [];
|
|
115
|
-
return [...normalizeContent(section).matchAll(/^\|\s*([EWI]\d+)\s*\|/gm)].map((result) => result[1]);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function extractReadmeStatusEntries(content) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
'
|
|
152
|
-
'
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
101
|
+
|
|
102
|
+
const drift = checkDrift(planningDir);
|
|
103
|
+
if (drift.drifted) {
|
|
104
|
+
warnings.push({
|
|
105
|
+
id: 'W12',
|
|
106
|
+
severity: 'WARN',
|
|
107
|
+
message: `Planning state drifted since last recorded session (${drift.details.join('; ')})`,
|
|
108
|
+
fix: 'Review the changed planning files. If the drift is intentional, rebaseline with `node .planning/bin/gsdd.mjs session-fingerprint write`, then rerun the blocked lifecycle preflight.',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return warnings;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function extractHealthTableIds(content) {
|
|
116
|
+
const section = extractSection(content, '## 20. Workspace Health Diagnostics', '## 21.');
|
|
117
|
+
if (!section) return [];
|
|
118
|
+
return [...normalizeContent(section).matchAll(/^\|\s*([EWI]\d+)\s*\|/gm)].map((result) => result[1]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function extractReadmeStatusEntries(content) {
|
|
122
|
+
const workflowSurface = extractSection(content, '## Workflow Surface', 'Architecture notes:');
|
|
123
|
+
const currentStatus = extractSection(content, '## Current Status', 'Architecture notes:');
|
|
124
|
+
const section = workflowSurface || currentStatus;
|
|
125
|
+
if (!section) return [];
|
|
126
|
+
return [...normalizeContent(section).matchAll(/\|\s*`([^`]+\.md)`\s*\|/g)].map((result) => result[1]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function extractReadmeWorkflowTreeEntries(content) {
|
|
130
|
+
const section = extractSection(content, '## Files In This Framework', '## ');
|
|
131
|
+
const match = normalizeContent(section || '').match(/```[^\n]*\n([\s\S]*?)\n```/);
|
|
132
|
+
if (!match) return [];
|
|
133
|
+
const entries = [];
|
|
134
|
+
let inWorkflows = false;
|
|
135
|
+
for (const rawLine of match[1].split('\n')) {
|
|
136
|
+
const line = rawLine.trim();
|
|
137
|
+
if (!line) continue;
|
|
138
|
+
if (!inWorkflows) {
|
|
139
|
+
if (/\bworkflows\/$/.test(line)) inWorkflows = true;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (/\.md\b/.test(line)) {
|
|
143
|
+
const file = line.match(/([^\s/`|├└─]+\.md)\b/);
|
|
144
|
+
if (file) entries.push(file[1]);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (/\b[^\s/]+\/$/.test(line)) break;
|
|
148
|
+
}
|
|
149
|
+
return entries;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function extractRepoLocalPaths(content) {
|
|
153
|
+
const allowedRoots = [
|
|
154
|
+
'AGENTS.md',
|
|
155
|
+
'README.md',
|
|
156
|
+
'CHANGELOG.md',
|
|
157
|
+
'SPEC.md',
|
|
158
|
+
'package.json',
|
|
159
|
+
'.planning/',
|
|
160
|
+
'.internal-research/',
|
|
161
|
+
'.agents/',
|
|
162
|
+
'.claude/',
|
|
163
|
+
'.opencode/',
|
|
164
|
+
'.codex/',
|
|
165
|
+
'.worktrees/',
|
|
166
|
+
'bin/',
|
|
167
|
+
'distilled/',
|
|
168
|
+
'tests/',
|
|
169
|
+
'fixtures/',
|
|
170
|
+
'agents/',
|
|
171
|
+
];
|
|
172
|
+
const refs = new Set();
|
|
173
|
+
for (const result of content.matchAll(/`([^`]+)`/g)) {
|
|
174
|
+
const value = result[1].trim();
|
|
175
|
+
if (!looksLikeRepoLocalPath(value, allowedRoots)) continue;
|
|
176
|
+
refs.add(value.replace(/\\/g, '/').replace(/\/$/, ''));
|
|
177
|
+
}
|
|
178
|
+
return [...refs];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function looksLikeRepoLocalPath(value, allowedRoots) {
|
|
182
|
+
if (value.includes(' ')) return false;
|
|
183
|
+
if (value.startsWith('/')) return false;
|
|
184
|
+
if (value.startsWith('$')) return false;
|
|
185
|
+
if (value.startsWith('..')) return false;
|
|
186
|
+
if (value.includes('://')) return false;
|
|
187
|
+
if (value.startsWith('feat/') || value.startsWith('fix/') || value.startsWith('pr') || value.startsWith('origin/')) return false;
|
|
188
|
+
if (allowedRoots.some((root) => value === root || value.startsWith(root))) return true;
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function extractSection(content, startMarker, endMarker) {
|
|
193
|
+
const normalized = normalizeContent(content);
|
|
194
|
+
const start = normalized.indexOf(startMarker);
|
|
195
|
+
if (start === -1) return '';
|
|
196
|
+
const rest = normalized.slice(start);
|
|
197
|
+
if (!endMarker) return rest;
|
|
198
|
+
const end = rest.indexOf(endMarker, startMarker.length);
|
|
199
|
+
return end === -1 ? rest : rest.slice(0, end);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function normalizeContent(content) {
|
|
203
|
+
return content.replace(/\r\n/g, '\n');
|
|
204
|
+
}
|