gsdd-cli 0.18.5 → 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.
Files changed (100) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +609 -608
  3. package/agents/DISTILLATION.md +421 -421
  4. package/agents/README.md +62 -62
  5. package/agents/approach-explorer.md +361 -361
  6. package/agents/debugger.md +82 -82
  7. package/agents/executor.md +394 -394
  8. package/agents/integration-checker.md +318 -318
  9. package/agents/mapper.md +103 -103
  10. package/agents/planner.md +313 -313
  11. package/agents/researcher.md +84 -84
  12. package/agents/roadmapper.md +296 -296
  13. package/agents/synthesizer.md +236 -236
  14. package/agents/verifier.md +337 -337
  15. package/bin/adapters/agents.mjs +34 -34
  16. package/bin/adapters/claude.mjs +191 -191
  17. package/bin/adapters/codex.mjs +85 -85
  18. package/bin/adapters/index.mjs +20 -20
  19. package/bin/adapters/opencode.mjs +278 -278
  20. package/bin/gsdd.mjs +123 -116
  21. package/bin/lib/cli-utils.mjs +28 -28
  22. package/bin/lib/evidence-contract.mjs +112 -112
  23. package/bin/lib/file-ops.mjs +186 -144
  24. package/bin/lib/health-truth.mjs +181 -178
  25. package/bin/lib/health.mjs +246 -226
  26. package/bin/lib/init-flow.mjs +247 -231
  27. package/bin/lib/init-prompts.mjs +248 -247
  28. package/bin/lib/init-runtime.mjs +191 -190
  29. package/bin/lib/init.mjs +17 -17
  30. package/bin/lib/lifecycle-preflight.mjs +347 -325
  31. package/bin/lib/lifecycle-state.mjs +351 -267
  32. package/bin/lib/manifest.mjs +116 -114
  33. package/bin/lib/models.mjs +411 -411
  34. package/bin/lib/phase.mjs +360 -358
  35. package/bin/lib/plan-constants.mjs +30 -30
  36. package/bin/lib/provenance.mjs +109 -106
  37. package/bin/lib/rendering.mjs +115 -83
  38. package/bin/lib/runtime-freshness.mjs +214 -214
  39. package/bin/lib/templates.mjs +225 -224
  40. package/bin/lib/workspace-root.mjs +2 -1
  41. package/distilled/DESIGN.md +2333 -2323
  42. package/distilled/EVIDENCE-INDEX.md +394 -392
  43. package/distilled/README.md +196 -193
  44. package/distilled/SKILL.md +86 -85
  45. package/distilled/templates/agents.block.md +21 -21
  46. package/distilled/templates/agents.md +6 -6
  47. package/distilled/templates/approach.md +232 -232
  48. package/distilled/templates/auth-matrix.md +78 -78
  49. package/distilled/templates/brownfield-change/CHANGE.md +99 -0
  50. package/distilled/templates/brownfield-change/HANDOFF.md +38 -0
  51. package/distilled/templates/brownfield-change/VERIFICATION.md +56 -0
  52. package/distilled/templates/codebase/architecture.md +110 -110
  53. package/distilled/templates/codebase/concerns.md +95 -95
  54. package/distilled/templates/codebase/conventions.md +193 -193
  55. package/distilled/templates/codebase/stack.md +96 -96
  56. package/distilled/templates/delegates/approach-explorer.md +25 -25
  57. package/distilled/templates/delegates/mapper-arch.md +26 -26
  58. package/distilled/templates/delegates/mapper-concerns.md +27 -27
  59. package/distilled/templates/delegates/mapper-quality.md +28 -28
  60. package/distilled/templates/delegates/mapper-tech.md +25 -25
  61. package/distilled/templates/delegates/plan-checker.md +68 -68
  62. package/distilled/templates/delegates/researcher-architecture.md +30 -30
  63. package/distilled/templates/delegates/researcher-features.md +30 -30
  64. package/distilled/templates/delegates/researcher-pitfalls.md +30 -30
  65. package/distilled/templates/delegates/researcher-stack.md +30 -30
  66. package/distilled/templates/delegates/researcher-synthesizer.md +31 -31
  67. package/distilled/templates/research/architecture.md +57 -57
  68. package/distilled/templates/research/features.md +23 -23
  69. package/distilled/templates/research/pitfalls.md +46 -46
  70. package/distilled/templates/research/stack.md +45 -45
  71. package/distilled/templates/research/summary.md +67 -67
  72. package/distilled/templates/roadmap.md +74 -62
  73. package/distilled/templates/spec.md +110 -110
  74. package/distilled/workflows/audit-milestone.md +275 -271
  75. package/distilled/workflows/complete-milestone.md +336 -332
  76. package/distilled/workflows/execute.md +454 -449
  77. package/distilled/workflows/map-codebase.md +253 -253
  78. package/distilled/workflows/new-milestone.md +242 -238
  79. package/distilled/workflows/new-project.md +398 -398
  80. package/distilled/workflows/pause.md +160 -156
  81. package/distilled/workflows/plan-milestone-gaps.md +183 -183
  82. package/distilled/workflows/plan.md +451 -447
  83. package/distilled/workflows/progress.md +227 -223
  84. package/distilled/workflows/quick.md +351 -347
  85. package/distilled/workflows/resume.md +220 -212
  86. package/distilled/workflows/verify-work.md +260 -260
  87. package/distilled/workflows/verify.md +431 -429
  88. package/docs/BROWNFIELD-PROOF.md +95 -95
  89. package/docs/RUNTIME-SUPPORT.md +80 -69
  90. package/docs/USER-GUIDE.md +394 -386
  91. package/docs/VERIFICATION-DISCIPLINE.md +59 -59
  92. package/docs/claude/context-monitor.md +98 -98
  93. package/docs/proof/consumer-node-cli/README.md +37 -37
  94. package/docs/proof/consumer-node-cli/ROADMAP.md +14 -14
  95. package/docs/proof/consumer-node-cli/SPEC.md +17 -17
  96. package/docs/proof/consumer-node-cli/brief.md +9 -9
  97. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-PLAN.md +34 -34
  98. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-SUMMARY.md +10 -10
  99. package/docs/proof/consumer-node-cli/phases/01-foundation/01-VERIFICATION.md +30 -30
  100. package/package.json +62 -61
