archgen-skill 0.0.5 → 0.0.6
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 +12 -6
- package/bin/archgen.mjs +22 -5
- package/lib/block.js +1 -1
- package/lib/bootstrap.js +103 -0
- package/lib/doctor.js +45 -2
- package/lib/init.js +32 -3
- package/lib/install.js +20 -1
- package/lib/mcp-server.js +159 -0
- package/lib/migrations/001-stamp-provenance.mjs +3 -3
- package/lib/spawn-npm.js +54 -0
- package/lib/store.js +75 -5
- package/lib/uninstall-project.js +16 -3
- package/lib/version.js +11 -3
- 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,
|
|
@@ -49,8 +49,13 @@ npx archgen-skill install # symlinks; --copy for real copies
|
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
Prefer shell? Clone the repo and run `./install.sh` (same behavior, plus
|
|
52
|
-
`--init`, `--project`, `--uninstall`). Requires Node.js ≥
|
|
53
|
-
|
|
52
|
+
`--init`, `--project`, `--uninstall`). Requires Node.js ≥ 20 — the official MCP
|
|
53
|
+
SDK v2 floor.
|
|
54
|
+
|
|
55
|
+
**What `init` actually does:** installs the skill (guidance only — no code),
|
|
56
|
+
wires the `archgen-mcp` MCP server into every detected harness, and ensures
|
|
57
|
+
CodeGraph (its CLI, its MCP entry, and the project index). The CLI itself is
|
|
58
|
+
zero-dependency; the MCP server it installs depends on the official MCP SDK.
|
|
54
59
|
|
|
55
60
|
## Quick start
|
|
56
61
|
|
|
@@ -125,11 +130,12 @@ are supported for all of them.
|
|
|
125
130
|
|
|
126
131
|
## VS Code extension (optional)
|
|
127
132
|
|
|
128
|
-
A
|
|
133
|
+
A visual layer that renders `.archgen/` as a live task DAG, your
|
|
129
134
|
code-dependency graph, and rendered docs — with a ▶ button that hands any task to
|
|
130
135
|
your agent. Install it from the VS Code Marketplace or from a `.vsix` on
|
|
131
136
|
[GitHub Releases](https://github.com/akashmaurya99/archgen/releases). It never
|
|
132
|
-
edits
|
|
137
|
+
edits your plan, your tasks, or your code — only its own runtime state under
|
|
138
|
+
`.archgen/.runtime/`, so uninstalling it loses nothing.
|
|
133
139
|
|
|
134
140
|
## Documentation
|
|
135
141
|
|
|
@@ -145,7 +151,7 @@ See [CONTRIBUTING.md](https://github.com/akashmaurya99/archgen/blob/main/CONTRIB
|
|
|
145
151
|
Skill suite and CLI tests run with zero dependencies:
|
|
146
152
|
|
|
147
153
|
```sh
|
|
148
|
-
node --test
|
|
154
|
+
node --test packages/mcp/test/**/*.test.mjs # MCP server suite
|
|
149
155
|
cd packages/cli && npm test # CLI suite
|
|
150
156
|
```
|
|
151
157
|
|
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 } from '../lib/spawn-npm.js';
|
|
25
26
|
import { spawnSync } from 'node:child_process';
|
|
26
27
|
import { readFileSync } from 'node:fs';
|
|
27
28
|
|
|
@@ -46,6 +47,8 @@ Usage:
|
|
|
46
47
|
and generate AGENTS.md / CLAUDE.md pointer blocks.
|
|
47
48
|
[--force] proceed automatically; divergent copies are
|
|
48
49
|
backed up to .archgen/.backup/ (never destroyed)
|
|
50
|
+
[--no-codegraph] skip the CodeGraph bootstrap
|
|
51
|
+
[--no-mcp] skip wiring the archgen MCP server
|
|
49
52
|
npx archgen-skill doctor [dir] Health-check a project install: store integrity,
|
|
50
53
|
version stamp, claude link, managed blocks,
|
|
51
54
|
manifest. Repairs safe issues in place.
|
|
@@ -53,6 +56,8 @@ Usage:
|
|
|
53
56
|
npx archgen-skill install Install into global harness skill dirs
|
|
54
57
|
[--copy] real copies instead of symlinks
|
|
55
58
|
[--uninstall] remove manifest-recorded entries
|
|
59
|
+
[--no-codegraph] skip the CodeGraph bootstrap
|
|
60
|
+
[--no-mcp] skip wiring the archgen MCP server
|
|
56
61
|
npx archgen-skill uninstall Remove globally-installed copies (manifest-based)
|
|
57
62
|
--project [dir] remove archgen from a project: claude link,
|
|
58
63
|
managed blocks, canonical store (kept when
|
|
@@ -92,7 +97,9 @@ try {
|
|
|
92
97
|
switch (cmd) {
|
|
93
98
|
case 'init': {
|
|
94
99
|
flag('--force');
|
|
95
|
-
const
|
|
100
|
+
const noCodegraph = flag('--no-codegraph');
|
|
101
|
+
const noMcp = flag('--no-mcp');
|
|
102
|
+
const r = initProject(positionalDir(), PACKAGE_ROOT, { codegraph: !noCodegraph, mcp: !noMcp });
|
|
96
103
|
console.log('archgen: project ready (single-store layout)');
|
|
97
104
|
console.log(' + store ' + r.storePath);
|
|
98
105
|
if (r.claudeLink === 'created') console.log(' + link .claude/skills/archgen -> ../../.agents/skills/archgen');
|
|
@@ -120,8 +127,10 @@ try {
|
|
|
120
127
|
}
|
|
121
128
|
case 'install': {
|
|
122
129
|
const copy = flag('--copy');
|
|
130
|
+
const noCodegraph = flag('--no-codegraph');
|
|
131
|
+
const noMcp = flag('--no-mcp');
|
|
123
132
|
if (flag('--uninstall')) { uninstall(); break; }
|
|
124
|
-
const r = installGlobal({ copy });
|
|
133
|
+
const r = installGlobal({ copy, codegraph: !noCodegraph, mcp: !noMcp });
|
|
125
134
|
console.log('archgen: global harness install (mode: ' + (copy ? 'copy' : 'symlink') + ')');
|
|
126
135
|
for (const [status, mode, path] of r.rows) console.log(` ${status.padEnd(7)} ${mode.padEnd(5)} ${path}`);
|
|
127
136
|
console.log('\nPer-project setup (recommended): npx archgen-skill init');
|
|
@@ -137,6 +146,7 @@ try {
|
|
|
137
146
|
if (r.storeRemoved) console.log(' - .agents/skills/archgen (unmodified store)');
|
|
138
147
|
if (r.storeKept) console.log(' = .agents/skills/archgen KEPT (customized)');
|
|
139
148
|
if (r.manifestRemoved) console.log(' - .archgen/.install-manifest.json');
|
|
149
|
+
for (const m of r.mcpRemoved ?? []) console.log(` - ${m} (archgen MCP entry)`);
|
|
140
150
|
for (const w of r.warnings) console.log(' note: ' + w);
|
|
141
151
|
console.log(' .archgen feature folders and backups are preserved.');
|
|
142
152
|
} else {
|
|
@@ -177,17 +187,24 @@ try {
|
|
|
177
187
|
runInitAndDoctor(dir);
|
|
178
188
|
break;
|
|
179
189
|
}
|
|
190
|
+
if (!/^\d+\.\d+\.\d+[-+]?[0-9A-Za-z.-]*$/.test(latest)) {
|
|
191
|
+
// Registry returned something that is not a semver tag — never
|
|
192
|
+
// interpolate it into a command line.
|
|
193
|
+
console.error(' ! registry returned a non-semver version string; refusing: ' + JSON.stringify(latest));
|
|
194
|
+
process.exitCode = 1;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
180
197
|
console.log(' ↑ newer version available: ' + latest);
|
|
181
198
|
console.log(' upgrading global install: npm install -g archgen-skill@' + latest);
|
|
182
|
-
const
|
|
199
|
+
const inv = npmInvocation(['install', '-g', 'archgen-skill@' + latest]);
|
|
200
|
+
const up = spawnSync(inv.command, inv.args, { stdio: 'inherit', shell: inv.shell });
|
|
183
201
|
if (up.status !== 0) {
|
|
184
202
|
console.error(' ! global upgrade failed — run it manually, then re-run: archgen-skill update');
|
|
185
203
|
process.exitCode = 1;
|
|
186
204
|
break;
|
|
187
205
|
}
|
|
188
206
|
// The running process is the OLD code; re-exec the freshly installed binary.
|
|
189
|
-
const
|
|
190
|
-
const freshBin = (bin.stdout || '').trim();
|
|
207
|
+
const freshBin = whichOnPath('archgen-skill');
|
|
191
208
|
if (!freshBin) {
|
|
192
209
|
console.log('\nUpgraded to v' + latest + '. Now finish per project with:\n npx archgen-skill@latest init');
|
|
193
210
|
break;
|
package/lib/block.js
CHANGED
|
@@ -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',
|
package/lib/bootstrap.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
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,
|
|
15
|
+
// never thrown.
|
|
16
|
+
import { bootstrapMcp, resolveMcpBinary } from './mcp-server.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {{
|
|
20
|
+
* projectDir: string,
|
|
21
|
+
* location?: 'project'|'global'|'both',
|
|
22
|
+
* log?: (line: string) => void,
|
|
23
|
+
* env?: Record<string, string|undefined>,
|
|
24
|
+
* noCodegraph?: boolean,
|
|
25
|
+
* noMcp?: boolean,
|
|
26
|
+
* }} opts
|
|
27
|
+
* @returns {{ok: boolean, skipped?: string, detail?: string, report?: any}}
|
|
28
|
+
*/
|
|
29
|
+
export function bootstrapServers(opts) {
|
|
30
|
+
const {
|
|
31
|
+
projectDir,
|
|
32
|
+
location = 'both',
|
|
33
|
+
log = () => {},
|
|
34
|
+
env = process.env,
|
|
35
|
+
noCodegraph = false,
|
|
36
|
+
noMcp = false,
|
|
37
|
+
} = opts;
|
|
38
|
+
|
|
39
|
+
// ARCHGEN_CODEGRAPH=0 skips the bootstrap (no spawn, no network, no writes)
|
|
40
|
+
// — without it the test suite would wire the developer's real MCP configs.
|
|
41
|
+
if (env.ARCHGEN_CODEGRAPH === '0' || env.ARCHGEN_CODEGRAPH === 'off') {
|
|
42
|
+
return { ok: true, skipped: 'ARCHGEN_CODEGRAPH=0 (bootstrap disabled)' };
|
|
43
|
+
}
|
|
44
|
+
if (noMcp && noCodegraph) {
|
|
45
|
+
return { ok: true, skipped: '--no-mcp and --no-codegraph passed' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const resolved = resolveMcpBinary({ projectRoot: projectDir, env });
|
|
49
|
+
log(` + mcp server: ${resolved.bin ?? 'npx -y archgen-mcp'} (${resolved.source})`);
|
|
50
|
+
|
|
51
|
+
if (noMcp) {
|
|
52
|
+
log(' ! archgen MCP wiring skipped (--no-mcp)');
|
|
53
|
+
}
|
|
54
|
+
if (noCodegraph) {
|
|
55
|
+
log(' ! CodeGraph bootstrap skipped (--no-codegraph)');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const r = bootstrapMcp({
|
|
59
|
+
projectDir,
|
|
60
|
+
location,
|
|
61
|
+
approved: true,
|
|
62
|
+
env,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!r.report) {
|
|
66
|
+
return {
|
|
67
|
+
ok: true, // never fatal — the skill install itself succeeded
|
|
68
|
+
detail: `server bootstrap incomplete: ${r.detail ?? 'unknown error'}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const report = r.report;
|
|
73
|
+
const mcp = report.mcp ?? {};
|
|
74
|
+
const codegraph = report.codegraph ?? {};
|
|
75
|
+
|
|
76
|
+
for (const w of mcp.wired ?? []) {
|
|
77
|
+
log(` + harness: ${w.harness} → ${w.path} [${w.key}] ${w.status}${w.detail ? ' — ' + w.detail : ''}`);
|
|
78
|
+
}
|
|
79
|
+
if (codegraph.cli) {
|
|
80
|
+
log(
|
|
81
|
+
` + codegraph ${codegraph.cli.version ?? 'unknown'}${codegraph.cli.action !== 'none' ? ' (' + codegraph.cli.action + ')' : ''}${codegraph.cli.latest ? ' · latest available ' + codegraph.cli.latest : ''}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (codegraph.telemetry) {
|
|
85
|
+
log(` + telemetry: ${codegraph.telemetry.state ?? 'unknown'}${codegraph.telemetry.action !== 'none' ? ' → ' + codegraph.telemetry.action : ''}`);
|
|
86
|
+
}
|
|
87
|
+
for (const w of codegraph.wiring?.native ?? []) {
|
|
88
|
+
log(` + harness: ${w.targets} (${w.location}) → ${w.status}${w.detail ? ' — ' + w.detail : ''}`);
|
|
89
|
+
}
|
|
90
|
+
if (codegraph.index) {
|
|
91
|
+
log(` + index: ${codegraph.index.action ?? 'none'}${codegraph.index.files != null ? ' (' + codegraph.index.files + ' files)' : ''}`);
|
|
92
|
+
}
|
|
93
|
+
log(' + realtime: the MCP server watches files — the index stays live, no re-indexing needed');
|
|
94
|
+
|
|
95
|
+
if (r.ok === false || codegraph.ok === false) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
report,
|
|
99
|
+
detail: 'one or more server bootstrap steps failed',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return { ok: true, report };
|
|
103
|
+
}
|
package/lib/doctor.js
CHANGED
|
@@ -22,9 +22,10 @@ import {
|
|
|
22
22
|
upsertManagedFile,
|
|
23
23
|
} from './block.js';
|
|
24
24
|
import { resolveSkillSource } from './init.js';
|
|
25
|
-
import { CLAUDE_LINK_REL, DEV_ONLY_RELPATHS, MANIFEST_REL, STORE_REL, VERSION_FILE, cliVersion, hashDir, loadManifest, pruneDevOnly, saveManifest } from './store.js';
|
|
25
|
+
import { CLAUDE_LINK_REL, DEV_ONLY_RELPATHS, MANIFEST_REL, STORE_REL, VERSION_FILE, cliVersion, findObsolete, hashDir, loadManifest, pruneDevOnly, pruneObsolete, saveManifest } from './store.js';
|
|
26
26
|
import { compareSemver, parseSemver } from './version.js';
|
|
27
27
|
import { readStamp, writeStamp } from './version-stamp.js';
|
|
28
|
+
import { mcpDoctor } from './mcp-server.js';
|
|
28
29
|
|
|
29
30
|
function lstatSafe(p) {
|
|
30
31
|
try { return lstatSync(p); } catch { return null; }
|
|
@@ -83,7 +84,21 @@ export function doctorProject(projectDir, packageRoot, opts = {}) {
|
|
|
83
84
|
if (!existsSync(join(storeAbs, 'SKILL.md'))) {
|
|
84
85
|
add('FAIL', 'store .agents/skills/archgen missing or incomplete - run `archgen init`');
|
|
85
86
|
} else {
|
|
86
|
-
|
|
87
|
+
// MIGRATION: the skill no longer ships code. An install carried over from
|
|
88
|
+
// the pre-MCP layout still has a scripts/ tree — dead weight that would
|
|
89
|
+
// otherwise read as "diverges from packaged skill (customized?)".
|
|
90
|
+
// Pruned BEFORE the divergence check so an upgrade is not misreported.
|
|
91
|
+
const packaged = (() => {
|
|
92
|
+
try { return resolveSkillSource(packageRoot); } catch { return null; }
|
|
93
|
+
})();
|
|
94
|
+
if (packaged) {
|
|
95
|
+
// --check reports WITHOUT mutating; repair mode prunes.
|
|
96
|
+
const obsolete = checkOnly ? findObsolete(storeAbs, packaged) : pruneObsolete(storeAbs, packaged);
|
|
97
|
+
for (const rel of obsolete) {
|
|
98
|
+
add(checkOnly ? 'WOULD-FIX' : 'FIXED', `store carried the obsolete ${rel}/ tree (pre-MCP layout) -> ${checkOnly ? 'would prune' : 'pruned'}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const missingCore = ['references'].filter((d) => !existsSync(join(storeAbs, d)));
|
|
87
102
|
if (missingCore.length > 0) {
|
|
88
103
|
// Deferred so the managed-block FAIL rows below stay FIRST: existing
|
|
89
104
|
// consumers read checks.find(FAIL) expecting the marker verdict.
|
|
@@ -296,6 +311,34 @@ export function doctorProject(projectDir, packageRoot, opts = {}) {
|
|
|
296
311
|
add(v ? 'OK' : 'WARN', `skill ${p}: ${slot}`);
|
|
297
312
|
}
|
|
298
313
|
|
|
314
|
+
// 8. MCP server wiring — the archgen MCP server is what actually gives the
|
|
315
|
+
// agent its tools. A skill with no MCP entry is a skill the agent cannot
|
|
316
|
+
// use, so this is a first-class check, not a footnote.
|
|
317
|
+
//
|
|
318
|
+
// READ-ONLY and NEVER FATAL: doctor must work on a machine with no npm, no
|
|
319
|
+
// network, and no MCP server installed. The MCP server is asked for its own
|
|
320
|
+
// report; if it cannot run, that is a WARN with the remediation command.
|
|
321
|
+
const mcp = mcpDoctor({ projectRoot: root, env: { ...process.env, HOME: homeDir } });
|
|
322
|
+
if (!mcp.report) {
|
|
323
|
+
add('WARN', `archgen MCP server not reachable${mcp.detail ? ` — ${mcp.detail}` : ''} — install with: npx archgen-mcp bootstrap --project . --yes`);
|
|
324
|
+
} else {
|
|
325
|
+
const rep = mcp.report;
|
|
326
|
+
add(
|
|
327
|
+
rep.skill ? 'OK' : 'WARN',
|
|
328
|
+
rep.skill
|
|
329
|
+
? `archgen MCP server v${rep.server} ready (skill ${rep.skill.version ?? 'unknown'})`
|
|
330
|
+
: 'archgen MCP server ready, but no skill was found',
|
|
331
|
+
);
|
|
332
|
+
const wired = (rep.mcpTargets ?? []).filter((t) => t.exists).length;
|
|
333
|
+
if (wired === 0) {
|
|
334
|
+
add('WARN', 'archgen MCP is not wired into any harness config — run: npx archgen-mcp bootstrap --project . --yes');
|
|
335
|
+
} else {
|
|
336
|
+
add('OK', `archgen MCP wiring present in ${wired} harness config(s)`);
|
|
337
|
+
}
|
|
338
|
+
if (rep.codegraph) add('OK', `codegraph ${rep.codegraph.version}`);
|
|
339
|
+
else add('WARN', 'codegraph not installed — run: npx archgen-mcp bootstrap --project . --yes');
|
|
340
|
+
}
|
|
341
|
+
|
|
299
342
|
const tally = { OK: 0, FIXED: 0, 'WOULD-FIX': 0, WARN: 0, FAIL: 0 };
|
|
300
343
|
for (const c of checks) tally[c.status]++;
|
|
301
344
|
return { root, checks, tally, failures: opFailures };
|
package/lib/init.js
CHANGED
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
// to a symlink; a customized one is left untouched with a warning;
|
|
18
18
|
// - installs are atomic: built in a temp sibling dir, then renamed.
|
|
19
19
|
|
|
20
|
-
import { cpSync, existsSync, lstatSync, mkdirSync,
|
|
20
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readlinkSync, renameSync, rmSync, symlinkSync, unlinkSync } from 'node:fs';
|
|
21
21
|
import { randomBytes } from 'node:crypto';
|
|
22
22
|
import { dirname, join, resolve } from 'node:path';
|
|
23
23
|
import { importsAgents, renderBlock, START, END, upsertManagedFile } from './block.js';
|
|
24
24
|
import { CLAUDE_LINK_REL, STORE_REL, VERSION_FILE, appendEntry, assertNotSymlink, assertWithinRoot, cliVersion, hashDir, moveToBackup, pruneDevOnly } from './store.js';
|
|
25
25
|
import { writeStamp } from './version-stamp.js';
|
|
26
|
+
import { bootstrapServers } from './bootstrap.js';
|
|
26
27
|
|
|
27
28
|
const CONTEXT_FILES = ['AGENTS.md', 'CLAUDE.md'];
|
|
28
29
|
|
|
@@ -99,8 +100,11 @@ function installClaudeAdapter(root, source, warnings) {
|
|
|
99
100
|
* claude adapter, and write context pointers.
|
|
100
101
|
* @param {string} projectDir target repo root
|
|
101
102
|
* @param {string} packageRoot dir containing this CLI package
|
|
102
|
-
* @param {{force?: boolean
|
|
103
|
-
* copies are always auto-backed-up
|
|
103
|
+
* @param {{force?: boolean, codegraph?: boolean, mcp?: boolean, log?: (s: string) => void, env?: NodeJS.ProcessEnv}} opts
|
|
104
|
+
* --force proceeds automatically; divergent copies are always auto-backed-up
|
|
105
|
+
* (never refused, never destroyed). opts.codegraph === false opts OUT of the
|
|
106
|
+
* automatic CodeGraph bootstrap (install + MCP wiring + index build);
|
|
107
|
+
* opts.mcp === false opts OUT of the archgen MCP wiring.
|
|
104
108
|
* @returns {{storePath: string, claudeLink: 'created'|'existing'|'migrated'|'skipped'|'kept-divergent',
|
|
105
109
|
* backups: string[], contextFiles: string[], createdContextFiles: string[], warnings: string[]}}
|
|
106
110
|
*/
|
|
@@ -110,6 +114,9 @@ export function initProject(projectDir, packageRoot, opts = {}) {
|
|
|
110
114
|
// both plain and --force runs take the same safe path.
|
|
111
115
|
const force = opts.force === true;
|
|
112
116
|
void force;
|
|
117
|
+
const codegraphAllowed = opts.codegraph !== false;
|
|
118
|
+
const mcpAllowed = opts.mcp !== false;
|
|
119
|
+
const log = opts.log ?? console.log;
|
|
113
120
|
|
|
114
121
|
const root = resolve(projectDir);
|
|
115
122
|
const source = resolveSkillSource(packageRoot);
|
|
@@ -165,5 +172,27 @@ export function initProject(projectDir, packageRoot, opts = {}) {
|
|
|
165
172
|
appendEntry(root, 'block', name);
|
|
166
173
|
}
|
|
167
174
|
|
|
175
|
+
// 4. Server bootstrap — wire the archgen MCP server into every detected
|
|
176
|
+
// harness AND ensure CodeGraph (its CLI, its MCP entry, the project
|
|
177
|
+
// index). Non-fatal by design: an offline machine still gets the skill
|
|
178
|
+
// installed.
|
|
179
|
+
if (codegraphAllowed || mcpAllowed) {
|
|
180
|
+
const r = bootstrapServers({
|
|
181
|
+
projectDir: root,
|
|
182
|
+
location: 'both',
|
|
183
|
+
log,
|
|
184
|
+
env: opts.env,
|
|
185
|
+
noCodegraph: !codegraphAllowed,
|
|
186
|
+
noMcp: !mcpAllowed,
|
|
187
|
+
});
|
|
188
|
+
if (!r.ok) {
|
|
189
|
+
warnings.push(`server bootstrap incomplete${r.detail ? ': ' + r.detail : ''} — install later with: npx archgen-mcp bootstrap --project . --yes`);
|
|
190
|
+
} else if (r.skipped) {
|
|
191
|
+
warnings.push(`server bootstrap skipped (${r.skipped})`);
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
warnings.push('server bootstrap skipped (--no-codegraph --no-mcp)');
|
|
195
|
+
}
|
|
196
|
+
|
|
168
197
|
return { storePath: storeAbs, claudeLink, backups, contextFiles, createdContextFiles, warnings };
|
|
169
198
|
}
|
package/lib/install.js
CHANGED
|
@@ -10,6 +10,7 @@ import { resolveSkillSource } from './init.js';
|
|
|
10
10
|
import { assertNotSymlink, assertWithinRoot, cliVersion, hashDir, moveToBackupInto, pruneDevOnly, refuseSymlinkMessage } from './store.js';
|
|
11
11
|
import { writeStamp } from './version-stamp.js';
|
|
12
12
|
import { loadConfig } from './config.js';
|
|
13
|
+
import { bootstrapServers } from './bootstrap.js';
|
|
13
14
|
|
|
14
15
|
// Module-anchored default: works from the npm package (cli/lib -> cli/vendor)
|
|
15
16
|
// AND from a monorepo checkout (packages/cli/lib -> ../../skill) without depending on cwd.
|
|
@@ -58,7 +59,7 @@ function nextBackupTs(tdir) {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
* @param {{copy?: boolean, home?: string, packageRoot?: string}} opts
|
|
62
|
+
* @param {{copy?: boolean, home?: string, packageRoot?: string, codegraph?: boolean, mcp?: boolean, log?: (s: string) => void, env?: NodeJS.ProcessEnv}} opts
|
|
62
63
|
* @returns {{rows: Array<[string, string, string]>, failures: number, manifest: string,
|
|
63
64
|
* backups: string[]}} rows are [STATUS, MODE, PATH]; `backups` lists
|
|
64
65
|
* absolute vault locations written by this run.
|
|
@@ -165,6 +166,24 @@ export function installGlobal(opts = {}) {
|
|
|
165
166
|
rows.push(['FAILED', '-', dest + ' (' + e.message + ')']);
|
|
166
167
|
}
|
|
167
168
|
}
|
|
169
|
+
// Server bootstrap — GLOBAL wiring only (project-local configs belong to
|
|
170
|
+
// `init`). Wires the archgen MCP server and CodeGraph into every DETECTED
|
|
171
|
+
// global harness. Non-fatal: an offline machine still gets the skill
|
|
172
|
+
// installed.
|
|
173
|
+
if (opts.codegraph !== false || opts.mcp !== false) {
|
|
174
|
+
const r = bootstrapServers({
|
|
175
|
+
projectDir: process.cwd(),
|
|
176
|
+
location: 'global',
|
|
177
|
+
log: opts.log ?? console.log,
|
|
178
|
+
env: opts.env,
|
|
179
|
+
noCodegraph: opts.codegraph === false,
|
|
180
|
+
noMcp: opts.mcp === false,
|
|
181
|
+
});
|
|
182
|
+
if (!r.ok) {
|
|
183
|
+
rows.push(['WARN', '-', 'server bootstrap incomplete — run: npx archgen-mcp bootstrap --location global --yes']);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
168
187
|
return { rows, failures, manifest, backups };
|
|
169
188
|
}
|
|
170
189
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// mcp-server.js — locate and drive the `archgen-mcp` server from the CLI.
|
|
2
|
+
//
|
|
3
|
+
// WHY THE CLI DELEGATES: archgen's deterministic capability now lives in ONE
|
|
4
|
+
// place — the MCP server. The CLI used to shell out to the skill's
|
|
5
|
+
// scripts/codegraph.mjs; it now shells out to `archgen-mcp bootstrap`, which
|
|
6
|
+
// runs the same code the agent's tools run. There is no second implementation
|
|
7
|
+
// of harness wiring, CodeGraph bootstrap, or doctor logic.
|
|
8
|
+
//
|
|
9
|
+
// NEVER FATAL: no npm, no network, a read-only config, or a missing MCP
|
|
10
|
+
// server must still leave a working skill install. Failures are REPORTED,
|
|
11
|
+
// never thrown — the same posture bootstrapCodegraph has always had.
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { spawnSync } from 'node:child_process';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { join, resolve } from 'node:path';
|
|
16
|
+
|
|
17
|
+
export const MCP_PACKAGE = 'archgen-mcp';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Locate the archgen-mcp binary.
|
|
21
|
+
*
|
|
22
|
+
* Resolution order (first hit wins):
|
|
23
|
+
* 1. $ARCHGEN_MCP_BIN — explicit override
|
|
24
|
+
* 2. <project>/node_modules/.bin/archgen-mcp
|
|
25
|
+
* 3. `archgen-mcp` on PATH — a global npm install
|
|
26
|
+
* 4. `npx -y archgen-mcp` — network fallback (npx caches it)
|
|
27
|
+
*
|
|
28
|
+
* @param {{projectRoot?: string, home?: string, env?: Record<string, string|undefined>}} opts
|
|
29
|
+
* @returns {{bin: string|null, source: 'override'|'local'|'path'|'npx', command: string, args: string[]}}
|
|
30
|
+
*/
|
|
31
|
+
export function resolveMcpBinary(opts = {}) {
|
|
32
|
+
const { projectRoot = process.cwd(), home = homedir(), env = process.env } = opts;
|
|
33
|
+
|
|
34
|
+
const override = env.ARCHGEN_MCP_BIN;
|
|
35
|
+
if (override && existsSync(override)) {
|
|
36
|
+
return { bin: override, source: 'override', command: override, args: [] };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const local = join(
|
|
40
|
+
resolve(projectRoot),
|
|
41
|
+
'node_modules',
|
|
42
|
+
'.bin',
|
|
43
|
+
process.platform === 'win32' ? 'archgen-mcp.cmd' : 'archgen-mcp',
|
|
44
|
+
);
|
|
45
|
+
if (existsSync(local)) return { bin: local, source: 'local', command: local, args: [] };
|
|
46
|
+
|
|
47
|
+
const onPath = whichOnPath('archgen-mcp', env);
|
|
48
|
+
if (onPath) return { bin: onPath, source: 'path', command: onPath, args: [] };
|
|
49
|
+
|
|
50
|
+
return { bin: null, source: 'npx', command: 'npx', args: ['-y', MCP_PACKAGE] };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Locate an executable on PATH (cross-platform, no shell).
|
|
55
|
+
* @param {string} bin
|
|
56
|
+
* @param {Record<string, string|undefined>} env
|
|
57
|
+
* @returns {string|null}
|
|
58
|
+
*/
|
|
59
|
+
export function whichOnPath(bin, env = process.env) {
|
|
60
|
+
const exts = process.platform === 'win32' ? (env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';') : [''];
|
|
61
|
+
const dirs = (env.PATH ?? '').split(process.platform === 'win32' ? ';' : ':').filter(Boolean);
|
|
62
|
+
for (const d of dirs) {
|
|
63
|
+
for (const ext of exts) {
|
|
64
|
+
const p = join(d, bin + ext);
|
|
65
|
+
try {
|
|
66
|
+
if (existsSync(p) && !p.endsWith('/')) return p;
|
|
67
|
+
} catch {
|
|
68
|
+
/* not here */
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Run one `archgen-mcp` subcommand and return its JSON output.
|
|
77
|
+
*
|
|
78
|
+
* @param {string[]} args the subcommand + flags
|
|
79
|
+
* @param {{projectRoot?: string, timeout?: number, env?: Record<string, string|undefined>}} opts
|
|
80
|
+
* @returns {{ok: boolean, code: number|null, data: any, stdout: string, stderr: string, detail?: string}}
|
|
81
|
+
*/
|
|
82
|
+
export function runMcp(args, opts = {}) {
|
|
83
|
+
const { projectRoot = process.cwd(), timeout = 600000, env = process.env } = opts;
|
|
84
|
+
const resolved = resolveMcpBinary({ projectRoot, env });
|
|
85
|
+
|
|
86
|
+
let r;
|
|
87
|
+
try {
|
|
88
|
+
r = spawnSync(resolved.command, [...resolved.args, ...args], {
|
|
89
|
+
encoding: 'utf8',
|
|
90
|
+
timeout,
|
|
91
|
+
env,
|
|
92
|
+
cwd: projectRoot,
|
|
93
|
+
shell: false,
|
|
94
|
+
// On Windows the npx shim is a .cmd file, which needs a shell.
|
|
95
|
+
...(process.platform === 'win32' && /\.(cmd|bat)$/i.test(resolved.command) ? { shell: true } : {}),
|
|
96
|
+
});
|
|
97
|
+
} catch (e) {
|
|
98
|
+
return { ok: false, code: null, data: null, stdout: '', stderr: '', detail: String(e?.message ?? e) };
|
|
99
|
+
}
|
|
100
|
+
if (r.error) {
|
|
101
|
+
return { ok: false, code: null, data: null, stdout: '', stderr: '', detail: String(r.error.message ?? r.error) };
|
|
102
|
+
}
|
|
103
|
+
const stdout = r.stdout ?? '';
|
|
104
|
+
let data = null;
|
|
105
|
+
try {
|
|
106
|
+
data = JSON.parse(stdout);
|
|
107
|
+
} catch {
|
|
108
|
+
/* not JSON — the caller renders the raw output */
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
ok: r.status === 0,
|
|
112
|
+
code: r.status ?? null,
|
|
113
|
+
data,
|
|
114
|
+
stdout,
|
|
115
|
+
stderr: r.stderr ?? '',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Wire the archgen MCP server into the detected harnesses and ensure CodeGraph.
|
|
121
|
+
*
|
|
122
|
+
* @param {{projectRoot: string, location?: 'project'|'global'|'both', approved?: boolean, env?: Record<string, string|undefined>}} opts
|
|
123
|
+
* @returns {{ok: boolean, skipped?: string, detail?: string, report?: any}}
|
|
124
|
+
*/
|
|
125
|
+
export function bootstrapMcp(opts) {
|
|
126
|
+
const { projectRoot, location = 'both', approved = true, env = process.env } = opts;
|
|
127
|
+
const args = ['bootstrap', '--project', projectRoot, '--location', location, '--json'];
|
|
128
|
+
if (approved) args.push('--yes');
|
|
129
|
+
|
|
130
|
+
const r = runMcp(args, { projectRoot, env });
|
|
131
|
+
if (r.detail) {
|
|
132
|
+
return { ok: false, detail: `cannot run ${MCP_PACKAGE}: ${r.detail}` };
|
|
133
|
+
}
|
|
134
|
+
if (r.data === null) {
|
|
135
|
+
const firstLine = (r.stderr || r.stdout || '').trim().split('\n')[0];
|
|
136
|
+
return { ok: false, detail: firstLine || `${MCP_PACKAGE} bootstrap produced no output` };
|
|
137
|
+
}
|
|
138
|
+
return { ok: r.ok, report: r.data, detail: r.ok ? undefined : r.stderr.trim().split('\n')[0] };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Health-check the archgen MCP install.
|
|
143
|
+
* @param {{projectRoot: string, env?: Record<string, string|undefined>}} opts
|
|
144
|
+
*/
|
|
145
|
+
export function mcpDoctor(opts) {
|
|
146
|
+
const { projectRoot, env = process.env } = opts;
|
|
147
|
+
const r = runMcp(['doctor', '--project', projectRoot, '--json'], { projectRoot, env, timeout: 60000 });
|
|
148
|
+
return { ok: r.ok, report: r.data, detail: r.detail ?? (r.data ? undefined : r.stderr.trim().split('\n')[0]) };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Remove the archgen MCP entries.
|
|
153
|
+
* @param {{projectRoot: string, env?: Record<string, string|undefined>}} opts
|
|
154
|
+
*/
|
|
155
|
+
export function uninstallMcp(opts) {
|
|
156
|
+
const { projectRoot, env = process.env } = opts;
|
|
157
|
+
const r = runMcp(['uninstall-mcp', '--project', projectRoot, '--yes', '--json'], { projectRoot, env, timeout: 60000 });
|
|
158
|
+
return { ok: r.ok, report: r.data, detail: r.detail };
|
|
159
|
+
}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
//
|
|
5
5
|
// What it writes: three YAML COMMENT lines at the very top of tasks.yaml and
|
|
6
6
|
// architecture.yaml. Comments are inert to every consumer by contract — the
|
|
7
|
-
//
|
|
8
|
-
// them verbatim),
|
|
9
|
-
// extension port behaves identically. Additive-only: no existing key is
|
|
7
|
+
// MCP core's parser records them positionally (`archgen_tasks{set_status}`
|
|
8
|
+
// preserves them verbatim), the verifier never sees them as data, and the VS
|
|
9
|
+
// Code extension port behaves identically. Additive-only: no existing key is
|
|
10
10
|
// touched, nothing becomes required.
|
|
11
11
|
//
|
|
12
12
|
// # schema_version: 1
|
package/lib/spawn-npm.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// spawn-npm.js — run npm on every platform without a shell.
|
|
2
|
+
// An unshelled spawnSync("npm", …) fails with EINVAL on Windows since the
|
|
3
|
+
// CVE-2024-27980 hardening, so npm's JS entry point is invoked with the
|
|
4
|
+
// current node binary instead.
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
/** npm-cli.js path: `npm_execpath` first, then node-relative layouts. */
|
|
9
|
+
export function npmCliPath() {
|
|
10
|
+
const fromEnv = process.env.npm_execpath;
|
|
11
|
+
if (typeof fromEnv === 'string' && fromEnv.endsWith('.js') && existsSync(fromEnv)) {
|
|
12
|
+
return fromEnv;
|
|
13
|
+
}
|
|
14
|
+
const dir = dirname(process.execPath);
|
|
15
|
+
const candidates =
|
|
16
|
+
process.platform === 'win32'
|
|
17
|
+
? [join(dir, 'node_modules', 'npm', 'bin', 'npm-cli.js')] // Windows: node.exe sits beside node_modules/
|
|
18
|
+
: [join(dir, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js')]; // POSIX: bin/../lib/...
|
|
19
|
+
for (const c of candidates) if (existsSync(c)) return c;
|
|
20
|
+
// Homebrew cellar layout: /opt/homebrew/{bin,lib}/...
|
|
21
|
+
if (process.platform !== 'win32') {
|
|
22
|
+
const brew = join(dir, '..', '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
23
|
+
if (existsSync(brew)) return brew;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Spawn npm with { command, args, shell }. Args MUST be registry-verified
|
|
30
|
+
* constants (the shell fallback on Windows interpolates them).
|
|
31
|
+
*/
|
|
32
|
+
export function npmInvocation(npmArgs) {
|
|
33
|
+
const cli = npmCliPath();
|
|
34
|
+
if (cli) return { command: process.execPath, args: [cli, ...npmArgs], shell: false };
|
|
35
|
+
const isWin = process.platform === 'win32';
|
|
36
|
+
return { command: isWin ? 'npm.cmd' : 'npm', args: npmArgs, shell: isWin };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Cross-platform `which` (PATHEXT on Windows). */
|
|
40
|
+
export function whichOnPath(bin) {
|
|
41
|
+
const exts = process.platform === 'win32'
|
|
42
|
+
? (process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';')
|
|
43
|
+
: [''];
|
|
44
|
+
const dirs = (process.env.PATH ?? '').split(process.platform === 'win32' ? ';' : ':').filter(Boolean);
|
|
45
|
+
for (const d of dirs) {
|
|
46
|
+
for (const ext of exts) {
|
|
47
|
+
const p = join(d, bin + ext);
|
|
48
|
+
try {
|
|
49
|
+
if (existsSync(p)) return p;
|
|
50
|
+
} catch { /* next */ }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|