@felixdotgo/aidlc-kit 0.1.1 → 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/DEVELOPMENT.md +14 -0
- package/README.md +11 -13
- package/dist/assets/.agents/aidlc/docker/Dockerfile.claude +7 -0
- package/dist/assets/.agents/aidlc/docker/Dockerfile.codex +9 -0
- package/dist/assets/.agents/aidlc/docker/Dockerfile.opencode +8 -0
- package/dist/assets/.agents/aidlc/orchestrator.md +1 -1
- package/dist/assets/.agents/aidlc/phase-clarify.md +1 -1
- package/dist/assets/.agents/aidlc/phase-plan.md +2 -2
- package/dist/assets/.agents/aidlc/schemas/config.schema.json +2 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-authority.mjs +245 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-commit-message.mjs +45 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +232 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +15 -3
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +86 -28
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +7 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +22 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +40 -15
- package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +14 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +23 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +42 -15
- package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +75 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +60 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +142 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor.mjs +9 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +31 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-image.mjs +64 -0
- package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +57 -2
- package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +85 -73
- package/dist/assets/.agents/aidlc/scripts/loop-gate.mjs +19 -7
- package/dist/assets/.agents/aidlc/scripts/loop.mjs +2 -1
- package/dist/assets/.agents/aidlc/templates/design.md +9 -0
- package/dist/assets/.agents/aidlc/templates/intent.md +4 -0
- package/dist/assets/.agents/aidlc/templates/workplan.md +1 -1
- package/dist/assets/.agents/skills/aidlc-plan/SKILL.md +1 -1
- package/dist/assets/ui/bulma.min.css +3 -0
- package/dist/assets/ui/dashboard.css +414 -0
- package/dist/services/aidlc-coordination/Dockerfile +11 -3
- package/dist/services/aidlc-coordination/compose.yaml +12 -0
- package/dist/services/aidlc-coordination/package-lock.json +3 -1
- package/dist/services/aidlc-coordination/package.json +9 -4
- package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +57 -2
- package/dist/services/aidlc-coordination/src/repository.mjs +66 -0
- package/dist/services/aidlc-coordination/src/server.mjs +9 -3
- package/dist/services/aidlc-coordination/test/fleet.test.mjs +21 -0
- package/dist/services/aidlc-coordination/test/server-health-auth.test.mjs +67 -0
- package/dist/src/adapters.d.ts +0 -3
- package/dist/src/adapters.js +3 -5
- package/dist/src/cli/argv.d.ts +12 -0
- package/dist/src/cli/argv.js +62 -0
- package/dist/src/cli/commands.d.ts +8 -0
- package/dist/src/cli/commands.js +129 -0
- package/dist/src/cli/options.d.ts +25 -0
- package/dist/src/cli/options.js +32 -0
- package/dist/src/cli/prompt.d.ts +9 -0
- package/dist/src/cli/prompt.js +66 -0
- package/dist/src/cli.js +25 -191
- package/dist/src/gate.js +3 -2
- package/dist/src/installer.d.ts +14 -7
- package/dist/src/installer.js +38 -141
- package/dist/src/legacy.js +1 -0
- package/dist/src/managed-content.d.ts +7 -0
- package/dist/src/managed-content.js +15 -4
- package/dist/src/managed-planning.d.ts +65 -0
- package/dist/src/managed-planning.js +219 -0
- package/dist/src/mcp-setup.js +3 -1
- package/dist/src/model/config-contracts.d.ts +65 -0
- package/dist/src/model/config-contracts.js +1 -0
- package/dist/src/model/install-contracts.d.ts +29 -0
- package/dist/src/model/install-contracts.js +1 -0
- package/dist/src/model/lifecycle-enums.d.ts +13 -0
- package/dist/src/model/lifecycle-enums.js +1 -0
- package/dist/src/model/loop.d.ts +20 -0
- package/dist/src/model/loop.js +1 -0
- package/dist/src/model/manifest.d.ts +16 -0
- package/dist/src/model/manifest.js +1 -0
- package/dist/src/model/records.d.ts +138 -0
- package/dist/src/model/records.js +1 -0
- package/dist/src/model.d.ts +11 -264
- package/dist/src/model.js +1 -1
- package/dist/src/profiles.d.ts +2 -0
- package/dist/src/profiles.js +53 -9
- package/dist/src/state/archive-record.d.ts +9 -0
- package/dist/src/state/archive-record.js +26 -0
- package/dist/src/state/commands.d.ts +18 -0
- package/dist/src/state/commands.js +27 -0
- package/dist/src/state/domain.d.ts +9 -0
- package/dist/src/state/domain.js +9 -0
- package/dist/src/state/guards.d.ts +7 -0
- package/dist/src/state/guards.js +17 -0
- package/dist/src/state/io.d.ts +10 -0
- package/dist/src/state/io.js +35 -0
- package/dist/src/state/items.d.ts +3 -4
- package/dist/src/state/items.js +58 -57
- package/dist/src/state/lessons.js +14 -7
- package/dist/src/state/lifecycle.js +2 -2
- package/dist/src/state/lock.js +7 -6
- package/dist/src/state/memory.js +24 -12
- package/dist/src/state/{archive.js → migrate.js} +7 -7
- package/dist/src/state/partition.d.ts +15 -0
- package/dist/src/state/partition.js +90 -0
- package/dist/src/state/paths.d.ts +11 -0
- package/dist/src/state/paths.js +12 -0
- package/dist/src/state/render.js +9 -6
- package/dist/src/state/store.js +9 -15
- package/dist/src/state/transitions.js +9 -7
- package/dist/src/state/validate.js +50 -7
- package/dist/src/state.d.ts +16 -11
- package/dist/src/state.js +16 -11
- package/dist/src/task-ui.d.ts +54 -0
- package/dist/src/task-ui.js +1570 -0
- package/dist/src/upgrade-board.js +10 -10
- package/dist/src/upgrade.d.ts +0 -1
- package/dist/src/upgrade.js +19 -83
- package/dist/src/workflow.js +3 -3
- package/docs/README.md +6 -2
- package/docs/architecture.md +9 -8
- package/docs/command-reference.md +81 -20
- package/docs/configuration.md +28 -8
- package/docs/coordination-service.md +18 -6
- package/docs/design-decisions.md +83 -0
- package/docs/development.md +27 -2
- package/docs/loop-local-run.md +495 -0
- package/docs/loop-runbook.md +235 -0
- package/docs/operating-workflow.md +2 -2
- package/docs/testing-and-release.md +5 -1
- package/docs/troubleshooting.md +125 -0
- package/package.json +4 -1
- package/dist/src/ledger.d.ts +0 -22
- package/dist/src/ledger.js +0 -119
- package/dist/src/state/shared.d.ts +0 -29
- package/dist/src/state/shared.js +0 -71
- package/dist/src/tracker.d.ts +0 -36
- package/dist/src/tracker.js +0 -44
- /package/dist/src/state/{archive.d.ts → migrate.d.ts} +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive layer: TTY detection, dynamic @inquirer/prompts loading, and the
|
|
3
|
+
* prompting flows (loop configuration, MCP setup selections, confirmations).
|
|
4
|
+
* Parsing of argv into options happens in ./argv.ts and ./options.ts.
|
|
5
|
+
*/
|
|
6
|
+
import { stdin, stdout } from "node:process";
|
|
7
|
+
import { LOOP_PROVIDERS } from "../model.js";
|
|
8
|
+
import { validateLoopConfig } from "../profiles.js";
|
|
9
|
+
import { loopSelection, mcpArgSelections, parseWorkerMapping } from "./options.js";
|
|
10
|
+
export const interactive = () => Boolean(stdin.isTTY && stdout.isTTY);
|
|
11
|
+
const prompts = async () => import("@inquirer/prompts");
|
|
12
|
+
export const chooseLoop = async (args, upgrade) => {
|
|
13
|
+
let { mode, lead: leadArg, workers: workersArg } = loopSelection(args);
|
|
14
|
+
if (!mode && (leadArg || workersArg))
|
|
15
|
+
throw new Error("--loop-lead and --loop-workers require --loop-mode");
|
|
16
|
+
if (!mode && interactive()) {
|
|
17
|
+
const { select } = await prompts();
|
|
18
|
+
mode = await select({ message: "Loop execution mode", choices: [
|
|
19
|
+
...(upgrade ? [{ name: "Keep existing loop configuration", value: "keep" }] : []),
|
|
20
|
+
{ name: "Off", value: "off" }, { name: "Local", value: "local" }, { name: "Fleet", value: "fleet" }
|
|
21
|
+
] });
|
|
22
|
+
}
|
|
23
|
+
if (!mode || mode === "keep")
|
|
24
|
+
return undefined;
|
|
25
|
+
if (mode === "off") {
|
|
26
|
+
if (leadArg || workersArg)
|
|
27
|
+
throw new Error("Disabled loop cannot specify agents");
|
|
28
|
+
return validateLoopConfig({ mode });
|
|
29
|
+
}
|
|
30
|
+
let lead = leadArg, rawWorkers = workersArg;
|
|
31
|
+
if (interactive() && (!lead || !rawWorkers)) {
|
|
32
|
+
const { select, input } = await prompts();
|
|
33
|
+
lead ??= await select({ message: "Lead agent", choices: LOOP_PROVIDERS.map((name) => ({ name, value: name })) });
|
|
34
|
+
rawWorkers ??= await input({ message: "Workers (id=provider, comma separated)", default: "codex-worker=codex,claude-worker=claude" });
|
|
35
|
+
}
|
|
36
|
+
if (!lead || !rawWorkers)
|
|
37
|
+
throw new Error("An enabled loop requires --loop-lead and --loop-workers");
|
|
38
|
+
return validateLoopConfig({ mode, agents: { lead, workers: parseWorkerMapping(rawWorkers) } });
|
|
39
|
+
};
|
|
40
|
+
export const mcpSetupSelections = async (args) => {
|
|
41
|
+
const selected = mcpArgSelections(args);
|
|
42
|
+
if ((!selected.deployment || !selected.storage || !selected.bind || !selected.workspace) && !interactive())
|
|
43
|
+
throw new Error("mcp setup outside a TTY requires --deployment, --storage, --bind and --workspace");
|
|
44
|
+
if (interactive()) {
|
|
45
|
+
const { input, select, checkbox } = await prompts();
|
|
46
|
+
selected.deployment ??= await select({ message: "State service deployment", choices: [{ name: "Docker on this machine", value: "docker" }, { name: "Remote service (configure endpoint later)", value: "remote" }] });
|
|
47
|
+
selected.storage ??= await select({ message: "Database", choices: [{ name: "SQLite (local Docker volume)", value: "sqlite" }, { name: "PostgreSQL (multi-host production)", value: "postgres" }] });
|
|
48
|
+
selected.bind ??= await select({ message: "Network binding", choices: [{ name: "localhost only", value: "localhost" }, { name: "network (requires token/TLS)", value: "network" }] });
|
|
49
|
+
selected.workspace ??= await input({ message: "Workspace ID", default: "default", required: true });
|
|
50
|
+
if (!selected.providers.length)
|
|
51
|
+
selected.providers = await checkbox({ message: "Project-management connectors to configure", choices: ["jira", "trello", "github-issues"].map((value) => ({ name: value, value })) });
|
|
52
|
+
}
|
|
53
|
+
if (!Number.isInteger(selected.pollMs) || selected.pollMs < 1_000 || selected.pollMs > 3_600_000)
|
|
54
|
+
throw new Error("--poll-ms must be an integer from 1000 to 3600000");
|
|
55
|
+
return selected;
|
|
56
|
+
};
|
|
57
|
+
export const confirmGeneral = async (options, operation) => {
|
|
58
|
+
if (options.dryRun)
|
|
59
|
+
return false;
|
|
60
|
+
if (options.yes)
|
|
61
|
+
return true;
|
|
62
|
+
if (!interactive())
|
|
63
|
+
return false;
|
|
64
|
+
const { confirm } = await prompts();
|
|
65
|
+
return confirm({ message: `${operation} this plan?`, default: false });
|
|
66
|
+
};
|
package/dist/src/cli.js
CHANGED
|
@@ -1,96 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { applyPlan, detectedAgents,
|
|
8
|
-
import { LOOP_PROVIDERS } from "./model.js";
|
|
9
|
-
import { loadProjectConfig, resolveProfiles, validateLoopConfig } from "./profiles.js";
|
|
2
|
+
import { resolvedAdapters } from "./adapters.js";
|
|
3
|
+
import { assertKnownOptions, flags, INIT_FLAGS, rootArg, UNINSTALL_FLAGS, UPGRADE_FLAGS, usage } from "./cli/argv.js";
|
|
4
|
+
import { runDoctor, runLoopCommand, runMcp, runProfile, runStatus, runUi } from "./cli/commands.js";
|
|
5
|
+
import { initOptions } from "./cli/options.js";
|
|
6
|
+
import { chooseLoop, confirmGeneral, interactive } from "./cli/prompt.js";
|
|
7
|
+
import { applyPlan, detectedAgents, formatPlan, planInit, planUninstall, selectAgents } from "./installer.js";
|
|
10
8
|
import { applyUpgrade, planUpgrade } from "./upgrade.js";
|
|
11
9
|
import { packageVersion } from "./workflow.js";
|
|
12
|
-
|
|
13
|
-
const usage = `Usage:
|
|
14
|
-
npx @felixdotgo/aidlc-kit init [path] [--agent <name[,name]> | --all] [--loop-mode off|local|fleet] [--loop-lead ${LOOP_PROVIDERS.join("|")}] [--loop-workers <id=provider,...>] [--yes] [--dry-run] [--force]
|
|
15
|
-
npx @felixdotgo/aidlc-kit upgrade [path] [--loop-mode off|local|fleet] [--loop-lead ${LOOP_PROVIDERS.join("|")}] [--loop-workers <id=provider,...>] [--dry-run]
|
|
16
|
-
npx @felixdotgo/aidlc-kit uninstall [path] [--yes] [--dry-run]
|
|
17
|
-
npx @felixdotgo/aidlc-kit status [path]
|
|
18
|
-
npx @felixdotgo/aidlc-kit doctor [path] [--strict]
|
|
19
|
-
npx @felixdotgo/aidlc-kit profile validate [path]
|
|
20
|
-
npx @felixdotgo/aidlc-kit loop start|status|resume [--task-id ID] [--root PATH] [--mode local|fleet]
|
|
21
|
-
npx @felixdotgo/aidlc-kit mcp setup [path] [--dry-run] [--yes] [--storage sqlite|postgres] [--deployment docker|remote] [--bind localhost|network] [--workspace <id>] [--poll-ms <n>] [--enable]`;
|
|
22
|
-
const interactive = () => Boolean(stdin.isTTY && stdout.isTTY);
|
|
10
|
+
/* Kept in this file: test/unit/cli.test.ts pins these prompt sources (and the upgrade boundary strings below) to src/cli.ts; the shared interactive flows live in ./cli/prompt.ts. */
|
|
23
11
|
const prompts = async () => import("@inquirer/prompts");
|
|
24
|
-
/* ---
|
|
25
|
-
const value = (args, name) => {
|
|
26
|
-
const index = args.indexOf(name);
|
|
27
|
-
return index < 0 ? undefined : args[index + 1];
|
|
28
|
-
};
|
|
29
|
-
const flags = (args) => new Set(args.filter((item) => item.startsWith("--")));
|
|
30
|
-
const valueFlags = new Set(["--agent", "--storage", "--deployment", "--bind", "--workspace", "--poll-ms", "--token-env", "--providers", "--loop-mode", "--loop-lead", "--loop-workers"]);
|
|
31
|
-
const COMMAND_KEYWORDS = ["init", "upgrade", "uninstall", "status", "doctor", "validate", "run", "list"];
|
|
32
|
-
const positional = (args) => {
|
|
33
|
-
const result = [];
|
|
34
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
35
|
-
if (valueFlags.has(args[index])) {
|
|
36
|
-
index += 1;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (!args[index].startsWith("--"))
|
|
40
|
-
result.push(args[index]);
|
|
41
|
-
}
|
|
42
|
-
return result;
|
|
43
|
-
};
|
|
44
|
-
const rootArg = (args, fallback = ".") => positional(args).find((item) => !COMMAND_KEYWORDS.includes(item)) ?? fallback;
|
|
45
|
-
/* --- Loop configuration --------------------------------------------------- */
|
|
46
|
-
const parseWorkerMapping = (rawWorkers) => {
|
|
47
|
-
const workers = Object.create(null);
|
|
48
|
-
for (const entry of rawWorkers.split(",")) {
|
|
49
|
-
const parts = entry.split("=");
|
|
50
|
-
if (parts.length !== 2 || !parts[0] || !parts[1] || Object.hasOwn(workers, parts[0]))
|
|
51
|
-
throw new Error("Invalid or duplicate loop worker mapping");
|
|
52
|
-
workers[parts[0]] = parts[1];
|
|
53
|
-
}
|
|
54
|
-
return workers;
|
|
55
|
-
};
|
|
56
|
-
const chooseLoop = async (args, upgrade) => {
|
|
57
|
-
let mode = value(args, "--loop-mode");
|
|
58
|
-
const leadArg = value(args, "--loop-lead"), workersArg = value(args, "--loop-workers");
|
|
59
|
-
if (!mode && (leadArg || workersArg))
|
|
60
|
-
throw new Error("--loop-lead and --loop-workers require --loop-mode");
|
|
61
|
-
if (!mode && interactive()) {
|
|
62
|
-
const { select } = await prompts();
|
|
63
|
-
mode = await select({ message: "Loop execution mode", choices: [
|
|
64
|
-
...(upgrade ? [{ name: "Keep existing loop configuration", value: "keep" }] : []),
|
|
65
|
-
{ name: "Off", value: "off" }, { name: "Local", value: "local" }, { name: "Fleet", value: "fleet" }
|
|
66
|
-
] });
|
|
67
|
-
}
|
|
68
|
-
if (!mode || mode === "keep")
|
|
69
|
-
return undefined;
|
|
70
|
-
if (mode === "off") {
|
|
71
|
-
if (leadArg || workersArg)
|
|
72
|
-
throw new Error("Disabled loop cannot specify agents");
|
|
73
|
-
return validateLoopConfig({ mode });
|
|
74
|
-
}
|
|
75
|
-
let lead = leadArg, rawWorkers = workersArg;
|
|
76
|
-
if (interactive() && (!lead || !rawWorkers)) {
|
|
77
|
-
const { select, input } = await prompts();
|
|
78
|
-
lead ??= await select({ message: "Lead agent", choices: LOOP_PROVIDERS.map((name) => ({ name, value: name })) });
|
|
79
|
-
rawWorkers ??= await input({ message: "Workers (id=provider, comma separated)", default: "codex-worker=codex,claude-worker=claude" });
|
|
80
|
-
}
|
|
81
|
-
if (!lead || !rawWorkers)
|
|
82
|
-
throw new Error("An enabled loop requires --loop-lead and --loop-workers");
|
|
83
|
-
return validateLoopConfig({ mode, agents: { lead, workers: parseWorkerMapping(rawWorkers) } });
|
|
84
|
-
};
|
|
85
|
-
/* --- Option builders and shared flows -------------------------------------- */
|
|
86
|
-
const initOptions = (args) => ({
|
|
87
|
-
root: rootArg(args),
|
|
88
|
-
agents: (value(args, "--agent") ?? "").split(",").filter(Boolean),
|
|
89
|
-
all: flags(args).has("--all"),
|
|
90
|
-
dryRun: flags(args).has("--dry-run"),
|
|
91
|
-
yes: flags(args).has("--yes"),
|
|
92
|
-
force: flags(args).has("--force")
|
|
93
|
-
});
|
|
12
|
+
/* --- Agent selection and plan preview -------------------------------------- */
|
|
94
13
|
const chooseAgents = async (options) => {
|
|
95
14
|
const selected = selectAgents(options);
|
|
96
15
|
if (selected.length)
|
|
@@ -101,7 +20,7 @@ const chooseAgents = async (options) => {
|
|
|
101
20
|
if (!interactive())
|
|
102
21
|
throw new Error(`Unable to select an agent automatically. Use --agent <name> or --all. Detected: ${detected.join(", ") || "none"}`);
|
|
103
22
|
const { checkbox } = await prompts();
|
|
104
|
-
const choices =
|
|
23
|
+
const choices = resolvedAdapters();
|
|
105
24
|
const agents = await checkbox({
|
|
106
25
|
message: "Select one or more agents",
|
|
107
26
|
choices: choices.map((adapter) => ({
|
|
@@ -116,67 +35,9 @@ const preview = (operation, plan) => {
|
|
|
116
35
|
console.log(`◆ ${operation} preview — changes have NOT been applied`);
|
|
117
36
|
console.log(formatPlan(plan));
|
|
118
37
|
};
|
|
119
|
-
|
|
120
|
-
if (options.dryRun)
|
|
121
|
-
return false;
|
|
122
|
-
if (options.yes)
|
|
123
|
-
return true;
|
|
124
|
-
if (!interactive())
|
|
125
|
-
return false;
|
|
126
|
-
const { confirm } = await prompts();
|
|
127
|
-
return confirm({ message: `${operation} this plan?`, default: false });
|
|
128
|
-
};
|
|
129
|
-
/* --- MCP setup options ----------------------------------------------------- */
|
|
130
|
-
const mcpSetupSelections = async (args) => {
|
|
131
|
-
const selected = {
|
|
132
|
-
deployment: value(args, "--deployment"),
|
|
133
|
-
storage: value(args, "--storage"),
|
|
134
|
-
bind: value(args, "--bind"),
|
|
135
|
-
workspace: value(args, "--workspace"),
|
|
136
|
-
pollMs: Number(value(args, "--poll-ms") ?? 30_000),
|
|
137
|
-
tokenEnv: value(args, "--token-env") ?? "AIDLC_MCP_TOKEN",
|
|
138
|
-
providers: (value(args, "--providers") ?? "").split(",").filter(Boolean)
|
|
139
|
-
};
|
|
140
|
-
if ((!selected.deployment || !selected.storage || !selected.bind || !selected.workspace) && !interactive())
|
|
141
|
-
throw new Error("mcp setup outside a TTY requires --deployment, --storage, --bind and --workspace");
|
|
142
|
-
if (interactive()) {
|
|
143
|
-
const { input, select, checkbox } = await prompts();
|
|
144
|
-
selected.deployment ??= await select({ message: "State service deployment", choices: [{ name: "Docker on this machine", value: "docker" }, { name: "Remote service (configure endpoint later)", value: "remote" }] });
|
|
145
|
-
selected.storage ??= await select({ message: "Database", choices: [{ name: "SQLite (local Docker volume)", value: "sqlite" }, { name: "PostgreSQL (multi-host production)", value: "postgres" }] });
|
|
146
|
-
selected.bind ??= await select({ message: "Network binding", choices: [{ name: "localhost only", value: "localhost" }, { name: "network (requires token/TLS)", value: "network" }] });
|
|
147
|
-
selected.workspace ??= await input({ message: "Workspace ID", default: "default", required: true });
|
|
148
|
-
if (!selected.providers.length)
|
|
149
|
-
selected.providers = await checkbox({ message: "Project-management connectors to configure", choices: ["jira", "trello", "github-issues"].map((value) => ({ name: value, value })) });
|
|
150
|
-
}
|
|
151
|
-
if (!Number.isInteger(selected.pollMs) || selected.pollMs < 1_000 || selected.pollMs > 3_600_000)
|
|
152
|
-
throw new Error("--poll-ms must be an integer from 1000 to 3600000");
|
|
153
|
-
return selected;
|
|
154
|
-
};
|
|
155
|
-
const mcpSetupOptions = async (args) => {
|
|
156
|
-
const selected = await mcpSetupSelections(args);
|
|
157
|
-
return { root: rootArg(args), template: join(fileURLToPath(new URL("../services/aidlc-coordination", import.meta.url))), dryRun: flags(args).has("--dry-run"), deployment: selected.deployment, storage: selected.storage, bind: selected.bind, workspace: selected.workspace, pollMs: selected.pollMs, tokenEnv: selected.tokenEnv, providers: selected.providers, enable: flags(args).has("--enable") };
|
|
158
|
-
};
|
|
159
|
-
/* --- Command handlers ------------------------------------------------------ */
|
|
160
|
-
const runLoopCommand = (args) => {
|
|
161
|
-
const action = args[0];
|
|
162
|
-
if (!["start", "status", "resume"].includes(action))
|
|
163
|
-
throw new Error("Expected loop start, status, or resume");
|
|
164
|
-
const parsed = new Map();
|
|
165
|
-
for (let index = 1; index < args.length; index += 2) {
|
|
166
|
-
const key = args[index], entry = args[index + 1];
|
|
167
|
-
if (!["--task-id", "--root", "--mode"].includes(key) || !entry || entry.startsWith("--") || parsed.has(key))
|
|
168
|
-
throw new Error("Invalid or duplicate loop option");
|
|
169
|
-
parsed.set(key, entry);
|
|
170
|
-
}
|
|
171
|
-
const root = resolve(parsed.get("--root") ?? ".");
|
|
172
|
-
const script = join(root, ".agents", "aidlc", "scripts", "loop-entry.mjs");
|
|
173
|
-
const child = spawnSync(process.execPath, [script, action, parsed.get("--task-id") ?? "", root, parsed.get("--mode") ?? ""], { stdio: "inherit" });
|
|
174
|
-
if (child.error)
|
|
175
|
-
throw child.error;
|
|
176
|
-
if (child.status !== 0)
|
|
177
|
-
process.exitCode = child.status ?? 1;
|
|
178
|
-
};
|
|
38
|
+
/* --- Command handlers that consume the pinned prompts above ----------------- */
|
|
179
39
|
const runInit = async (args) => {
|
|
40
|
+
assertKnownOptions(args, INIT_FLAGS);
|
|
180
41
|
const options = { ...await chooseAgents(initOptions(args)), loop: await chooseLoop(args, false) };
|
|
181
42
|
const plan = planInit(options);
|
|
182
43
|
preview("INIT", plan);
|
|
@@ -194,6 +55,7 @@ const runInit = async (args) => {
|
|
|
194
55
|
}
|
|
195
56
|
};
|
|
196
57
|
const runUninstall = async (args) => {
|
|
58
|
+
assertKnownOptions(args, UNINSTALL_FLAGS);
|
|
197
59
|
const options = initOptions(args);
|
|
198
60
|
const plan = planUninstall(options.root);
|
|
199
61
|
preview("UNINSTALL", plan);
|
|
@@ -203,6 +65,7 @@ const runUninstall = async (args) => {
|
|
|
203
65
|
console.log("Removed eligible managed assets; project config and state were preserved.");
|
|
204
66
|
};
|
|
205
67
|
const runUpgrade = async (args) => {
|
|
68
|
+
assertKnownOptions(args, UPGRADE_FLAGS);
|
|
206
69
|
if (flags(args).has("--yes") || flags(args).has("--force"))
|
|
207
70
|
throw new Error("upgrade does not support --yes or --force; apply requires a human interactive TTY confirmation");
|
|
208
71
|
const root = rootArg(args);
|
|
@@ -221,48 +84,19 @@ const runUpgrade = async (args) => {
|
|
|
221
84
|
const result = applyUpgrade(root, plan);
|
|
222
85
|
console.log(`Upgrade complete. Backup: ${result.backup}; changed: ${result.changed}`);
|
|
223
86
|
};
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
console.log(`◆ MCP SETUP preview — ${options.dryRun ? "no files will be written" : "review before applying"}`);
|
|
228
|
-
console.log(JSON.stringify(plan, null, 2));
|
|
229
|
-
if (options.dryRun)
|
|
230
|
-
return;
|
|
231
|
-
if (!await confirmGeneral({ ...initOptions(args), root: options.root }, "Apply MCP setup"))
|
|
232
|
-
return;
|
|
233
|
-
applyMcpSetup(options);
|
|
234
|
-
console.log(`MCP setup written to ${plan.target}. MCP remains ${options.enable ? "enabled" : "disabled"}; start Docker explicitly with docker compose --env-file .env -f ${join(plan.target, "compose.yaml")} up -d.`);
|
|
235
|
-
};
|
|
87
|
+
const runHelp = () => console.log(usage);
|
|
88
|
+
const runVersion = () => console.log(packageVersion());
|
|
89
|
+
const REGISTRY = { init: runInit, upgrade: runUpgrade, uninstall: runUninstall, status: runStatus, doctor: runDoctor, loop: runLoopCommand, ui: runUi, mcp: runMcp, profile: runProfile, version: runVersion, help: runHelp };
|
|
236
90
|
const main = async () => {
|
|
237
|
-
const
|
|
238
|
-
if (
|
|
91
|
+
const argv = process.argv.slice(2);
|
|
92
|
+
if (argv.includes("--version") || argv.includes("-v"))
|
|
93
|
+
return void console.log(packageVersion());
|
|
94
|
+
if (argv.includes("--help"))
|
|
239
95
|
return void console.log(usage);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const result = doctor(rootArg(args), flags(args).has("--strict"));
|
|
246
|
-
console.log(result);
|
|
247
|
-
if (result.startsWith("ERROR:"))
|
|
248
|
-
process.exitCode = 1;
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
if (command === "init")
|
|
252
|
-
return void await runInit(args);
|
|
253
|
-
if (command === "uninstall")
|
|
254
|
-
return void await runUninstall(args);
|
|
255
|
-
if (command === "upgrade")
|
|
256
|
-
return void await runUpgrade(args);
|
|
257
|
-
if (command === "mcp" && args[0] === "setup")
|
|
258
|
-
return void await runMcpSetup(args);
|
|
259
|
-
if (command === "profile" && args[0] === "validate") {
|
|
260
|
-
const root = rootArg(args.slice(1));
|
|
261
|
-
const config = loadProjectConfig(root);
|
|
262
|
-
const profiles = resolveProfiles(root, config.extends);
|
|
263
|
-
console.log(`OK: ${profiles.map((item) => item.id).join(" → ")}`);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
throw new Error(`Unknown command: ${command}`);
|
|
96
|
+
const [command = "help", ...args] = argv;
|
|
97
|
+
const handler = Object.hasOwn(REGISTRY, command) ? REGISTRY[command] : undefined;
|
|
98
|
+
if (!handler)
|
|
99
|
+
throw new Error(`Unknown command: ${command}`);
|
|
100
|
+
return void await handler(args);
|
|
267
101
|
};
|
|
268
102
|
main().catch((error) => { console.error(`aidlc-kit: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; });
|
package/dist/src/gate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { resolveProjectPathWithoutSymlinks } from "./project-path.js";
|
|
3
|
-
import { hasAreaVerification, hasReview, isLegacyG2Wait, nextAction, repairBounds, transitionDiagnostics, transitionTask } from "./state.js";
|
|
3
|
+
import { hasAreaVerification, hasReview, isLegacyG2Wait, loopPartitionStrict, nextAction, partitionDiagnostics, repairBounds, transitionDiagnostics, transitionTask } from "./state.js";
|
|
4
4
|
const safeArtifactTarget = (root, path) => resolveProjectPathWithoutSymlinks(root, path, "Unsafe artifact path", "Artifact path crosses a symlink");
|
|
5
5
|
const artifactContent = (root, path) => {
|
|
6
6
|
if (!path)
|
|
@@ -40,6 +40,7 @@ const reviewGateDiagnostics = (root, task) => {
|
|
|
40
40
|
if (content !== undefined)
|
|
41
41
|
for (const heading of missingHeadings(content, ["## 🧩 Solution per affected area", "## 📌 Spec traceability", "## 🔗 Cross-service contracts", "## ⚠️ Risks / edge cases"]))
|
|
42
42
|
diagnostics.push({ level: "ERROR", code: "DESIGN_HEADING", message: `Design is missing ${heading}` });
|
|
43
|
+
diagnostics.push(...partitionDiagnostics(task, { strict: loopPartitionStrict(root) }));
|
|
43
44
|
return diagnostics;
|
|
44
45
|
};
|
|
45
46
|
const codeReviewGateDiagnostics = (root, task) => {
|
|
@@ -82,7 +83,7 @@ export const checkGate = (root, state, taskId, gate) => {
|
|
|
82
83
|
diagnostics.push(...reviewGateDiagnostics(root, task));
|
|
83
84
|
if (gate === "G2_codereview")
|
|
84
85
|
diagnostics.push(...codeReviewGateDiagnostics(root, task));
|
|
85
|
-
if (!diagnostics.
|
|
86
|
+
if (!diagnostics.some((item) => item.level === "ERROR"))
|
|
86
87
|
diagnostics.push({ level: "INFO", code: "GATE_OK", message: `${gate} checks passed for ${taskId}` });
|
|
87
88
|
return diagnostics;
|
|
88
89
|
};
|
package/dist/src/installer.d.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import type { AgentId,
|
|
2
|
-
/**
|
|
3
|
-
export
|
|
4
|
-
export declare const managedContentHash: (spec: Pick<FileSpec, "owner" | "strategy">, content: string) => string | undefined;
|
|
5
|
-
export declare const readManifest: (root: string) => WorkflowManifest | undefined;
|
|
1
|
+
import type { AgentId, InitOptions, PlannedWrite } from "./model.js";
|
|
2
|
+
/** Manifest reading lives in the managed-planning core; this export path stays stable for existing importers. */
|
|
3
|
+
export { readManifest } from "./managed-planning.js";
|
|
6
4
|
export declare const detectedAgents: (root: string) => AgentId[];
|
|
7
5
|
export declare const selectAgents: (options: Pick<InitOptions, "root" | "agents" | "all">) => AgentId[];
|
|
8
|
-
/** Include an explicitly selected loop config in the same preview/transaction as setup. */
|
|
9
|
-
export declare const planLoopSelection: (plan: PlannedWrite[], selection?: LoopConfig) => void;
|
|
10
6
|
/** Plans an init transaction: managed workflow files, manifest, and project-owned files. */
|
|
11
7
|
export declare const planInit: (options: InitOptions) => PlannedWrite[];
|
|
12
8
|
/** Applies a planned init/uninstall transaction to disk. */
|
|
@@ -14,7 +10,18 @@ export declare const applyPlan: (root: string, plan: PlannedWrite[]) => void;
|
|
|
14
10
|
export declare const formatPlan: (plan: PlannedWrite[]) => string;
|
|
15
11
|
/** Plans an uninstall transaction that removes only unchanged managed assets. */
|
|
16
12
|
export declare const planUninstall: (root: string) => PlannedWrite[];
|
|
13
|
+
export declare const statusData: (root: string) => {
|
|
14
|
+
workflow: string;
|
|
15
|
+
installedVersion: string;
|
|
16
|
+
schema: string;
|
|
17
|
+
detectedAgents: string[];
|
|
18
|
+
};
|
|
17
19
|
/** Reports installed workflow version, schema, and detected agents. */
|
|
18
20
|
export declare const status: (root: string) => string;
|
|
19
21
|
/** Validates the installed workflow manifest and local config/state. */
|
|
20
22
|
export declare const doctor: (root: string, strict?: boolean) => string;
|
|
23
|
+
export declare const doctorData: (root: string, strict?: boolean) => {
|
|
24
|
+
level: "ok" | "warn" | "error";
|
|
25
|
+
message: string;
|
|
26
|
+
strict: boolean;
|
|
27
|
+
};
|
package/dist/src/installer.js
CHANGED
|
@@ -1,113 +1,23 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { adapters, findAdapter,
|
|
4
|
-
import { loadProjectConfig
|
|
3
|
+
import { adapters, findAdapter, resolvedAdapters } from "./adapters.js";
|
|
4
|
+
import { loadProjectConfig } from "./profiles.js";
|
|
5
5
|
import { loadState } from "./state.js";
|
|
6
6
|
import { planProjectLayoutMigration } from "./project-migration.js";
|
|
7
|
-
import { managedBlock,
|
|
7
|
+
import { legacyManagedMarker, managedBlock, removeManagedBlock } from "./managed-content.js";
|
|
8
|
+
import { hasOwnedManifest, managedContentHash, planLoopSelection, planManagedWrites, readManifest, readManifestFile, splitInventoryKey } from "./managed-planning.js";
|
|
8
9
|
import { resolveProjectPathWithoutSymlinks } from "./project-path.js";
|
|
9
10
|
import { contentHash, coreWorkflowFiles, initialProjectFiles, manifestSpec, packageVersion } from "./workflow.js";
|
|
11
|
+
/** Manifest reading lives in the managed-planning core; this export path stays stable for existing importers. */
|
|
12
|
+
export { readManifest } from "./managed-planning.js";
|
|
10
13
|
const shortHash = (content) => contentHash(content).slice(0, 12);
|
|
11
14
|
const safeTarget = (root, path) => resolveProjectPathWithoutSymlinks(root, path, "Managed path escapes the project", "Managed path crosses a symlink");
|
|
12
|
-
|
|
13
|
-
export const installedHashFor = (files, spec) => {
|
|
14
|
-
if (!files)
|
|
15
|
-
return undefined;
|
|
16
|
-
const keyed = files[`${spec.path}#${spec.owner}`];
|
|
17
|
-
if (keyed)
|
|
18
|
-
return keyed.hash;
|
|
19
|
-
const legacy = files[spec.path];
|
|
20
|
-
return legacy && (spec.strategy !== "managed-block" || legacy.owner === spec.owner) ? legacy.hash : undefined;
|
|
21
|
-
};
|
|
22
|
-
export const managedContentHash = (spec, content) => {
|
|
23
|
-
const managed = managedBlock(spec, content);
|
|
24
|
-
return managed === undefined ? undefined : contentHash(managed);
|
|
25
|
-
};
|
|
26
|
-
export const readManifest = (root) => {
|
|
27
|
-
const path = join(resolve(root), ".agents/aidlc/manifest.json");
|
|
28
|
-
if (!existsSync(path))
|
|
29
|
-
return undefined;
|
|
30
|
-
try {
|
|
31
|
-
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
32
|
-
const validFiles = value.files && typeof value.files === "object" && !Array.isArray(value.files) && Object.values(value.files).every((item) => item && typeof item.hash === "string" && typeof item.owner === "string" && ["replace", "managed-block"].includes(item.strategy));
|
|
33
|
-
return value.schemaVersion === 2 && typeof value.packageVersion === "string" && value.workflow === "AI-DLC" && value.source === "local-package-assets" && value.remoteUpdates === false && (value.managedBy === "aidlc-kit" || value.managedBy === "aidlc-workflow") && Array.isArray(value.adapters) && validFiles ? value : undefined;
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return undefined;
|
|
37
|
-
} /* intentional: a malformed manifest is treated as "not installed" */
|
|
38
|
-
};
|
|
39
|
-
export const detectedAgents = (root) => installableAdapters().filter((adapter) => adapter.detect(root)).map((adapter) => adapter.id);
|
|
15
|
+
export const detectedAgents = (root) => resolvedAdapters().filter((adapter) => adapter.detect(root)).map((adapter) => adapter.id);
|
|
40
16
|
export const selectAgents = (options) => {
|
|
41
17
|
if (options.all)
|
|
42
18
|
return adapters.map((adapter) => adapter.id);
|
|
43
19
|
return [...new Set(options.agents)];
|
|
44
20
|
};
|
|
45
|
-
const planFile = (root, spec, force, installed) => {
|
|
46
|
-
const target = safeTarget(root, spec.path);
|
|
47
|
-
if (spec.ownershipClass === "project" || spec.ownershipClass === "state") {
|
|
48
|
-
return existsSync(target)
|
|
49
|
-
? { ...spec, content: readFileSync(target, "utf8"), action: "preserve", reason: `${spec.ownershipClass}-owned file` }
|
|
50
|
-
: { ...spec, action: "create", reason: `new ${spec.ownershipClass}-owned file` };
|
|
51
|
-
}
|
|
52
|
-
if (spec.strategy === "managed-block") {
|
|
53
|
-
const current = existsSync(target) ? readFileSync(target, "utf8") : "";
|
|
54
|
-
const next = mergeManagedBlock(spec, current);
|
|
55
|
-
return { ...spec, content: next, action: existsSync(target) ? (next === current ? "skip" : "update") : "create", reason: existsSync(target) ? "managed prompt block" : "new managed prompt" };
|
|
56
|
-
}
|
|
57
|
-
if (!existsSync(target))
|
|
58
|
-
return { ...spec, action: "create", reason: "new managed file" };
|
|
59
|
-
const current = readFileSync(target, "utf8");
|
|
60
|
-
if (current === spec.content)
|
|
61
|
-
return { ...spec, action: "skip", reason: "already current" };
|
|
62
|
-
if (spec.path === ".agents/aidlc/manifest.json" && installed)
|
|
63
|
-
return { ...spec, action: "update", reason: "refresh manifest inventory" };
|
|
64
|
-
const installedHash = installedHashFor(installed?.files, spec);
|
|
65
|
-
if (installedHash && contentHash(current) === installedHash)
|
|
66
|
-
return { ...spec, action: "update", reason: "unchanged managed file" };
|
|
67
|
-
if (force)
|
|
68
|
-
return { ...spec, action: "update", reason: "--force permits replacement of an unmanaged file" };
|
|
69
|
-
return { ...spec, action: "conflict", reason: "managed ownership cannot be proven; use upgrade or review --force" };
|
|
70
|
-
};
|
|
71
|
-
/** Include an explicitly selected loop config in the same preview/transaction as setup. */
|
|
72
|
-
export const planLoopSelection = (plan, selection) => {
|
|
73
|
-
if (selection === undefined)
|
|
74
|
-
return;
|
|
75
|
-
const loop = validateLoopConfig(selection);
|
|
76
|
-
const config = plan.find((item) => item.path === ".agents/config.json");
|
|
77
|
-
if (!config || !["create", "preserve", "skip", "migrate"].includes(config.action))
|
|
78
|
-
throw new Error("Cannot change loop configuration while the project config has another planned change");
|
|
79
|
-
const parsed = JSON.parse(config.content);
|
|
80
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
81
|
-
throw new Error("Invalid project config");
|
|
82
|
-
const current = parsed;
|
|
83
|
-
if (JSON.stringify(current.loop) === JSON.stringify(loop))
|
|
84
|
-
return;
|
|
85
|
-
const previousAction = config.action;
|
|
86
|
-
current.loop = loop;
|
|
87
|
-
config.content = `${JSON.stringify(current, null, 2)}\n`;
|
|
88
|
-
config.action = previousAction === "create" || previousAction === "migrate" ? previousAction : "update";
|
|
89
|
-
config.reason = "explicit loop mode selection";
|
|
90
|
-
};
|
|
91
|
-
/** Merges a later owner of an already-planned shared managed-block path into the emitted entry. */
|
|
92
|
-
const mergeIntoPlannedBlock = (root, previous, spec) => {
|
|
93
|
-
const target = safeTarget(root, spec.path);
|
|
94
|
-
const current = existsSync(target) ? readFileSync(target, "utf8") : "";
|
|
95
|
-
previous.content = mergeManagedBlock(spec, previous.content);
|
|
96
|
-
previous.action = existsSync(target) ? (previous.content === current ? "skip" : "update") : "create";
|
|
97
|
-
previous.reason = existsSync(target) ? "managed prompt block" : "new managed prompt";
|
|
98
|
-
};
|
|
99
|
-
/** Plans managed workflow files, the manifest, and project-owned files; later block owners merge in line. */
|
|
100
|
-
const planManagedWrites = (root, files, force, installed) => {
|
|
101
|
-
const plan = [];
|
|
102
|
-
for (const spec of files) {
|
|
103
|
-
const previous = spec.strategy === "managed-block" ? plan.find((item) => item.path === spec.path && ["create", "update", "skip"].includes(item.action)) : undefined;
|
|
104
|
-
if (!previous)
|
|
105
|
-
plan.push(planFile(root, spec, force, installed));
|
|
106
|
-
else
|
|
107
|
-
mergeIntoPlannedBlock(root, previous, spec);
|
|
108
|
-
}
|
|
109
|
-
return plan;
|
|
110
|
-
};
|
|
111
21
|
/** Plans an init transaction: managed workflow files, manifest, and project-owned files. */
|
|
112
22
|
export const planInit = (options) => {
|
|
113
23
|
const root = resolve(options.root);
|
|
@@ -121,7 +31,7 @@ export const planInit = (options) => {
|
|
|
121
31
|
const agentFiles = selected.flatMap((id) => findAdapter(id)?.files() ?? []).map((file) => ({ ...file, ownershipClass: "managed" }));
|
|
122
32
|
const managedFiles = [...coreWorkflowFiles(), ...agentFiles];
|
|
123
33
|
const files = [...managedFiles, manifestSpec(managedFiles, selected), ...initialProjectFiles()];
|
|
124
|
-
const plan = planManagedWrites(root, files, options.force, installed);
|
|
34
|
+
const plan = planManagedWrites(root, files, { flow: "init", force: options.force, installed, resolvePath: safeTarget });
|
|
125
35
|
planProjectLayoutMigration(root, plan);
|
|
126
36
|
planLoopSelection(plan, options.loop);
|
|
127
37
|
return plan;
|
|
@@ -137,28 +47,10 @@ export const applyPlan = (root, plan) => {
|
|
|
137
47
|
}
|
|
138
48
|
};
|
|
139
49
|
export const formatPlan = (plan) => plan.map((item) => `${item.action.padEnd(8)} ${item.path} (${item.reason}; sha=${shortHash(item.contentEncoding === "base64" ? Buffer.from(item.content, "base64") : item.content)})`).join("\n");
|
|
140
|
-
const hasOwnedManifest = (root) => {
|
|
141
|
-
const path = join(resolve(root), ".agents/aidlc/manifest.json");
|
|
142
|
-
if (!existsSync(path))
|
|
143
|
-
return false;
|
|
144
|
-
try {
|
|
145
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
146
|
-
return parsed.managedBy === "aidlc-kit" || parsed.managedBy === "aidlc-workflow";
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
return false;
|
|
150
|
-
} /* intentional: a malformed/missing manifest is not owned */
|
|
151
|
-
};
|
|
152
|
-
const removeManagedBlock = (current, owner) => {
|
|
153
|
-
const start = `<!-- aidlc-installer:${owner}:start -->`;
|
|
154
|
-
const end = `<!-- aidlc-installer:${owner}:end -->`;
|
|
155
|
-
return current.replace(new RegExp(`${start}[\\s\\S]*?${end}\\n?`, "g"), "").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
156
|
-
};
|
|
157
50
|
/** Maps one manifest inventory record onto its current spec and installed hash. */
|
|
158
51
|
const uninstallEntry = (key, item, currentSpecs) => {
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const owner = separator > 0 ? key.slice(separator + 1) : item.owner;
|
|
52
|
+
const { path, owner: keyedOwner } = splitInventoryKey(key);
|
|
53
|
+
const owner = keyedOwner ?? item.owner;
|
|
162
54
|
const spec = currentSpecs.find((candidate) => candidate.path === path && (candidate.strategy !== "managed-block" || candidate.owner === owner)) ?? { path, owner, strategy: item.strategy, content: "", ownershipClass: "managed" };
|
|
163
55
|
return { spec, hash: item.hash };
|
|
164
56
|
};
|
|
@@ -178,7 +70,7 @@ const stripManagedBlocks = (group, current) => {
|
|
|
178
70
|
const block = managedBlock(spec, next);
|
|
179
71
|
// A v0.2.1 single-marker block cannot be removed safely; retain ownership instead of orphaning it.
|
|
180
72
|
if (!block) {
|
|
181
|
-
if (next.includes(
|
|
73
|
+
if (next.includes(legacyManagedMarker(spec.owner)))
|
|
182
74
|
modified = spec;
|
|
183
75
|
continue;
|
|
184
76
|
}
|
|
@@ -200,7 +92,7 @@ const uninstallWholeFile = (group, exists, current) => {
|
|
|
200
92
|
return contentHash(current) === installedHash ? { ...spec, action: "delete", reason: "remove unchanged managed file" } : { ...spec, action: "preserve", reason: "managed file was modified" };
|
|
201
93
|
}
|
|
202
94
|
const { next, modified } = stripManagedBlocks(group, current);
|
|
203
|
-
const legacyResidue = group.find(({ spec }) => next.includes(
|
|
95
|
+
const legacyResidue = group.find(({ spec }) => next.includes(legacyManagedMarker(spec.owner)));
|
|
204
96
|
const ownerSpec = group[0].spec;
|
|
205
97
|
if (modified)
|
|
206
98
|
return { ...modified, action: "preserve", reason: "managed block was modified" };
|
|
@@ -233,45 +125,45 @@ export const planUninstall = (root) => {
|
|
|
233
125
|
plan.push(manifestDisposition(plan));
|
|
234
126
|
return plan;
|
|
235
127
|
};
|
|
236
|
-
|
|
237
|
-
export const status = (root) => {
|
|
128
|
+
export const statusData = (root) => {
|
|
238
129
|
const manifest = readManifest(root);
|
|
239
130
|
const legacyPath = join(resolve(root), ".agents/aidlc/manifest.json");
|
|
131
|
+
return {
|
|
132
|
+
workflow: manifest || existsSync(legacyPath) ? "installed" : "not installed",
|
|
133
|
+
installedVersion: manifest?.packageVersion ?? (existsSync(legacyPath) ? "legacy/unknown" : "none"),
|
|
134
|
+
schema: `${manifest?.schemaVersion ?? (existsSync(legacyPath) ? "legacy" : "none")}`,
|
|
135
|
+
detectedAgents: detectedAgents(root)
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
/** Reports installed workflow version, schema, and detected agents. */
|
|
139
|
+
export const status = (root) => {
|
|
140
|
+
const data = statusData(root);
|
|
240
141
|
return [
|
|
241
|
-
`workflow: ${
|
|
242
|
-
`installed version: ${
|
|
243
|
-
`schema: ${
|
|
244
|
-
`detected agents: ${detectedAgents
|
|
142
|
+
`workflow: ${data.workflow}`,
|
|
143
|
+
`installed version: ${data.installedVersion}`,
|
|
144
|
+
`schema: ${data.schema}`,
|
|
145
|
+
`detected agents: ${data.detectedAgents.join(", ") || "none"}`
|
|
245
146
|
].join("\n");
|
|
246
147
|
};
|
|
247
|
-
/** Path portion of a manifest inventory key (`path#owner` for shared managed blocks). */
|
|
248
|
-
const inventoryKeyPath = (key) => {
|
|
249
|
-
const separator = key.lastIndexOf("#");
|
|
250
|
-
return separator > 0 ? key.slice(0, separator) : key;
|
|
251
|
-
};
|
|
252
148
|
/** Manifest inventory paths whose on-disk content no longer matches the recorded owner hash. */
|
|
253
149
|
const modifiedManagedPaths = (root, manifest) => {
|
|
254
150
|
const modified = Object.entries(manifest.files).filter(([key, item]) => {
|
|
255
|
-
const target = safeTarget(root,
|
|
151
|
+
const target = safeTarget(root, splitInventoryKey(key).path);
|
|
256
152
|
if (!existsSync(target))
|
|
257
153
|
return true;
|
|
258
154
|
const spec = { owner: item.owner, strategy: item.strategy };
|
|
259
155
|
return managedContentHash(spec, readFileSync(target, "utf8")) !== item.hash;
|
|
260
156
|
});
|
|
261
|
-
return [...new Set(modified.map(([key]) =>
|
|
157
|
+
return [...new Set(modified.map(([key]) => splitInventoryKey(key).path))];
|
|
262
158
|
};
|
|
263
159
|
/** Validates the installed workflow manifest and local config/state. */
|
|
264
160
|
export const doctor = (root, strict = false) => {
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
161
|
+
const read = readManifestFile(root);
|
|
162
|
+
if (read.status === "missing")
|
|
267
163
|
return "ERROR: .agents/aidlc/manifest.json is missing. Run npx @felixdotgo/aidlc-kit init --yes.";
|
|
268
|
-
|
|
269
|
-
try {
|
|
270
|
-
parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
271
|
-
}
|
|
272
|
-
catch {
|
|
164
|
+
if (read.status === "invalid")
|
|
273
165
|
return "ERROR: workflow manifest is invalid JSON.";
|
|
274
|
-
|
|
166
|
+
const parsed = read.value;
|
|
275
167
|
if (parsed.remoteUpdates || parsed.source !== "local-package-assets" || (parsed.managedBy !== "aidlc-kit" && parsed.managedBy !== "aidlc-workflow"))
|
|
276
168
|
return "ERROR: workflow manifest violates local-only asset policy.";
|
|
277
169
|
if (typeof parsed.schemaVersion === "number" && parsed.schemaVersion > 2)
|
|
@@ -291,3 +183,8 @@ export const doctor = (root, strict = false) => {
|
|
|
291
183
|
return `${strict ? "ERROR" : "WARN"}: managed integrity differs: ${modified.join(", ")}`;
|
|
292
184
|
return `OK: local workflow ${manifest.packageVersion} is valid; remote updates are disabled.`;
|
|
293
185
|
};
|
|
186
|
+
export const doctorData = (root, strict = false) => {
|
|
187
|
+
const message = doctor(root, strict);
|
|
188
|
+
const level = message.startsWith("ERROR:") ? "error" : message.startsWith("WARN:") ? "warn" : "ok";
|
|
189
|
+
return { level, message, strict };
|
|
190
|
+
};
|