archgen-skill 0.0.7 → 0.1.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 +1 -10
- package/bin/archgen.mjs +13 -1
- package/lib/bootstrap.js +25 -3
- package/lib/doctor.js +3 -3
- package/lib/global-server.js +154 -0
- package/lib/init.js +8 -1
- package/lib/mcp-server.js +66 -26
- package/lib/migrations/001-stamp-provenance.mjs +3 -3
- package/lib/spawn-npm.js +14 -4
- package/lib/uninstall-project.js +25 -5
- package/lib/version-stamp.js +2 -2
- package/package.json +3 -3
- package/vendor/skills/archgen/SKILL.md +156 -375
- package/vendor/skills/archgen/archgen.config.json +1 -1
- package/vendor/skills/archgen/references/code-standards.md +28 -44
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ by git.
|
|
|
87
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 |
|
|
88
88
|
| `npx archgen-skill install [--copy]` | Install into global harness skill dirs (symlinks by default, real copies with `--copy`) |
|
|
89
89
|
| `npx archgen-skill uninstall` | Remove globally-installed copies (manifest-recorded, safe removal) |
|
|
90
|
-
| `npx archgen-skill uninstall [dir]` | Remove a **project** install (same as `--project [dir]`): strips managed blocks, symlink,
|
|
90
|
+
| `npx archgen-skill uninstall [dir]` | Remove a **project** install (same as `--project [dir]`): strips managed blocks, symlink, the store **only if unmodified**, and the dead `.archgen/.runtime/` extension state — `.archgen/` feature folders and all user content are preserved |
|
|
91
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) |
|
|
92
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 |
|
|
93
93
|
| `npx archgen-skill restore` | List backup snapshots; `--snapshot <ts>` restores one (current state is backed up first) |
|
|
@@ -135,15 +135,6 @@ Claude Code, OpenCode, Cursor, Codex, Gemini CLI, Antigravity — and any
|
|
|
135
135
|
agentskills.io-compatible harness. Both global install and project-local `init`
|
|
136
136
|
are supported for all of them.
|
|
137
137
|
|
|
138
|
-
## VS Code extension (optional)
|
|
139
|
-
|
|
140
|
-
A visual layer that renders `.archgen/` as a live task DAG, your
|
|
141
|
-
code-dependency graph, and rendered docs — with a ▶ button that hands any task to
|
|
142
|
-
your agent. Install it from the VS Code Marketplace or from a `.vsix` on
|
|
143
|
-
[GitHub Releases](https://github.com/akashmaurya99/archgen/releases). It never
|
|
144
|
-
edits your plan, your tasks, or your code — only its own runtime state under
|
|
145
|
-
`.archgen/.runtime/`, so uninstalling it loses nothing.
|
|
146
|
-
|
|
147
138
|
## Documentation
|
|
148
139
|
|
|
149
140
|
| Doc | Contents |
|
package/bin/archgen.mjs
CHANGED
|
@@ -57,6 +57,9 @@ Usage:
|
|
|
57
57
|
backed up to .archgen/.backup/ (never destroyed)
|
|
58
58
|
[--no-codegraph] skip the CodeGraph bootstrap
|
|
59
59
|
[--no-mcp] skip wiring the archgen MCP server
|
|
60
|
+
[--location-project] wire the PROJECT-local harness configs too
|
|
61
|
+
(by default only the global ones are wired,
|
|
62
|
+
so a project carries no MCP configuration)
|
|
60
63
|
npx archgen-skill doctor [dir] Health-check a project install: store integrity,
|
|
61
64
|
version stamp, claude link, managed blocks,
|
|
62
65
|
manifest. Repairs safe issues in place.
|
|
@@ -166,9 +169,17 @@ try {
|
|
|
166
169
|
flag('--force');
|
|
167
170
|
const noCodegraph = flag('--no-codegraph');
|
|
168
171
|
const noMcp = flag('--no-mcp');
|
|
172
|
+
// The harness configs live in the user's home by default, so a project
|
|
173
|
+
// carries no MCP configuration of its own. `--location project` opts
|
|
174
|
+
// into wiring the project-local configs instead.
|
|
175
|
+
const location = flag('--location-project') ? 'project' : 'global';
|
|
169
176
|
const dir = targetDir();
|
|
170
177
|
assertNoUnknownFlags();
|
|
171
|
-
const r = initProject(dir, PACKAGE_ROOT, {
|
|
178
|
+
const r = initProject(dir, PACKAGE_ROOT, {
|
|
179
|
+
codegraph: !noCodegraph,
|
|
180
|
+
mcp: !noMcp,
|
|
181
|
+
location,
|
|
182
|
+
});
|
|
172
183
|
console.log('archgen: project ready (single-store layout)');
|
|
173
184
|
console.log(' + store ' + r.storePath);
|
|
174
185
|
if (r.claudeLink === 'created') console.log(' + link .claude/skills/archgen -> ../../.agents/skills/archgen');
|
|
@@ -238,6 +249,7 @@ try {
|
|
|
238
249
|
if (r.storeRemoved) console.log(' - .agents/skills/archgen (unmodified store)');
|
|
239
250
|
if (r.storeKept) console.log(' = .agents/skills/archgen KEPT (customized)');
|
|
240
251
|
if (r.manifestRemoved) console.log(' - .archgen/.install-manifest.json');
|
|
252
|
+
if (r.runtimeRemoved) console.log(' - .archgen/.runtime (legacy extension state)');
|
|
241
253
|
for (const m of r.mcpRemoved ?? []) console.log(` - ${m} (archgen MCP entry)`);
|
|
242
254
|
for (const w of r.warnings) console.log(' note: ' + w);
|
|
243
255
|
console.log(' .archgen feature folders and backups are preserved.');
|
package/lib/bootstrap.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
// The CLI used to shell out to the skill's scripts/codegraph.mjs. That script
|
|
5
5
|
// no longer exists: ALL deterministic capability now lives in the archgen MCP
|
|
6
|
-
// server, so the CLI drives THAT instead. One implementation,
|
|
7
|
-
// (agent -> MCP stdio, CLI -> `archgen-mcp bootstrap
|
|
6
|
+
// server, so the CLI drives THAT instead. One implementation, two ways in
|
|
7
|
+
// (agent -> MCP stdio, CLI -> `archgen-mcp bootstrap`).
|
|
8
8
|
//
|
|
9
9
|
// The bootstrap does two things in one pass:
|
|
10
10
|
// 1. wires the archgen MCP entry into every detected harness
|
|
@@ -15,26 +15,31 @@
|
|
|
15
15
|
// the caller (ok:false + detail) and rendered as a warning by it — never
|
|
16
16
|
// thrown, and never swallowed.
|
|
17
17
|
import { bootstrapMcp, resolveMcpBinary } from './mcp-server.js';
|
|
18
|
+
import { ensureGlobalServer, serverVersion } from './global-server.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* @param {{
|
|
21
22
|
* projectDir: string,
|
|
23
|
+
* packageRoot?: string,
|
|
22
24
|
* location?: 'project'|'global'|'both',
|
|
23
25
|
* log?: (line: string) => void,
|
|
24
26
|
* env?: Record<string, string|undefined>,
|
|
25
27
|
* noCodegraph?: boolean,
|
|
26
28
|
* noMcp?: boolean,
|
|
29
|
+
* global?: boolean,
|
|
27
30
|
* }} opts
|
|
28
31
|
* @returns {{ok: boolean, skipped?: string, detail?: string, report?: any}}
|
|
29
32
|
*/
|
|
30
33
|
export function bootstrapServers(opts) {
|
|
31
34
|
const {
|
|
32
35
|
projectDir,
|
|
36
|
+
packageRoot = null,
|
|
33
37
|
location = 'both',
|
|
34
38
|
log = () => {},
|
|
35
39
|
env = process.env,
|
|
36
40
|
noCodegraph = false,
|
|
37
41
|
noMcp = false,
|
|
42
|
+
global = true,
|
|
38
43
|
} = opts;
|
|
39
44
|
|
|
40
45
|
// ARCHGEN_CODEGRAPH=0 skips the bootstrap (no spawn, no network, no writes)
|
|
@@ -46,8 +51,25 @@ export function bootstrapServers(opts) {
|
|
|
46
51
|
return { ok: true, skipped: '--no-mcp and --no-codegraph passed' };
|
|
47
52
|
}
|
|
48
53
|
|
|
54
|
+
// ONE server for every harness. A global install gives every harness the
|
|
55
|
+
// same binary at a stable path, so a harness config can name an absolute
|
|
56
|
+
// executable that keeps working — instead of pointing into npm's npx
|
|
57
|
+
// CACHE, which `npm cache clean` removes out from under it.
|
|
58
|
+
//
|
|
59
|
+
// The server is refreshed to the version THIS CLI ships with: the CLI and
|
|
60
|
+
// the server are released together, so an older server is an older
|
|
61
|
+
// implementation of the very tools the CLI wires.
|
|
62
|
+
let serverNote = '';
|
|
63
|
+
if (global && !noMcp) {
|
|
64
|
+
const version = packageRoot ? serverVersion(packageRoot) : 'latest';
|
|
65
|
+
const installed = ensureGlobalServer({ env, version });
|
|
66
|
+
serverNote = installed.note;
|
|
67
|
+
if (installed.detail) serverNote += ` (${installed.detail})`;
|
|
68
|
+
}
|
|
69
|
+
|
|
49
70
|
const resolved = resolveMcpBinary({ projectRoot: projectDir, env });
|
|
50
71
|
log(` + mcp server: ${resolved.bin ?? 'npx -y archgen-mcp'} (${resolved.source})`);
|
|
72
|
+
if (serverNote) log(` + mcp server: ${serverNote}`);
|
|
51
73
|
|
|
52
74
|
if (noMcp) {
|
|
53
75
|
log(' ! archgen MCP wiring skipped (--no-mcp)');
|
|
@@ -57,7 +79,7 @@ export function bootstrapServers(opts) {
|
|
|
57
79
|
}
|
|
58
80
|
|
|
59
81
|
const r = bootstrapMcp({
|
|
60
|
-
projectDir,
|
|
82
|
+
projectRoot: projectDir,
|
|
61
83
|
location,
|
|
62
84
|
approved: true,
|
|
63
85
|
env,
|
package/lib/doctor.js
CHANGED
|
@@ -363,9 +363,9 @@ export function doctorProject(projectDir, packageRoot, opts = {}) {
|
|
|
363
363
|
|
|
364
364
|
// 7. Version-stamp inventory — every archgen skill tree visible from this
|
|
365
365
|
// project/machine, one line each, for tools that probe
|
|
366
|
-
// <skillRoot>/.archgen-version (the
|
|
367
|
-
//
|
|
368
|
-
// section 2, never global installs.
|
|
366
|
+
// <skillRoot>/.archgen-version (the MCP server reads it to report the
|
|
367
|
+
// installed skill's version). Report-only: doctor repairs the project
|
|
368
|
+
// stamp in section 2, never global installs.
|
|
369
369
|
const seen = new Set();
|
|
370
370
|
const installs = [];
|
|
371
371
|
const noteInstall = (p) => {
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// global-server.js — ONE archgen MCP server for every harness, kept current.
|
|
2
|
+
//
|
|
3
|
+
// WHY A GLOBAL INSTALL.
|
|
4
|
+
//
|
|
5
|
+
// The harness configs name the server's executable by ABSOLUTE PATH, so the
|
|
6
|
+
// path has to keep working. The npx fallback points into npm's npx CACHE
|
|
7
|
+
// (~/.npm/_npx/<hash>/…), which `npm cache clean --force` removes — and the
|
|
8
|
+
// harness then fails to start a server that was wired minutes earlier.
|
|
9
|
+
//
|
|
10
|
+
// A global install (`npm install -g archgen-mcp`) puts the binary in the
|
|
11
|
+
// npm prefix's bin directory: one copy, shared by every harness, at a path
|
|
12
|
+
// that does not move when the registry publishes a new version.
|
|
13
|
+
//
|
|
14
|
+
// THE UPGRADE.
|
|
15
|
+
//
|
|
16
|
+
// The CLI and the server are released TOGETHER, so the CLI knows exactly
|
|
17
|
+
// which server version it was tested against. An installed server that is
|
|
18
|
+
// OLDER is refreshed to that version; one that is NEWER (a dev build, or a
|
|
19
|
+
// version published after this CLI) is left alone — downgrading it would
|
|
20
|
+
// break a working setup.
|
|
21
|
+
//
|
|
22
|
+
// The install is BEST EFFORT. A machine without a writable npm prefix, an
|
|
23
|
+
// offline machine, or a user who does not want a global package must still
|
|
24
|
+
// get a working skill install: the npx fallback covers all of them, and the
|
|
25
|
+
// OLD server keeps working until it is replaced.
|
|
26
|
+
import { spawnSync } from 'node:child_process';
|
|
27
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
28
|
+
import { dirname, join } from 'node:path';
|
|
29
|
+
import { npmInvocation, whichOnPath } from './spawn-npm.js';
|
|
30
|
+
import { compareSemver } from './version.js';
|
|
31
|
+
|
|
32
|
+
export const MCP_PACKAGE = 'archgen-mcp';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The version of the server this CLI ships with.
|
|
36
|
+
*
|
|
37
|
+
* The CLI and the server are released together, so the installer can ask for
|
|
38
|
+
* exactly the version it was tested against instead of "whatever is latest".
|
|
39
|
+
*
|
|
40
|
+
* @param {string} packageRoot the CLI's own package root
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
export function serverVersion(packageRoot) {
|
|
44
|
+
try {
|
|
45
|
+
const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
|
|
46
|
+
return typeof pkg.version === 'string' ? pkg.version : 'latest';
|
|
47
|
+
} catch {
|
|
48
|
+
return 'latest';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The version of an INSTALLED server, read from its own package.
|
|
54
|
+
*
|
|
55
|
+
* The binary is a symlink into the global package's bin directory, so the
|
|
56
|
+
* package root is one level above the script's directory.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} bin the resolved executable
|
|
59
|
+
* @returns {string|null} the version, or null when it cannot be read
|
|
60
|
+
*/
|
|
61
|
+
export function installedServerVersion(bin) {
|
|
62
|
+
try {
|
|
63
|
+
const real = realpathSync(bin);
|
|
64
|
+
const pkg = JSON.parse(readFileSync(join(dirname(real), '..', 'package.json'), 'utf8'));
|
|
65
|
+
return typeof pkg.version === 'string' ? pkg.version : null;
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Make sure `archgen-mcp` is installed globally, at the right version.
|
|
73
|
+
*
|
|
74
|
+
* @param {{env?: Record<string, string|undefined>, version?: string}} opts
|
|
75
|
+
* @returns {{installed: boolean, bin: string|null, upgraded: boolean,
|
|
76
|
+
* note: string, detail?: string}}
|
|
77
|
+
*/
|
|
78
|
+
export function ensureGlobalServer(opts = {}) {
|
|
79
|
+
const { env = process.env, version = 'latest' } = opts;
|
|
80
|
+
|
|
81
|
+
const onPath = whichOnPath(MCP_PACKAGE, env);
|
|
82
|
+
if (onPath) {
|
|
83
|
+
const installed = installedServerVersion(onPath);
|
|
84
|
+
if (installed === version) {
|
|
85
|
+
return {
|
|
86
|
+
installed: true,
|
|
87
|
+
bin: onPath,
|
|
88
|
+
upgraded: false,
|
|
89
|
+
note: `already installed at ${onPath} (v${installed})`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (installed && version !== 'latest' && compareSemver(installed, version) > 0) {
|
|
93
|
+
// A newer server is already there — a dev build, or a release published
|
|
94
|
+
// after this CLI. Downgrading it would break a working setup.
|
|
95
|
+
return {
|
|
96
|
+
installed: true,
|
|
97
|
+
bin: onPath,
|
|
98
|
+
upgraded: false,
|
|
99
|
+
note: `installed v${installed} is newer than this CLI's v${version} — left as it is`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const refreshed = installGlobal(version, env);
|
|
103
|
+
if (refreshed.installed) {
|
|
104
|
+
return {
|
|
105
|
+
installed: true,
|
|
106
|
+
bin: refreshed.bin,
|
|
107
|
+
upgraded: true,
|
|
108
|
+
note: `upgraded the archgen MCP server to v${version} at ${refreshed.bin}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// The upgrade failed — the OLD server still works, so keep it and say so.
|
|
112
|
+
return {
|
|
113
|
+
installed: true,
|
|
114
|
+
bin: onPath,
|
|
115
|
+
upgraded: false,
|
|
116
|
+
note: `could not upgrade the archgen MCP server to v${version} — the installed v${installed} stays`,
|
|
117
|
+
detail: refreshed.detail,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const fresh = installGlobal(version, env);
|
|
122
|
+
return {
|
|
123
|
+
installed: fresh.installed,
|
|
124
|
+
bin: fresh.bin,
|
|
125
|
+
upgraded: false,
|
|
126
|
+
note: fresh.installed
|
|
127
|
+
? `installed the archgen MCP server globally at ${fresh.bin}`
|
|
128
|
+
: `could not install the archgen MCP server globally — the npx fallback will be used`,
|
|
129
|
+
detail: fresh.detail,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** `npm install -g archgen-mcp@<version>`. */
|
|
134
|
+
function installGlobal(version, env) {
|
|
135
|
+
const spec = version === 'latest' ? MCP_PACKAGE : `${MCP_PACKAGE}@${version}`;
|
|
136
|
+
const inv = npmInvocation(['install', '-g', spec], env);
|
|
137
|
+
const r = spawnSync(inv.command, inv.args, {
|
|
138
|
+
encoding: 'utf8',
|
|
139
|
+
timeout: 300000,
|
|
140
|
+
env,
|
|
141
|
+
shell: inv.shell,
|
|
142
|
+
});
|
|
143
|
+
if (r.status !== 0) {
|
|
144
|
+
const detail = (r.stderr || r.stdout || '').trim().split('\n').pop();
|
|
145
|
+
return { installed: false, bin: null, detail: detail || `npm exited ${r.status}` };
|
|
146
|
+
}
|
|
147
|
+
// npm succeeded — but the install only COUNTS if the binary is now
|
|
148
|
+
// findable. A prefix that is not on PATH, or a package that shipped no
|
|
149
|
+
// bin, would otherwise be reported as a success.
|
|
150
|
+
const bin = whichOnPath(MCP_PACKAGE, env);
|
|
151
|
+
return bin
|
|
152
|
+
? { installed: true, bin, detail: undefined }
|
|
153
|
+
: { installed: false, bin: null, detail: 'npm reported success but the binary is not on PATH' };
|
|
154
|
+
}
|
package/lib/init.js
CHANGED
|
@@ -205,10 +205,17 @@ export function initProject(projectDir, packageRoot, opts = {}) {
|
|
|
205
205
|
// harness AND ensure CodeGraph (its CLI, its MCP entry, the project
|
|
206
206
|
// index). Non-fatal by design: an offline machine still gets the skill
|
|
207
207
|
// installed.
|
|
208
|
+
//
|
|
209
|
+
// The wiring is GLOBAL by default: the server is one binary for every
|
|
210
|
+
// harness, and the harness configs live in the user's home. A project
|
|
211
|
+
// that carries its own `.mcp.json`, `.codex/` and `.gemini/` is a
|
|
212
|
+
// project that has to be re-wired in every clone. `--location project`
|
|
213
|
+
// opts into the per-project wiring.
|
|
208
214
|
if (codegraphAllowed || mcpAllowed) {
|
|
209
215
|
const r = bootstrapServers({
|
|
210
216
|
projectDir: root,
|
|
211
|
-
|
|
217
|
+
packageRoot: packageRoot,
|
|
218
|
+
location: opts.location ?? 'global',
|
|
212
219
|
log,
|
|
213
220
|
env: opts.env,
|
|
214
221
|
noCodegraph: !codegraphAllowed,
|
package/lib/mcp-server.js
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
// never thrown — the same posture bootstrapCodegraph has always had.
|
|
12
12
|
import { existsSync, readFileSync } from 'node:fs';
|
|
13
13
|
import { spawnSync } from 'node:child_process';
|
|
14
|
+
import { whichOnPath } from './spawn-npm.js';
|
|
14
15
|
import { homedir } from 'node:os';
|
|
15
|
-
import { basename, dirname, join, resolve } from 'node:path';
|
|
16
|
+
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
16
17
|
|
|
17
18
|
export const MCP_PACKAGE = 'archgen-mcp';
|
|
18
19
|
|
|
@@ -52,25 +53,16 @@ export function resolveMcpBinary(opts = {}) {
|
|
|
52
53
|
|
|
53
54
|
/**
|
|
54
55
|
* Locate an executable on PATH (cross-platform, no shell).
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
56
|
+
*
|
|
57
|
+
* ONE implementation, in spawn-npm.js — this module used to carry its own
|
|
58
|
+
* copy, and the two had drifted: the copy here skipped a path ending in '/',
|
|
59
|
+
* the one there did not, and neither checked that the path was a FILE. Two
|
|
60
|
+
* implementations of one rule is how the rules start disagreeing.
|
|
61
|
+
*
|
|
62
|
+
* Imported for this module's own use AND re-exported, so the callers that
|
|
63
|
+
* already import it from here keep working.
|
|
58
64
|
*/
|
|
59
|
-
export
|
|
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
|
-
}
|
|
65
|
+
export { whichOnPath };
|
|
74
66
|
|
|
75
67
|
/**
|
|
76
68
|
* Quote one argument for cmd.exe.
|
|
@@ -155,6 +147,33 @@ export function mcpInvocation(command, args, platform = process.platform, env =
|
|
|
155
147
|
return { command: quoteWindowsArg(cmd), args: args.map(quoteWindowsArg), shell: true };
|
|
156
148
|
}
|
|
157
149
|
|
|
150
|
+
/**
|
|
151
|
+
* The project root an MCP call is made against.
|
|
152
|
+
*
|
|
153
|
+
* It MUST be an ABSOLUTE path.
|
|
154
|
+
*
|
|
155
|
+
* The child process resolves a relative root against ITS OWN working
|
|
156
|
+
* directory, so a root that was missing — or a property-name mismatch
|
|
157
|
+
* between the caller and the callee — did not fail. It silently wrote the
|
|
158
|
+
* harness configs, the CodeGraph index and the managed blocks into a
|
|
159
|
+
* directory named "undefined", next to the real project.
|
|
160
|
+
*
|
|
161
|
+
* The guard turns that class of mistake into an immediate, named failure.
|
|
162
|
+
*
|
|
163
|
+
* @param {unknown} projectRoot
|
|
164
|
+
* @param {string} fn the caller's name, for the message
|
|
165
|
+
* @returns {string} the absolute project root
|
|
166
|
+
*/
|
|
167
|
+
export function requireProjectRoot(projectRoot, fn) {
|
|
168
|
+
if (typeof projectRoot !== 'string' || projectRoot.trim() === '') {
|
|
169
|
+
throw new Error(`${fn}: projectRoot is required and must be an absolute path`);
|
|
170
|
+
}
|
|
171
|
+
if (!isAbsolute(projectRoot)) {
|
|
172
|
+
throw new Error(`${fn}: projectRoot must be an absolute path (got '${projectRoot}')`);
|
|
173
|
+
}
|
|
174
|
+
return resolve(projectRoot);
|
|
175
|
+
}
|
|
176
|
+
|
|
158
177
|
/**
|
|
159
178
|
* Run one `archgen-mcp` subcommand and return its JSON output.
|
|
160
179
|
*
|
|
@@ -163,7 +182,8 @@ export function mcpInvocation(command, args, platform = process.platform, env =
|
|
|
163
182
|
* @returns {{ok: boolean, code: number|null, data: any, stdout: string, stderr: string, detail?: string}}
|
|
164
183
|
*/
|
|
165
184
|
export function runMcp(args, opts = {}) {
|
|
166
|
-
const {
|
|
185
|
+
const { timeout = 600000, env = process.env } = opts;
|
|
186
|
+
const projectRoot = requireProjectRoot(opts.projectRoot ?? process.cwd(), 'runMcp');
|
|
167
187
|
const resolved = resolveMcpBinary({ projectRoot, env });
|
|
168
188
|
const inv = mcpInvocation(resolved.command, [...resolved.args, ...args], process.platform, env);
|
|
169
189
|
|
|
@@ -198,6 +218,25 @@ export function runMcp(args, opts = {}) {
|
|
|
198
218
|
};
|
|
199
219
|
}
|
|
200
220
|
|
|
221
|
+
/**
|
|
222
|
+
* The arguments for the server's bootstrap call.
|
|
223
|
+
*
|
|
224
|
+
* Exported so the project root in them can be asserted directly: the bug
|
|
225
|
+
* this guards against was a property-name mismatch that put the literal
|
|
226
|
+
* string "undefined" in this exact position.
|
|
227
|
+
*
|
|
228
|
+
* @param {string} projectRoot
|
|
229
|
+
* @param {{location?: 'project'|'global'|'both', approved?: boolean}} [opts]
|
|
230
|
+
* @returns {string[]}
|
|
231
|
+
*/
|
|
232
|
+
export function bootstrapArgs(projectRoot, opts = {}) {
|
|
233
|
+
const { location = 'both', approved = true } = opts;
|
|
234
|
+
const root = requireProjectRoot(projectRoot, 'bootstrapArgs');
|
|
235
|
+
const args = ['bootstrap', '--project', root, '--location', location, '--json'];
|
|
236
|
+
if (approved) args.push('--yes');
|
|
237
|
+
return args;
|
|
238
|
+
}
|
|
239
|
+
|
|
201
240
|
/**
|
|
202
241
|
* Wire the archgen MCP server into the detected harnesses and ensure CodeGraph.
|
|
203
242
|
*
|
|
@@ -205,11 +244,10 @@ export function runMcp(args, opts = {}) {
|
|
|
205
244
|
* @returns {{ok: boolean, skipped?: string, detail?: string, report?: any}}
|
|
206
245
|
*/
|
|
207
246
|
export function bootstrapMcp(opts) {
|
|
208
|
-
const {
|
|
209
|
-
const args =
|
|
210
|
-
if (approved) args.push('--yes');
|
|
247
|
+
const { location = 'both', approved = true, env = process.env } = opts;
|
|
248
|
+
const args = bootstrapArgs(opts.projectRoot, { location, approved });
|
|
211
249
|
|
|
212
|
-
const r = runMcp(args, { projectRoot, env });
|
|
250
|
+
const r = runMcp(args, { projectRoot: opts.projectRoot, env });
|
|
213
251
|
if (r.detail) {
|
|
214
252
|
return { ok: false, detail: `cannot run ${MCP_PACKAGE}: ${r.detail}` };
|
|
215
253
|
}
|
|
@@ -225,7 +263,8 @@ export function bootstrapMcp(opts) {
|
|
|
225
263
|
* @param {{projectRoot: string, env?: Record<string, string|undefined>}} opts
|
|
226
264
|
*/
|
|
227
265
|
export function mcpDoctor(opts) {
|
|
228
|
-
const {
|
|
266
|
+
const { env = process.env } = opts;
|
|
267
|
+
const projectRoot = requireProjectRoot(opts.projectRoot, 'mcpDoctor');
|
|
229
268
|
const r = runMcp(['doctor', '--project', projectRoot, '--json'], { projectRoot, env, timeout: 60000 });
|
|
230
269
|
return { ok: r.ok, report: r.data, detail: r.detail ?? (r.data ? undefined : r.stderr.trim().split('\n')[0]) };
|
|
231
270
|
}
|
|
@@ -235,7 +274,8 @@ export function mcpDoctor(opts) {
|
|
|
235
274
|
* @param {{projectRoot: string, env?: Record<string, string|undefined>}} opts
|
|
236
275
|
*/
|
|
237
276
|
export function uninstallMcp(opts) {
|
|
238
|
-
const {
|
|
277
|
+
const { env = process.env } = opts;
|
|
278
|
+
const projectRoot = requireProjectRoot(opts.projectRoot, 'uninstallMcp');
|
|
239
279
|
const r = runMcp(['uninstall-mcp', '--project', projectRoot, '--yes', '--json'], { projectRoot, env, timeout: 60000 });
|
|
240
280
|
return { ok: r.ok, report: r.data, detail: r.detail };
|
|
241
281
|
}
|
|
@@ -5,9 +5,9 @@
|
|
|
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
7
|
// MCP core's parser records them positionally (`archgen_tasks{set_status}`
|
|
8
|
-
// preserves them verbatim), the verifier never sees them as data, and
|
|
9
|
-
//
|
|
10
|
-
// touched, nothing becomes required.
|
|
8
|
+
// preserves them verbatim), the verifier never sees them as data, and every
|
|
9
|
+
// other YAML consumer ignores them by the same contract. Additive-only: no
|
|
10
|
+
// existing key is touched, nothing becomes required.
|
|
11
11
|
//
|
|
12
12
|
// # schema_version: 1
|
|
13
13
|
// # generator: archgen v1.2.3 (version token omitted when unknowable)
|
package/lib/spawn-npm.js
CHANGED
|
@@ -37,12 +37,22 @@ export function npmInvocation(npmArgs, env = process.env) {
|
|
|
37
37
|
return { command: isWin ? 'npm.cmd' : 'npm', args: npmArgs, shell: isWin };
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/**
|
|
41
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Cross-platform `which` (PATHEXT on Windows).
|
|
42
|
+
*
|
|
43
|
+
* The env is a PARAMETER, not an assumption: a caller may be searching a
|
|
44
|
+
* modified PATH (a test, a sandboxed HOME), and reading process.env behind
|
|
45
|
+
* its back would answer a different question than the one asked.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} bin
|
|
48
|
+
* @param {Record<string, string|undefined>} [env]
|
|
49
|
+
* @returns {string|null}
|
|
50
|
+
*/
|
|
51
|
+
export function whichOnPath(bin, env = process.env) {
|
|
42
52
|
const exts = process.platform === 'win32'
|
|
43
|
-
? (
|
|
53
|
+
? (env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';')
|
|
44
54
|
: [''];
|
|
45
|
-
const dirs = (
|
|
55
|
+
const dirs = (env.PATH ?? '').split(process.platform === 'win32' ? ';' : ':').filter(Boolean);
|
|
46
56
|
for (const d of dirs) {
|
|
47
57
|
for (const ext of exts) {
|
|
48
58
|
const p = join(d, bin + ext);
|
package/lib/uninstall-project.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Removes exactly what archgen owns in a project: the claude symlink, the
|
|
4
4
|
// managed blocks, the canonical store (ONLY when unmodified vs the vendor
|
|
5
|
-
// skill — a customized store is kept with a warning),
|
|
6
|
-
// manifest. `.archgen/<slug>`
|
|
7
|
-
// always preserved. Parents are
|
|
8
|
-
// never past the guarded
|
|
5
|
+
// skill — a customized store is kept with a warning), the install
|
|
6
|
+
// manifest, and the legacy extension runtime directory. `.archgen/<slug>`
|
|
7
|
+
// feature folders and `.archgen/.backup/` are always preserved. Parents are
|
|
8
|
+
// pruned only when left completely empty, and never past the guarded
|
|
9
|
+
// .claude/.agents levels.
|
|
9
10
|
|
|
10
11
|
import { existsSync, lstatSync, readlinkSync, rmSync, unlinkSync } from 'node:fs';
|
|
11
12
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -36,6 +37,7 @@ function relJoin(rel) {
|
|
|
36
37
|
* @param {string} packageRoot dir containing this CLI package (vendor source)
|
|
37
38
|
* @returns {{root: string, linkRemoved: boolean, strippedBlocks: string[],
|
|
38
39
|
* storeRemoved: boolean, storeKept: boolean, manifestRemoved: boolean,
|
|
40
|
+
* runtimeRemoved: boolean,
|
|
39
41
|
* mcpRemoved: string[], warnings: string[]}}
|
|
40
42
|
*/
|
|
41
43
|
export function uninstallProject(projectDir, packageRoot) {
|
|
@@ -122,5 +124,23 @@ export function uninstallProject(projectDir, packageRoot) {
|
|
|
122
124
|
const manifestRemoved = rmIfExists(join(root, relJoin(MANIFEST_REL)));
|
|
123
125
|
if (manifestRemoved) pruneEmptyParents(root, dirname(MANIFEST_REL));
|
|
124
126
|
|
|
125
|
-
|
|
127
|
+
// 6. The legacy extension runtime directory. The VS Code extension wrote a
|
|
128
|
+
// heartbeat and per-command acknowledgements under `.archgen/.runtime/`;
|
|
129
|
+
// it is dead state now that the extension and its bridge are gone, and it
|
|
130
|
+
// is archgen-owned, so the uninstall removes it. Feature folders and
|
|
131
|
+
// backups beside it are untouched.
|
|
132
|
+
const runtimeRemoved = rmIfExists(join(root, '.archgen', '.runtime'));
|
|
133
|
+
if (runtimeRemoved) pruneEmptyParents(root, '.archgen/.runtime');
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
root,
|
|
137
|
+
linkRemoved,
|
|
138
|
+
strippedBlocks,
|
|
139
|
+
storeRemoved,
|
|
140
|
+
storeKept,
|
|
141
|
+
manifestRemoved,
|
|
142
|
+
runtimeRemoved,
|
|
143
|
+
mcpRemoved,
|
|
144
|
+
warnings,
|
|
145
|
+
};
|
|
126
146
|
}
|
package/lib/version-stamp.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// version-stamp.js — the `.archgen-version` stamp contract.
|
|
2
2
|
//
|
|
3
|
-
// WHY: the
|
|
4
|
-
// outdated skill copy from a current one and
|
|
3
|
+
// WHY: the MCP server reads `<skillRoot>/.archgen-version` to tell an
|
|
4
|
+
// outdated skill copy from a current one and reports it (missing or
|
|
5
5
|
// unparseable content means "unknown (legacy)" — a pre-stamp install). The
|
|
6
6
|
// CLI is the only writer: every operation that lays down skill files (init,
|
|
7
7
|
// global install in symlink AND --copy mode, update via its init/doctor
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archgen-skill",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Architecture generation & autonomous task execution for coding agents - conversational requirements in, dependency-ordered tasks out, executed wave-by-wave with verifier gates. Works with Claude Code, OpenCode, Cursor, Codex, Gemini CLI and any agentskills.io harness.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"bugs": {
|
|
39
39
|
"url": "https://github.com/akashmaurya99/archgen/issues"
|
|
40
40
|
},
|
|
41
|
-
"//": "vendor/skills/archgen is a full mirror of ../../skill synced by scripts/sync-vendor.mjs (run automatically via prepublishOnly).
|
|
41
|
+
"//": "vendor/skills/archgen is a full mirror of ../../skill synced by scripts/sync-vendor.mjs (run automatically via prepublishOnly).",
|
|
42
42
|
"scripts": {
|
|
43
43
|
"sync:config": "node scripts/sync-config.mjs",
|
|
44
|
-
"sync:check": "node scripts/sync-config.mjs --check",
|
|
44
|
+
"sync:check": "node scripts/sync-config.mjs --check && node scripts/sync-vendor.mjs --check",
|
|
45
45
|
"sync": "node scripts/sync-config.mjs && node scripts/sync-vendor.mjs",
|
|
46
46
|
"prepublishOnly": "node scripts/sync-config.mjs && node scripts/sync-vendor.mjs",
|
|
47
47
|
"test": "ARCHGEN_CODEGRAPH=0 node --test test/*.test.mjs"
|