magic-spec 1.0.0 β 1.2.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 +196 -180
- package/installers/node/index.js +275 -0
- package/package.json +11 -20
- package/.agent/workflows/magic/plan.md +0 -12
- package/.agent/workflows/magic/retrospective.md +0 -12
- package/.agent/workflows/magic/rule.md +0 -12
- package/.agent/workflows/magic/specification.md +0 -12
- package/.agent/workflows/magic/task.md +0 -12
- package/.magic/init.md +0 -63
- package/.magic/plan.md +0 -289
- package/.magic/retrospective.md +0 -276
- package/.magic/rule.md +0 -96
- package/.magic/scripts/init.ps1 +0 -88
- package/.magic/scripts/init.sh +0 -85
- package/.magic/specification.md +0 -378
- package/.magic/task.md +0 -443
- package/src/index.js +0 -81
package/README.md
CHANGED
|
@@ -1,180 +1,196 @@
|
|
|
1
|
-
# πͺ Magic Spec
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/magic-spec)
|
|
4
|
-
[](https://pypi.org/project/magic-spec/)
|
|
5
|
-
[](./LICENSE)
|
|
6
|
-
|
|
7
|
-
**Specification-Driven Development (SDD) workflow for AI coding agents.**
|
|
8
|
-
|
|
9
|
-
Stop your AI from writing code before it understands the problem.
|
|
10
|
-
`magic-spec` installs a structured pipeline β *Thought β Spec β Plan β Task β Code* β directly into any project, regardless of stack.
|
|
11
|
-
|
|
12
|
-
## β¨ What is Magic Spec?
|
|
13
|
-
|
|
14
|
-
`magic-spec` is a set of **markdown-based workflow instructions** for AI coding agents (Cursor, Claude, Gemini, Copilot, etc.). It acts as an operating system for agentic development, enforcing a rigorous, structured pipeline:
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
π‘ Idea β π Specification β πΊοΈ Plan β β‘ Task β π Code
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Once installed, your AI agent will automatically:
|
|
21
|
-
|
|
22
|
-
- Convert raw thoughts into structured specification files.
|
|
23
|
-
- Build a phased implementation plan from approved specs.
|
|
24
|
-
- Decompose the plan into atomic, trackable tasks.
|
|
25
|
-
- Analyze its own workflow and suggest improvements.
|
|
26
|
-
|
|
27
|
-
**No code is written until a specification exists. No spec is implemented without a plan.**
|
|
28
|
-
|
|
29
|
-
## π Quick Start
|
|
30
|
-
|
|
31
|
-
Works with **any project** β Rust, Go, Python, JavaScript, or anything else.
|
|
32
|
-
No runtime lock-in. Requires only Node.js *or* Python to install.
|
|
33
|
-
|
|
34
|
-
### Option A β Node.js (npx)
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
npx magic-spec@latest
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Option B β Python (uvx)
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
uvx magic-spec
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Both commands do exactly the same thing:
|
|
47
|
-
|
|
48
|
-
1. Copy `.magic/` (the SDD engine) into your project.
|
|
49
|
-
2. Copy `.agent/workflows/magic
|
|
50
|
-
3. Run the init script β creates your `.design/` workspace with `INDEX.md` and `RULES.md`.
|
|
51
|
-
|
|
52
|
-
## π§ Core Philosophy
|
|
53
|
-
|
|
54
|
-
| Principle | Description |
|
|
55
|
-
| :--- | :--- |
|
|
56
|
-
| **Specs First, Code Later** | The agent is forbidden from writing code from raw input. All ideas become specs first. |
|
|
57
|
-
| **Deterministic Process** | A strict pipeline is enforced: *Thought β Spec β Plan β Task β Code*. |
|
|
58
|
-
| **Constitution-Driven** | All project decisions live in `.design/RULES.md` β the project's living constitution. |
|
|
59
|
-
| **Self-Improving** |
|
|
60
|
-
|
|
61
|
-
##
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
1
|
+
# πͺ Magic Spec
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/magic-spec)
|
|
4
|
+
[](https://pypi.org/project/magic-spec/)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
**Specification-Driven Development (SDD) workflow for AI coding agents.**
|
|
8
|
+
|
|
9
|
+
Stop your AI from writing code before it understands the problem.
|
|
10
|
+
`magic-spec` installs a structured pipeline β *Thought β Spec β Plan β Task β Code* β directly into any project, regardless of stack.
|
|
11
|
+
|
|
12
|
+
## β¨ What is Magic Spec?
|
|
13
|
+
|
|
14
|
+
`magic-spec` is a set of **markdown-based workflow instructions** for AI coding agents (Cursor, Claude, Gemini, Copilot, etc.). It acts as an operating system for agentic development, enforcing a rigorous, structured pipeline:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
π‘ Idea β π Specification β πΊοΈ Plan β β‘ Task β π Code
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Once installed, your AI agent will automatically:
|
|
21
|
+
|
|
22
|
+
- Convert raw thoughts into structured specification files.
|
|
23
|
+
- Build a phased implementation plan from approved specs.
|
|
24
|
+
- Decompose the plan into atomic, trackable tasks.
|
|
25
|
+
- Analyze its own workflow and suggest improvements β automatically, at phase completion.
|
|
26
|
+
|
|
27
|
+
**No code is written until a specification exists. No spec is implemented without a plan.**
|
|
28
|
+
|
|
29
|
+
## π Quick Start
|
|
30
|
+
|
|
31
|
+
Works with **any project** β Rust, Go, Python, JavaScript, or anything else.
|
|
32
|
+
No runtime lock-in. Requires only Node.js *or* Python to install.
|
|
33
|
+
|
|
34
|
+
### Option A β Node.js (npx)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx magic-spec@latest
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Option B β Python (uvx)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uvx magic-spec
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Both commands do exactly the same thing:
|
|
47
|
+
|
|
48
|
+
1. Copy `.magic/` (the SDD engine) into your project.
|
|
49
|
+
2. Copy `.agent/workflows/magic.*.md` (agent trigger wrappers) into your project.
|
|
50
|
+
3. Run the init script β creates your `.design/` workspace with `INDEX.md` and `RULES.md`.
|
|
51
|
+
|
|
52
|
+
## π§ Core Philosophy
|
|
53
|
+
|
|
54
|
+
| Principle | Description |
|
|
55
|
+
| :--- | :--- |
|
|
56
|
+
| **Specs First, Code Later** | The agent is forbidden from writing code from raw input. All ideas become specs first. |
|
|
57
|
+
| **Deterministic Process** | A strict pipeline is enforced: *Thought β Spec β Plan β Task β Code*. |
|
|
58
|
+
| **Constitution-Driven** | All project decisions live in `.design/RULES.md` β the project's living constitution. |
|
|
59
|
+
| **Self-Improving** | After each phase and at plan completion, the Task workflow automatically runs a retrospective and generates improvement recommendations. |
|
|
60
|
+
|
|
61
|
+
## π©Ί System Health (CLI Doctor)
|
|
62
|
+
|
|
63
|
+
You can check if your SDD workspace is properly initialized and healthy without invoking the AI. Just append the `--doctor` (or `--check`) flag:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx magic-spec@latest --doctor
|
|
67
|
+
# or
|
|
68
|
+
uvx magic-spec --doctor
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
This returns a visually formatted validation report of your project's `.design` structure, preventing broken context before you start coding.
|
|
72
|
+
|
|
73
|
+
## π What Gets Installed
|
|
74
|
+
|
|
75
|
+
After running `npx magic-spec@latest` in your project root:
|
|
76
|
+
|
|
77
|
+
```plaintext
|
|
78
|
+
your-project/
|
|
79
|
+
β
|
|
80
|
+
βββ .agent/workflows/ # Agent entry points (slash commands)
|
|
81
|
+
β βββ magic.onboard.md # Interactive tutorial for new devs
|
|
82
|
+
β βββ magic.rule.md
|
|
83
|
+
β βββ magic.run.md
|
|
84
|
+
β βββ magic.spec.md
|
|
85
|
+
β βββ magic.task.md
|
|
86
|
+
β
|
|
87
|
+
βββ .magic/ # SDD Engine (workflow logic, read-only)
|
|
88
|
+
β βββ init.md
|
|
89
|
+
β βββ onboard.md # Onboarding script payload
|
|
90
|
+
β βββ retrospective.md
|
|
91
|
+
β βββ rule.md
|
|
92
|
+
β βββ run.md
|
|
93
|
+
β βββ spec.md
|
|
94
|
+
β βββ task.md
|
|
95
|
+
β βββ scripts/
|
|
96
|
+
β βββ check-prerequisites.* # Used by --doctor
|
|
97
|
+
β βββ generate-context.* # Auto-compiles CONTEXT.md
|
|
98
|
+
β βββ init.sh # Init for macOS / Linux
|
|
99
|
+
β βββ init.ps1 # Init for Windows
|
|
100
|
+
β
|
|
101
|
+
βββ .design/ # Your project workspace (generated)
|
|
102
|
+
βββ INDEX.md # Spec registry
|
|
103
|
+
βββ RULES.md # Project constitution
|
|
104
|
+
βββ PLAN.md # Implementation plan
|
|
105
|
+
βββ specifications/ # Your specification files
|
|
106
|
+
βββ tasks/ # Task breakdowns per phase
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## π The Workflow Pipeline
|
|
110
|
+
|
|
111
|
+
```mermaid
|
|
112
|
+
graph TD
|
|
113
|
+
IDEA["π‘ Idea"] --> INIT{"ποΈ Auto-Init"}
|
|
114
|
+
INIT -->|.design/ exists| SPEC
|
|
115
|
+
INIT -->|.design/ missing| CREATE["Create .design/ structure"] --> SPEC
|
|
116
|
+
SPEC["π Specification"] <--> RULE["π Rule"]
|
|
117
|
+
SPEC --> TASK["πΊοΈ Task & Plan"]
|
|
118
|
+
TASK --> RUN["β‘ Run"]
|
|
119
|
+
RUN --> CODE["π Code"]
|
|
120
|
+
RUN -.->|"auto: phase done"| RETRO["π Retrospective"]
|
|
121
|
+
RETRO -.->|Feedback loop| SPEC
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Core Workflows
|
|
125
|
+
|
|
126
|
+
| # | Workflow | Purpose |
|
|
127
|
+
| :--- | :--- | :--- |
|
|
128
|
+
| 1 | **Specification** | Converts raw thoughts into structured specs. Verifies specs against project state. Manages statuses: `Draft β RFC β Stable β Deprecated`. |
|
|
129
|
+
| 2 | **Task** | Reads Stable specs, builds a dependency graph, produces a phased `PLAN.md`, and decomposes into atomic tasks. |
|
|
130
|
+
| 3 | **Run** | Executes tasks with sequential and parallel tracks. Automatically runs a retrospective at phase and plan completion. |
|
|
131
|
+
|
|
132
|
+
### Auxiliary Workflows
|
|
133
|
+
|
|
134
|
+
| Workflow | Purpose |
|
|
135
|
+
| :--- | :--- |
|
|
136
|
+
| **Rule** | Manages the project constitution (`RULES.md Β§7`). Add, amend, or remove conventions. |
|
|
137
|
+
| **Onboard** | Interactive tutorial guiding new developers through their first Magic SDD cycle. |
|
|
138
|
+
|
|
139
|
+
> **Retrospective** runs automatically inside the Run workflow β at phase completion (snapshot) and plan completion (full analysis). No manual command needed.
|
|
140
|
+
|
|
141
|
+
## π¬ How to Use (with any AI agent)
|
|
142
|
+
|
|
143
|
+
Just talk to your AI agent naturally. Initialization is **automatic** β no setup command required.
|
|
144
|
+
|
|
145
|
+
```plaintext
|
|
146
|
+
"Dispatch this thought into specs: I want a user auth system with JWT and Redis..."
|
|
147
|
+
β Runs Specification workflow
|
|
148
|
+
|
|
149
|
+
"Create an implementation plan"
|
|
150
|
+
β Runs Task workflow
|
|
151
|
+
|
|
152
|
+
"Generate tasks for Phase 1"
|
|
153
|
+
β Runs Task workflow
|
|
154
|
+
|
|
155
|
+
"Execute the next task"
|
|
156
|
+
β Runs Run workflow
|
|
157
|
+
|
|
158
|
+
"Add rule: always use snake_case for file names"
|
|
159
|
+
β Runs Rule workflow
|
|
160
|
+
|
|
161
|
+
"Check if specs match the actual project state"
|
|
162
|
+
β Runs Specification workflow (Consistency Check)
|
|
163
|
+
|
|
164
|
+
"Start the interactive tutorial"
|
|
165
|
+
β Runs Onboard workflow
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The AI reads the corresponding `.magic/*.md` workflow file and executes the request within the bounds of the SDD system. **No code escapes the pipeline.** β¨
|
|
169
|
+
|
|
170
|
+
## π Updating
|
|
171
|
+
|
|
172
|
+
Pull the latest engine improvements without touching your project data:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Node.js
|
|
176
|
+
npx magic-spec@latest --update
|
|
177
|
+
|
|
178
|
+
# Python
|
|
179
|
+
uvx magic-spec --update
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The update overwrites `.magic/` (the engine) but **never touches** `.design/` (your specs, plans, and tasks).
|
|
183
|
+
|
|
184
|
+
## π€ Compatibility
|
|
185
|
+
|
|
186
|
+
Works with any AI coding agent that can read markdown workflow files:
|
|
187
|
+
|
|
188
|
+
- [Cursor](https://cursor.sh) (`.cursorrules` + Agent mode)
|
|
189
|
+
- [Claude](https://claude.ai) (Projects)
|
|
190
|
+
- [Gemini](https://gemini.google.com) (via Gemini Code)
|
|
191
|
+
- [GitHub Copilot](https://github.com/features/copilot) (Agent mode)
|
|
192
|
+
- Any terminal-based or API-driven agent
|
|
193
|
+
|
|
194
|
+
## π License
|
|
195
|
+
|
|
196
|
+
[MIT](./LICENSE) Β© 2026 Oleg Alexandrov
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const { version } = require('./package.json');
|
|
9
|
+
|
|
10
|
+
const GITHUB_REPO = 'teratron/magic-spec';
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const isUpdate = args.includes('--update');
|
|
15
|
+
const isDoctor = args.includes('--doctor') || args.includes('--check');
|
|
16
|
+
const isFallbackMain = args.includes('--fallback-main');
|
|
17
|
+
|
|
18
|
+
const envFlag = args.find(a => a.startsWith('--env'));
|
|
19
|
+
const envValues = envFlag
|
|
20
|
+
? envFlag.includes('=')
|
|
21
|
+
? envFlag.split('=')[1].split(',')
|
|
22
|
+
: (args[args.indexOf(envFlag) + 1] || '').split(',').filter(Boolean)
|
|
23
|
+
: [];
|
|
24
|
+
|
|
25
|
+
function copyDir(src, dest) {
|
|
26
|
+
if (!fs.existsSync(src)) {
|
|
27
|
+
console.warn(`β οΈ Source not found: ${src}`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
fs.cpSync(src, dest, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function installAdapter(sourceRoot, env, adapters) {
|
|
34
|
+
const adapter = adapters[env];
|
|
35
|
+
if (!adapter) {
|
|
36
|
+
console.warn(`β οΈ Unknown --env value: "${env}".`);
|
|
37
|
+
console.warn(` Valid values: ${Object.keys(adapters).join(', ')}`);
|
|
38
|
+
console.warn(` Falling back to default .agent/`);
|
|
39
|
+
copyDir(path.join(sourceRoot, '.agent'), path.join(cwd, '.agent'));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const srcDir = path.join(sourceRoot, '.agent', 'workflows');
|
|
44
|
+
const destDir = path.join(cwd, adapter.dest);
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(srcDir)) {
|
|
47
|
+
console.warn(`β οΈ Source .agent/workflows/ not found.`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
52
|
+
|
|
53
|
+
const files = fs.readdirSync(srcDir).filter(f => f.endsWith('.md'));
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
const srcFile = path.join(srcDir, file);
|
|
56
|
+
let destName = file.replace(/\.md$/, adapter.ext);
|
|
57
|
+
if (adapter.removePrefix) {
|
|
58
|
+
destName = destName.replace(adapter.removePrefix, '');
|
|
59
|
+
}
|
|
60
|
+
const destFile = path.join(destDir, destName);
|
|
61
|
+
fs.copyFileSync(srcFile, destFile);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`β
Adapter installed: ${env} β ${adapter.dest}/ (${adapter.ext})`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function runDoctor() {
|
|
68
|
+
const isWindows = process.platform === 'win32';
|
|
69
|
+
const checkScript = isWindows
|
|
70
|
+
? path.join(cwd, '.magic', 'scripts', 'check-prerequisites.ps1')
|
|
71
|
+
: path.join(cwd, '.magic', 'scripts', 'check-prerequisites.sh');
|
|
72
|
+
|
|
73
|
+
if (!fs.existsSync(checkScript)) {
|
|
74
|
+
console.error('β Error: SDD engine not initialized. Run magic-spec first.');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log('π Magic-spec Doctor:');
|
|
79
|
+
try {
|
|
80
|
+
let result;
|
|
81
|
+
if (isWindows) {
|
|
82
|
+
result = spawnSync('powershell.exe', ['-ExecutionPolicy', 'Bypass', '-File', checkScript, '-json'], { encoding: 'utf-8' });
|
|
83
|
+
} else {
|
|
84
|
+
result = spawnSync('bash', [checkScript, '--json'], { encoding: 'utf-8' });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let jsonStr = result.stdout.trim();
|
|
88
|
+
const match = jsonStr.match(/\{[\s\S]*\}/);
|
|
89
|
+
if (match) {
|
|
90
|
+
jsonStr = match[0];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const data = JSON.parse(jsonStr);
|
|
94
|
+
const arts = data.artifacts || {};
|
|
95
|
+
const checkItem = (name, item, requiredHint) => {
|
|
96
|
+
if (item && item.exists) {
|
|
97
|
+
console.log(`β
${item.path || name} is present`);
|
|
98
|
+
} else {
|
|
99
|
+
const hint = requiredHint ? ` (Hint: ${requiredHint})` : '';
|
|
100
|
+
console.log(`β .design/${name} is missing${hint}`);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
checkItem('INDEX.md', arts['INDEX.md'], 'Run /magic.spec');
|
|
105
|
+
checkItem('RULES.md', arts['RULES.md'], 'Created at init');
|
|
106
|
+
|
|
107
|
+
if (arts['PLAN.md']) {
|
|
108
|
+
checkItem('PLAN.md', arts['PLAN.md'], 'Run /magic.task');
|
|
109
|
+
}
|
|
110
|
+
if (arts['TASKS.md']) {
|
|
111
|
+
checkItem('TASKS.md', arts['TASKS.md'], 'Run /magic.task');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (data.warnings && data.warnings.length > 0) {
|
|
115
|
+
data.warnings.forEach(warn => {
|
|
116
|
+
console.log(`β οΈ ${warn}`);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (arts.specs) {
|
|
121
|
+
const { stable } = arts.specs;
|
|
122
|
+
if (stable > 0) console.log(`β
${stable} specifications are Stable`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.error('β Failed to parse doctor output', err.message);
|
|
127
|
+
}
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function downloadPayload(targetVersion) {
|
|
132
|
+
const url = targetVersion === 'main'
|
|
133
|
+
? `https://github.com/${GITHUB_REPO}/archive/refs/heads/main.tar.gz`
|
|
134
|
+
: `https://github.com/${GITHUB_REPO}/archive/refs/tags/v${targetVersion}.tar.gz`;
|
|
135
|
+
|
|
136
|
+
console.log(`π₯ Downloading magic-spec payload (${targetVersion}) from GitHub...`);
|
|
137
|
+
|
|
138
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'magic-spec-'));
|
|
139
|
+
const archivePath = path.join(tempDir, 'payload.tar.gz');
|
|
140
|
+
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const file = fs.createWriteStream(archivePath);
|
|
143
|
+
|
|
144
|
+
const makeRequest = (requestUrl) => {
|
|
145
|
+
https.get(requestUrl, { headers: { 'User-Agent': 'magic-spec-node' } }, (response) => {
|
|
146
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
147
|
+
// Follow redirect
|
|
148
|
+
return makeRequest(response.headers.location);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (response.statusCode === 404) {
|
|
152
|
+
console.error(`β Error: Release ${targetVersion} not found on GitHub.`);
|
|
153
|
+
console.error(' (Use --fallback-main to pull from the main branch instead)');
|
|
154
|
+
reject(new Error('Payload not found'));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (response.statusCode !== 200) {
|
|
159
|
+
reject(new Error(`Failed with status code: ${response.statusCode}`));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
response.pipe(file);
|
|
164
|
+
|
|
165
|
+
file.on('finish', () => {
|
|
166
|
+
file.close();
|
|
167
|
+
console.log('π¦ Extracting payload...');
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
// Using spawnSync for tar extraction. Requires 'tar' available on system.
|
|
171
|
+
// For a pure JS solution without dependencies, you would use a package like 'tar'
|
|
172
|
+
const isWindows = process.platform === 'win32';
|
|
173
|
+
|
|
174
|
+
let result;
|
|
175
|
+
if (isWindows) {
|
|
176
|
+
// Use PowerShell to extract tar on Windows 10+
|
|
177
|
+
result = spawnSync('tar', ['-xzf', archivePath, '-C', tempDir]);
|
|
178
|
+
} else {
|
|
179
|
+
// Standard Linux/macOS tar
|
|
180
|
+
result = spawnSync('tar', ['-xzf', archivePath, '-C', tempDir]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (result.error || result.status !== 0) {
|
|
184
|
+
throw new Error('Tar extraction failed. ' + (result.stderr ? result.stderr.toString() : ''));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const items = fs.readdirSync(tempDir);
|
|
188
|
+
const extractedFolder = items.find(item => {
|
|
189
|
+
const p = path.join(tempDir, item);
|
|
190
|
+
return item !== 'payload.tar.gz' && fs.statSync(p).isDirectory();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (extractedFolder) {
|
|
194
|
+
resolve(path.join(tempDir, extractedFolder));
|
|
195
|
+
} else {
|
|
196
|
+
resolve(tempDir);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
} catch (err) {
|
|
200
|
+
reject(err);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}).on('error', (err) => {
|
|
204
|
+
fs.unlink(archivePath, () => reject(err));
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
makeRequest(url);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function main() {
|
|
213
|
+
if (isDoctor) {
|
|
214
|
+
runDoctor();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
219
|
+
console.log("Usage: npx magic-spec [--env <adapter>] [--update] [--doctor | --check] [--fallback-main]");
|
|
220
|
+
process.exit(0);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log(isUpdate ? 'πͺ Updating magic-spec (.magic only)...' : 'πͺ Initializing magic-spec...');
|
|
224
|
+
|
|
225
|
+
const versionToFetch = isFallbackMain ? 'main' : version;
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const sourceRoot = await downloadPayload(versionToFetch);
|
|
229
|
+
|
|
230
|
+
let ADAPTERS = {};
|
|
231
|
+
try {
|
|
232
|
+
ADAPTERS = JSON.parse(fs.readFileSync(path.join(sourceRoot, 'installers', 'adapters.json'), 'utf8'));
|
|
233
|
+
} catch (e) { }
|
|
234
|
+
|
|
235
|
+
// 1. Copy .magic
|
|
236
|
+
copyDir(path.join(sourceRoot, '.magic'), path.join(cwd, '.magic'));
|
|
237
|
+
|
|
238
|
+
// 2. Adapters
|
|
239
|
+
if (!isUpdate) {
|
|
240
|
+
if (envValues.length > 0) {
|
|
241
|
+
for (const env of envValues) {
|
|
242
|
+
installAdapter(sourceRoot, env, ADAPTERS);
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
copyDir(path.join(sourceRoot, '.agent'), path.join(cwd, '.agent'));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// 3. Init script
|
|
250
|
+
if (!isUpdate) {
|
|
251
|
+
const isWindows = process.platform === 'win32';
|
|
252
|
+
const initScript = isWindows
|
|
253
|
+
? path.join(cwd, '.magic', 'scripts', 'init.ps1')
|
|
254
|
+
: path.join(cwd, '.magic', 'scripts', 'init.sh');
|
|
255
|
+
|
|
256
|
+
if (fs.existsSync(initScript)) {
|
|
257
|
+
if (isWindows) {
|
|
258
|
+
spawnSync('powershell.exe', ['-ExecutionPolicy', 'Bypass', '-File', initScript], { stdio: 'inherit' });
|
|
259
|
+
} else {
|
|
260
|
+
fs.chmodSync(initScript, '755');
|
|
261
|
+
spawnSync('bash', [initScript], { stdio: 'inherit' });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
console.log('β
magic-spec initialized successfully!');
|
|
265
|
+
} else {
|
|
266
|
+
console.log('β
magic-spec updated successfully!');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
} catch (err) {
|
|
270
|
+
console.error('β magic-spec initialization failed:', err.message);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
main();
|