codictator 0.1.2 → 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/builtin-packs/core/fixtures/conventional-commit/passing/basic/case.json +1 -1
- package/dist/builtin-packs/core/fixtures/conventional-commit/violating/basic/case.json +1 -1
- package/dist/builtin-packs/typescript/fixtures/lint/passing/exported/case.json +8 -0
- package/dist/builtin-packs/typescript/pack.json +1 -1
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +3 -1
- package/dist/cli/gate-scope.d.ts +1 -0
- package/dist/cli/gate-scope.js +11 -0
- package/dist/cli/git-hook.js +13 -8
- 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/plan-launcher.d.ts +1 -0
- package/dist/cli/plan-launcher.js +53 -3
- 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/cli/task-advance.js +16 -16
- package/dist/features/adapters/claude-shaped.d.ts +1 -0
- package/dist/features/adapters/claude-shaped.js +29 -31
- package/dist/features/adapters/copilot.js +22 -29
- package/dist/features/audit/append.js +9 -4
- package/dist/features/audit/surfaces.js +15 -8
- package/dist/features/ci/protection.js +37 -13
- package/dist/features/deps/npm.js +39 -24
- package/dist/features/deps/registry.js +45 -34
- package/dist/features/deps/stable.js +52 -27
- package/dist/features/deps/upgrade.d.ts +1 -1
- package/dist/features/deps/upgrade.js +29 -19
- package/dist/features/engine/evaluate.js +6 -1
- package/dist/features/packs/enforcers/ci-guards.js +4 -1
- package/dist/features/packs/enforcers/code.js +2 -1
- package/dist/features/packs/enforcers/engine-version-guards.d.ts +6 -0
- package/dist/features/packs/enforcers/engine-version-guards.js +42 -0
- package/dist/features/packs/enforcers/external-bind.js +62 -28
- package/dist/features/packs/enforcers/external-sarif.js +30 -24
- package/dist/features/packs/enforcers/gate-guards.d.ts +0 -1
- package/dist/features/packs/enforcers/gate-guards.js +83 -136
- package/dist/features/packs/enforcers/health-measures.d.ts +2 -0
- package/dist/features/packs/enforcers/health-measures.js +10 -1
- package/dist/features/packs/enforcers/health-refs.d.ts +1 -2
- package/dist/features/packs/enforcers/health-refs.js +71 -56
- package/dist/features/packs/enforcers/secret-scan.d.ts +4 -20
- package/dist/features/packs/enforcers/secret-scan.js +61 -67
- package/dist/features/packs/enforcers/ts-checks.js +104 -58
- package/dist/features/packs/enforcers/ts-security-patterns.js +12 -8
- package/dist/features/packs/manifest-decl.d.ts +13 -0
- package/dist/features/packs/manifest-decl.js +121 -0
- package/dist/features/packs/manifest.d.ts +2 -12
- package/dist/features/packs/manifest.js +5 -90
- package/dist/features/packs/selftest.js +22 -5
- package/dist/features/remediation/adoption.js +40 -29
- package/dist/features/testing/scenarios.js +49 -27
- package/dist/features/testing/tap-parser.js +38 -26
- package/dist/features/workflow/phases.js +45 -42
- package/dist/features/workflow/review.js +17 -10
- package/dist/kernel/git.js +4 -1
- package/dist/kernel/pattern.js +120 -86
- package/dist/kernel/secret-paths.js +34 -20
- package/dist/kernel/shell-cmd.js +23 -6
- 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/gate-scope.d.ts
CHANGED
|
@@ -7,4 +7,5 @@ export interface GateScope {
|
|
|
7
7
|
readonly note: string;
|
|
8
8
|
readonly proposedContent?: string;
|
|
9
9
|
}
|
|
10
|
+
export declare function addedLines(diff: string): string;
|
|
10
11
|
export declare function evaluateGateScope(ports: CommandPorts, packIds: ReadonlyArray<string>, scope: GateScope, now: string, configHash: string): Promise<Result<Array<Finding>>>;
|
package/dist/cli/gate-scope.js
CHANGED
|
@@ -51,6 +51,17 @@ function mergeGateFindings(first, second) {
|
|
|
51
51
|
}
|
|
52
52
|
return merged.sort(compareGateFindings);
|
|
53
53
|
}
|
|
54
|
+
// BUG-0022 + SPEC §4.7.1: the L3 gate judges added lines only, so a commit
|
|
55
|
+
// deleting a flagged literal passes while staging one still blocks. A diff
|
|
56
|
+
// carries its removed lines as `-` rows; scanning the raw diff would match
|
|
57
|
+
// those and make the removal uncommittable.
|
|
58
|
+
export function addedLines(diff) {
|
|
59
|
+
return diff
|
|
60
|
+
.split("\n")
|
|
61
|
+
.filter((line) => line.startsWith("+") && !line.startsWith("+++"))
|
|
62
|
+
.map((line) => line.slice(1))
|
|
63
|
+
.join("\n");
|
|
64
|
+
}
|
|
54
65
|
// BUG-0014 + SPEC §4.3.2: the gate evaluation over a diff scope. The
|
|
55
66
|
// pre-tool leg keeps today's fail-closed local verdict byte for byte; the
|
|
56
67
|
// review leg adds the diff-sound review subset over the same files, so the
|
package/dist/cli/git-hook.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
// the change with the gate rule subset `verify` loads over staged scopes —
|
|
5
5
|
// new scope plumbing, no new detection logic. commit-msg appends
|
|
6
6
|
// the Codictator-L3 trailer §4.14.2 checks at L4.
|
|
7
|
+
import path from "node:path";
|
|
7
8
|
import { ENGINE_VERSION, HOOK_MARKER } from "../features/config/index.js";
|
|
8
9
|
import { err, makeError, ok, readHostText, writeHostText, } from "../kernel/index.js";
|
|
9
|
-
import { evaluateGateScope } from "./gate-scope.js";
|
|
10
|
+
import { addedLines, evaluateGateScope } from "./gate-scope.js";
|
|
10
11
|
import { printFinding } from "./inspect.js";
|
|
11
12
|
import { commandEnv, fail, loadRepoConfig, MIN_NODE_MAJOR, needRoot, nodeMajor, nodeTooOldMessage, printHuman, printJson, reportError, usageError, } from "./shared.js";
|
|
12
13
|
const GIT_TIMEOUT_MS = 30000;
|
|
@@ -53,7 +54,7 @@ async function evaluateGitChange(ports, req, hook, packIds, files, content, now,
|
|
|
53
54
|
const evaluated = await evaluateGateScope(ports, packIds, {
|
|
54
55
|
files: [...files],
|
|
55
56
|
note: hook,
|
|
56
|
-
...(content === "" ? {} : { proposedContent: content }),
|
|
57
|
+
...(content === "" ? {} : { proposedContent: addedLines(content) }),
|
|
57
58
|
}, now, configHash);
|
|
58
59
|
if (!evaluated.ok) {
|
|
59
60
|
return reportError(evaluated.error, req.json);
|
|
@@ -77,16 +78,20 @@ async function runPreCommit(req, ports, packIds, now, configHash) {
|
|
|
77
78
|
}
|
|
78
79
|
return evaluateGitChange(ports, req, "pre-commit", packIds, staged.value, diff.value, now, configHash);
|
|
79
80
|
}
|
|
80
|
-
// SPEC §4.14.4: git hands commit-msg
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
81
|
+
// SPEC §4.14.4: git hands commit-msg the COMMIT_EDITMSG path, absolute in a
|
|
82
|
+
// linked worktree (under the main repo's .git/worktrees/<name>/ dir, outside
|
|
83
|
+
// the worktree root) but relative to the repo root (`.git/COMMIT_EDITMSG`)
|
|
84
|
+
// in the main worktree. The confined Fs port rejects absolute paths
|
|
85
|
+
// (CDX-E701), so the hook uses kernel-owned host I/O — which itself
|
|
86
|
+
// requires absolute paths — resolving a relative handoff against the
|
|
87
|
+
// invocation cwd first; an unreadable or unwritable message file blocks
|
|
88
|
+
// fail-closed.
|
|
85
89
|
async function runCommitMsg(req, configHash) {
|
|
86
90
|
if (req.args.length !== 2 || req.args[1] === undefined) {
|
|
87
91
|
return usageError("usage: codictator git commit-msg <file>");
|
|
88
92
|
}
|
|
89
|
-
const
|
|
93
|
+
const raw = req.args[1];
|
|
94
|
+
const file = path.isAbsolute(raw) ? raw : path.resolve(req.cwd, raw);
|
|
90
95
|
const trailer = `Codictator-L3: ${configHash.slice(0, 12)}`;
|
|
91
96
|
const read = await readHostText(file);
|
|
92
97
|
if (!read.ok) {
|
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
|
}
|
|
@@ -20,7 +20,11 @@ export const LAUNCHER_TEXT = [
|
|
|
20
20
|
"// any newer syntax could load, and only node: built-ins are imported. It",
|
|
21
21
|
"// sets CODICTATOR_HOOK=1 in the engine process, then resolves the engine at",
|
|
22
22
|
"// <root>/node_modules/codictator/dist/cli/main.js else",
|
|
23
|
-
"// <root>/.codictator/tool/node_modules/codictator/dist/cli/main.js.
|
|
23
|
+
"// <root>/.codictator/tool/node_modules/codictator/dist/cli/main.js. The",
|
|
24
|
+
"// launcher reads the resolved engine's manifest version and passes it",
|
|
25
|
+
"// as main's third argument, so hook validation sees the installed",
|
|
26
|
+
"// version (SPEC §4.2.1 EC-11); an unreadable manifest falls back to",
|
|
27
|
+
"// the two-argument call. A",
|
|
24
28
|
"// missing engine answers the harness's embedded static CDX-E003 block",
|
|
25
29
|
"// reply; outside hook it prints CDX-E003 and exits 6. Git hooks arrive",
|
|
26
30
|
"// as a `git` first word and route to the engine like hook words do, so",
|
|
@@ -28,7 +32,7 @@ export const LAUNCHER_TEXT = [
|
|
|
28
32
|
"// Version arbitration",
|
|
29
33
|
"// between the root and tool-dir engines (SPEC §4.19 EC-6) belongs to the",
|
|
30
34
|
"// phase 9 CLI: until then the first candidate that exists wins.",
|
|
31
|
-
launcherImport("import { existsSync }", "fs"),
|
|
35
|
+
launcherImport("import { existsSync, readFileSync }", "fs"),
|
|
32
36
|
launcherImport("import path", "path"),
|
|
33
37
|
launcherImport("import { fileURLToPath, pathToFileURL }", "url"),
|
|
34
38
|
"",
|
|
@@ -141,6 +145,48 @@ export const LAUNCHER_TEXT = [
|
|
|
141
145
|
"\treturn null;",
|
|
142
146
|
"}",
|
|
143
147
|
"",
|
|
148
|
+
"// SPEC §4.2.1 EC-11: the engine entry validates config.codictator",
|
|
149
|
+
"// against the installed engine version, so the launcher extracts it",
|
|
150
|
+
"// from the resolved engine's own manifest text and passes it as",
|
|
151
|
+
"// main's third argument. A version-field scan keeps JSON parsing in",
|
|
152
|
+
"// the kernel per §10.3 item 6 (and a regex literal would confuse the",
|
|
153
|
+
"// blankCode lexer the ts/lint gate runs on); any unreadable or",
|
|
154
|
+
"// version-less manifest answers null and the caller falls back to",
|
|
155
|
+
"// the two-argument call, which keeps the pinned placeholder —",
|
|
156
|
+
"// fail-closed downstream, never an allow.",
|
|
157
|
+
"function installedEngineVersion(enginePath) {",
|
|
158
|
+
"\ttry {",
|
|
159
|
+
"\t\tconst manifestPath = path.join(",
|
|
160
|
+
"\t\t\tpath.dirname(path.dirname(path.dirname(enginePath))),",
|
|
161
|
+
`\t\t\t"package.json",`,
|
|
162
|
+
"\t\t);",
|
|
163
|
+
`\t\tconst text = readFileSync(manifestPath, "utf8");`,
|
|
164
|
+
'\t\tconst marker = "\\"version\\":";',
|
|
165
|
+
"\t\tconst at = text.indexOf(marker);",
|
|
166
|
+
"\t\tif (at === -1) {",
|
|
167
|
+
"\t\t\treturn null;",
|
|
168
|
+
"\t\t}",
|
|
169
|
+
"\t\tlet index = at + marker.length;",
|
|
170
|
+
'\t\twhile (text[index] === " " || text[index] === "\\t" || text[index] === "\\n" || text[index] === "\\r") {',
|
|
171
|
+
"\t\t\tindex += 1;",
|
|
172
|
+
"\t\t}",
|
|
173
|
+
'\t\tif (text[index] !== "\\"") {',
|
|
174
|
+
"\t\t\treturn null;",
|
|
175
|
+
"\t\t}",
|
|
176
|
+
'\t\tconst end = text.indexOf("\\"", index + 1);',
|
|
177
|
+
"\t\tif (end === -1) {",
|
|
178
|
+
"\t\t\treturn null;",
|
|
179
|
+
"\t\t}",
|
|
180
|
+
"\t\tconst version = text.slice(index + 1, end);",
|
|
181
|
+
'\t\tif (version === "") {',
|
|
182
|
+
"\t\t\treturn null;",
|
|
183
|
+
"\t\t}",
|
|
184
|
+
"\t\treturn version;",
|
|
185
|
+
"\t} catch (ignored) {",
|
|
186
|
+
"\t\treturn null;",
|
|
187
|
+
"\t}",
|
|
188
|
+
"}",
|
|
189
|
+
"",
|
|
144
190
|
"function reportMissing(harness) {",
|
|
145
191
|
"\tconst missing = staticReply(",
|
|
146
192
|
"\t\tharness,",
|
|
@@ -175,7 +221,11 @@ export const LAUNCHER_TEXT = [
|
|
|
175
221
|
`\t\t\t\tif (engine === null || typeof engine.main !== "function") {`,
|
|
176
222
|
`\t\t\t\t\tthrow new Error("engine entry has no main");`,
|
|
177
223
|
"\t\t\t\t}",
|
|
178
|
-
"\t\t\t\
|
|
224
|
+
"\t\t\t\tconst version = installedEngineVersion(enginePath);",
|
|
225
|
+
"\t\t\t\tif (version === null) {",
|
|
226
|
+
"\t\t\t\t\treturn engine.main(args, process.cwd());",
|
|
227
|
+
"\t\t\t\t}",
|
|
228
|
+
"\t\t\t\treturn engine.main(args, process.cwd(), version);",
|
|
179
229
|
"\t\t\t},",
|
|
180
230
|
"\t\t\t(error) => {",
|
|
181
231
|
"\t\t\t\tthrow error;",
|
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>>;
|