codictator 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +3 -1
- package/dist/cli/guardrail.d.ts +2 -0
- package/dist/cli/guardrail.js +11 -13
- package/dist/cli/init-plan.d.ts +7 -0
- package/dist/cli/init-plan.js +21 -2
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.js +8 -17
- package/dist/cli/serve-run.js +1 -0
- package/dist/cli/setup-gitignore.d.ts +7 -0
- package/dist/cli/setup-gitignore.js +41 -0
- package/dist/cli/setup-handoff.d.ts +7 -0
- package/dist/cli/setup-handoff.js +44 -0
- package/dist/cli/setup-install.d.ts +11 -0
- package/dist/cli/setup-install.js +84 -0
- package/dist/cli/setup.d.ts +9 -0
- package/dist/cli/setup.js +143 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/cli/commands.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ import type { Result } from "../kernel/index.ts";
|
|
|
2
2
|
import type { CommandRequest } from "./shared.ts";
|
|
3
3
|
export type Handler = (req: CommandRequest) => Promise<Result<number>>;
|
|
4
4
|
export declare const COMMANDS: Readonly<Record<string, Handler>>;
|
|
5
|
-
export declare const USAGE = "usage: codictator <init|install-hooks|regen|uninstall|verify|status|explain|replay|report|context|hook|task|test|review|waive|baseline|deps|remediate|ci|generate|pack|adapter|serve> [--json] [--cwd <path>] [--quiet]";
|
|
5
|
+
export declare const USAGE = "usage: codictator <init|setup|install-hooks|regen|uninstall|verify|status|explain|replay|report|context|hook|task|test|review|waive|baseline|deps|remediate|ci|generate|pack|adapter|serve> [--json] [--cwd <path>] [--quiet]";
|
package/dist/cli/commands.js
CHANGED
|
@@ -9,11 +9,13 @@ import { handleReview } from "./proofs.js";
|
|
|
9
9
|
import { handleTestCmd } from "./proofs-test.js";
|
|
10
10
|
import { handleRemediate } from "./remediation.js";
|
|
11
11
|
import { handleContext, handleReport } from "./report.js";
|
|
12
|
+
import { handleSetup } from "./setup.js";
|
|
12
13
|
import { handleTask } from "./task-advance.js";
|
|
13
14
|
import { handleExplain, handleReplay } from "./transparency.js";
|
|
14
15
|
import { handleWaive } from "./waivers.js";
|
|
15
16
|
export const COMMANDS = {
|
|
16
17
|
init: handleInit,
|
|
18
|
+
setup: handleSetup,
|
|
17
19
|
"install-hooks": handleInstallHooks,
|
|
18
20
|
regen: handleRegen,
|
|
19
21
|
uninstall: handleUninstall,
|
|
@@ -36,4 +38,4 @@ export const COMMANDS = {
|
|
|
36
38
|
adapter: handleAdapter,
|
|
37
39
|
serve: handleServe,
|
|
38
40
|
};
|
|
39
|
-
export const USAGE = "usage: codictator <init|install-hooks|regen|uninstall|verify|status|explain|replay|report|context|hook|task|test|review|waive|baseline|deps|remediate|ci|generate|pack|adapter|serve> [--json] [--cwd <path>] [--quiet]";
|
|
41
|
+
export const USAGE = "usage: codictator <init|setup|install-hooks|regen|uninstall|verify|status|explain|replay|report|context|hook|task|test|review|waive|baseline|deps|remediate|ci|generate|pack|adapter|serve> [--json] [--cwd <path>] [--quiet]";
|
package/dist/cli/guardrail.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Result } from "../kernel/index.ts";
|
|
2
2
|
import type { CommandRequest } from "./shared.ts";
|
|
3
3
|
export declare function handleRegen(req: CommandRequest): Promise<Result<number>>;
|
|
4
|
+
export declare function writeHookFiles(root: string): Promise<Result<true>>;
|
|
5
|
+
export declare function configureHooksPath(root: string, req: CommandRequest): Promise<void>;
|
|
4
6
|
export declare function handleInstallHooks(req: CommandRequest): Promise<Result<number>>;
|
|
5
7
|
export declare function handleUninstall(req: CommandRequest): Promise<Result<number>>;
|
package/dist/cli/guardrail.js
CHANGED
|
@@ -7,8 +7,9 @@ import { renderCustomRegenFragment } from "../features/adapters/index.js";
|
|
|
7
7
|
import { ENGINE_VERSION } from "../features/config/index.js";
|
|
8
8
|
import { EXPECTED_HOOKS_PATH, HOOK_NAMES, hookInstallPlan, renderHookScript, } from "../features/integrity/index.js";
|
|
9
9
|
import { err, ok, resolveRepoRoot } from "../kernel/index.js";
|
|
10
|
+
import { initPreamble } from "./init-plan.js";
|
|
10
11
|
import { guardrailPlan, writePlan } from "./plan.js";
|
|
11
|
-
import {
|
|
12
|
+
import { fail, loadRepoConfig, portsFor, printHuman, printJson, readStateText, usageError, } from "./shared.js";
|
|
12
13
|
function repoNameOf(root) {
|
|
13
14
|
return root.replaceAll("\\", "/").split("/").pop() ?? "repo";
|
|
14
15
|
}
|
|
@@ -89,7 +90,7 @@ export async function handleRegen(req) {
|
|
|
89
90
|
function encode(text) {
|
|
90
91
|
return new TextEncoder().encode(text);
|
|
91
92
|
}
|
|
92
|
-
async function writeHookFiles(root) {
|
|
93
|
+
export async function writeHookFiles(root) {
|
|
93
94
|
const ports = await portsFor(root);
|
|
94
95
|
if (!ports.ok) {
|
|
95
96
|
return err(ports.error);
|
|
@@ -107,7 +108,7 @@ async function writeHookFiles(root) {
|
|
|
107
108
|
}
|
|
108
109
|
return ok(true);
|
|
109
110
|
}
|
|
110
|
-
async function configureHooksPath(root, req) {
|
|
111
|
+
export async function configureHooksPath(root, req) {
|
|
111
112
|
const ports = await portsFor(root);
|
|
112
113
|
if (!ports.ok) {
|
|
113
114
|
return;
|
|
@@ -169,21 +170,18 @@ export async function handleUninstall(req) {
|
|
|
169
170
|
if (rejected !== undefined) {
|
|
170
171
|
return rejected;
|
|
171
172
|
}
|
|
172
|
-
const
|
|
173
|
-
if (
|
|
174
|
-
return
|
|
173
|
+
const preamble = await initPreamble(req);
|
|
174
|
+
if (typeof preamble !== "string") {
|
|
175
|
+
return preamble;
|
|
175
176
|
}
|
|
176
|
-
const root =
|
|
177
|
-
|
|
178
|
-
return fail("CDX-E703", root.error.message, root.error.remediation, req.json);
|
|
179
|
-
}
|
|
180
|
-
await removeGuardrails(root.value);
|
|
177
|
+
const root = preamble;
|
|
178
|
+
await removeGuardrails(root);
|
|
181
179
|
const note = "CI will fail until its workflow and required check are removed too.";
|
|
182
180
|
if (req.json) {
|
|
183
|
-
printJson({ uninstalled: true, root
|
|
181
|
+
printJson({ uninstalled: true, root, note });
|
|
184
182
|
}
|
|
185
183
|
else {
|
|
186
|
-
printHuman(`uninstalled guardrails from ${root
|
|
184
|
+
printHuman(`uninstalled guardrails from ${root}`, req.quiet);
|
|
187
185
|
printHuman(note, req.quiet);
|
|
188
186
|
}
|
|
189
187
|
return ok(0);
|
package/dist/cli/init-plan.d.ts
CHANGED
|
@@ -20,4 +20,11 @@ export declare function emitInitResult(req: CommandRequest, root: string, writte
|
|
|
20
20
|
path: string;
|
|
21
21
|
}>, baselineWritten: boolean): Result<number>;
|
|
22
22
|
export declare function initPreamble(req: CommandRequest): Promise<string | Result<number>>;
|
|
23
|
+
export interface InitSequence {
|
|
24
|
+
readonly written: ReadonlyArray<{
|
|
25
|
+
path: string;
|
|
26
|
+
}>;
|
|
27
|
+
readonly baseline: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function runInitSequence(root: string, req: CommandRequest, flags: InitFlags, planned: ReadonlyArray<string>, engineVersion: string): Promise<Result<InitSequence>>;
|
|
23
30
|
export declare function buildGuardrailPlan(root: string, flags: InitFlags, planned: ReadonlyArray<string>, engineVersion?: string): Promise<ReturnType<typeof guardrailPlan>>;
|
package/dist/cli/init-plan.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { CODICTATOR_MODULES } from "../features/arch/index.js";
|
|
5
5
|
import { ENGINE_VERSION } from "../features/config/index.js";
|
|
6
6
|
import { captureBaseline } from "../features/remediation/index.js";
|
|
7
|
-
import { ok, parseJson, resolveRepoRoot, stableStringify, } from "../kernel/index.js";
|
|
8
|
-
import { buildHelperTool, guardrailPlan } from "./plan.js";
|
|
7
|
+
import { err, ok, parseJson, resolveRepoRoot, stableStringify, } from "../kernel/index.js";
|
|
8
|
+
import { buildHelperTool, guardrailPlan, writePlan } from "./plan.js";
|
|
9
9
|
import { checkHuman, commandEnv, fail, loadRepoConfig, printHuman, printJson, readStateText, writeStateText, } from "./shared.js";
|
|
10
10
|
// SPEC §4.19.2: a helper tool proposal names the helper role, the binary
|
|
11
11
|
// and the exact pin from the human's registry lookup: role=command@pin.
|
|
@@ -114,6 +114,25 @@ export async function initPreamble(req) {
|
|
|
114
114
|
}
|
|
115
115
|
return root.value;
|
|
116
116
|
}
|
|
117
|
+
// SPEC §4.13.1: the shared write-then-guard tail of the init path: plan,
|
|
118
|
+
// write, fresh-config guard, baseline guard. init and setup run the same
|
|
119
|
+
// tail; one helper owns it.
|
|
120
|
+
export async function runInitSequence(root, req, flags, planned, engineVersion) {
|
|
121
|
+
const plan = await buildGuardrailPlan(root, flags, planned, engineVersion);
|
|
122
|
+
const wrote = await writePlan(root, plan, engineVersion);
|
|
123
|
+
if (!wrote.ok) {
|
|
124
|
+
return fail("CDX-E903", wrote.error.message, wrote.error.remediation, req.json);
|
|
125
|
+
}
|
|
126
|
+
const fresh = await checkFreshConfig(root, req);
|
|
127
|
+
if (!fresh.ok) {
|
|
128
|
+
return err(fresh.error);
|
|
129
|
+
}
|
|
130
|
+
const baseline = await ensureBaseline(root, req);
|
|
131
|
+
if (!baseline.ok) {
|
|
132
|
+
return fail(baseline.error.code, baseline.error.message, baseline.error.remediation, req.json);
|
|
133
|
+
}
|
|
134
|
+
return ok({ written: wrote.value, baseline: baseline.value });
|
|
135
|
+
}
|
|
117
136
|
// The plan assembly handleInit writes: read-only inputs in, GuardrailPlan
|
|
118
137
|
// out. Writing stays on the command so the plan builder never touches
|
|
119
138
|
// the disk.
|
package/dist/cli/init.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { Result } from "../kernel/index.ts";
|
|
2
|
+
import { type InitFlags } from "./init-plan.ts";
|
|
2
3
|
import type { CommandRequest } from "./shared.ts";
|
|
4
|
+
export declare function parseInitFlags(args: ReadonlyArray<string>): InitFlags | undefined;
|
|
3
5
|
export declare function handleInit(req: CommandRequest): Promise<Result<number>>;
|
package/dist/cli/init.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
import { ENGINE_VERSION } from "../features/config/index.js";
|
|
6
6
|
import { isBuiltinPackId } from "../features/packs/index.js";
|
|
7
|
-
import {
|
|
8
|
-
import { KNOWN_HELPER_ROLES
|
|
9
|
-
import {
|
|
7
|
+
import { defaultHarnesses, emitInitResult, initPreamble, reportExisting, runInitSequence, } from "./init-plan.js";
|
|
8
|
+
import { KNOWN_HELPER_ROLES } from "./plan.js";
|
|
9
|
+
import { readStateText, usageError } from "./shared.js";
|
|
10
10
|
const KNOWN_PROFILES = new Set([
|
|
11
11
|
"app",
|
|
12
12
|
"library",
|
|
@@ -55,7 +55,7 @@ function narrowInitTools(specs) {
|
|
|
55
55
|
}
|
|
56
56
|
return tools;
|
|
57
57
|
}
|
|
58
|
-
function parseInitFlags(args) {
|
|
58
|
+
export function parseInitFlags(args) {
|
|
59
59
|
try {
|
|
60
60
|
const parsed = parseArgs({
|
|
61
61
|
args: [...args],
|
|
@@ -105,18 +105,9 @@ export async function handleInit(req) {
|
|
|
105
105
|
if (existing !== undefined && !flags.repair) {
|
|
106
106
|
return reportExisting(req, root);
|
|
107
107
|
}
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return fail("CDX-E903", wrote.error.message, wrote.error.remediation, req.json);
|
|
108
|
+
const sequenced = await runInitSequence(root, req, flags, planned, engineVersion);
|
|
109
|
+
if (!sequenced.ok) {
|
|
110
|
+
return sequenced;
|
|
112
111
|
}
|
|
113
|
-
|
|
114
|
-
if (!fresh.ok) {
|
|
115
|
-
return fresh;
|
|
116
|
-
}
|
|
117
|
-
const baseline = await ensureBaseline(root, req);
|
|
118
|
-
if (!baseline.ok) {
|
|
119
|
-
return fail(baseline.error.code, baseline.error.message, baseline.error.remediation, req.json);
|
|
120
|
-
}
|
|
121
|
-
return emitInitResult(req, root, wrote.value, baseline.value);
|
|
112
|
+
return emitInitResult(req, root, sequenced.value.written, sequenced.value.baseline);
|
|
122
113
|
}
|
package/dist/cli/serve-run.js
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Result } from "../kernel/index.ts";
|
|
2
|
+
import type { SetupStep } from "./setup.ts";
|
|
3
|
+
import type { CommandRequest } from "./shared.ts";
|
|
4
|
+
export declare function ensureGitignoreEntries(root: string): Promise<Result<{
|
|
5
|
+
added: Array<string>;
|
|
6
|
+
}>>;
|
|
7
|
+
export declare function setupGitignoreStep(req: CommandRequest, root: string, steps: Array<SetupStep>): Promise<Result<number>>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { err, ok } from "../kernel/index.js";
|
|
2
|
+
import { fail, printHuman, readStateText, writeStateText } from "./shared.js";
|
|
3
|
+
const SETUP_GITIGNORE_ENTRIES = [
|
|
4
|
+
".codictator/state/",
|
|
5
|
+
".codictator/cache/",
|
|
6
|
+
".codictator/tool/node_modules/",
|
|
7
|
+
];
|
|
8
|
+
export async function ensureGitignoreEntries(root) {
|
|
9
|
+
const existing = await readStateText(root, ".gitignore");
|
|
10
|
+
const present = new Set((existing ?? "").split("\n").map((line) => line.trim()));
|
|
11
|
+
const missing = SETUP_GITIGNORE_ENTRIES.filter((entry) => !present.has(entry));
|
|
12
|
+
if (missing.length === 0) {
|
|
13
|
+
return ok({ added: [] });
|
|
14
|
+
}
|
|
15
|
+
const base = existing === undefined || existing === ""
|
|
16
|
+
? ""
|
|
17
|
+
: existing.endsWith("\n")
|
|
18
|
+
? existing
|
|
19
|
+
: `${existing}\n`;
|
|
20
|
+
const saved = await writeStateText(root, ".gitignore", `${base}${missing.join("\n")}\n`);
|
|
21
|
+
if (!saved.ok) {
|
|
22
|
+
return err(saved.error);
|
|
23
|
+
}
|
|
24
|
+
return ok({ added: [...missing] });
|
|
25
|
+
}
|
|
26
|
+
// SPEC §4.19.6 step 3: guarantee the manifest-owned .gitignore entries.
|
|
27
|
+
export async function setupGitignoreStep(req, root, steps) {
|
|
28
|
+
const ensured = await ensureGitignoreEntries(root);
|
|
29
|
+
if (!ensured.ok) {
|
|
30
|
+
return fail("CDX-E903", ensured.error.message, ensured.error.remediation, req.json);
|
|
31
|
+
}
|
|
32
|
+
steps.push({
|
|
33
|
+
step: "gitignore",
|
|
34
|
+
ok: true,
|
|
35
|
+
detail: { added: ensured.value.added },
|
|
36
|
+
});
|
|
37
|
+
if (ensured.value.added.length > 0) {
|
|
38
|
+
printHuman(`setup: added ${String(ensured.value.added.length)} .gitignore entries`, req.quiet);
|
|
39
|
+
}
|
|
40
|
+
return ok(0);
|
|
41
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { InitFlags } from "./init-plan.ts";
|
|
2
|
+
export declare const SETUP_EXTENSION_ID = "codictator.codictator-vscode";
|
|
3
|
+
export declare const SETUP_EXTENSION_INSTALL = "code --install-extension codictator.codictator-vscode";
|
|
4
|
+
export declare const SETUP_PACK_REPAIR = "npx codictator setup --repair --pack typescript";
|
|
5
|
+
export declare const SETUP_PACK_FRESH = "npx codictator setup --pack typescript";
|
|
6
|
+
export declare function setupHandoffLines(emptyPacks: boolean): Array<string>;
|
|
7
|
+
export declare function hasEffectivePacks(root: string, flags: InitFlags): Promise<boolean>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// SPEC §4.19.6 step 8: pack-less handoff text. Split from setup.ts
|
|
2
|
+
// (orchestration) by responsibility so setup.ts stays below the
|
|
3
|
+
// core/file-size 300 cap; the effective pack list mirrors the plan data.
|
|
4
|
+
import { parseJson } from "../kernel/index.js";
|
|
5
|
+
import { readStateText } from "./shared.js";
|
|
6
|
+
// The extension empty-state lives on the vscode branch (§4.20.1);
|
|
7
|
+
// handoff here only prints the install command.
|
|
8
|
+
export const SETUP_EXTENSION_ID = "codictator.codictator-vscode";
|
|
9
|
+
export const SETUP_EXTENSION_INSTALL = "code --install-extension codictator.codictator-vscode";
|
|
10
|
+
export const SETUP_PACK_REPAIR = "npx codictator setup --repair --pack typescript";
|
|
11
|
+
export const SETUP_PACK_FRESH = "npx codictator setup --pack typescript";
|
|
12
|
+
export function setupHandoffLines(emptyPacks) {
|
|
13
|
+
if (!emptyPacks) {
|
|
14
|
+
return [
|
|
15
|
+
"setup complete: open this folder in VS Code to see the Codictator views",
|
|
16
|
+
`extension: ${SETUP_EXTENSION_INSTALL}`,
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
return [
|
|
20
|
+
"Setup complete — core rules only. No language packs installed.",
|
|
21
|
+
"To add a language pack, run exactly one line and press Enter:",
|
|
22
|
+
` ${SETUP_PACK_REPAIR}`,
|
|
23
|
+
"To install the VS Code extension, run:",
|
|
24
|
+
` ${SETUP_EXTENSION_INSTALL}`,
|
|
25
|
+
`Fresh adoption omits --repair: ${SETUP_PACK_FRESH}`,
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
// SPEC §4.19.6 step 8: the effective pack list mirrors the plan data:
|
|
29
|
+
// explicit --pack flags win, otherwise the written config owns it.
|
|
30
|
+
export async function hasEffectivePacks(root, flags) {
|
|
31
|
+
if (flags.packs.length > 0) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
const text = await readStateText(root, ".codictator/config.json");
|
|
35
|
+
if (text === undefined) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const parsed = parseJson(text);
|
|
39
|
+
if (!parsed.ok || typeof parsed.value !== "object" || parsed.value === null) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const packs = parsed.value.packs;
|
|
43
|
+
return Array.isArray(packs) && packs.length > 0;
|
|
44
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Result } from "../kernel/index.ts";
|
|
2
|
+
import type { SetupStep } from "./setup.ts";
|
|
3
|
+
import type { CommandRequest } from "./shared.ts";
|
|
4
|
+
export interface SetupInstallTarget {
|
|
5
|
+
readonly dir: string;
|
|
6
|
+
readonly lockRel: string;
|
|
7
|
+
readonly dirRel: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function setupInstallLayout(req: CommandRequest, root: string, steps: Array<SetupStep>): Promise<Result<SetupInstallTarget>>;
|
|
10
|
+
export declare function setupNpmArgv(platform?: string, execPath?: string): ReadonlyArray<string>;
|
|
11
|
+
export declare function setupInstallHere(req: CommandRequest, root: string, target: SetupInstallTarget, steps: Array<SetupStep>): Promise<Result<number>>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// SPEC §4.19.4 + §4.19.6 steps 4-5: the engine install layout and the
|
|
2
|
+
// clean install here. npm repos install at the root; other repos get
|
|
3
|
+
// .codictator/tool/package.json pinning the engine exactly, then npm ci
|
|
4
|
+
// runs in the directory owning the lockfile. A registry or network outage
|
|
5
|
+
// maps to CDX-E401 (exit 5); any other install failure is a missing
|
|
6
|
+
// engine, CDX-E003 (exit 6).
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { ENGINE_VERSION } from "../features/config/index.js";
|
|
9
|
+
import { err, ok, stableStringify } from "../kernel/index.js";
|
|
10
|
+
import { fail, portsFor, printHuman, readStateText, writeStateText, } from "./shared.js";
|
|
11
|
+
export async function setupInstallLayout(req, root, steps) {
|
|
12
|
+
const rootManifest = await readStateText(root, "package.json");
|
|
13
|
+
if (rootManifest !== undefined) {
|
|
14
|
+
steps.push({ step: "install", ok: true, detail: { dir: "." } });
|
|
15
|
+
return ok({ dir: root, lockRel: "package-lock.json", dirRel: "." });
|
|
16
|
+
}
|
|
17
|
+
const toolManifest = await readStateText(root, ".codictator/tool/package.json");
|
|
18
|
+
if (toolManifest === undefined) {
|
|
19
|
+
const engineVersion = req.engineVersion ?? ENGINE_VERSION;
|
|
20
|
+
const saved = await writeStateText(root, ".codictator/tool/package.json", `${stableStringify({
|
|
21
|
+
name: "codictator-tool",
|
|
22
|
+
private: true,
|
|
23
|
+
dependencies: { codictator: engineVersion },
|
|
24
|
+
})}\n`);
|
|
25
|
+
if (!saved.ok) {
|
|
26
|
+
return err(saved.error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
steps.push({
|
|
30
|
+
step: "install",
|
|
31
|
+
ok: true,
|
|
32
|
+
detail: { dir: ".codictator/tool" },
|
|
33
|
+
});
|
|
34
|
+
return ok({
|
|
35
|
+
dir: `${root}/.codictator/tool`,
|
|
36
|
+
lockRel: ".codictator/tool/package-lock.json",
|
|
37
|
+
dirRel: ".codictator/tool",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function looksLikeRegistryOutage(output) {
|
|
41
|
+
return /ENOTFOUND|EAI_AGAIN|ETIMEDOUT|ECONNRESET|ECONNREFUSED|registry/i.test(output);
|
|
42
|
+
}
|
|
43
|
+
// SPEC §4.1.1: the child spawns with shell:false always, so on win32 the
|
|
44
|
+
// bare `npm` shim never resolves: it is an npm.cmd batch file, which the
|
|
45
|
+
// shell-less spawn cannot execute (ENOENT without, EINVAL with, the
|
|
46
|
+
// extension). The fix stays shell-free by exec'ing the real entry beside
|
|
47
|
+
// the running node: node <node-dir>/node_modules/npm/bin/npm-cli.js ci.
|
|
48
|
+
// POSIX keeps `npm` from PATH. Pure in platform and execPath so tests pin
|
|
49
|
+
// the exact proc argv without spawning anything.
|
|
50
|
+
export function setupNpmArgv(platform = process.platform, execPath = process.execPath) {
|
|
51
|
+
if (platform !== "win32") {
|
|
52
|
+
return ["npm", "ci"];
|
|
53
|
+
}
|
|
54
|
+
return [
|
|
55
|
+
execPath,
|
|
56
|
+
path.join(path.dirname(execPath), "node_modules", "npm", "bin", "npm-cli.js"),
|
|
57
|
+
"ci",
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
export async function setupInstallHere(req, root, target, steps) {
|
|
61
|
+
const lock = await readStateText(root, target.lockRel);
|
|
62
|
+
if (lock === undefined) {
|
|
63
|
+
return fail("CDX-E003", `no lockfile at ${target.lockRel}, so npm ci cannot run`, `Run npm install in ${target.dirRel} (needs network access), then rerun codictator setup.`, req.json);
|
|
64
|
+
}
|
|
65
|
+
const ports = await portsFor(root);
|
|
66
|
+
if (!ports.ok) {
|
|
67
|
+
return fail("CDX-E903", ports.error.message, ports.error.remediation, req.json);
|
|
68
|
+
}
|
|
69
|
+
const ran = await ports.value.proc.run(setupNpmArgv(), {
|
|
70
|
+
cwd: target.dir,
|
|
71
|
+
env: {},
|
|
72
|
+
timeoutMs: 300000,
|
|
73
|
+
maxOutputBytes: 4 * 1024 * 1024,
|
|
74
|
+
});
|
|
75
|
+
if (!ran.timedOut && !ran.truncated && ran.code === 0) {
|
|
76
|
+
steps.push({ step: "install-here", ok: true });
|
|
77
|
+
printHuman(`setup: npm ci passed in ${target.dirRel}`, req.quiet);
|
|
78
|
+
return ok(0);
|
|
79
|
+
}
|
|
80
|
+
if (ran.timedOut || looksLikeRegistryOutage(`${ran.stdout}\n${ran.stderr}`)) {
|
|
81
|
+
return fail("CDX-E401", "registry or network unavailable during install", "Retry with network access, then rerun codictator setup.", req.json);
|
|
82
|
+
}
|
|
83
|
+
return fail("CDX-E003", `engine install failed in ${target.dirRel} (npm ci exit ${String(ran.code)})`, `Run npm ci in ${target.dirRel}, then rerun codictator setup.`, req.json);
|
|
84
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Result } from "../kernel/index.ts";
|
|
2
|
+
import type { CommandRequest } from "./shared.ts";
|
|
3
|
+
export declare const SETUP_USAGE = "usage: codictator setup [--harness <id> ...] [--profile <app|library|cli|service>] [--pack <id> ...] [--tool <role>=<command>@<pin> ...] [--repair]";
|
|
4
|
+
export interface SetupStep {
|
|
5
|
+
readonly step: string;
|
|
6
|
+
readonly ok: boolean;
|
|
7
|
+
readonly detail?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export declare function handleSetup(req: CommandRequest): Promise<Result<number>>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// SPEC §4.19.6: one-shot adoption. Flags mirror init (§4.19.2); the run
|
|
2
|
+
// is the init path (or regen over an existing config) plus install, hooks
|
|
3
|
+
// and verify, ending at the extension handoff (§4.20.1). Every step
|
|
4
|
+
// delegates to the existing init, guardrail and verify handlers; no step
|
|
5
|
+
// logic is duplicated here.
|
|
6
|
+
import { ENGINE_VERSION } from "../features/config/index.js";
|
|
7
|
+
import { err, ok } from "../kernel/index.js";
|
|
8
|
+
import { configureHooksPath, handleRegen, writeHookFiles, } from "./guardrail.js";
|
|
9
|
+
import { parseInitFlags } from "./init.js";
|
|
10
|
+
import { defaultHarnesses, initPreamble, runInitSequence, } from "./init-plan.js";
|
|
11
|
+
import { handleVerify } from "./inspect.js";
|
|
12
|
+
import { setupGitignoreStep } from "./setup-gitignore.js";
|
|
13
|
+
import { hasEffectivePacks, SETUP_EXTENSION_ID, SETUP_EXTENSION_INSTALL, setupHandoffLines, } from "./setup-handoff.js";
|
|
14
|
+
import { setupInstallHere, setupInstallLayout } from "./setup-install.js";
|
|
15
|
+
import { fail, MIN_NODE_MAJOR, nodeMajor, nodeTooOldMessage, printHuman, printJson, readStateText, usageError, } from "./shared.js";
|
|
16
|
+
export const SETUP_USAGE = "usage: codictator setup [--harness <id> ...] [--profile <app|library|cli|service>] [--pack <id> ...] [--tool <role>=<command>@<pin> ...] [--repair]";
|
|
17
|
+
// SPEC §4.19.6 step 1: the Node floor, the human gate and the repo root.
|
|
18
|
+
async function setupPreflight(req) {
|
|
19
|
+
const version = req.nodeVersion ?? process.version;
|
|
20
|
+
if (nodeMajor(version) < MIN_NODE_MAJOR) {
|
|
21
|
+
return fail("CDX-E002", nodeTooOldMessage(version), "Install a newer Node, then retry the tool call.", req.json);
|
|
22
|
+
}
|
|
23
|
+
return initPreamble(req);
|
|
24
|
+
}
|
|
25
|
+
// SPEC §4.19.6 step 2: the adoption path. An existing config without
|
|
26
|
+
// --repair re-renders from config; otherwise the init path runs.
|
|
27
|
+
async function setupRegenPath(req, steps) {
|
|
28
|
+
const regen = await handleRegen({ ...req, args: [] });
|
|
29
|
+
if (!regen.ok) {
|
|
30
|
+
return regen;
|
|
31
|
+
}
|
|
32
|
+
steps.push({ step: "init", ok: true, detail: { mode: "regen" } });
|
|
33
|
+
printHuman("setup: guardrails regenerated from the existing config", req.quiet);
|
|
34
|
+
return ok(0);
|
|
35
|
+
}
|
|
36
|
+
async function setupInitPath(req, root, flags, steps) {
|
|
37
|
+
const engineVersion = req.engineVersion ?? ENGINE_VERSION;
|
|
38
|
+
const sequenced = await runInitSequence(root, req, flags, defaultHarnesses(flags), engineVersion);
|
|
39
|
+
if (!sequenced.ok) {
|
|
40
|
+
return sequenced;
|
|
41
|
+
}
|
|
42
|
+
steps.push({
|
|
43
|
+
step: "init",
|
|
44
|
+
ok: true,
|
|
45
|
+
detail: {
|
|
46
|
+
mode: flags.repair ? "repair" : "fresh",
|
|
47
|
+
files: sequenced.value.written.map((entry) => entry.path),
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
printHuman("setup: guardrails written from the init path", req.quiet);
|
|
51
|
+
return ok(0);
|
|
52
|
+
}
|
|
53
|
+
async function setupAdopt(req, root, flags, steps) {
|
|
54
|
+
const existing = await readStateText(root, ".codictator/config.json");
|
|
55
|
+
if (existing !== undefined && !flags.repair) {
|
|
56
|
+
return setupRegenPath(req, steps);
|
|
57
|
+
}
|
|
58
|
+
return setupInitPath(req, root, flags, steps);
|
|
59
|
+
}
|
|
60
|
+
// SPEC §4.19.6 step 6: hooks via the install-hooks writers.
|
|
61
|
+
async function setupHooksStep(req, root, steps) {
|
|
62
|
+
const written = await writeHookFiles(root);
|
|
63
|
+
if (!written.ok) {
|
|
64
|
+
return fail("CDX-E903", written.error.message, written.error.remediation, req.json);
|
|
65
|
+
}
|
|
66
|
+
await configureHooksPath(root, req);
|
|
67
|
+
steps.push({ step: "hooks", ok: true });
|
|
68
|
+
printHuman("setup: hooks installed", req.quiet);
|
|
69
|
+
return ok(0);
|
|
70
|
+
}
|
|
71
|
+
// SPEC §4.19.6 step 7: the first evaluation over the adopted repo. A
|
|
72
|
+
// blocking verdict stops the run with verify's own mapping (exit 1).
|
|
73
|
+
async function setupVerifyStep(req, steps) {
|
|
74
|
+
const verdict = await handleVerify({ ...req, args: [] });
|
|
75
|
+
if (!verdict.ok || verdict.value !== 0) {
|
|
76
|
+
return verdict;
|
|
77
|
+
}
|
|
78
|
+
steps.push({ step: "verify", ok: true });
|
|
79
|
+
return ok(0);
|
|
80
|
+
}
|
|
81
|
+
// SPEC §4.19.6 step 8 + §4.20.1: the views connect once a config exists;
|
|
82
|
+
// the human installs the extension with the printed command.
|
|
83
|
+
function setupHandoff(req, root, steps, emptyPacks) {
|
|
84
|
+
steps.push({
|
|
85
|
+
step: "handoff",
|
|
86
|
+
ok: true,
|
|
87
|
+
detail: { extension: SETUP_EXTENSION_ID },
|
|
88
|
+
});
|
|
89
|
+
if (req.json) {
|
|
90
|
+
printJson({
|
|
91
|
+
setup: true,
|
|
92
|
+
root,
|
|
93
|
+
steps,
|
|
94
|
+
extension: { id: SETUP_EXTENSION_ID, install: SETUP_EXTENSION_INSTALL },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
for (const line of setupHandoffLines(emptyPacks)) {
|
|
99
|
+
printHuman(line, req.quiet);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return ok(0);
|
|
103
|
+
}
|
|
104
|
+
// SPEC §4.19.6: the eight adoption steps in order; a step failure stops
|
|
105
|
+
// the run with that step's §8.2 mapping.
|
|
106
|
+
export async function handleSetup(req) {
|
|
107
|
+
const flags = parseInitFlags(req.args);
|
|
108
|
+
if (flags === undefined) {
|
|
109
|
+
return usageError(SETUP_USAGE);
|
|
110
|
+
}
|
|
111
|
+
const preflight = await setupPreflight(req);
|
|
112
|
+
if (typeof preflight !== "string") {
|
|
113
|
+
return preflight;
|
|
114
|
+
}
|
|
115
|
+
const root = preflight;
|
|
116
|
+
const steps = [{ step: "preflight", ok: true }];
|
|
117
|
+
const adopted = await setupAdopt(req, root, flags, steps);
|
|
118
|
+
if (!adopted.ok) {
|
|
119
|
+
return adopted;
|
|
120
|
+
}
|
|
121
|
+
const ignored = await setupGitignoreStep(req, root, steps);
|
|
122
|
+
if (!ignored.ok) {
|
|
123
|
+
return ignored;
|
|
124
|
+
}
|
|
125
|
+
const layout = await setupInstallLayout(req, root, steps);
|
|
126
|
+
if (!layout.ok) {
|
|
127
|
+
return err(layout.error);
|
|
128
|
+
}
|
|
129
|
+
const installed = await setupInstallHere(req, root, layout.value, steps);
|
|
130
|
+
if (!installed.ok) {
|
|
131
|
+
return installed;
|
|
132
|
+
}
|
|
133
|
+
const hooked = await setupHooksStep(req, root, steps);
|
|
134
|
+
if (!hooked.ok) {
|
|
135
|
+
return hooked;
|
|
136
|
+
}
|
|
137
|
+
const verified = await setupVerifyStep(req, steps);
|
|
138
|
+
if (!verified.ok) {
|
|
139
|
+
return verified;
|
|
140
|
+
}
|
|
141
|
+
const emptyPacks = !(await hasEffectivePacks(root, flags));
|
|
142
|
+
return setupHandoff(req, root, steps, emptyPacks);
|
|
143
|
+
}
|