archgen-skill 0.0.5 → 0.0.7
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 +20 -7
- package/bin/archgen.mjs +142 -15
- package/lib/block.js +52 -11
- package/lib/bootstrap.js +107 -0
- package/lib/config.js +3 -2
- package/lib/doctor.js +197 -77
- package/lib/init.js +77 -19
- package/lib/install.js +79 -7
- package/lib/mcp-server.js +241 -0
- package/lib/migrations/001-stamp-provenance.mjs +15 -8
- package/lib/migrations/index.mjs +19 -8
- package/lib/restore.js +52 -16
- package/lib/spawn-npm.js +99 -0
- package/lib/store.js +175 -28
- package/lib/uninstall-project.js +38 -7
- package/lib/version.js +20 -4
- package/package.json +3 -3
- package/vendor/skills/archgen/SKILL.md +363 -213
- package/vendor/skills/archgen/archgen.config.json +1 -1
- package/vendor/skills/archgen/references/code-standards.md +2 -2
- package/vendor/skills/archgen/assets/mcp-template.json +0 -42
- package/vendor/skills/archgen/assets/skill-registry.json +0 -24
- package/vendor/skills/archgen/references/artifact-templates.md +0 -406
- package/vendor/skills/archgen/references/interview.md +0 -275
- package/vendor/skills/archgen/references/mcp-and-skills.md +0 -117
- package/vendor/skills/archgen/references/orchestration.md +0 -292
- package/vendor/skills/archgen/references/platforms.md +0 -138
- package/vendor/skills/archgen/scripts/doc-index.mjs +0 -457
- package/vendor/skills/archgen/scripts/impact.mjs +0 -77
- package/vendor/skills/archgen/scripts/lib/graph.mjs +0 -222
- package/vendor/skills/archgen/scripts/lib/plan-coverage.mjs +0 -32
- package/vendor/skills/archgen/scripts/lib/scope.mjs +0 -65
- package/vendor/skills/archgen/scripts/lib/yaml.mjs +0 -492
- package/vendor/skills/archgen/scripts/next-tasks.mjs +0 -55
- package/vendor/skills/archgen/scripts/plan-graph.mjs +0 -329
- package/vendor/skills/archgen/scripts/set-status.mjs +0 -82
- package/vendor/skills/archgen/scripts/update-agents.mjs +0 -213
- package/vendor/skills/archgen/scripts/validate.mjs +0 -87
- package/vendor/skills/archgen/scripts/verify-plan.mjs +0 -70
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://github.com/akashmaurya99/archgen/actions/workflows/ci.yml)
|
|
8
8
|
[](https://github.com/akashmaurya99/archgen/blob/main/LICENSE)
|
|
9
|
-
[](https://nodejs.org)
|
|
10
10
|
|
|
11
11
|
Describe what you want to build. archgen interviews you (or surveys your existing
|
|
12
12
|
codebase), produces an architecture contract plus a dependency-ordered task graph,
|
|
@@ -42,6 +42,13 @@ copies are backed up to `.archgen/.backup/<timestamp>/` before refresh, and lega
|
|
|
42
42
|
dual-copy layouts are migrated to symlinks automatically. Verify anytime with
|
|
43
43
|
`archgen-skill doctor`.
|
|
44
44
|
|
|
45
|
+
Every destructive step is behind a rail: the project root is asserted **before**
|
|
46
|
+
anything is written or deleted, a symlinked target is never written through, the
|
|
47
|
+
store swap is atomic (the old store is moved aside, not deleted, and restored if
|
|
48
|
+
the swap fails), and a restore puts back exactly the paths the snapshot recorded
|
|
49
|
+
— never a sibling skill. `archgen-skill uninstall <dir>` removes that project;
|
|
50
|
+
the global uninstall happens only when no project is named.
|
|
51
|
+
|
|
45
52
|
**Or install globally** into every detected harness:
|
|
46
53
|
|
|
47
54
|
```bash
|
|
@@ -49,8 +56,13 @@ npx archgen-skill install # symlinks; --copy for real copies
|
|
|
49
56
|
```
|
|
50
57
|
|
|
51
58
|
Prefer shell? Clone the repo and run `./install.sh` (same behavior, plus
|
|
52
|
-
`--init`, `--project`, `--uninstall`). Requires Node.js ≥
|
|
53
|
-
|
|
59
|
+
`--init`, `--project`, `--uninstall`). Requires Node.js ≥ 20 — the official MCP
|
|
60
|
+
SDK v2 floor.
|
|
61
|
+
|
|
62
|
+
**What `init` actually does:** installs the skill (guidance only — no code),
|
|
63
|
+
wires the `archgen-mcp` MCP server into every detected harness, and ensures
|
|
64
|
+
CodeGraph (its CLI, its MCP entry, and the project index). The CLI itself is
|
|
65
|
+
zero-dependency; the MCP server it installs depends on the official MCP SDK.
|
|
54
66
|
|
|
55
67
|
## Quick start
|
|
56
68
|
|
|
@@ -75,7 +87,7 @@ by git.
|
|
|
75
87
|
| `npx archgen-skill init [dir]` | Prepare a project: canonical skill store at `.agents/skills/archgen`, Claude symlink adapter, AGENTS.md hub + CLAUDE.md bridge |
|
|
76
88
|
| `npx archgen-skill install [--copy]` | Install into global harness skill dirs (symlinks by default, real copies with `--copy`) |
|
|
77
89
|
| `npx archgen-skill uninstall` | Remove globally-installed copies (manifest-recorded, safe removal) |
|
|
78
|
-
| `npx archgen-skill uninstall
|
|
90
|
+
| `npx archgen-skill uninstall [dir]` | Remove a **project** install (same as `--project [dir]`): strips managed blocks, symlink, and store **only if unmodified** — `.archgen/` feature folders and all user content are preserved |
|
|
79
91
|
| `npx archgen-skill doctor [dir] [--check]` | Verify and auto-repair an installation: store integrity, version stamp, Claude link, managed blocks, manifest (`--check` reports without fixing) |
|
|
80
92
|
| `npx archgen-skill update [dir]` | Check npm for a newer `archgen-skill`, upgrade the global install if outdated, then re-init the project and run doctor against the new version |
|
|
81
93
|
| `npx archgen-skill restore` | List backup snapshots; `--snapshot <ts>` restores one (current state is backed up first) |
|
|
@@ -125,11 +137,12 @@ are supported for all of them.
|
|
|
125
137
|
|
|
126
138
|
## VS Code extension (optional)
|
|
127
139
|
|
|
128
|
-
A
|
|
140
|
+
A visual layer that renders `.archgen/` as a live task DAG, your
|
|
129
141
|
code-dependency graph, and rendered docs — with a ▶ button that hands any task to
|
|
130
142
|
your agent. Install it from the VS Code Marketplace or from a `.vsix` on
|
|
131
143
|
[GitHub Releases](https://github.com/akashmaurya99/archgen/releases). It never
|
|
132
|
-
edits
|
|
144
|
+
edits your plan, your tasks, or your code — only its own runtime state under
|
|
145
|
+
`.archgen/.runtime/`, so uninstalling it loses nothing.
|
|
133
146
|
|
|
134
147
|
## Documentation
|
|
135
148
|
|
|
@@ -145,7 +158,7 @@ See [CONTRIBUTING.md](https://github.com/akashmaurya99/archgen/blob/main/CONTRIB
|
|
|
145
158
|
Skill suite and CLI tests run with zero dependencies:
|
|
146
159
|
|
|
147
160
|
```sh
|
|
148
|
-
node --test
|
|
161
|
+
node --test packages/mcp/test/**/*.test.mjs # MCP server suite
|
|
149
162
|
cd packages/cli && npm test # CLI suite
|
|
150
163
|
```
|
|
151
164
|
|
package/bin/archgen.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import { uninstallProject } from '../lib/uninstall-project.js';
|
|
|
22
22
|
import { restoreMain } from '../lib/restore.js';
|
|
23
23
|
import { migrateMain } from '../lib/migrate.js';
|
|
24
24
|
import { compareSemver, fetchLatestVersion } from '../lib/version.js';
|
|
25
|
+
import { npmInvocation, whichOnPath, globalBinPath } from '../lib/spawn-npm.js';
|
|
25
26
|
import { spawnSync } from 'node:child_process';
|
|
26
27
|
import { readFileSync } from 'node:fs';
|
|
27
28
|
|
|
@@ -34,7 +35,13 @@ function cliVersion() {
|
|
|
34
35
|
function runInitAndDoctor(dir) {
|
|
35
36
|
const r = initProject(dir, PACKAGE_ROOT);
|
|
36
37
|
console.log(' = store refreshed at ' + r.storePath);
|
|
37
|
-
doctorProject(dir, PACKAGE_ROOT, {});
|
|
38
|
+
const d = doctorProject(dir, PACKAGE_ROOT, {});
|
|
39
|
+
// A failed doctor must fail the update too: the project was NOT brought to
|
|
40
|
+
// the new version.
|
|
41
|
+
if (d.failures > 0) {
|
|
42
|
+
console.error(' ! doctor reported ' + d.failures + ' failure(s)');
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
}
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
const HELP = `archgen — architecture generation & autonomous task execution for coding agents
|
|
@@ -44,16 +51,26 @@ Usage:
|
|
|
44
51
|
store at .agents/skills/archgen, symlink
|
|
45
52
|
.claude/skills/archgen -> ../../.agents/skills/archgen,
|
|
46
53
|
and generate AGENTS.md / CLAUDE.md pointer blocks.
|
|
54
|
+
[--project <dir>] the project directory (same as the positional
|
|
55
|
+
form; give it once, not both)
|
|
47
56
|
[--force] proceed automatically; divergent copies are
|
|
48
57
|
backed up to .archgen/.backup/ (never destroyed)
|
|
58
|
+
[--no-codegraph] skip the CodeGraph bootstrap
|
|
59
|
+
[--no-mcp] skip wiring the archgen MCP server
|
|
49
60
|
npx archgen-skill doctor [dir] Health-check a project install: store integrity,
|
|
50
61
|
version stamp, claude link, managed blocks,
|
|
51
62
|
manifest. Repairs safe issues in place.
|
|
63
|
+
[--project <dir>] the project directory (same as the positional form)
|
|
52
64
|
[--check] report only - change nothing
|
|
53
65
|
npx archgen-skill install Install into global harness skill dirs
|
|
54
66
|
[--copy] real copies instead of symlinks
|
|
55
67
|
[--uninstall] remove manifest-recorded entries
|
|
56
|
-
|
|
68
|
+
[--no-codegraph] skip the CodeGraph bootstrap
|
|
69
|
+
[--no-mcp] skip wiring the archgen MCP server
|
|
70
|
+
npx archgen-skill uninstall [dir] Remove archgen from a project (the same
|
|
71
|
+
as the positional form); without a directory
|
|
72
|
+
the globally-installed copies are removed
|
|
73
|
+
(manifest-based)
|
|
57
74
|
--project [dir] remove archgen from a project: claude link,
|
|
58
75
|
managed blocks, canonical store (kept when
|
|
59
76
|
customized) and manifest; .archgen feature
|
|
@@ -84,15 +101,74 @@ function flag(name) {
|
|
|
84
101
|
return true;
|
|
85
102
|
}
|
|
86
103
|
|
|
87
|
-
|
|
88
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Consume an option that takes a value: `--name <value>` or `--name=<value>`.
|
|
106
|
+
*
|
|
107
|
+
* Returns null when the option is absent, '' when it is present with no
|
|
108
|
+
* value ("use the default"), and the value otherwise.
|
|
109
|
+
*/
|
|
110
|
+
function takeValue(name) {
|
|
111
|
+
const eq = rest.findIndex((a) => a.startsWith(name + '='));
|
|
112
|
+
if (eq !== -1) {
|
|
113
|
+
const v = rest[eq].slice(name.length + 1);
|
|
114
|
+
rest.splice(eq, 1);
|
|
115
|
+
return v;
|
|
116
|
+
}
|
|
117
|
+
const i = rest.indexOf(name);
|
|
118
|
+
if (i === -1) return null;
|
|
119
|
+
rest.splice(i, 1);
|
|
120
|
+
const v = rest[i];
|
|
121
|
+
if (v === undefined || v.startsWith('-')) return '';
|
|
122
|
+
rest.splice(i, 1);
|
|
123
|
+
return v;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The project directory for a command.
|
|
128
|
+
*
|
|
129
|
+
* Accepts a POSITIONAL dir (`init myproj`) and `--project <dir>` alike. The
|
|
130
|
+
* same flag name is used by `uninstall` and `restore`, and the MCP server's
|
|
131
|
+
* own commands take `--project` too — so a user who typed one form must
|
|
132
|
+
* never get a directory literally named "--project" created in their cwd.
|
|
133
|
+
*
|
|
134
|
+
* Extra positional arguments are REFUSED: silently ignoring the second one
|
|
135
|
+
* would install into a directory the user did not name.
|
|
136
|
+
*/
|
|
137
|
+
function targetDir() {
|
|
138
|
+
const explicit = takeValue('--project');
|
|
139
|
+
const positionals = rest.filter((a) => !a.startsWith('-'));
|
|
140
|
+
if (positionals.length > 1) {
|
|
141
|
+
throw new Error(`unexpected argument: ${positionals[1]} — give the project directory once`);
|
|
142
|
+
}
|
|
143
|
+
const positional = positionals[0];
|
|
144
|
+
const explicitDir = explicit === null ? null : (explicit === '' ? process.cwd() : resolve(explicit));
|
|
145
|
+
if (explicitDir !== null && positional !== undefined && explicitDir !== resolve(positional)) {
|
|
146
|
+
throw new Error('give the project directory once: either positionally or with --project');
|
|
147
|
+
}
|
|
148
|
+
return explicitDir ?? (positional !== undefined ? resolve(positional) : process.cwd());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Refuse an option we do not understand.
|
|
153
|
+
*
|
|
154
|
+
* An unknown flag is a TYPO. It is never a directory name — creating a
|
|
155
|
+
* directory called "--no-mcp" because the flag was misspelled is exactly the
|
|
156
|
+
* kind of silent mess this check exists to prevent.
|
|
157
|
+
*/
|
|
158
|
+
function assertNoUnknownFlags() {
|
|
159
|
+
const unknown = rest.find((a) => a.startsWith('-'));
|
|
160
|
+
if (unknown) throw new Error(`unknown option: ${unknown}`);
|
|
89
161
|
}
|
|
90
162
|
|
|
91
163
|
try {
|
|
92
164
|
switch (cmd) {
|
|
93
165
|
case 'init': {
|
|
94
166
|
flag('--force');
|
|
95
|
-
const
|
|
167
|
+
const noCodegraph = flag('--no-codegraph');
|
|
168
|
+
const noMcp = flag('--no-mcp');
|
|
169
|
+
const dir = targetDir();
|
|
170
|
+
assertNoUnknownFlags();
|
|
171
|
+
const r = initProject(dir, PACKAGE_ROOT, { codegraph: !noCodegraph, mcp: !noMcp });
|
|
96
172
|
console.log('archgen: project ready (single-store layout)');
|
|
97
173
|
console.log(' + store ' + r.storePath);
|
|
98
174
|
if (r.claudeLink === 'created') console.log(' + link .claude/skills/archgen -> ../../.agents/skills/archgen');
|
|
@@ -110,7 +186,9 @@ try {
|
|
|
110
186
|
}
|
|
111
187
|
case 'doctor': {
|
|
112
188
|
const check = flag('--check');
|
|
113
|
-
const
|
|
189
|
+
const dir = targetDir();
|
|
190
|
+
assertNoUnknownFlags();
|
|
191
|
+
const r = doctorProject(dir, PACKAGE_ROOT, { check });
|
|
114
192
|
console.log('archgen doctor — ' + r.root + (check ? ' (check mode: no changes made)' : ''));
|
|
115
193
|
for (const c of r.checks) console.log(` [${c.status.toLowerCase().padEnd(9)}] ${c.msg}`);
|
|
116
194
|
const t = r.tally;
|
|
@@ -120,8 +198,14 @@ try {
|
|
|
120
198
|
}
|
|
121
199
|
case 'install': {
|
|
122
200
|
const copy = flag('--copy');
|
|
123
|
-
|
|
124
|
-
const
|
|
201
|
+
const noCodegraph = flag('--no-codegraph');
|
|
202
|
+
const noMcp = flag('--no-mcp');
|
|
203
|
+
const uninstallMode = flag('--uninstall');
|
|
204
|
+
assertNoUnknownFlags();
|
|
205
|
+
const stray = rest.find((a) => !a.startsWith('-'));
|
|
206
|
+
if (stray) throw new Error(`unexpected argument: ${stray} — install takes no directory`);
|
|
207
|
+
if (uninstallMode) { uninstall(); break; }
|
|
208
|
+
const r = installGlobal({ copy, codegraph: !noCodegraph, mcp: !noMcp });
|
|
125
209
|
console.log('archgen: global harness install (mode: ' + (copy ? 'copy' : 'symlink') + ')');
|
|
126
210
|
for (const [status, mode, path] of r.rows) console.log(` ${status.padEnd(7)} ${mode.padEnd(5)} ${path}`);
|
|
127
211
|
console.log('\nPer-project setup (recommended): npx archgen-skill init');
|
|
@@ -129,14 +213,32 @@ try {
|
|
|
129
213
|
break;
|
|
130
214
|
}
|
|
131
215
|
case 'uninstall': {
|
|
132
|
-
|
|
133
|
-
|
|
216
|
+
// `--project` may carry a directory (`uninstall --project myproj`) or
|
|
217
|
+
// stand alone (`uninstall --project` → this directory). A POSITIONAL
|
|
218
|
+
// directory (`uninstall myproj`) is a project uninstall as well: the
|
|
219
|
+
// GLOBAL uninstall happens only when no project was named at all.
|
|
220
|
+
const explicit = takeValue('--project');
|
|
221
|
+
const positionals = rest.filter((a) => !a.startsWith('-'));
|
|
222
|
+
assertNoUnknownFlags();
|
|
223
|
+
if (positionals.length > 1) {
|
|
224
|
+
throw new Error(`unexpected argument: ${positionals[1]} — give the project directory once`);
|
|
225
|
+
}
|
|
226
|
+
const positional = positionals[0];
|
|
227
|
+
const projectTarget = explicit !== null
|
|
228
|
+
? (explicit === '' ? process.cwd() : resolve(explicit))
|
|
229
|
+
: (positional !== undefined ? resolve(positional) : null);
|
|
230
|
+
if (explicit !== null && positional !== undefined && projectTarget !== resolve(positional)) {
|
|
231
|
+
throw new Error('give the project directory once: either positionally or with --project');
|
|
232
|
+
}
|
|
233
|
+
if (projectTarget !== null) {
|
|
234
|
+
const r = uninstallProject(projectTarget, PACKAGE_ROOT);
|
|
134
235
|
console.log('archgen: project uninstall — ' + r.root);
|
|
135
236
|
if (r.linkRemoved) console.log(' - .claude/skills/archgen (link)');
|
|
136
237
|
if (r.strippedBlocks.length) console.log(' - managed blocks stripped from ' + r.strippedBlocks.join(', '));
|
|
137
238
|
if (r.storeRemoved) console.log(' - .agents/skills/archgen (unmodified store)');
|
|
138
239
|
if (r.storeKept) console.log(' = .agents/skills/archgen KEPT (customized)');
|
|
139
240
|
if (r.manifestRemoved) console.log(' - .archgen/.install-manifest.json');
|
|
241
|
+
for (const m of r.mcpRemoved ?? []) console.log(` - ${m} (archgen MCP entry)`);
|
|
140
242
|
for (const w of r.warnings) console.log(' note: ' + w);
|
|
141
243
|
console.log(' .archgen feature folders and backups are preserved.');
|
|
142
244
|
} else {
|
|
@@ -156,7 +258,8 @@ try {
|
|
|
156
258
|
break;
|
|
157
259
|
}
|
|
158
260
|
case 'update': {
|
|
159
|
-
const dir =
|
|
261
|
+
const dir = targetDir();
|
|
262
|
+
assertNoUnknownFlags();
|
|
160
263
|
const current = cliVersion();
|
|
161
264
|
console.log('archgen update — running v' + current + ', checking npm registry…');
|
|
162
265
|
const latest = fetchLatestVersion('archgen-skill');
|
|
@@ -165,6 +268,14 @@ try {
|
|
|
165
268
|
break;
|
|
166
269
|
}
|
|
167
270
|
const cmp = compareSemver(latest, current);
|
|
271
|
+
if (cmp === null) {
|
|
272
|
+
// The registry answered with something that is not a semver (e.g. a
|
|
273
|
+
// "beta" dist-tag). It is NOT "already the latest version" — refuse
|
|
274
|
+
// instead, and never interpolate it into a command line.
|
|
275
|
+
console.error(' ! registry returned a non-semver version string; refusing: ' + JSON.stringify(latest));
|
|
276
|
+
process.exitCode = 1;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
168
279
|
if (cmp === 0) {
|
|
169
280
|
console.log(' = already on the latest published version (' + latest + ').');
|
|
170
281
|
console.log(' Refreshing this project against it anyway…');
|
|
@@ -177,17 +288,26 @@ try {
|
|
|
177
288
|
runInitAndDoctor(dir);
|
|
178
289
|
break;
|
|
179
290
|
}
|
|
291
|
+
if (!/^\d+\.\d+\.\d+[-+]?[0-9A-Za-z.-]*$/.test(latest)) {
|
|
292
|
+
// Registry returned something that is not a semver tag — never
|
|
293
|
+
// interpolate it into a command line.
|
|
294
|
+
console.error(' ! registry returned a non-semver version string; refusing: ' + JSON.stringify(latest));
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
180
298
|
console.log(' ↑ newer version available: ' + latest);
|
|
181
299
|
console.log(' upgrading global install: npm install -g archgen-skill@' + latest);
|
|
182
|
-
const
|
|
300
|
+
const inv = npmInvocation(['install', '-g', 'archgen-skill@' + latest]);
|
|
301
|
+
const up = spawnSync(inv.command, inv.args, { stdio: 'inherit', shell: inv.shell });
|
|
183
302
|
if (up.status !== 0) {
|
|
184
303
|
console.error(' ! global upgrade failed — run it manually, then re-run: archgen-skill update');
|
|
185
304
|
process.exitCode = 1;
|
|
186
305
|
break;
|
|
187
306
|
}
|
|
188
307
|
// The running process is the OLD code; re-exec the freshly installed binary.
|
|
189
|
-
|
|
190
|
-
|
|
308
|
+
// The PATH search would find the FIRST match — often the OLD global
|
|
309
|
+
// install — so the binary npm actually wrote is preferred.
|
|
310
|
+
const freshBin = globalBinPath('archgen-skill') ?? whichOnPath('archgen-skill');
|
|
191
311
|
if (!freshBin) {
|
|
192
312
|
console.log('\nUpgraded to v' + latest + '. Now finish per project with:\n npx archgen-skill@latest init');
|
|
193
313
|
break;
|
|
@@ -201,7 +321,13 @@ try {
|
|
|
201
321
|
process.exitCode = upInit.status ?? 1;
|
|
202
322
|
break;
|
|
203
323
|
}
|
|
204
|
-
spawnSync(freshBin, ['doctor', dir], { stdio: 'inherit' });
|
|
324
|
+
const upDoctor = spawnSync(freshBin, ['doctor', dir], { stdio: 'inherit' });
|
|
325
|
+
if (upDoctor.status !== 0) {
|
|
326
|
+
// A failed doctor must fail the update too — the project was NOT
|
|
327
|
+
// brought to the new version.
|
|
328
|
+
console.error(' ! doctor failed during update (see message above)');
|
|
329
|
+
process.exitCode = upDoctor.status ?? 1;
|
|
330
|
+
}
|
|
205
331
|
break;
|
|
206
332
|
}
|
|
207
333
|
case '--version': case '-v': {
|
|
@@ -226,5 +352,6 @@ function uninstall() {
|
|
|
226
352
|
const r = uninstallGlobal();
|
|
227
353
|
if (r.noop) { console.log('archgen: nothing to uninstall (no manifest found).'); return; }
|
|
228
354
|
console.log(`archgen: removed ${r.removed} entr${r.removed === 1 ? 'y' : 'ies'}` + (r.failed ? `, ${r.failed} FAILED` : ''));
|
|
355
|
+
for (const b of r.backups ?? []) console.log(' ~ backup ' + b + ' (edited copy saved before removal)');
|
|
229
356
|
if (r.failed > 0) process.exitCode = 1;
|
|
230
357
|
}
|
package/lib/block.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// place on the next init/doctor pass and detectBlockVersion() lets doctor
|
|
17
17
|
// report stale formats precisely.
|
|
18
18
|
|
|
19
|
-
import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
20
20
|
import { loadConfig } from './config.js';
|
|
21
21
|
import { assertNotSymlink } from './store.js';
|
|
22
22
|
|
|
@@ -98,7 +98,7 @@ export function renderBlock(skillRelPath = '.agents/skills/archgen', eol = '\n')
|
|
|
98
98
|
'',
|
|
99
99
|
'Rules of the road:',
|
|
100
100
|
'- Generated artifacts live ONLY under `.archgen/<slug>/`',
|
|
101
|
-
'- Never hand-edit task statuses -
|
|
101
|
+
'- Never hand-edit task statuses - call the `archgen_tasks` MCP tool (comment-safe)',
|
|
102
102
|
'- Two gates before any execution: verifier approval, then human approval',
|
|
103
103
|
'',
|
|
104
104
|
'## Features registry',
|
|
@@ -171,11 +171,14 @@ export function upsertBlock(existing, block, eol = '\n') {
|
|
|
171
171
|
if (s === -1 || e === -1) {
|
|
172
172
|
throw new Error('found only one archgen marker - fix or remove it manually');
|
|
173
173
|
}
|
|
174
|
+
const prefix = trimmed.slice(0, s);
|
|
174
175
|
const tail = stripCompleteBlocks(trimmed.slice(e + END.length));
|
|
175
|
-
|
|
176
|
+
// An orphan marker ANYWHERE (before the first block as well as after it)
|
|
177
|
+
// would survive the rewrite and leave the file broken — refuse instead.
|
|
178
|
+
if (prefix.includes(START) || prefix.includes(END) || tail.includes(START) || tail.includes(END)) {
|
|
176
179
|
throw new Error('found only one archgen marker - fix or remove it manually');
|
|
177
180
|
}
|
|
178
|
-
return
|
|
181
|
+
return prefix + fresh + tail;
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
const startIdx = trimmed.indexOf(START);
|
|
@@ -209,13 +212,37 @@ function splitBom(raw) {
|
|
|
209
212
|
return { bom, body: bom ? raw.slice(1) : raw };
|
|
210
213
|
}
|
|
211
214
|
|
|
212
|
-
/**
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Write via sibling temp file + rename so readers never observe partial content.
|
|
217
|
+
* @param {string} absPath target file
|
|
218
|
+
* @param {string} data contents
|
|
219
|
+
*/
|
|
220
|
+
export function atomicWrite(absPath, data) {
|
|
221
|
+
let tmp = absPath + '.tmp-' + process.pid + '-' + Date.now();
|
|
222
|
+
for (let attempt = 0; ; attempt++) {
|
|
223
|
+
try {
|
|
224
|
+
// 'wx' (O_CREAT|O_EXCL): if the tmp name already exists — e.g. an attacker
|
|
225
|
+
// planted a symlink there to redirect the write — fail instead of following.
|
|
226
|
+
writeFileSync(tmp, data, { flag: 'wx' });
|
|
227
|
+
break;
|
|
228
|
+
} catch (e) {
|
|
229
|
+
// A collision (two writes inside the same millisecond) is not a failure:
|
|
230
|
+
// retry with a fresh name instead of failing the whole write.
|
|
231
|
+
if (e && e.code === 'EEXIST' && attempt < 10) {
|
|
232
|
+
tmp = absPath + '.tmp-' + process.pid + '-' + Date.now() + '-' + (attempt + 1);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
throw e;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
renameSync(tmp, absPath);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
// Never leave the temp file behind: a failed rename must not litter the
|
|
242
|
+
// project with half-written siblings.
|
|
243
|
+
try { unlinkSync(tmp); } catch { /* original intact; leftover tmp is harmless */ }
|
|
244
|
+
throw e;
|
|
245
|
+
}
|
|
219
246
|
}
|
|
220
247
|
|
|
221
248
|
/**
|
|
@@ -250,6 +277,20 @@ export function importsAgents(absPath) {
|
|
|
250
277
|
return body.split(/\r?\n/).some((l) => l.trim() === '@AGENTS.md');
|
|
251
278
|
}
|
|
252
279
|
|
|
280
|
+
/**
|
|
281
|
+
* True when the file carries a COMPLETE managed block (both markers).
|
|
282
|
+
*
|
|
283
|
+
* A file with a hand-written `@AGENTS.md` import has none — that import is
|
|
284
|
+
* the user's, not ours, and must survive untouched. A file WITH a managed
|
|
285
|
+
* block is ours: its provenance stamp is refreshed on every init, exactly
|
|
286
|
+
* like the AGENTS.md block.
|
|
287
|
+
*/
|
|
288
|
+
export function hasManagedBlock(absPath) {
|
|
289
|
+
let raw = '';
|
|
290
|
+
try { raw = readFileSync(absPath, 'utf8'); } catch { return false; }
|
|
291
|
+
return raw.includes(START) && raw.includes(END);
|
|
292
|
+
}
|
|
293
|
+
|
|
253
294
|
/** Strip the managed block from a file on disk, preserving BOM. */
|
|
254
295
|
export function stripManagedFile(absPath) {
|
|
255
296
|
assertNotSymlink(absPath);
|
package/lib/bootstrap.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// bootstrap.js — the install-time server bootstrap for `archgen init` /
|
|
2
|
+
// `archgen install`.
|
|
3
|
+
//
|
|
4
|
+
// The CLI used to shell out to the skill's scripts/codegraph.mjs. That script
|
|
5
|
+
// no longer exists: ALL deterministic capability now lives in the archgen MCP
|
|
6
|
+
// server, so the CLI drives THAT instead. One implementation, three ways in
|
|
7
|
+
// (agent -> MCP stdio, CLI -> `archgen-mcp bootstrap`, extension -> exec).
|
|
8
|
+
//
|
|
9
|
+
// The bootstrap does two things in one pass:
|
|
10
|
+
// 1. wires the archgen MCP entry into every detected harness
|
|
11
|
+
// 2. ensures CodeGraph: its CLI, its MCP entry, and the project index
|
|
12
|
+
//
|
|
13
|
+
// NEVER FATAL: no npm, no network, a read-only config, or a missing MCP
|
|
14
|
+
// server must still leave a working skill install. Failures are REPORTED to
|
|
15
|
+
// the caller (ok:false + detail) and rendered as a warning by it — never
|
|
16
|
+
// thrown, and never swallowed.
|
|
17
|
+
import { bootstrapMcp, resolveMcpBinary } from './mcp-server.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {{
|
|
21
|
+
* projectDir: string,
|
|
22
|
+
* location?: 'project'|'global'|'both',
|
|
23
|
+
* log?: (line: string) => void,
|
|
24
|
+
* env?: Record<string, string|undefined>,
|
|
25
|
+
* noCodegraph?: boolean,
|
|
26
|
+
* noMcp?: boolean,
|
|
27
|
+
* }} opts
|
|
28
|
+
* @returns {{ok: boolean, skipped?: string, detail?: string, report?: any}}
|
|
29
|
+
*/
|
|
30
|
+
export function bootstrapServers(opts) {
|
|
31
|
+
const {
|
|
32
|
+
projectDir,
|
|
33
|
+
location = 'both',
|
|
34
|
+
log = () => {},
|
|
35
|
+
env = process.env,
|
|
36
|
+
noCodegraph = false,
|
|
37
|
+
noMcp = false,
|
|
38
|
+
} = opts;
|
|
39
|
+
|
|
40
|
+
// ARCHGEN_CODEGRAPH=0 skips the bootstrap (no spawn, no network, no writes)
|
|
41
|
+
// — without it the test suite would wire the developer's real MCP configs.
|
|
42
|
+
if (env.ARCHGEN_CODEGRAPH === '0' || env.ARCHGEN_CODEGRAPH === 'off') {
|
|
43
|
+
return { ok: true, skipped: 'ARCHGEN_CODEGRAPH=0 (bootstrap disabled)' };
|
|
44
|
+
}
|
|
45
|
+
if (noMcp && noCodegraph) {
|
|
46
|
+
return { ok: true, skipped: '--no-mcp and --no-codegraph passed' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const resolved = resolveMcpBinary({ projectRoot: projectDir, env });
|
|
50
|
+
log(` + mcp server: ${resolved.bin ?? 'npx -y archgen-mcp'} (${resolved.source})`);
|
|
51
|
+
|
|
52
|
+
if (noMcp) {
|
|
53
|
+
log(' ! archgen MCP wiring skipped (--no-mcp)');
|
|
54
|
+
}
|
|
55
|
+
if (noCodegraph) {
|
|
56
|
+
log(' ! CodeGraph bootstrap skipped (--no-codegraph)');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const r = bootstrapMcp({
|
|
60
|
+
projectDir,
|
|
61
|
+
location,
|
|
62
|
+
approved: true,
|
|
63
|
+
env,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!r.report) {
|
|
67
|
+
// The bootstrap did NOT run: report it as a failure so the caller tells
|
|
68
|
+
// the user. The skill install itself still succeeded — this is a warning,
|
|
69
|
+
// not a fatal error — but it must never be swallowed silently.
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
detail: `server bootstrap incomplete: ${r.detail ?? 'unknown error'}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const report = r.report;
|
|
77
|
+
const mcp = report.mcp ?? {};
|
|
78
|
+
const codegraph = report.codegraph ?? {};
|
|
79
|
+
|
|
80
|
+
for (const w of mcp.wired ?? []) {
|
|
81
|
+
log(` + harness: ${w.harness} → ${w.path} [${w.key}] ${w.status}${w.detail ? ' — ' + w.detail : ''}`);
|
|
82
|
+
}
|
|
83
|
+
if (codegraph.cli) {
|
|
84
|
+
log(
|
|
85
|
+
` + codegraph ${codegraph.cli.version ?? 'unknown'}${codegraph.cli.action !== 'none' ? ' (' + codegraph.cli.action + ')' : ''}${codegraph.cli.latest ? ' · latest available ' + codegraph.cli.latest : ''}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (codegraph.telemetry) {
|
|
89
|
+
log(` + telemetry: ${codegraph.telemetry.state ?? 'unknown'}${codegraph.telemetry.action !== 'none' ? ' → ' + codegraph.telemetry.action : ''}`);
|
|
90
|
+
}
|
|
91
|
+
for (const w of codegraph.wiring?.native ?? []) {
|
|
92
|
+
log(` + harness: ${w.targets} (${w.location}) → ${w.status}${w.detail ? ' — ' + w.detail : ''}`);
|
|
93
|
+
}
|
|
94
|
+
if (codegraph.index) {
|
|
95
|
+
log(` + index: ${codegraph.index.action ?? 'none'}${codegraph.index.files != null ? ' (' + codegraph.index.files + ' files)' : ''}`);
|
|
96
|
+
}
|
|
97
|
+
log(' + realtime: the MCP server watches files — the index stays live, no re-indexing needed');
|
|
98
|
+
|
|
99
|
+
if (r.ok === false || codegraph.ok === false) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
report,
|
|
103
|
+
detail: r.detail ? `server bootstrap incomplete: ${r.detail}` : 'one or more server bootstrap steps failed',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return { ok: true, report };
|
|
107
|
+
}
|
package/lib/config.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
//
|
|
3
3
|
// WHY: the version, managed-block marker strings, and well-known filenames
|
|
4
4
|
// were hardcoded independently in block.js, store.js, install.js, install.sh,
|
|
5
|
-
// and SKILL.md frontmatter — and drifted (SKILL.md advertised
|
|
6
|
-
// CLI shipped
|
|
5
|
+
// and SKILL.md frontmatter — and drifted (SKILL.md advertised a version the
|
|
6
|
+
// CLI had never shipped). The canonical file lives at
|
|
7
|
+
// <repo-root>/archgen.config.json;
|
|
7
8
|
// a byte-identical derived copy ships inside the published npm package at
|
|
8
9
|
// vendor/skills/archgen/archgen.config.json (kept in sync by
|
|
9
10
|
// scripts/sync-config.mjs + scripts/sync-vendor.mjs).
|