synarcx 0.2.0 → 0.3.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/README.md +237 -43
- package/dist/commands/config.js +7 -6
- package/dist/core/command-generation/adapters/bob.js +7 -20
- package/dist/core/command-generation/adapters/claude.js +9 -29
- package/dist/core/command-generation/adapters/cursor.js +9 -22
- package/dist/core/command-generation/adapters/pi.js +6 -19
- package/dist/core/command-generation/adapters/windsurf.js +9 -29
- package/dist/core/command-generation/yaml-utils.d.ts +3 -0
- package/dist/core/command-generation/yaml-utils.js +12 -0
- package/dist/core/completions/installers/bash-installer.d.ts +1 -0
- package/dist/core/completions/installers/bash-installer.js +14 -3
- package/dist/core/completions/installers/powershell-installer.d.ts +1 -0
- package/dist/core/completions/installers/powershell-installer.js +18 -10
- package/dist/core/config.js +0 -3
- package/dist/core/index.js +1 -1
- package/dist/core/init.d.ts +0 -2
- package/dist/core/init.js +27 -77
- package/dist/core/migration.js +1 -2
- package/dist/core/profile-sync-drift.d.ts +0 -7
- package/dist/core/profile-sync-drift.js +2 -17
- package/dist/core/profiles.d.ts +0 -11
- package/dist/core/profiles.js +1 -20
- package/dist/core/shared/artifact-cleanup.d.ts +5 -0
- package/dist/core/shared/artifact-cleanup.js +89 -0
- package/dist/core/shared/skill-generation.js +3 -3
- package/dist/core/shared/tool-detection.d.ts +4 -10
- package/dist/core/shared/tool-detection.js +3 -31
- package/dist/core/shared/workflow-registry.d.ts +40 -0
- package/dist/core/shared/workflow-registry.js +19 -0
- package/dist/core/templates/skill-templates.d.ts +1 -0
- package/dist/core/templates/skill-templates.js +1 -0
- package/dist/core/templates/types.d.ts +7 -0
- package/dist/core/templates/types.js +9 -1
- package/dist/core/templates/workflows/analyze.js +84 -84
- package/dist/core/templates/workflows/apply-change.js +291 -291
- package/dist/core/templates/workflows/archive-change.js +254 -254
- package/dist/core/templates/workflows/clarify.js +115 -93
- package/dist/core/templates/workflows/debug.js +100 -100
- package/dist/core/templates/workflows/explore.js +462 -462
- package/dist/core/templates/workflows/propose.js +199 -199
- package/dist/core/templates/workflows/quick.js +112 -112
- package/dist/core/templates/workflows/refactor.js +109 -109
- package/dist/core/templates/workflows/review.d.ts +4 -0
- package/dist/core/templates/workflows/review.js +293 -0
- package/dist/core/templates/workflows/sync.js +225 -148
- package/dist/core/update.d.ts +1 -21
- package/dist/core/update.js +18 -117
- package/dist/core/view.js +8 -8
- package/dist/core/workspace/open-surface.d.ts +2 -2
- package/dist/core/workspace/open-surface.js +13 -13
- package/package.json +84 -76
|
@@ -1,101 +1,144 @@
|
|
|
1
|
+
import { commandFromSkill } from '../types.js';
|
|
1
2
|
export function getSynSyncSkillTemplate() {
|
|
2
3
|
return {
|
|
3
4
|
name: 'syn-sync',
|
|
4
5
|
description: 'Generate or update synspec/constitution.md with README validation, supporting file scan, guardrail Q&A, and structured constitution generation.',
|
|
5
|
-
instructions:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
Generate
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
**
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
3.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
6
|
+
instructions: `## Step 0: SynArcX Version Check (MUST run first)
|
|
7
|
+
|
|
8
|
+
Do NOT read any project files yet. Run this version check first.
|
|
9
|
+
|
|
10
|
+
### 0.1 Read the daily cache
|
|
11
|
+
|
|
12
|
+
Read \`synspec/.version-cache.json\`. If \`lastCheck\` matches today's UTC date (YYYY-MM-DD), skip the version check entirely — proceed to "Main Sync Flow" below.
|
|
13
|
+
|
|
14
|
+
Expected cache format:
|
|
15
|
+
\`\`\`json
|
|
16
|
+
{ "lastCheck": "2026-05-13", "latestVersion": "0.4.0" }
|
|
17
|
+
\`\`\`
|
|
18
|
+
|
|
19
|
+
If missing or malformed, treat as cache miss and continue.
|
|
20
|
+
|
|
21
|
+
### 0.2 Fetch latest from npm
|
|
22
|
+
|
|
23
|
+
Run \`npm view synarcx version\`. On failure (no npm, no network, non-zero exit): silently skip to 0.5, write cache with \`latestVersion: null\`.
|
|
24
|
+
|
|
25
|
+
### 0.3 Get installed version
|
|
26
|
+
|
|
27
|
+
Run \`synarcx --version\`. On failure: silently skip to 0.5.
|
|
28
|
+
|
|
29
|
+
### 0.4 Compare and prompt
|
|
30
|
+
|
|
31
|
+
Parse both as semver: split on \`.\`, parse each as integer, compare major→minor→patch. If npm version > installed:
|
|
32
|
+
|
|
33
|
+
1. Print update banner.
|
|
34
|
+
2. Use AskUserQuestion tool: "Update now?" with \`["Yes", "No"]\`.
|
|
35
|
+
3. **Yes**: Run \`npm install -g synarcx@latest\`. On success: print "✓ SynArcX updated" + "Run \`synarcx update\` to refresh skill files." On failure: print error + manual command.
|
|
36
|
+
4. **No**: Print manual command.
|
|
37
|
+
|
|
38
|
+
If versions match, proceed silently.
|
|
39
|
+
|
|
40
|
+
### 0.5 Write cache
|
|
41
|
+
|
|
42
|
+
Write \`synspec/.version-cache.json\` with today's UTC date and latest version (or \`null\` on failure). Use \`new Date().toISOString().split('T')[0]\`.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Main Sync Flow
|
|
47
|
+
|
|
48
|
+
Generate or update the project constitution — a living document in \`synspec/constitution.md\` that captures validated project context.
|
|
49
|
+
|
|
50
|
+
**Input**: The user can specify a focus area, or just run the command to proceed through the validation flow.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## README Gate (Required)
|
|
55
|
+
|
|
56
|
+
Before any generation, the README MUST pass quality validation.
|
|
57
|
+
|
|
58
|
+
1. **Check that README.md exists and is not empty.**
|
|
59
|
+
- If missing or blank → stop, output: "SYNC BLOCKED: README.md is missing or empty. Please write a README that describes what this project does and what problem it solves, then re-run \`/syn:sync\`."
|
|
60
|
+
- Do NOT write any files. Do NOT proceed.
|
|
61
|
+
|
|
62
|
+
2. **Check that README meaningfully describes the project.**
|
|
63
|
+
- A passing README must have both:
|
|
64
|
+
- At least one sentence describing what the project does
|
|
65
|
+
- At least one sentence describing the problem it solves or who it is for
|
|
66
|
+
- AI judges quality at runtime. Examples of THIN content that should fail:
|
|
67
|
+
- Only a title and install instructions
|
|
68
|
+
- Auto-generated placeholder text ("# My Project", "## Getting Started")
|
|
69
|
+
- Single-line descriptions with no substance
|
|
70
|
+
- If README is too thin → stop, output: "SYNC BLOCKED: README is too thin. A passing README must describe (1) what the project does and (2) what problem it solves, in at least one sentence each. Please update README.md and re-run \`/syn:sync\`."
|
|
71
|
+
|
|
72
|
+
3. **If README passes**, proceed to the next phase.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Supporting File Scan
|
|
77
|
+
|
|
78
|
+
After README passes, read supporting project files for context:
|
|
79
|
+
- \`AGENTS.md\` — AI agent conventions
|
|
80
|
+
- \`package.json\` — dependencies, scripts, metadata
|
|
81
|
+
- \`src/\` structure — code organization (top-level directories)
|
|
82
|
+
- Any other notable config files (\`tsconfig.json\`, \`.eslintrc.*\`, etc.)
|
|
83
|
+
|
|
84
|
+
Note what information is already well-covered by the README so guardrail questions avoid repeating it.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Guardrail Q&A
|
|
89
|
+
|
|
90
|
+
Generate up to 5 targeted questions. Each question should probe areas that CANNOT be reliably inferred from code or README alone:
|
|
91
|
+
|
|
92
|
+
| Topic Area | Example Question |
|
|
93
|
+
|------------|-----------------|
|
|
94
|
+
| Off-limits areas | "Are there any parts of the codebase AI should never modify?" |
|
|
95
|
+
| Hard constraints | "Are there compliance, security, or infrastructure constraints?" |
|
|
96
|
+
| Coding rules | "Are there coding conventions not obvious from the code?" |
|
|
97
|
+
| Out-of-scope | "What is explicitly out of scope for this project right now?" |
|
|
98
|
+
| Dependencies | "Are there any planned or pending dependency changes?" |
|
|
99
|
+
|
|
100
|
+
**Rules:**
|
|
101
|
+
- Adapt questions to the project's stack, structure, and domain — don't ask generic questions that don't apply
|
|
102
|
+
- Skip questions already answered by the README, AGENTS.md, or other supporting files
|
|
103
|
+
- Maximum 5 questions per session
|
|
104
|
+
- Ask one at a time using the AskUserQuestion tool, wait for each answer
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Constitution Generation
|
|
109
|
+
|
|
110
|
+
With README validated, supporting files scanned, and Q&A answers collected, generate \`synspec/constitution.md\` with these sections:
|
|
111
|
+
|
|
112
|
+
1. **\`# Constitution: <project-name>\`** — derived from package.json name, with Version field
|
|
113
|
+
2. **\`## Purpose\`** — 2-3 sentences synthesized from README
|
|
114
|
+
3. **\`## Principles\`** — key design principles inferred from codebase and Q&A
|
|
115
|
+
4. **\`## Tech Stack\`** — languages, frameworks, key dependencies from package.json
|
|
116
|
+
5. **\`## Constraints\`** — from Q&A answers: hard constraints, compliance, infra limits
|
|
117
|
+
6. **\`## Off-Limits\`** — from Q&A answers: areas AI must not touch, out-of-scope items
|
|
118
|
+
7. **\`## Conventions\`** — code style, naming, patterns observed from code and AGENTS.md
|
|
119
|
+
8. **\`## Architecture\`** — high-level structure overview from src/ scan
|
|
120
|
+
9. **\`## Decision Log\`** — table with Date, Decision, Rationale (append-only)
|
|
121
|
+
|
|
122
|
+
**The Constraints and Off-Limits sections MUST be written from Q&A answers, not inferred from docs.**
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Re-run (Update)
|
|
127
|
+
|
|
128
|
+
When re-run and \`synspec/constitution.md\` already exists, still run the README gate — README may have degraded since last sync. Offer a semver bump choice:
|
|
129
|
+
- **MAJOR** — constitution structure changed or reorganized
|
|
130
|
+
- **MINOR** — new section added
|
|
131
|
+
- **PATCH** — content update, typo fixes
|
|
132
|
+
|
|
133
|
+
Append a Sync Impact Report as an HTML comment at the top:
|
|
134
|
+
\`\`\`
|
|
135
|
+
<!-- Sync Impact: MAJOR — constitution structure reorganized -->
|
|
136
|
+
\`\`\`
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Output
|
|
141
|
+
|
|
99
142
|
After completion, summarize what was created or updated, note the version, and list how many Q&A questions were answered.`,
|
|
100
143
|
license: 'MIT',
|
|
101
144
|
compatibility: 'Requires synarcx CLI.',
|
|
@@ -103,63 +146,97 @@ After completion, summarize what was created or updated, note the version, and l
|
|
|
103
146
|
};
|
|
104
147
|
}
|
|
105
148
|
export function getSynSyncCommandTemplate() {
|
|
106
|
-
return {
|
|
149
|
+
return commandFromSkill(getSynSyncSkillTemplate(), {
|
|
107
150
|
name: 'syn:sync',
|
|
108
151
|
description: 'Generate/update project constitution with README validation, guardrail Q&A, and constraint capture',
|
|
109
|
-
category: 'Workflow',
|
|
110
152
|
tags: ['workflow', 'sync', 'project'],
|
|
111
|
-
content:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
153
|
+
content: `## Step 0: SynArcX Version Check (MUST run first)
|
|
154
|
+
|
|
155
|
+
Do NOT read any project files yet. Run this version check first.
|
|
156
|
+
|
|
157
|
+
### 0.1 Read the daily cache
|
|
158
|
+
|
|
159
|
+
Read \`synspec/.version-cache.json\`. If \`lastCheck\` matches today's UTC date (YYYY-MM-DD), skip to "Main Sync Flow" below. Expected format: \`{ "lastCheck": "2026-05-13", "latestVersion": "0.4.0" }\`.
|
|
160
|
+
|
|
161
|
+
### 0.2 Fetch latest from npm
|
|
162
|
+
|
|
163
|
+
Run \`npm view synarcx version\`. On failure, silently skip to 0.5, write cache with \`latestVersion: null\`.
|
|
164
|
+
|
|
165
|
+
### 0.3 Get installed version
|
|
166
|
+
|
|
167
|
+
Run \`synarcx --version\`. On failure, silently skip to 0.5.
|
|
168
|
+
|
|
169
|
+
### 0.4 Compare and prompt
|
|
170
|
+
|
|
171
|
+
Parse both as semver: split on \`.\`, parse each as integer, compare major→minor→patch. If npm version > installed:
|
|
172
|
+
|
|
173
|
+
1. Print update banner.
|
|
174
|
+
2. Use AskUserQuestion tool: "Update now?" with \`["Yes", "No"]\`.
|
|
175
|
+
3. **Yes**: Run \`npm install -g synarcx@latest\`. On success: print success + "Run \`synarcx update\` to refresh skill files." On failure: print error + manual command.
|
|
176
|
+
4. **No**: Print manual command.
|
|
177
|
+
|
|
178
|
+
If versions match, proceed silently.
|
|
179
|
+
|
|
180
|
+
### 0.5 Write cache
|
|
181
|
+
|
|
182
|
+
Write \`synspec/.version-cache.json\` with today's UTC date and latest version (or \`null\` on failure). Use \`new Date().toISOString().split('T')[0]\`.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Main Sync Flow
|
|
187
|
+
|
|
188
|
+
Generate or update the project constitution — a living document in \`synspec/constitution.md\` that captures validated project context.
|
|
189
|
+
|
|
190
|
+
**Input**: The user can specify a focus area, or just run the command to proceed through the validation flow.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## README Gate (Required)
|
|
195
|
+
|
|
196
|
+
Before any generation, the README MUST pass quality validation.
|
|
197
|
+
|
|
198
|
+
1. **Check that README.md exists and is not empty.**
|
|
199
|
+
- If missing or blank → stop with message explaining README must exist and describe the project.
|
|
200
|
+
- Do NOT write any files.
|
|
201
|
+
|
|
202
|
+
2. **Check that README meaningfully describes the project.**
|
|
203
|
+
- A passing README must have both: (1) what the project does, (2) what problem it solves.
|
|
204
|
+
- AI judges quality at runtime.
|
|
205
|
+
- If too thin → stop with message explaining what's needed.
|
|
206
|
+
|
|
207
|
+
3. **If README passes**, proceed.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Supporting File Scan
|
|
212
|
+
|
|
213
|
+
Read AGENTS.md, package.json, src/ structure, and other notable config files. Note what's already covered by README to avoid redundant questions.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Guardrail Q&A
|
|
218
|
+
|
|
219
|
+
Generate up to 5 targeted questions about off-limits areas, hard constraints, coding rules, out-of-scope items, and dependency plans. Adapt to the project's stack and domain. Skip questions already answered by README or supporting files.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Constitution Generation
|
|
224
|
+
|
|
225
|
+
Generate \`synspec/constitution.md\` with sections: Purpose, Principles, Tech Stack, Constraints (from Q&A), Off-Limits (from Q&A), Conventions, Architecture, Decision Log.
|
|
226
|
+
|
|
227
|
+
**Constraints and Off-Limits MUST come from Q&A answers, not inferred.**
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Re-run (Update)
|
|
232
|
+
|
|
233
|
+
Still run the README gate even when constitution exists — README may have degraded. Offer semver bump. Append Sync Impact Report HTML comment.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Output
|
|
238
|
+
|
|
162
239
|
Summarize what was created/updated, note the version, and show how many Q&A questions were answered.`
|
|
163
|
-
};
|
|
240
|
+
});
|
|
164
241
|
}
|
|
165
242
|
//# sourceMappingURL=sync.js.map
|
package/dist/core/update.d.ts
CHANGED
|
@@ -44,27 +44,7 @@ export declare class UpdateCommand {
|
|
|
44
44
|
*/
|
|
45
45
|
private displayOldCoreCustomProfileNote;
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
* Returns the number of directories removed.
|
|
49
|
-
*/
|
|
50
|
-
private removeSkillDirs;
|
|
51
|
-
/**
|
|
52
|
-
* Removes skill directories for workflows that are no longer selected in the active profile.
|
|
53
|
-
* Returns the number of directories removed.
|
|
54
|
-
*/
|
|
55
|
-
private removeUnselectedSkillDirs;
|
|
56
|
-
/**
|
|
57
|
-
* Removes command files for workflows when delivery changed to skills-only.
|
|
58
|
-
* Returns the number of files removed.
|
|
59
|
-
*/
|
|
60
|
-
private removeCommandFiles;
|
|
61
|
-
/**
|
|
62
|
-
* Removes command files for workflows that are no longer selected in the active profile.
|
|
63
|
-
* Returns the number of files removed.
|
|
64
|
-
*/
|
|
65
|
-
private removeUnselectedCommandFiles;
|
|
66
|
-
/**
|
|
67
|
-
* Detect and handle legacy OpenSpec artifacts.
|
|
47
|
+
* Detect and handle legacy artifacts.
|
|
68
48
|
* Unlike init, update warns but continues if legacy files found in non-interactive mode.
|
|
69
49
|
* Returns array of tool IDs that were newly configured during legacy upgrade.
|
|
70
50
|
*/
|
package/dist/core/update.js
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import ora from 'ora';
|
|
10
|
-
import * as fs from 'fs';
|
|
11
10
|
import { createRequire } from 'module';
|
|
12
11
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
13
12
|
import { transformToHyphenCommands } from '../utils/command-references.js';
|
|
@@ -17,12 +16,14 @@ import { getToolVersionStatus, getSkillTemplates, getCommandContents, generateSk
|
|
|
17
16
|
import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDetectionSummary, getToolsFromLegacyArtifacts, } from './legacy-cleanup.js';
|
|
18
17
|
import { isInteractive } from '../utils/interactive.js';
|
|
19
18
|
import { getGlobalConfig } from './global-config.js';
|
|
20
|
-
import { getProfileWorkflows
|
|
19
|
+
import { getProfileWorkflows } from './profiles.js';
|
|
20
|
+
import { ALL_WORKFLOWS } from './shared/workflow-registry.js';
|
|
21
|
+
import { removeSkillDirs, removeUnselectedSkillDirs, removeCommandFiles, removeUnselectedCommandFiles, } from './shared/artifact-cleanup.js';
|
|
21
22
|
import { getAvailableTools } from './available-tools.js';
|
|
22
|
-
import {
|
|
23
|
+
import { getCommandConfiguredTools, getConfiguredToolsForProfileSync, getToolsNeedingProfileSync, } from './profile-sync-drift.js';
|
|
23
24
|
import { scanInstalledWorkflows as scanInstalledWorkflowsShared, migrateIfNeeded as migrateIfNeededShared, } from './migration.js';
|
|
24
25
|
const require = createRequire(import.meta.url);
|
|
25
|
-
const { version:
|
|
26
|
+
const { version: SYNARCX_VERSION } = require('../../package.json');
|
|
26
27
|
const OLD_CORE_WORKFLOWS = ['propose', 'explore', 'apply', 'archive'];
|
|
27
28
|
/**
|
|
28
29
|
* Scans installed workflow artifacts (skills and managed commands) across all configured tools.
|
|
@@ -43,9 +44,9 @@ export class UpdateCommand {
|
|
|
43
44
|
}
|
|
44
45
|
async execute(projectPath) {
|
|
45
46
|
const resolvedProjectPath = path.resolve(projectPath);
|
|
46
|
-
const
|
|
47
|
+
const synspecPath = path.join(resolvedProjectPath, SYNSPEC_DIR_NAME);
|
|
47
48
|
// 1. Check synspec directory exists
|
|
48
|
-
if (!await FileSystemUtils.directoryExists(
|
|
49
|
+
if (!await FileSystemUtils.directoryExists(synspecPath)) {
|
|
49
50
|
throw new Error(`No synarcx directory found. Run 'synarcx init' first.`);
|
|
50
51
|
}
|
|
51
52
|
// 2. Perform one-time migration if needed before any legacy upgrade generation.
|
|
@@ -73,7 +74,7 @@ export class UpdateCommand {
|
|
|
73
74
|
const commandConfiguredTools = getCommandConfiguredTools(resolvedProjectPath);
|
|
74
75
|
const commandConfiguredSet = new Set(commandConfiguredTools);
|
|
75
76
|
const toolStatuses = configuredTools.map((toolId) => {
|
|
76
|
-
const status = getToolVersionStatus(resolvedProjectPath, toolId,
|
|
77
|
+
const status = getToolVersionStatus(resolvedProjectPath, toolId, SYNARCX_VERSION);
|
|
77
78
|
if (!status.configured && commandConfiguredSet.has(toolId)) {
|
|
78
79
|
return { ...status, configured: true };
|
|
79
80
|
}
|
|
@@ -132,14 +133,14 @@ export class UpdateCommand {
|
|
|
132
133
|
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
133
134
|
// Use hyphen-based command references for tools where filename = command name
|
|
134
135
|
const transformer = tool.value === 'pi' ? transformToHyphenCommands : undefined;
|
|
135
|
-
const skillContent = generateSkillContent(template,
|
|
136
|
+
const skillContent = generateSkillContent(template, SYNARCX_VERSION, transformer);
|
|
136
137
|
await FileSystemUtils.writeFile(skillFile, skillContent);
|
|
137
138
|
}
|
|
138
|
-
removedDeselectedSkillCount += await
|
|
139
|
+
removedDeselectedSkillCount += await removeUnselectedSkillDirs(skillsDir, desiredWorkflows);
|
|
139
140
|
}
|
|
140
141
|
// Delete skill directories if delivery is commands-only
|
|
141
142
|
if (!shouldGenerateSkills) {
|
|
142
|
-
removedSkillCount += await
|
|
143
|
+
removedSkillCount += await removeSkillDirs(skillsDir);
|
|
143
144
|
}
|
|
144
145
|
// Generate commands if delivery includes commands
|
|
145
146
|
if (shouldGenerateCommands) {
|
|
@@ -150,12 +151,12 @@ export class UpdateCommand {
|
|
|
150
151
|
const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(resolvedProjectPath, cmd.path);
|
|
151
152
|
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
152
153
|
}
|
|
153
|
-
removedDeselectedCommandCount += await
|
|
154
|
+
removedDeselectedCommandCount += await removeUnselectedCommandFiles(resolvedProjectPath, toolId, desiredWorkflows);
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
// Delete command files if delivery is skills-only
|
|
157
158
|
if (!shouldGenerateCommands) {
|
|
158
|
-
removedCommandCount += await
|
|
159
|
+
removedCommandCount += await removeCommandFiles(resolvedProjectPath, toolId);
|
|
159
160
|
}
|
|
160
161
|
spinner.succeed(`Updated ${tool.name}`);
|
|
161
162
|
updatedTools.push(tool.name);
|
|
@@ -171,7 +172,7 @@ export class UpdateCommand {
|
|
|
171
172
|
// 11. Summary
|
|
172
173
|
console.log();
|
|
173
174
|
if (updatedTools.length > 0) {
|
|
174
|
-
console.log(chalk.green(`✓ Updated: ${updatedTools.join(', ')} (v${
|
|
175
|
+
console.log(chalk.green(`✓ Updated: ${updatedTools.join(', ')} (v${SYNARCX_VERSION})`));
|
|
175
176
|
}
|
|
176
177
|
if (failedTools.length > 0) {
|
|
177
178
|
console.log(chalk.red(`✗ Failed: ${failedTools.map(f => `${f.name} (${f.error})`).join(', ')}`));
|
|
@@ -215,7 +216,7 @@ export class UpdateCommand {
|
|
|
215
216
|
*/
|
|
216
217
|
displayUpToDateMessage(toolStatuses) {
|
|
217
218
|
const toolNames = toolStatuses.map((s) => s.toolId);
|
|
218
|
-
console.log(chalk.green(`✓ All ${toolStatuses.length} tool(s) up to date (v${
|
|
219
|
+
console.log(chalk.green(`✓ All ${toolStatuses.length} tool(s) up to date (v${SYNARCX_VERSION})`));
|
|
219
220
|
console.log(chalk.dim(` Tools: ${toolNames.join(', ')}`));
|
|
220
221
|
console.log();
|
|
221
222
|
console.log(chalk.dim('Use --force to refresh files anyway.'));
|
|
@@ -228,7 +229,7 @@ export class UpdateCommand {
|
|
|
228
229
|
const status = statusByTool.get(toolId);
|
|
229
230
|
if (status?.needsUpdate) {
|
|
230
231
|
const fromVersion = status.generatedByVersion ?? 'unknown';
|
|
231
|
-
return `${status.toolId} (${fromVersion} → ${
|
|
232
|
+
return `${status.toolId} (${fromVersion} → ${SYNARCX_VERSION})`;
|
|
232
233
|
}
|
|
233
234
|
return `${toolId} (config sync)`;
|
|
234
235
|
});
|
|
@@ -283,107 +284,7 @@ export class UpdateCommand {
|
|
|
283
284
|
console.log(chalk.dim('Run `synarcx config profile core` and then `synarcx update` to add sync.'));
|
|
284
285
|
}
|
|
285
286
|
/**
|
|
286
|
-
*
|
|
287
|
-
* Returns the number of directories removed.
|
|
288
|
-
*/
|
|
289
|
-
async removeSkillDirs(skillsDir) {
|
|
290
|
-
let removed = 0;
|
|
291
|
-
for (const workflow of ALL_WORKFLOWS) {
|
|
292
|
-
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
293
|
-
if (!dirName)
|
|
294
|
-
continue;
|
|
295
|
-
const skillDir = path.join(skillsDir, dirName);
|
|
296
|
-
try {
|
|
297
|
-
if (fs.existsSync(skillDir)) {
|
|
298
|
-
await fs.promises.rm(skillDir, { recursive: true, force: true });
|
|
299
|
-
removed++;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
// Ignore errors
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return removed;
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Removes skill directories for workflows that are no longer selected in the active profile.
|
|
310
|
-
* Returns the number of directories removed.
|
|
311
|
-
*/
|
|
312
|
-
async removeUnselectedSkillDirs(skillsDir, desiredWorkflows) {
|
|
313
|
-
const desiredSet = new Set(desiredWorkflows);
|
|
314
|
-
let removed = 0;
|
|
315
|
-
for (const workflow of ALL_WORKFLOWS) {
|
|
316
|
-
if (desiredSet.has(workflow))
|
|
317
|
-
continue;
|
|
318
|
-
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
319
|
-
if (!dirName)
|
|
320
|
-
continue;
|
|
321
|
-
const skillDir = path.join(skillsDir, dirName);
|
|
322
|
-
try {
|
|
323
|
-
if (fs.existsSync(skillDir)) {
|
|
324
|
-
await fs.promises.rm(skillDir, { recursive: true, force: true });
|
|
325
|
-
removed++;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
catch {
|
|
329
|
-
// Ignore errors
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return removed;
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Removes command files for workflows when delivery changed to skills-only.
|
|
336
|
-
* Returns the number of files removed.
|
|
337
|
-
*/
|
|
338
|
-
async removeCommandFiles(projectPath, toolId) {
|
|
339
|
-
let removed = 0;
|
|
340
|
-
const adapter = CommandAdapterRegistry.get(toolId);
|
|
341
|
-
if (!adapter)
|
|
342
|
-
return 0;
|
|
343
|
-
for (const workflow of ALL_WORKFLOWS) {
|
|
344
|
-
const cmdPath = adapter.getFilePath(workflow);
|
|
345
|
-
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
346
|
-
try {
|
|
347
|
-
if (fs.existsSync(fullPath)) {
|
|
348
|
-
await fs.promises.unlink(fullPath);
|
|
349
|
-
removed++;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
// Ignore errors
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
return removed;
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* Removes command files for workflows that are no longer selected in the active profile.
|
|
360
|
-
* Returns the number of files removed.
|
|
361
|
-
*/
|
|
362
|
-
async removeUnselectedCommandFiles(projectPath, toolId, desiredWorkflows) {
|
|
363
|
-
let removed = 0;
|
|
364
|
-
const adapter = CommandAdapterRegistry.get(toolId);
|
|
365
|
-
if (!adapter)
|
|
366
|
-
return 0;
|
|
367
|
-
const desiredSet = new Set(desiredWorkflows);
|
|
368
|
-
for (const workflow of ALL_WORKFLOWS) {
|
|
369
|
-
if (desiredSet.has(workflow))
|
|
370
|
-
continue;
|
|
371
|
-
const cmdPath = adapter.getFilePath(workflow);
|
|
372
|
-
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
373
|
-
try {
|
|
374
|
-
if (fs.existsSync(fullPath)) {
|
|
375
|
-
await fs.promises.unlink(fullPath);
|
|
376
|
-
removed++;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
catch {
|
|
380
|
-
// Ignore errors
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
return removed;
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Detect and handle legacy OpenSpec artifacts.
|
|
287
|
+
* Detect and handle legacy artifacts.
|
|
387
288
|
* Unlike init, update warns but continues if legacy files found in non-interactive mode.
|
|
388
289
|
* Returns array of tool IDs that were newly configured during legacy upgrade.
|
|
389
290
|
*/
|
|
@@ -523,7 +424,7 @@ export class UpdateCommand {
|
|
|
523
424
|
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
524
425
|
// Use hyphen-based command references for tools where filename = command name
|
|
525
426
|
const transformer = tool.value === 'pi' ? transformToHyphenCommands : undefined;
|
|
526
|
-
const skillContent = generateSkillContent(template,
|
|
427
|
+
const skillContent = generateSkillContent(template, SYNARCX_VERSION, transformer);
|
|
527
428
|
await FileSystemUtils.writeFile(skillFile, skillContent);
|
|
528
429
|
}
|
|
529
430
|
}
|