synarcx 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -39
- 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/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/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 +91 -91
- 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/sync.js +148 -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
package/README.md
CHANGED
|
@@ -1,20 +1,82 @@
|
|
|
1
|
-
# SynArcX
|
|
1
|
+
# SynArcX
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
  
|
|
4
|
+
|
|
5
|
+
> Structured engineering workflows for AI coding assistants — persistent project memory, spec-driven development, and architecture drift prevention across every session.
|
|
6
|
+
|
|
7
|
+
Works with **Claude Code, Cursor, GitHub Copilot, Cline, Windsurf, Codex**, and more AI coding tools.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The Problem: Architecture Drift in AI-Assisted Development
|
|
12
|
+
|
|
13
|
+
AI coding assistants like Claude Code and Cursor lose context fast. Requirements live in chat history. Architecture decisions vanish between sessions. Generated code drifts from design intent.
|
|
14
|
+
|
|
15
|
+
Without an explicit workflow, AI-generated code gradually drifts from your architecture — each session introduces small misalignments that compound into structural debt. This is architecture drift, and it gets worse the longer the project runs.
|
|
16
|
+
|
|
17
|
+
SynArcX fixes this by adding a lightweight spec layer between you and your AI — so both you and the assistant agree on what to build before any code is written.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Why Not Just Prompt Better?
|
|
22
|
+
|
|
23
|
+
Better prompts help, but they don't survive session resets, tool switches, or team handoffs. Every new session starts cold. Every new contributor re-explains the same constraints.
|
|
24
|
+
|
|
25
|
+
SynArcX makes your engineering decisions durable. The `constitution.md` is always there. The specs don't live in someone's chat history.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Why Not Just Use Another Spec Format?
|
|
30
|
+
|
|
31
|
+
Spec formats describe *what* to build.
|
|
32
|
+
|
|
33
|
+
SynArcX structures *how you get there* from exploration to proposal to implementation, and helps keep AI-generated changes aligned with the evolving codebase at every stage, not just at planning time.
|
|
34
|
+
|
|
35
|
+
If you already have markdown specs in your repo, `/syn:sync` will incorporate them into the `constitution.md`.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## What Happens If You Ignore SynArcX?
|
|
40
|
+
|
|
41
|
+
Nothing breaks immediately. That's the problem.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Week 1 AI reads the codebase, builds the feature correctly.
|
|
45
|
+
|
|
46
|
+
Week 3 New session. AI re-derives context from code alone.
|
|
47
|
+
Small assumptions diverge from your actual design.
|
|
48
|
+
|
|
49
|
+
Week 6 Three sessions in. The auth module now does things
|
|
50
|
+
no spec ever said it should. The AI was "helpful."
|
|
51
|
+
|
|
52
|
+
Week 10 You're untangling AI-introduced architecture violations
|
|
53
|
+
instead of shipping features. The specs live in a chat
|
|
54
|
+
log nobody can find.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
SynArcX makes the spec the source of truth, not the chat history.
|
|
58
|
+
|
|
59
|
+
---
|
|
4
60
|
|
|
5
61
|
## Install
|
|
6
62
|
|
|
63
|
+
Requires **Node.js 20+**
|
|
64
|
+
|
|
7
65
|
```bash
|
|
8
66
|
npm install -g synarcx
|
|
9
67
|
```
|
|
10
68
|
|
|
11
|
-
Or with pnpm:
|
|
12
|
-
|
|
13
69
|
```bash
|
|
14
70
|
pnpm add -g synarcx
|
|
15
71
|
```
|
|
16
72
|
|
|
17
|
-
|
|
73
|
+
Verify:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
synarcx --help
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
18
80
|
|
|
19
81
|
## Quick Start
|
|
20
82
|
|
|
@@ -23,66 +85,196 @@ cd your-project
|
|
|
23
85
|
synarcx init
|
|
24
86
|
```
|
|
25
87
|
|
|
26
|
-
Then in your AI tool:
|
|
88
|
+
Then in your AI coding tool:
|
|
27
89
|
|
|
28
|
-
1. `/syn:sync` —
|
|
29
|
-
2. `/syn:explore "your idea"` — think through the problem
|
|
30
|
-
3. `/syn:propose my-feature` — create proposal, specs, design, and tasks in one step
|
|
31
|
-
4. `/syn:clarify` — sharpen
|
|
90
|
+
1. `/syn:sync` — scan the project and generate the `constitution.md` (persistent project memory)
|
|
91
|
+
2. `/syn:explore "your idea"` — think through the problem with your AI
|
|
92
|
+
3. `/syn:propose "my-feature"` — create proposal, specs, design, and tasks in one step
|
|
93
|
+
4. `/syn:clarify` — sharpen artifacts with targeted questions
|
|
32
94
|
5. `/syn:analyze` — cross-artifact consistency check
|
|
33
95
|
6. `/syn:apply` — implement the tasks
|
|
34
96
|
7. `/syn:archive` — archive when done
|
|
35
97
|
|
|
36
|
-
For
|
|
37
|
-
For structural improvements, use `/syn:refactor`.
|
|
38
|
-
For small, low-risk changes (typos, config tweaks), use `/syn:quick` — no artifacts, inline preview, then apply.
|
|
98
|
+
For specific cases, use these instead of `/syn:explore`:
|
|
39
99
|
|
|
40
|
-
|
|
100
|
+
* For bugs: use `/syn:debug`.
|
|
101
|
+
* For structural improvements: use `/syn:refactor`.
|
|
102
|
+
* For small low-risk changes (typos, config tweaks): use `/syn:quick` with no artifacts, just apply.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## How It Works: Spec-Driven AI Coding Workflow
|
|
107
|
+
|
|
108
|
+
**Without SynArcX, development drift compounds silently:**
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
Session 1 ──► Session 2 ──► Session 3 ──► Session N
|
|
112
|
+
✓ correct ~ close ✗ diverged ✗✗ structural debt
|
|
113
|
+
(nobody noticed)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**With SynArcX — alignment is maintained explicitly:**
|
|
41
117
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
| `/syn:quick` | Fast-path for small low-risk changes — inline preview, confirm, apply — no artifacts |
|
|
49
|
-
| `/syn:propose` | Create a new change with proposal, specs, design, and tasks in one step |
|
|
50
|
-
| `/syn:clarify` | Ask up to 5 targeted questions to sharpen artifacts before implementation |
|
|
51
|
-
| `/syn:analyze` | Cross-artifact consistency check across proposal, specs, design, and tasks |
|
|
52
|
-
| `/syn:apply` | Implement tasks from a change's task list |
|
|
53
|
-
| `/syn:archive` | Archive a completed change and sync specs |
|
|
118
|
+
```
|
|
119
|
+
Session 1 ──► constitution.md ──► Session 2 ──► constitution.md ──► Session N
|
|
120
|
+
✓ correct (updated) ✓ correct (updated) ✓ correct
|
|
121
|
+
|
|
122
|
+
specs · architecture · intent preserved across every reset
|
|
123
|
+
```
|
|
54
124
|
|
|
55
|
-
|
|
125
|
+
---
|
|
56
126
|
|
|
57
|
-
|
|
127
|
+
**The workflow:**
|
|
58
128
|
|
|
59
129
|
```
|
|
60
|
-
sync
|
|
130
|
+
sync ──────────────────────────────────────────────────► constitution
|
|
61
131
|
|
|
62
132
|
explore ──┐
|
|
63
133
|
debug ──┤
|
|
64
134
|
├──► propose ──► clarify ──► analyze ──► apply ──► archive
|
|
65
135
|
refactor ──┘
|
|
66
136
|
|
|
67
|
-
quick
|
|
137
|
+
quick ────────────────────────────────────────────────────────► apply
|
|
68
138
|
```
|
|
69
139
|
|
|
70
|
-
Each step suggests the next — you decide when to advance.
|
|
140
|
+
Each step suggests the next — you decide when to advance. Works in Claude Code, Cursor, Cline, and any AI coding tool that supports slash commands.
|
|
71
141
|
|
|
72
|
-
- `sync` generates
|
|
142
|
+
- `sync` generates the `constitution.md` — run once, re-run when the project shifts
|
|
73
143
|
- `explore`, `debug`, and `refactor` are entry points that hand off to `propose`
|
|
74
|
-
- `quick`
|
|
144
|
+
- `quick` skips the pipeline for small, low-risk changes
|
|
75
145
|
|
|
76
146
|
Each change gets its own folder under `synspec/changes/` with:
|
|
77
147
|
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
148
|
+
```
|
|
149
|
+
synspec/changes/my-feature/
|
|
150
|
+
├── proposal.md # what and why
|
|
151
|
+
├── design.md # how to build it
|
|
152
|
+
├── tasks.md # implementation checklist
|
|
153
|
+
└── specs/
|
|
154
|
+
└── *.md # what the system shall do
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Persistent Project Memory
|
|
158
|
+
|
|
159
|
+
`constitution.md` is the core of SynArcX. Generated by `/syn:sync`, it preserves architectural intent, conventions, constraints, and engineering decisions across AI sessions — keeping specifications, architecture, and implementation in sync.
|
|
160
|
+
|
|
161
|
+
Unlike documentation, the constitution is optimized for AI operational context — not human reading.
|
|
162
|
+
|
|
163
|
+
A typical `constitution.md` includes:
|
|
164
|
+
|
|
165
|
+
```markdown
|
|
166
|
+
## Architecture Principles
|
|
167
|
+
- All API routes are RESTful, no GraphQL
|
|
168
|
+
- Business logic lives in /services, not controllers
|
|
169
|
+
|
|
170
|
+
## Module Boundaries
|
|
171
|
+
- auth/ owns all token lifecycle — nothing else touches JWTs
|
|
172
|
+
|
|
173
|
+
## Known Pitfalls
|
|
174
|
+
- DB migrations must be backwards-compatible (rolling deploys)
|
|
175
|
+
|
|
176
|
+
## Coding Patterns
|
|
177
|
+
- Use Result<T, E> for fallible operations, never throw in services
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Commands
|
|
183
|
+
|
|
184
|
+
Used inside your AI coding tool (Claude Code, Cursor, Cline, etc.):
|
|
185
|
+
|
|
186
|
+
| Command | Description |
|
|
187
|
+
| ----------------- | ------------------------------------------------------------------------ |
|
|
188
|
+
| `/syn:sync` | Scan project, run guardrail Q&A, generate/update `constitution.md` |
|
|
189
|
+
| `/syn:explore` | Think through ideas, investigate problems, clarify requirements |
|
|
190
|
+
| `/syn:debug` | Diagnose a known error (3-phase analysis), then prompts `/syn:propose` |
|
|
191
|
+
| `/syn:refactor` | Map current vs target structure, then prompts `/syn:propose` |
|
|
192
|
+
| `/syn:quick` | Fast-path for small low-risk changes — inline preview, confirm, apply |
|
|
193
|
+
| `/syn:propose` | Create a new change with proposal, specs, design, and tasks |
|
|
194
|
+
| `/syn:clarify` | Ask up to 5 targeted questions to sharpen artifacts |
|
|
195
|
+
| `/syn:analyze` | Cross-artifact consistency check across all change artifacts |
|
|
196
|
+
| `/syn:apply` | Implement tasks from a change's task list |
|
|
197
|
+
| `/syn:archive` | Archive a completed change and sync specs |
|
|
198
|
+
|
|
199
|
+
### CLI Commands
|
|
200
|
+
|
|
201
|
+
Used in your terminal:
|
|
202
|
+
|
|
203
|
+
| Command | Description |
|
|
204
|
+
| ------------------- | ---------------------------------------------------- |
|
|
205
|
+
| `synarcx init` | Set up SynArcX workflow structure in your repository |
|
|
206
|
+
| `synarcx sync` | Regenerate `constitution.md` |
|
|
207
|
+
| `synarcx explore` | Open explore session |
|
|
208
|
+
| `synarcx propose` | Create a structured change proposal |
|
|
209
|
+
| `synarcx clarify` | Refine requirements into explicit specifications |
|
|
210
|
+
| `synarcx analyze` | Evaluate architecture impact and tradeoffs |
|
|
211
|
+
| `synarcx apply` | Execute implementation tasks |
|
|
212
|
+
| `synarcx quick` | Fast-path execution for small changes |
|
|
82
213
|
|
|
83
|
-
|
|
214
|
+
---
|
|
84
215
|
|
|
85
|
-
|
|
216
|
+
## Artifacts
|
|
217
|
+
|
|
218
|
+
Each workflow stage produces explicit, reviewable files:
|
|
219
|
+
|
|
220
|
+
| Artifact | Purpose |
|
|
221
|
+
| ------------------- | -------------------------------------------------- |
|
|
222
|
+
| `proposal.md` | Problem framing : what, why, and scope |
|
|
223
|
+
| `specs/*.md` | Clarified requirements : what the system shall do |
|
|
224
|
+
| `design.md` | Architectural reasoning : how to build it |
|
|
225
|
+
| `tasks.md` | Implementation checklist |
|
|
226
|
+
| `constitution.md` | Persistent project memory for AI agents |
|
|
227
|
+
|
|
228
|
+
Artifacts create a traceable chain from requirements → reasoning → implementation → architecture decisions.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## SynArcX vs. Unstructured AI Coding
|
|
233
|
+
|
|
234
|
+
| Capability | AI Coding Without SynArcX | With SynArcX |
|
|
235
|
+
| ------------------------------------- | ------------------------- | ------------------------------------ |
|
|
236
|
+
| Persistent engineering memory | Lost between sessions | Preserved in `constitution.md` |
|
|
237
|
+
| Structured specification flow | Informal, chat-based | Explicit staged workflow |
|
|
238
|
+
| Architecture-aware changes | Inconsistent | Built into every step |
|
|
239
|
+
| Artifact traceability | None | Proposal → spec → design → tasks |
|
|
240
|
+
| Session continuity | Weak | Persistent across tools and sessions |
|
|
241
|
+
| Structured, traceable workflow stages | Rare | Core design |
|
|
242
|
+
| Low-risk fast path | Manual | `/syn:quick` |
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Supported AI Coding Tools
|
|
247
|
+
|
|
248
|
+
SynArcX works with Claude Code, Cursor, GitHub Copilot, Cline, Windsurf, Codex, Gemini, OpenCode, and more. Slash commands are generated per tool on `synarcx init` — each tool gets its own command syntax automatically.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Built for AI-Assisted Software Engineering Teams
|
|
253
|
+
|
|
254
|
+
SynArcX is evolving toward an architecture-aware workflow system for long-running AI-assisted software engineering. It is designed for:
|
|
255
|
+
|
|
256
|
+
- long-running projects with evolving requirements
|
|
257
|
+
- architecture-sensitive systems where drift is costly
|
|
258
|
+
- AI-assisted engineering teams
|
|
259
|
+
- spec-driven development practices
|
|
260
|
+
- multi-session and multi-tool workflows
|
|
261
|
+
- controlled implementation pipelines
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Status
|
|
266
|
+
|
|
267
|
+
**v0.2.x** — core workflow stable (init, sync, propose, clarify, analyze, apply, archive, quick)
|
|
268
|
+
|
|
269
|
+
Active development roadmap:
|
|
270
|
+
|
|
271
|
+
- stronger repository cognition
|
|
272
|
+
- architecture-aware execution validation
|
|
273
|
+
- workflow guardrails
|
|
274
|
+
- context continuity across tool switches
|
|
275
|
+
- structured, traceable AI engineering pipelines
|
|
276
|
+
|
|
277
|
+
---
|
|
86
278
|
|
|
87
279
|
## Development
|
|
88
280
|
|
|
@@ -97,6 +289,8 @@ pnpm link --global
|
|
|
97
289
|
pnpm build
|
|
98
290
|
```
|
|
99
291
|
|
|
292
|
+
---
|
|
293
|
+
|
|
100
294
|
## License
|
|
101
295
|
|
|
102
296
|
MIT
|
package/dist/commands/config.js
CHANGED
|
@@ -3,7 +3,8 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { getGlobalConfigPath, getGlobalConfig, saveGlobalConfig, } from '../core/global-config.js';
|
|
5
5
|
import { getNestedValue, setNestedValue, deleteNestedValue, coerceValue, formatValueYaml, validateConfigKeyPath, validateConfig, DEFAULT_CONFIG, } from '../core/config-schema.js';
|
|
6
|
-
import {
|
|
6
|
+
import { getProfileWorkflows } from '../core/profiles.js';
|
|
7
|
+
import { CORE_WORKFLOWS, ALL_WORKFLOWS } from '../core/shared/workflow-registry.js';
|
|
7
8
|
import { SYNSPEC_DIR_NAME } from '../core/config.js';
|
|
8
9
|
import { hasProjectConfigDrift } from '../core/profile-sync-drift.js';
|
|
9
10
|
const WORKFLOW_PROMPT_META = {
|
|
@@ -133,8 +134,8 @@ export function diffProfileState(before, after) {
|
|
|
133
134
|
};
|
|
134
135
|
}
|
|
135
136
|
function maybeWarnConfigDrift(projectDir, state, colorize) {
|
|
136
|
-
const
|
|
137
|
-
if (!fs.existsSync(
|
|
137
|
+
const synspecDir = path.join(projectDir, SYNSPEC_DIR_NAME);
|
|
138
|
+
if (!fs.existsSync(synspecDir)) {
|
|
138
139
|
return;
|
|
139
140
|
}
|
|
140
141
|
if (!hasProjectConfigDrift(projectDir, state.workflows, state.delivery)) {
|
|
@@ -517,10 +518,10 @@ export function registerConfigCommand(program) {
|
|
|
517
518
|
config.delivery = nextState.delivery;
|
|
518
519
|
config.workflows = nextState.workflows;
|
|
519
520
|
saveGlobalConfig(config);
|
|
520
|
-
// Check if inside
|
|
521
|
+
// Check if inside a synarcx project
|
|
521
522
|
const projectDir = process.cwd();
|
|
522
|
-
const
|
|
523
|
-
if (fs.existsSync(
|
|
523
|
+
const synspecDir = path.join(projectDir, SYNSPEC_DIR_NAME);
|
|
524
|
+
if (fs.existsSync(synspecDir)) {
|
|
524
525
|
const applyNow = await confirm({
|
|
525
526
|
message: 'Apply changes to this project now?',
|
|
526
527
|
default: true,
|
|
@@ -6,20 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { transformToHyphenCommands } from '../../../utils/command-references.js';
|
|
9
|
-
|
|
10
|
-
* Escapes a string value for safe YAML output.
|
|
11
|
-
* Quotes the string if it contains special YAML characters.
|
|
12
|
-
*/
|
|
13
|
-
function escapeYamlValue(value) {
|
|
14
|
-
// Check if value needs quoting (contains special YAML characters or starts/ends with whitespace)
|
|
15
|
-
const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
|
|
16
|
-
if (needsQuoting) {
|
|
17
|
-
// Use double quotes and escape internal double quotes and backslashes
|
|
18
|
-
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
19
|
-
return `"${escaped}"`;
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
9
|
+
import { escapeYamlValue } from '../yaml-utils.js';
|
|
23
10
|
/**
|
|
24
11
|
* Bob Shell adapter for command generation.
|
|
25
12
|
* File path: .bob/commands/syn-<id>.md
|
|
@@ -33,12 +20,12 @@ export const bobAdapter = {
|
|
|
33
20
|
formatFile(content) {
|
|
34
21
|
// Transform command references from colon to hyphen format for Bob
|
|
35
22
|
const transformedBody = transformToHyphenCommands(content.body);
|
|
36
|
-
return `---
|
|
37
|
-
description: ${escapeYamlValue(content.description)}
|
|
38
|
-
argument-hint: command arguments
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
${transformedBody}
|
|
23
|
+
return `---
|
|
24
|
+
description: ${escapeYamlValue(content.description)}
|
|
25
|
+
argument-hint: command arguments
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
${transformedBody}
|
|
42
29
|
`;
|
|
43
30
|
},
|
|
44
31
|
};
|
|
@@ -4,27 +4,7 @@
|
|
|
4
4
|
* Formats commands for Claude Code following its frontmatter specification.
|
|
5
5
|
*/
|
|
6
6
|
import path from 'path';
|
|
7
|
-
|
|
8
|
-
* Escapes a string value for safe YAML output.
|
|
9
|
-
* Quotes the string if it contains special YAML characters.
|
|
10
|
-
*/
|
|
11
|
-
function escapeYamlValue(value) {
|
|
12
|
-
// Check if value needs quoting (contains special YAML characters or starts/ends with whitespace)
|
|
13
|
-
const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
|
|
14
|
-
if (needsQuoting) {
|
|
15
|
-
// Use double quotes and escape internal double quotes and backslashes
|
|
16
|
-
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
17
|
-
return `"${escaped}"`;
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Formats a tags array as a YAML array with proper escaping.
|
|
23
|
-
*/
|
|
24
|
-
function formatTagsArray(tags) {
|
|
25
|
-
const escapedTags = tags.map((tag) => escapeYamlValue(tag));
|
|
26
|
-
return `[${escapedTags.join(', ')}]`;
|
|
27
|
-
}
|
|
7
|
+
import { escapeYamlValue, formatTagsArray } from '../yaml-utils.js';
|
|
28
8
|
/**
|
|
29
9
|
* Claude Code adapter for command generation.
|
|
30
10
|
* File path: .claude/commands/syn/<id>.md
|
|
@@ -36,14 +16,14 @@ export const claudeAdapter = {
|
|
|
36
16
|
return path.join('.claude', 'commands', 'syn', `${commandId}.md`);
|
|
37
17
|
},
|
|
38
18
|
formatFile(content) {
|
|
39
|
-
return `---
|
|
40
|
-
name: ${escapeYamlValue(content.name)}
|
|
41
|
-
description: ${escapeYamlValue(content.description)}
|
|
42
|
-
category: ${escapeYamlValue(content.category)}
|
|
43
|
-
tags: ${formatTagsArray(content.tags)}
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
${content.body}
|
|
19
|
+
return `---
|
|
20
|
+
name: ${escapeYamlValue(content.name)}
|
|
21
|
+
description: ${escapeYamlValue(content.description)}
|
|
22
|
+
category: ${escapeYamlValue(content.category)}
|
|
23
|
+
tags: ${formatTagsArray(content.tags)}
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
${content.body}
|
|
47
27
|
`;
|
|
48
28
|
},
|
|
49
29
|
};
|
|
@@ -5,20 +5,7 @@
|
|
|
5
5
|
* Cursor uses a different frontmatter format and file naming convention.
|
|
6
6
|
*/
|
|
7
7
|
import path from 'path';
|
|
8
|
-
|
|
9
|
-
* Escapes a string value for safe YAML output.
|
|
10
|
-
* Quotes the string if it contains special YAML characters.
|
|
11
|
-
*/
|
|
12
|
-
function escapeYamlValue(value) {
|
|
13
|
-
// Check if value needs quoting (contains special YAML characters or starts/ends with whitespace)
|
|
14
|
-
const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
|
|
15
|
-
if (needsQuoting) {
|
|
16
|
-
// Use double quotes and escape internal double quotes and backslashes
|
|
17
|
-
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
18
|
-
return `"${escaped}"`;
|
|
19
|
-
}
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
8
|
+
import { escapeYamlValue } from '../yaml-utils.js';
|
|
22
9
|
/**
|
|
23
10
|
* Cursor adapter for command generation.
|
|
24
11
|
* File path: .cursor/commands/syn-<id>.md
|
|
@@ -30,14 +17,14 @@ export const cursorAdapter = {
|
|
|
30
17
|
return path.join('.cursor', 'commands', `syn-${commandId}.md`);
|
|
31
18
|
},
|
|
32
19
|
formatFile(content) {
|
|
33
|
-
return `---
|
|
34
|
-
name: /syn-${content.id}
|
|
35
|
-
id: syn-${content.id}
|
|
36
|
-
category: ${escapeYamlValue(content.category)}
|
|
37
|
-
description: ${escapeYamlValue(content.description)}
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
${content.body}
|
|
20
|
+
return `---
|
|
21
|
+
name: /syn-${content.id}
|
|
22
|
+
id: syn-${content.id}
|
|
23
|
+
category: ${escapeYamlValue(content.category)}
|
|
24
|
+
description: ${escapeYamlValue(content.description)}
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
${content.body}
|
|
41
28
|
`;
|
|
42
29
|
},
|
|
43
30
|
};
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { transformToHyphenCommands } from '../../../utils/command-references.js';
|
|
9
|
+
import { escapeYamlValue } from '../yaml-utils.js';
|
|
9
10
|
const PI_INPUT_HEADING = /^\*\*Input\*\*:[^\n]*$/m;
|
|
10
11
|
function injectPiArgs(body) {
|
|
11
12
|
if (body.includes('$@') || body.includes('$ARGUMENTS')) {
|
|
@@ -13,20 +14,6 @@ function injectPiArgs(body) {
|
|
|
13
14
|
}
|
|
14
15
|
return body.replace(PI_INPUT_HEADING, (heading) => `${heading}\n**Provided arguments**: $@`);
|
|
15
16
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Escapes a string value for safe YAML output.
|
|
18
|
-
* Quotes the string if it contains special YAML characters.
|
|
19
|
-
*/
|
|
20
|
-
function escapeYamlValue(value) {
|
|
21
|
-
// Check if value needs quoting (contains special YAML characters or starts/ends with whitespace)
|
|
22
|
-
const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
|
|
23
|
-
if (needsQuoting) {
|
|
24
|
-
// Use double quotes and escape internal double quotes and backslashes
|
|
25
|
-
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
26
|
-
return `"${escaped}"`;
|
|
27
|
-
}
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
17
|
/**
|
|
31
18
|
* Pi adapter for prompt template generation.
|
|
32
19
|
* File path: .pi/prompts/syn-<id>.md
|
|
@@ -44,11 +31,11 @@ export const piAdapter = {
|
|
|
44
31
|
formatFile(content) {
|
|
45
32
|
// Transform /syn: references to /syn- and inject $@ for template args
|
|
46
33
|
const transformedBody = transformToHyphenCommands(content.body);
|
|
47
|
-
return `---
|
|
48
|
-
description: ${escapeYamlValue(content.description)}
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
${injectPiArgs(transformedBody)}
|
|
34
|
+
return `---
|
|
35
|
+
description: ${escapeYamlValue(content.description)}
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
${injectPiArgs(transformedBody)}
|
|
52
39
|
`;
|
|
53
40
|
},
|
|
54
41
|
};
|
|
@@ -5,27 +5,7 @@
|
|
|
5
5
|
* Windsurf uses a similar format to Claude but may have different conventions.
|
|
6
6
|
*/
|
|
7
7
|
import path from 'path';
|
|
8
|
-
|
|
9
|
-
* Escapes a string value for safe YAML output.
|
|
10
|
-
* Quotes the string if it contains special YAML characters.
|
|
11
|
-
*/
|
|
12
|
-
function escapeYamlValue(value) {
|
|
13
|
-
// Check if value needs quoting (contains special YAML characters or starts/ends with whitespace)
|
|
14
|
-
const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
|
|
15
|
-
if (needsQuoting) {
|
|
16
|
-
// Use double quotes and escape internal double quotes and backslashes
|
|
17
|
-
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
18
|
-
return `"${escaped}"`;
|
|
19
|
-
}
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Formats a tags array as a YAML array with proper escaping.
|
|
24
|
-
*/
|
|
25
|
-
function formatTagsArray(tags) {
|
|
26
|
-
const escapedTags = tags.map((tag) => escapeYamlValue(tag));
|
|
27
|
-
return `[${escapedTags.join(', ')}]`;
|
|
28
|
-
}
|
|
8
|
+
import { escapeYamlValue, formatTagsArray } from '../yaml-utils.js';
|
|
29
9
|
/**
|
|
30
10
|
* Windsurf adapter for command generation.
|
|
31
11
|
* File path: .windsurf/workflows/syn-<id>.md
|
|
@@ -37,14 +17,14 @@ export const windsurfAdapter = {
|
|
|
37
17
|
return path.join('.windsurf', 'workflows', `syn-${commandId}.md`);
|
|
38
18
|
},
|
|
39
19
|
formatFile(content) {
|
|
40
|
-
return `---
|
|
41
|
-
name: ${escapeYamlValue(content.name)}
|
|
42
|
-
description: ${escapeYamlValue(content.description)}
|
|
43
|
-
category: ${escapeYamlValue(content.category)}
|
|
44
|
-
tags: ${formatTagsArray(content.tags)}
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
${content.body}
|
|
20
|
+
return `---
|
|
21
|
+
name: ${escapeYamlValue(content.name)}
|
|
22
|
+
description: ${escapeYamlValue(content.description)}
|
|
23
|
+
category: ${escapeYamlValue(content.category)}
|
|
24
|
+
tags: ${formatTagsArray(content.tags)}
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
${content.body}
|
|
48
28
|
`;
|
|
49
29
|
},
|
|
50
30
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function escapeYamlValue(value) {
|
|
2
|
+
const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
|
|
3
|
+
if (needsQuoting) {
|
|
4
|
+
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
5
|
+
return `"${escaped}"`;
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
export function formatTagsArray(tags) {
|
|
10
|
+
return `[${tags.map(escapeYamlValue).join(', ')}]`;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=yaml-utils.js.map
|
|
@@ -12,6 +12,10 @@ export class BashInstaller {
|
|
|
12
12
|
* Markers for .bashrc configuration management
|
|
13
13
|
*/
|
|
14
14
|
BASHRC_MARKERS = {
|
|
15
|
+
start: '# SYNARCX:START',
|
|
16
|
+
end: '# SYNARCX:END',
|
|
17
|
+
};
|
|
18
|
+
LEGACY_MARKERS = {
|
|
15
19
|
start: '# OPENSPEC:START',
|
|
16
20
|
end: '# OPENSPEC:END',
|
|
17
21
|
};
|
|
@@ -147,15 +151,22 @@ export class BashInstaller {
|
|
|
147
151
|
}
|
|
148
152
|
// Read file content
|
|
149
153
|
const content = await fs.readFile(bashrcPath, 'utf-8');
|
|
154
|
+
// Determine which markers to use (check new first, fall back to legacy)
|
|
155
|
+
const startMarker = content.includes(this.BASHRC_MARKERS.start)
|
|
156
|
+
? this.BASHRC_MARKERS.start
|
|
157
|
+
: this.LEGACY_MARKERS.start;
|
|
158
|
+
const endMarker = content.includes(this.BASHRC_MARKERS.end)
|
|
159
|
+
? this.BASHRC_MARKERS.end
|
|
160
|
+
: this.LEGACY_MARKERS.end;
|
|
150
161
|
// Check if markers exist
|
|
151
|
-
if (!content.includes(
|
|
162
|
+
if (!content.includes(startMarker) || !content.includes(endMarker)) {
|
|
152
163
|
// Markers don't exist, nothing to remove
|
|
153
164
|
return true;
|
|
154
165
|
}
|
|
155
166
|
// Remove content between markers (including markers)
|
|
156
167
|
const lines = content.split('\n');
|
|
157
|
-
const startIndex = lines.findIndex((line) => line.trim() ===
|
|
158
|
-
const endIndex = lines.findIndex((line) => line.trim() ===
|
|
168
|
+
const startIndex = lines.findIndex((line) => line.trim() === startMarker);
|
|
169
|
+
const endIndex = lines.findIndex((line) => line.trim() === endMarker);
|
|
159
170
|
if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
|
|
160
171
|
// Invalid marker placement
|
|
161
172
|
return false;
|