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.
Files changed (101) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +610 -608
  3. package/agents/DISTILLATION.md +421 -421
  4. package/agents/README.md +62 -62
  5. package/agents/approach-explorer.md +370 -361
  6. package/agents/debugger.md +82 -82
  7. package/agents/executor.md +473 -394
  8. package/agents/integration-checker.md +318 -318
  9. package/agents/mapper.md +103 -103
  10. package/agents/planner.md +342 -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 +193 -191
  17. package/bin/adapters/codex.mjs +85 -85
  18. package/bin/adapters/index.mjs +20 -20
  19. package/bin/adapters/opencode.mjs +280 -278
  20. package/bin/gsdd.mjs +123 -116
  21. package/bin/lib/cli-utils.mjs +28 -28
  22. package/bin/lib/evidence-contract.mjs +325 -112
  23. package/bin/lib/file-ops.mjs +186 -144
  24. package/bin/lib/health-truth.mjs +196 -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 +193 -190
  29. package/bin/lib/init.mjs +17 -17
  30. package/bin/lib/lifecycle-preflight.mjs +760 -326
  31. package/bin/lib/lifecycle-state.mjs +356 -267
  32. package/bin/lib/manifest.mjs +116 -114
  33. package/bin/lib/models.mjs +411 -411
  34. package/bin/lib/phase.mjs +365 -358
  35. package/bin/lib/plan-constants.mjs +35 -30
  36. package/bin/lib/provenance.mjs +109 -106
  37. package/bin/lib/rendering.mjs +119 -83
  38. package/bin/lib/runtime-freshness.mjs +214 -214
  39. package/bin/lib/session-fingerprint.mjs +91 -14
  40. package/bin/lib/templates.mjs +225 -224
  41. package/bin/lib/workspace-root.mjs +2 -1
  42. package/distilled/DESIGN.md +2461 -2323
  43. package/distilled/EVIDENCE-INDEX.md +418 -392
  44. package/distilled/README.md +196 -193
  45. package/distilled/SKILL.md +86 -85
  46. package/distilled/templates/agents.block.md +21 -21
  47. package/distilled/templates/agents.md +6 -6
  48. package/distilled/templates/approach.md +272 -232
  49. package/distilled/templates/auth-matrix.md +78 -78
  50. package/distilled/templates/brownfield-change/CHANGE.md +99 -0
  51. package/distilled/templates/brownfield-change/HANDOFF.md +38 -0
  52. package/distilled/templates/brownfield-change/VERIFICATION.md +56 -0
  53. package/distilled/templates/codebase/architecture.md +110 -110
  54. package/distilled/templates/codebase/concerns.md +95 -95
  55. package/distilled/templates/codebase/conventions.md +193 -193
  56. package/distilled/templates/codebase/stack.md +96 -96
  57. package/distilled/templates/delegates/approach-explorer.md +28 -25
  58. package/distilled/templates/delegates/mapper-arch.md +26 -26
  59. package/distilled/templates/delegates/mapper-concerns.md +27 -27
  60. package/distilled/templates/delegates/mapper-quality.md +28 -28
  61. package/distilled/templates/delegates/mapper-tech.md +25 -25
  62. package/distilled/templates/delegates/plan-checker.md +78 -68
  63. package/distilled/templates/delegates/researcher-architecture.md +30 -30
  64. package/distilled/templates/delegates/researcher-features.md +30 -30
  65. package/distilled/templates/delegates/researcher-pitfalls.md +30 -30
  66. package/distilled/templates/delegates/researcher-stack.md +30 -30
  67. package/distilled/templates/delegates/researcher-synthesizer.md +31 -31
  68. package/distilled/templates/research/architecture.md +57 -57
  69. package/distilled/templates/research/features.md +23 -23
  70. package/distilled/templates/research/pitfalls.md +46 -46
  71. package/distilled/templates/research/stack.md +45 -45
  72. package/distilled/templates/research/summary.md +67 -67
  73. package/distilled/templates/roadmap.md +74 -62
  74. package/distilled/templates/spec.md +110 -110
  75. package/distilled/workflows/audit-milestone.md +303 -271
  76. package/distilled/workflows/complete-milestone.md +349 -332
  77. package/distilled/workflows/execute.md +457 -450
  78. package/distilled/workflows/map-codebase.md +253 -253
  79. package/distilled/workflows/new-milestone.md +242 -238
  80. package/distilled/workflows/new-project.md +398 -398
  81. package/distilled/workflows/pause.md +160 -156
  82. package/distilled/workflows/plan-milestone-gaps.md +183 -183
  83. package/distilled/workflows/plan.md +454 -448
  84. package/distilled/workflows/progress.md +227 -223
  85. package/distilled/workflows/quick.md +351 -347
  86. package/distilled/workflows/resume.md +220 -212
  87. package/distilled/workflows/verify-work.md +260 -260
  88. package/distilled/workflows/verify.md +431 -429
  89. package/docs/BROWNFIELD-PROOF.md +95 -95
  90. package/docs/RUNTIME-SUPPORT.md +80 -69
  91. package/docs/USER-GUIDE.md +394 -386
  92. package/docs/VERIFICATION-DISCIPLINE.md +59 -59
  93. package/docs/claude/context-monitor.md +98 -98
  94. package/docs/proof/consumer-node-cli/README.md +37 -37
  95. package/docs/proof/consumer-node-cli/ROADMAP.md +14 -14
  96. package/docs/proof/consumer-node-cli/SPEC.md +17 -17
  97. package/docs/proof/consumer-node-cli/brief.md +9 -9
  98. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-PLAN.md +34 -34
  99. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-SUMMARY.md +10 -10
  100. package/docs/proof/consumer-node-cli/phases/01-foundation/01-VERIFICATION.md +30 -30
  101. 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) {