great-cto 3.8.0 → 3.9.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.
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "great_cto",
|
|
3
3
|
"id": "great_cto",
|
|
4
4
|
"description": "Engineering process for solo founders and teams up to 50 engineers. Agents do architecture, code review, QA, and security. You make two decisions per feature.",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.9.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Great CTO",
|
|
8
8
|
"url": "https://github.com/avelikiy/great_cto"
|
|
@@ -416,6 +416,31 @@ function resolveProjectInfo(slugOrPath) {
|
|
|
416
416
|
const matches = reg.projects.filter(p => p.slug === slugOrPath);
|
|
417
417
|
const found = pickBestBySlug(matches);
|
|
418
418
|
if (found) return { cwd: found.path, resolved: 'slug' };
|
|
419
|
+
|
|
420
|
+
// The identifier the board SHOWS and the identifier it RESOLVES were two
|
|
421
|
+
// different things.
|
|
422
|
+
//
|
|
423
|
+
// `listProjects()` derives a display slug at :184 —
|
|
424
|
+
// `project` / `name` / basename(dir) — while resolution matched only
|
|
425
|
+
// `p.slug` as stored in the registry. A project listed as `<private-project>`
|
|
426
|
+
// (basename of its directory) had a different slug on disk, so asking for the
|
|
427
|
+
// name the UI itself printed resolved to nothing and fell back to the server's
|
|
428
|
+
// own project: one project's session logs served under another's name. The
|
|
429
|
+
// fallback header said so, which is why this was a wrong answer rather than a
|
|
430
|
+
// silent one — but a caller that asks by the name it was given should not have
|
|
431
|
+
// to read a header to learn it was ignored.
|
|
432
|
+
//
|
|
433
|
+
// So the derived identifier is accepted too, and only after the stored one
|
|
434
|
+
// fails. Basename matching is last because it is the weakest claim: two
|
|
435
|
+
// directories can share a name, and pickBestBySlug already knows how to
|
|
436
|
+
// choose between candidates.
|
|
437
|
+
const byDerived = reg.projects.filter((p) => {
|
|
438
|
+
if (!p?.path) return false;
|
|
439
|
+
return path.basename(p.path) === slugOrPath;
|
|
440
|
+
});
|
|
441
|
+
const derived = pickBestBySlug(byDerived);
|
|
442
|
+
if (derived) return { cwd: derived.path, resolved: 'slug' };
|
|
443
|
+
|
|
419
444
|
return { cwd: process.cwd(), resolved: 'fallback', requested: slugOrPath };
|
|
420
445
|
}
|
|
421
446
|
|
package/dist/installer.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// Install the great_cto plugin into ~/.claude/plugins/cache/local/great_cto/<version>/.
|
|
2
2
|
// Uses git clone. Falls back to tarball fetch if git is unavailable.
|
|
3
3
|
import { spawnSync, execFileSync } from "node:child_process";
|
|
4
|
+
import { cpSync } from "node:fs";
|
|
4
5
|
import { existsSync, mkdirSync, rmSync, readFileSync, readdirSync } from "node:fs";
|
|
5
6
|
import { homedir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
7
9
|
import { log, success, warn, dim } from "./ui.js";
|
|
8
10
|
const REPO_URL = "https://github.com/avelikiy/great_cto.git";
|
|
9
11
|
export function hasGit() {
|
|
@@ -113,14 +115,81 @@ export function install(opts = {}) {
|
|
|
113
115
|
throw new Error(`git clone failed: ${stderr}`);
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
// A clone carries sources; the plugin runs on the build. Supply it from this
|
|
119
|
+
// CLI's own dist before checking whether the result can run.
|
|
120
|
+
const supplied = supplyBuiltDist(pluginDir);
|
|
121
|
+
if (supplied != null)
|
|
122
|
+
log(dim(` supplied ${supplied} built file(s) into packages/cli/dist/`));
|
|
123
|
+
// Sanity check: can it RUN, not merely "did files arrive".
|
|
124
|
+
const missing = missingRuntimeParts(pluginDir);
|
|
125
|
+
if (missing.length) {
|
|
126
|
+
throw new Error(`Install appeared to succeed but the plugin cannot run — missing:\n` +
|
|
127
|
+
missing.map((m) => ` - ${m}`).join("\n") +
|
|
128
|
+
`\nThis is an install bug, not a configuration problem. Please report it with this list.`);
|
|
120
129
|
}
|
|
121
130
|
success(`plugin installed at ${pluginDir}`);
|
|
122
131
|
return { installed: true, pluginDir, version, alreadyInstalled: false };
|
|
123
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* The plugin needs BUILT JavaScript, and a git clone does not contain any.
|
|
135
|
+
*
|
|
136
|
+
* `packages/cli/dist/` is a build artefact. Five of its thirty-two files are in
|
|
137
|
+
* git by accident; the rest, including `archetypes.js`, are not. So a cloned
|
|
138
|
+
* plugin gets 5 of 32, and `scripts/lib/gate-plan.mjs` — which the board's
|
|
139
|
+
* project reader imports — dies on
|
|
140
|
+
*
|
|
141
|
+
* ERR_MODULE_NOT_FOUND … packages/cli/dist/archetypes.js
|
|
142
|
+
*
|
|
143
|
+
* The board therefore did not start for anyone installing this the documented
|
|
144
|
+
* way. It started for the author, whose plugin cache is populated by
|
|
145
|
+
* `install-local.sh` from a working tree with a full local build, and it started
|
|
146
|
+
* from the npm tarball, which ships all 32. It failed on exactly one path: the
|
|
147
|
+
* one a new user takes.
|
|
148
|
+
*
|
|
149
|
+
* The build is not fetched or rebuilt — it is already here. This CLI IS the
|
|
150
|
+
* published package, so the version being installed and the version doing the
|
|
151
|
+
* installing are the same artefacts. Copying them across is both the cheapest
|
|
152
|
+
* source and the only one that cannot drift.
|
|
153
|
+
*
|
|
154
|
+
* @returns how many files were supplied, or null when this CLI has no dist of
|
|
155
|
+
* its own to give (running from source in the monorepo, where the clone is
|
|
156
|
+
* not what gets used anyway).
|
|
157
|
+
*/
|
|
158
|
+
function supplyBuiltDist(pluginDir) {
|
|
159
|
+
const here = dirname(fileURLToPath(import.meta.url)); // …/dist
|
|
160
|
+
const target = join(pluginDir, "packages", "cli", "dist");
|
|
161
|
+
try {
|
|
162
|
+
if (!existsSync(join(here, "archetypes.js")))
|
|
163
|
+
return null;
|
|
164
|
+
mkdirSync(target, { recursive: true });
|
|
165
|
+
cpSync(here, target, { recursive: true });
|
|
166
|
+
return readdirSync(target).length;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Can the plugin actually run, or did we merely receive files?
|
|
174
|
+
*
|
|
175
|
+
* The check this replaces asked whether `.claude-plugin/plugin.json` exists —
|
|
176
|
+
* "did we get a plugin?" — which a clone always satisfies while the board is
|
|
177
|
+
* still unable to start. A sanity check that a broken install passes is not a
|
|
178
|
+
* sanity check.
|
|
179
|
+
*
|
|
180
|
+
* @returns a list of what is missing; empty means runnable.
|
|
181
|
+
*/
|
|
182
|
+
export function missingRuntimeParts(pluginDir) {
|
|
183
|
+
const required = [
|
|
184
|
+
[".claude-plugin/plugin.json", "the plugin manifest"],
|
|
185
|
+
["packages/board/server.mjs", "the board server"],
|
|
186
|
+
["packages/cli/dist/archetypes.js", "the built archetype table the board imports"],
|
|
187
|
+
["scripts/lib/gate-plan.mjs", "the gate planner"],
|
|
188
|
+
];
|
|
189
|
+
return required
|
|
190
|
+
.filter(([rel]) => !existsSync(join(pluginDir, rel)))
|
|
191
|
+
.map(([rel, what]) => `${rel} (${what})`);
|
|
192
|
+
}
|
|
124
193
|
function readPluginVersion(pluginDir) {
|
|
125
194
|
try {
|
|
126
195
|
const manifest = join(pluginDir, ".claude-plugin", "plugin.json");
|
package/package.json
CHANGED