gsdd-cli 0.18.4 → 0.19.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 -21
- package/README.md +625 -607
- package/agents/DISTILLATION.md +421 -421
- package/agents/README.md +62 -62
- package/agents/approach-explorer.md +361 -361
- package/agents/debugger.md +82 -82
- package/agents/executor.md +394 -394
- package/agents/integration-checker.md +318 -318
- package/agents/mapper.md +103 -103
- package/agents/planner.md +313 -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 +191 -191
- package/bin/adapters/codex.mjs +85 -85
- package/bin/adapters/index.mjs +20 -20
- package/bin/adapters/opencode.mjs +278 -278
- package/bin/gsdd.mjs +123 -116
- package/bin/lib/cli-utils.mjs +28 -28
- package/bin/lib/evidence-contract.mjs +112 -112
- package/bin/lib/file-ops.mjs +209 -161
- package/bin/lib/health-truth.mjs +181 -178
- package/bin/lib/health.mjs +265 -235
- package/bin/lib/init-flow.mjs +252 -236
- package/bin/lib/init-prompts.mjs +248 -247
- package/bin/lib/init-runtime.mjs +225 -212
- package/bin/lib/init.mjs +17 -17
- package/bin/lib/lifecycle-preflight.mjs +360 -333
- package/bin/lib/lifecycle-state.mjs +351 -267
- package/bin/lib/manifest.mjs +116 -114
- package/bin/lib/models.mjs +411 -411
- package/bin/lib/phase.mjs +397 -378
- package/bin/lib/plan-constants.mjs +30 -30
- package/bin/lib/provenance.mjs +109 -106
- package/bin/lib/rendering.mjs +178 -130
- package/bin/lib/runtime-freshness.mjs +221 -219
- package/bin/lib/templates.mjs +225 -224
- package/bin/lib/workspace-root.mjs +132 -0
- package/distilled/DESIGN.md +2347 -2327
- package/distilled/EVIDENCE-INDEX.md +397 -394
- 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 +232 -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 +25 -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 +68 -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 +275 -271
- package/distilled/workflows/complete-milestone.md +336 -332
- package/distilled/workflows/execute.md +454 -449
- 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 +451 -447
- 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 +93 -75
- package/docs/USER-GUIDE.md +440 -399
- 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/templates.mjs
CHANGED
|
@@ -1,224 +1,225 @@
|
|
|
1
|
-
// templates.mjs - Project template and role installation/refresh helpers
|
|
2
|
-
|
|
3
|
-
import { existsSync, mkdirSync, readdirSync, cpSync, unlinkSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import { fileHash, readManifest } from './manifest.mjs';
|
|
6
|
-
|
|
7
|
-
export function installProjectTemplates({ planningDir, distilledDir, agentsDir }) {
|
|
8
|
-
const localTemplatesDir = join(planningDir, 'templates');
|
|
9
|
-
const globalTemplatesDir = join(distilledDir, 'templates');
|
|
10
|
-
|
|
11
|
-
if (!existsSync(localTemplatesDir)) {
|
|
12
|
-
if (existsSync(globalTemplatesDir)) {
|
|
13
|
-
cpSync(globalTemplatesDir, localTemplatesDir, { recursive: true });
|
|
14
|
-
console.log(' - copied templates to .planning/templates/');
|
|
15
|
-
// Warn-only by design: init should not fail on missing templates because
|
|
16
|
-
// the user may still proceed and fix later. The hard gate lives in
|
|
17
|
-
// `gsdd health` (E6/E7/E8) which reports these as errors. This is the
|
|
18
|
-
// first layer of the 3-layer scaffold defense (warn at init, error at
|
|
19
|
-
// health, regression tests in manifest suite).
|
|
20
|
-
const expectedSubdirs = ['delegates', 'research', 'codebase'];
|
|
21
|
-
for (const subdir of expectedSubdirs) {
|
|
22
|
-
if (!existsSync(join(localTemplatesDir, subdir))) {
|
|
23
|
-
console.log(` - WARN: missing expected template subdir: ${subdir}/`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
const expectedRootFiles = ['spec.md', 'roadmap.md', 'auth-matrix.md'];
|
|
27
|
-
for (const file of expectedRootFiles) {
|
|
28
|
-
if (!existsSync(join(localTemplatesDir, file))) {
|
|
29
|
-
console.log(` - WARN: missing expected root template file: ${file}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
console.log(' - WARN: missing distilled/templates/; cannot copy templates');
|
|
34
|
-
}
|
|
35
|
-
} else {
|
|
36
|
-
console.log(' - .planning/templates/ already exists');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const localRolesDir = join(localTemplatesDir, 'roles');
|
|
40
|
-
if (!existsSync(localRolesDir)) {
|
|
41
|
-
if (existsSync(agentsDir)) {
|
|
42
|
-
mkdirSync(localRolesDir, { recursive: true });
|
|
43
|
-
for (const file of listRoleFiles(agentsDir)) {
|
|
44
|
-
cpSync(join(agentsDir, file), join(localRolesDir, file));
|
|
45
|
-
}
|
|
46
|
-
console.log(' - copied role contracts to .planning/templates/roles/');
|
|
47
|
-
} else {
|
|
48
|
-
console.log(' - WARN: missing agents/; cannot copy role contracts');
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
console.log(' - .planning/templates/roles/ already exists');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function refreshTemplates({ planningDir, distilledDir, agentsDir, isDry = false }) {
|
|
56
|
-
const existingManifest = readManifest(planningDir);
|
|
57
|
-
const globalTemplatesDir = join(distilledDir, 'templates');
|
|
58
|
-
const localTemplatesDir = join(planningDir, 'templates');
|
|
59
|
-
|
|
60
|
-
const categories = [
|
|
61
|
-
{ name: 'delegates', src: join(globalTemplatesDir, 'delegates'), dest: join(localTemplatesDir, 'delegates'), manifestKey: 'delegates' },
|
|
62
|
-
{ name: 'research', src: join(globalTemplatesDir, 'research'), dest: join(localTemplatesDir, 'research'), manifestKey: 'research' },
|
|
63
|
-
{ name: 'codebase', src: join(globalTemplatesDir, 'codebase'), dest: join(localTemplatesDir, 'codebase'), manifestKey: 'codebase' },
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!existsSync(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
const
|
|
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
|
-
if (!existsSync(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
1
|
+
// templates.mjs - Project template and role installation/refresh helpers
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, cpSync, unlinkSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { fileHash, readManifest } from './manifest.mjs';
|
|
6
|
+
|
|
7
|
+
export function installProjectTemplates({ planningDir, distilledDir, agentsDir }) {
|
|
8
|
+
const localTemplatesDir = join(planningDir, 'templates');
|
|
9
|
+
const globalTemplatesDir = join(distilledDir, 'templates');
|
|
10
|
+
|
|
11
|
+
if (!existsSync(localTemplatesDir)) {
|
|
12
|
+
if (existsSync(globalTemplatesDir)) {
|
|
13
|
+
cpSync(globalTemplatesDir, localTemplatesDir, { recursive: true });
|
|
14
|
+
console.log(' - copied templates to .planning/templates/');
|
|
15
|
+
// Warn-only by design: init should not fail on missing templates because
|
|
16
|
+
// the user may still proceed and fix later. The hard gate lives in
|
|
17
|
+
// `gsdd health` (E6/E7/E8) which reports these as errors. This is the
|
|
18
|
+
// first layer of the 3-layer scaffold defense (warn at init, error at
|
|
19
|
+
// health, regression tests in manifest suite).
|
|
20
|
+
const expectedSubdirs = ['delegates', 'research', 'codebase', 'brownfield-change'];
|
|
21
|
+
for (const subdir of expectedSubdirs) {
|
|
22
|
+
if (!existsSync(join(localTemplatesDir, subdir))) {
|
|
23
|
+
console.log(` - WARN: missing expected template subdir: ${subdir}/`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const expectedRootFiles = ['spec.md', 'roadmap.md', 'auth-matrix.md'];
|
|
27
|
+
for (const file of expectedRootFiles) {
|
|
28
|
+
if (!existsSync(join(localTemplatesDir, file))) {
|
|
29
|
+
console.log(` - WARN: missing expected root template file: ${file}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
console.log(' - WARN: missing distilled/templates/; cannot copy templates');
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
console.log(' - .planning/templates/ already exists');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const localRolesDir = join(localTemplatesDir, 'roles');
|
|
40
|
+
if (!existsSync(localRolesDir)) {
|
|
41
|
+
if (existsSync(agentsDir)) {
|
|
42
|
+
mkdirSync(localRolesDir, { recursive: true });
|
|
43
|
+
for (const file of listRoleFiles(agentsDir)) {
|
|
44
|
+
cpSync(join(agentsDir, file), join(localRolesDir, file));
|
|
45
|
+
}
|
|
46
|
+
console.log(' - copied role contracts to .planning/templates/roles/');
|
|
47
|
+
} else {
|
|
48
|
+
console.log(' - WARN: missing agents/; cannot copy role contracts');
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
console.log(' - .planning/templates/roles/ already exists');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function refreshTemplates({ planningDir, distilledDir, agentsDir, isDry = false }) {
|
|
56
|
+
const existingManifest = readManifest(planningDir);
|
|
57
|
+
const globalTemplatesDir = join(distilledDir, 'templates');
|
|
58
|
+
const localTemplatesDir = join(planningDir, 'templates');
|
|
59
|
+
|
|
60
|
+
const categories = [
|
|
61
|
+
{ name: 'delegates', src: join(globalTemplatesDir, 'delegates'), dest: join(localTemplatesDir, 'delegates'), manifestKey: 'delegates' },
|
|
62
|
+
{ name: 'research', src: join(globalTemplatesDir, 'research'), dest: join(localTemplatesDir, 'research'), manifestKey: 'research' },
|
|
63
|
+
{ name: 'codebase', src: join(globalTemplatesDir, 'codebase'), dest: join(localTemplatesDir, 'codebase'), manifestKey: 'codebase' },
|
|
64
|
+
{ name: 'brownfield-change', src: join(globalTemplatesDir, 'brownfield-change'), dest: join(localTemplatesDir, 'brownfield-change'), manifestKey: 'brownfieldChange' },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
for (const category of categories) {
|
|
68
|
+
refreshCategory(category, existingManifest, isDry);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
refreshRootTemplates(globalTemplatesDir, localTemplatesDir, existingManifest, isDry);
|
|
72
|
+
refreshRoles(agentsDir, join(localTemplatesDir, 'roles'), existingManifest, isDry);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function listRoleFiles(agentsDir) {
|
|
76
|
+
return readdirSync(agentsDir).filter(
|
|
77
|
+
(file) => file.endsWith('.md') && file !== 'README.md' && !file.startsWith('_')
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function refreshCategory({ name, src, dest, manifestKey }, existingManifest, isDry) {
|
|
82
|
+
if (!existsSync(src)) return;
|
|
83
|
+
if (!existsSync(dest) && !isDry) {
|
|
84
|
+
mkdirSync(dest, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const manifestHashes = existingManifest?.templates?.[manifestKey] || null;
|
|
88
|
+
const sourceFiles = readdirSync(src).filter((file) => file.endsWith('.md'));
|
|
89
|
+
const installedFiles = existsSync(dest) ? readdirSync(dest).filter((file) => file.endsWith('.md')) : [];
|
|
90
|
+
|
|
91
|
+
for (const file of sourceFiles) {
|
|
92
|
+
const srcPath = join(src, file);
|
|
93
|
+
const destPath = join(dest, file);
|
|
94
|
+
const srcHash = fileHash(srcPath);
|
|
95
|
+
|
|
96
|
+
if (existsSync(destPath)) {
|
|
97
|
+
const destHash = fileHash(destPath);
|
|
98
|
+
if (destHash === srcHash) continue;
|
|
99
|
+
|
|
100
|
+
const manifestHash = manifestHashes?.[file];
|
|
101
|
+
if (manifestHash && destHash !== manifestHash) {
|
|
102
|
+
console.log(` - WARN: ${name}/${file} was modified locally; overwriting with framework source`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (isDry) {
|
|
107
|
+
console.log(` - would refresh ${name}/${file}`);
|
|
108
|
+
} else {
|
|
109
|
+
cpSync(srcPath, destPath);
|
|
110
|
+
console.log(` - refreshed ${name}/${file}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const file of installedFiles) {
|
|
115
|
+
if (!sourceFiles.includes(file)) {
|
|
116
|
+
if (isDry) {
|
|
117
|
+
console.log(` - would remove orphan ${name}/${file}`);
|
|
118
|
+
} else {
|
|
119
|
+
const orphanPath = join(dest, file);
|
|
120
|
+
if (existsSync(orphanPath)) {
|
|
121
|
+
unlinkSync(orphanPath);
|
|
122
|
+
}
|
|
123
|
+
console.log(` - removed orphan ${name}/${file}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function refreshRootTemplates(globalTemplatesDir, localTemplatesDir, existingManifest, isDry) {
|
|
130
|
+
if (!existsSync(globalTemplatesDir)) return;
|
|
131
|
+
|
|
132
|
+
const manifestHashes = existingManifest?.templates?.root || null;
|
|
133
|
+
const sourceFiles = readdirSync(globalTemplatesDir).filter((file) => file.endsWith('.md'));
|
|
134
|
+
|
|
135
|
+
for (const file of sourceFiles) {
|
|
136
|
+
const srcPath = join(globalTemplatesDir, file);
|
|
137
|
+
const destPath = join(localTemplatesDir, file);
|
|
138
|
+
const srcHash = fileHash(srcPath);
|
|
139
|
+
|
|
140
|
+
if (existsSync(destPath)) {
|
|
141
|
+
const destHash = fileHash(destPath);
|
|
142
|
+
if (destHash === srcHash) continue;
|
|
143
|
+
|
|
144
|
+
const manifestHash = manifestHashes?.[file];
|
|
145
|
+
if (manifestHash && destHash !== manifestHash) {
|
|
146
|
+
console.log(` - WARN: templates/${file} was modified locally; overwriting with framework source`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (isDry) {
|
|
151
|
+
console.log(` - would refresh templates/${file}`);
|
|
152
|
+
} else {
|
|
153
|
+
cpSync(srcPath, destPath);
|
|
154
|
+
console.log(` - refreshed templates/${file}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const installedRootFiles = existsSync(localTemplatesDir)
|
|
159
|
+
? readdirSync(localTemplatesDir).filter((file) => file.endsWith('.md'))
|
|
160
|
+
: [];
|
|
161
|
+
|
|
162
|
+
for (const file of installedRootFiles) {
|
|
163
|
+
if (!sourceFiles.includes(file)) {
|
|
164
|
+
if (isDry) {
|
|
165
|
+
console.log(` - would remove orphan templates/${file}`);
|
|
166
|
+
} else {
|
|
167
|
+
const orphanPath = join(localTemplatesDir, file);
|
|
168
|
+
if (existsSync(orphanPath)) {
|
|
169
|
+
unlinkSync(orphanPath);
|
|
170
|
+
}
|
|
171
|
+
console.log(` - removed orphan templates/${file}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function refreshRoles(agentsDir, localRolesDir, existingManifest, isDry) {
|
|
178
|
+
if (!existsSync(agentsDir)) return;
|
|
179
|
+
if (!existsSync(localRolesDir) && !isDry) {
|
|
180
|
+
mkdirSync(localRolesDir, { recursive: true });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const manifestHashes = existingManifest?.roles || null;
|
|
184
|
+
const sourceFiles = listRoleFiles(agentsDir);
|
|
185
|
+
const installedFiles = existsSync(localRolesDir)
|
|
186
|
+
? readdirSync(localRolesDir).filter((file) => file.endsWith('.md'))
|
|
187
|
+
: [];
|
|
188
|
+
|
|
189
|
+
for (const file of sourceFiles) {
|
|
190
|
+
const srcPath = join(agentsDir, file);
|
|
191
|
+
const destPath = join(localRolesDir, file);
|
|
192
|
+
const srcHash = fileHash(srcPath);
|
|
193
|
+
|
|
194
|
+
if (existsSync(destPath)) {
|
|
195
|
+
const destHash = fileHash(destPath);
|
|
196
|
+
if (destHash === srcHash) continue;
|
|
197
|
+
|
|
198
|
+
const manifestHash = manifestHashes?.[file];
|
|
199
|
+
if (manifestHash && destHash !== manifestHash) {
|
|
200
|
+
console.log(` - WARN: roles/${file} was modified locally; overwriting with framework source`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (isDry) {
|
|
205
|
+
console.log(` - would refresh roles/${file}`);
|
|
206
|
+
} else {
|
|
207
|
+
cpSync(srcPath, destPath);
|
|
208
|
+
console.log(` - refreshed roles/${file}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (const file of installedFiles) {
|
|
213
|
+
if (!sourceFiles.includes(file)) {
|
|
214
|
+
if (isDry) {
|
|
215
|
+
console.log(` - would remove orphan roles/${file}`);
|
|
216
|
+
} else {
|
|
217
|
+
const orphanPath = join(localRolesDir, file);
|
|
218
|
+
if (existsSync(orphanPath)) {
|
|
219
|
+
unlinkSync(orphanPath);
|
|
220
|
+
}
|
|
221
|
+
console.log(` - removed orphan roles/${file}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { dirname, join, resolve } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
function normalizePath(value, cwd) {
|
|
6
|
+
return resolve(cwd, String(value));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function hasPlanningMarker(root) {
|
|
10
|
+
return existsSync(join(root, '.planning', 'config.json')) || existsSync(join(root, '.planning'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function consumeWorkspaceRootArg(rawArgs = []) {
|
|
14
|
+
const args = [];
|
|
15
|
+
let workspaceRootArg = null;
|
|
16
|
+
let invalid = false;
|
|
17
|
+
|
|
18
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
19
|
+
const arg = rawArgs[index];
|
|
20
|
+
if (arg !== '--workspace-root') {
|
|
21
|
+
args.push(arg);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const value = rawArgs[index + 1] ?? null;
|
|
26
|
+
if (!value || value.startsWith('--')) {
|
|
27
|
+
invalid = true;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
workspaceRootArg = value;
|
|
32
|
+
index += 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { args, workspaceRootArg, invalid };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function findWorkspaceRoot(startDir = process.cwd()) {
|
|
39
|
+
let current = resolve(startDir);
|
|
40
|
+
|
|
41
|
+
while (true) {
|
|
42
|
+
if (hasPlanningMarker(current)) return current;
|
|
43
|
+
const parent = dirname(current);
|
|
44
|
+
if (parent === current) return null;
|
|
45
|
+
current = parent;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function deriveWorkspaceRootFromHelperLocation(entryFileUrl) {
|
|
50
|
+
if (!entryFileUrl) return null;
|
|
51
|
+
|
|
52
|
+
const entryPath = entryFileUrl.startsWith('file:')
|
|
53
|
+
? fileURLToPath(entryFileUrl)
|
|
54
|
+
: resolve(String(entryFileUrl));
|
|
55
|
+
const binDir = dirname(entryPath);
|
|
56
|
+
const planningDir = dirname(binDir);
|
|
57
|
+
const workspaceRoot = dirname(planningDir);
|
|
58
|
+
|
|
59
|
+
if (hasPlanningMarker(workspaceRoot) && binDir === join(planningDir, 'bin')) {
|
|
60
|
+
return workspaceRoot;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function resolveWorkspaceContext(rawArgs = [], { cwd = process.cwd(), env = process.env } = {}) {
|
|
67
|
+
const { args, workspaceRootArg, invalid } = consumeWorkspaceRootArg(rawArgs);
|
|
68
|
+
if (invalid) {
|
|
69
|
+
return {
|
|
70
|
+
args,
|
|
71
|
+
invalid: true,
|
|
72
|
+
error: 'Usage: --workspace-root <path>',
|
|
73
|
+
workspaceRoot: resolve(cwd),
|
|
74
|
+
planningDir: join(resolve(cwd), '.planning'),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (workspaceRootArg) {
|
|
79
|
+
const explicitRoot = normalizePath(workspaceRootArg, cwd);
|
|
80
|
+
if (!hasPlanningMarker(explicitRoot)) {
|
|
81
|
+
return {
|
|
82
|
+
args,
|
|
83
|
+
invalid: true,
|
|
84
|
+
error: `Workspace root does not contain .planning/: ${workspaceRootArg}`,
|
|
85
|
+
workspaceRoot: explicitRoot,
|
|
86
|
+
planningDir: join(explicitRoot, '.planning'),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const candidates = [];
|
|
92
|
+
|
|
93
|
+
if (workspaceRootArg) candidates.push(normalizePath(workspaceRootArg, cwd));
|
|
94
|
+
|
|
95
|
+
const discovered = findWorkspaceRoot(cwd);
|
|
96
|
+
if (discovered) candidates.push(discovered);
|
|
97
|
+
|
|
98
|
+
if (env.GSDD_WORKSPACE_ROOT) candidates.push(normalizePath(env.GSDD_WORKSPACE_ROOT, cwd));
|
|
99
|
+
|
|
100
|
+
candidates.push(resolve(cwd));
|
|
101
|
+
|
|
102
|
+
for (const candidate of candidates) {
|
|
103
|
+
if (hasPlanningMarker(candidate)) {
|
|
104
|
+
return {
|
|
105
|
+
args,
|
|
106
|
+
invalid: false,
|
|
107
|
+
workspaceRoot: candidate,
|
|
108
|
+
planningDir: join(candidate, '.planning'),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const fallbackRoot = candidates[0] ?? resolve(cwd);
|
|
114
|
+
return {
|
|
115
|
+
args,
|
|
116
|
+
invalid: false,
|
|
117
|
+
workspaceRoot: fallbackRoot,
|
|
118
|
+
planningDir: join(fallbackRoot, '.planning'),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function bootstrapHelperWorkspace(entryFileUrl, env = process.env) {
|
|
123
|
+
const helperRoot = deriveWorkspaceRootFromHelperLocation(entryFileUrl);
|
|
124
|
+
if (!helperRoot) return null;
|
|
125
|
+
env.GSDD_WORKSPACE_ROOT = helperRoot;
|
|
126
|
+
try {
|
|
127
|
+
process.chdir(helperRoot);
|
|
128
|
+
} catch {
|
|
129
|
+
// best-effort: commands also resolve from env/upward search
|
|
130
|
+
}
|
|
131
|
+
return helperRoot;
|
|
132
|
+
}
|