@@ -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
- for (const category of categories) {
67
- refreshCategory(category, existingManifest, isDry);
68
- }
69
-
70
- refreshRootTemplates(globalTemplatesDir, localTemplatesDir, existingManifest, isDry);
71
- refreshRoles(agentsDir, join(localTemplatesDir, 'roles'), existingManifest, isDry);
72
- }
73
-
74
- function listRoleFiles(agentsDir) {
75
- return readdirSync(agentsDir).filter(
76
- (file) => file.endsWith('.md') && file !== 'README.md' && !file.startsWith('_')
77
- );
78
- }
79
-
80
- function refreshCategory({ name, src, dest, manifestKey }, existingManifest, isDry) {
81
- if (!existsSync(src)) return;
82
- if (!existsSync(dest) && !isDry) {
83
- mkdirSync(dest, { recursive: true });
84
- }
85
-
86
- const manifestHashes = existingManifest?.templates?.[manifestKey] || null;
87
- const sourceFiles = readdirSync(src).filter((file) => file.endsWith('.md'));
88
- const installedFiles = existsSync(dest) ? readdirSync(dest).filter((file) => file.endsWith('.md')) : [];
89
-
90
- for (const file of sourceFiles) {
91
- const srcPath = join(src, file);
92
- const destPath = join(dest, file);
93
- const srcHash = fileHash(srcPath);
94
-
95
- if (existsSync(destPath)) {
96
- const destHash = fileHash(destPath);
97
- if (destHash === srcHash) continue;
98
-
99
- const manifestHash = manifestHashes?.[file];
100
- if (manifestHash && destHash !== manifestHash) {
101
- console.log(` - WARN: ${name}/${file} was modified locally; overwriting with framework source`);
102
- }
103
- }
104
-
105
- if (isDry) {
106
- console.log(` - would refresh ${name}/${file}`);
107
- } else {
108
- cpSync(srcPath, destPath);
109
- console.log(` - refreshed ${name}/${file}`);
110
- }
111
- }
112
-
113
- for (const file of installedFiles) {
114
- if (!sourceFiles.includes(file)) {
115
- if (isDry) {
116
- console.log(` - would remove orphan ${name}/${file}`);
117
- } else {
118
- const orphanPath = join(dest, file);
119
- if (existsSync(orphanPath)) {
120
- unlinkSync(orphanPath);
121
- }
122
- console.log(` - removed orphan ${name}/${file}`);
123
- }
124
- }
125
- }
126
- }
127
-
128
- function refreshRootTemplates(globalTemplatesDir, localTemplatesDir, existingManifest, isDry) {
129
- if (!existsSync(globalTemplatesDir)) return;
130
-
131
- const manifestHashes = existingManifest?.templates?.root || null;
132
- const sourceFiles = readdirSync(globalTemplatesDir).filter((file) => file.endsWith('.md'));
133
-
134
- for (const file of sourceFiles) {
135
- const srcPath = join(globalTemplatesDir, file);
136
- const destPath = join(localTemplatesDir, file);
137
- const srcHash = fileHash(srcPath);
138
-
139
- if (existsSync(destPath)) {
140
- const destHash = fileHash(destPath);
141
- if (destHash === srcHash) continue;
142
-
143
- const manifestHash = manifestHashes?.[file];
144
- if (manifestHash && destHash !== manifestHash) {
145
- console.log(` - WARN: templates/${file} was modified locally; overwriting with framework source`);
146
- }
147
- }
148
-
149
- if (isDry) {
150
- console.log(` - would refresh templates/${file}`);
151
- } else {
152
- cpSync(srcPath, destPath);
153
- console.log(` - refreshed templates/${file}`);
154
- }
155
- }
156
-
157
- const installedRootFiles = existsSync(localTemplatesDir)
158
- ? readdirSync(localTemplatesDir).filter((file) => file.endsWith('.md'))
159
- : [];
160
-
161
- for (const file of installedRootFiles) {
162
- if (!sourceFiles.includes(file)) {
163
- if (isDry) {
164
- console.log(` - would remove orphan templates/${file}`);
165
- } else {
166
- const orphanPath = join(localTemplatesDir, file);
167
- if (existsSync(orphanPath)) {
168
- unlinkSync(orphanPath);
169
- }
170
- console.log(` - removed orphan templates/${file}`);
171
- }
172
- }
173
- }
174
- }
175
-
176
- function refreshRoles(agentsDir, localRolesDir, existingManifest, isDry) {
177
- if (!existsSync(agentsDir)) return;
178
- if (!existsSync(localRolesDir) && !isDry) {
179
- mkdirSync(localRolesDir, { recursive: true });
180
- }
181
-
182
- const manifestHashes = existingManifest?.roles || null;
183
- const sourceFiles = listRoleFiles(agentsDir);
184
- const installedFiles = existsSync(localRolesDir)
185
- ? readdirSync(localRolesDir).filter((file) => file.endsWith('.md'))
186
- : [];
187
-
188
- for (const file of sourceFiles) {
189
- const srcPath = join(agentsDir, file);
190
- const destPath = join(localRolesDir, file);
191
- const srcHash = fileHash(srcPath);
192
-
193
- if (existsSync(destPath)) {
194
- const destHash = fileHash(destPath);
195
- if (destHash === srcHash) continue;
196
-
197
- const manifestHash = manifestHashes?.[file];
198
- if (manifestHash && destHash !== manifestHash) {
199
- console.log(` - WARN: roles/${file} was modified locally; overwriting with framework source`);
200
- }
201
- }
202
-
203
- if (isDry) {
204
- console.log(` - would refresh roles/${file}`);
205
- } else {
206
- cpSync(srcPath, destPath);
207
- console.log(` - refreshed roles/${file}`);
208
- }
209
- }
210
-
211
- for (const file of installedFiles) {
212
- if (!sourceFiles.includes(file)) {
213
- if (isDry) {
214
- console.log(` - would remove orphan roles/${file}`);
215
- } else {
216
- const orphanPath = join(localRolesDir, file);
217
- if (existsSync(orphanPath)) {
218
- unlinkSync(orphanPath);
219
- }
220
- console.log(` - removed orphan roles/${file}`);
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
+ }
@@ -91,11 +91,12 @@ export function resolveWorkspaceContext(rawArgs = [], { cwd = process.cwd(), env
91
91
  const candidates = [];
92
92
 
93
93
  if (workspaceRootArg) candidates.push(normalizePath(workspaceRootArg, cwd));
94
- if (env.GSDD_WORKSPACE_ROOT) candidates.push(normalizePath(env.GSDD_WORKSPACE_ROOT, cwd));
95
94
 
96
95
  const discovered = findWorkspaceRoot(cwd);
97
96
  if (discovered) candidates.push(discovered);
98
97
 
98
+ if (env.GSDD_WORKSPACE_ROOT) candidates.push(normalizePath(env.GSDD_WORKSPACE_ROOT, cwd));
99
+
99
100
  candidates.push(resolve(cwd));
100
101
 
101
102
  for (const candidate of candidates) {