@felixdotgo/aidlc-kit 0.1.1 → 0.3.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 -13
- package/README.md +37 -43
- 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 +2 -2
- 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/schemas/state.schema.json +2 -0
- package/dist/assets/.agents/aidlc/scripts/lib/assignment-store.mjs +94 -48
- package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +5 -4
- package/dist/assets/.agents/aidlc/scripts/lib/loop-authority.mjs +261 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-commit-message.mjs +45 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +206 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +100 -14
- package/dist/assets/.agents/aidlc/scripts/lib/loop-env.mjs +279 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +105 -32
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +17 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +24 -7
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +42 -16
- package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +20 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-holder.mjs +376 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +33 -7
- package/dist/assets/.agents/aidlc/scripts/lib/loop-lease.mjs +67 -61
- package/dist/assets/.agents/aidlc/scripts/lib/loop-leases.mjs +268 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +116 -28
- package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +114 -12
- package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +117 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-local.mjs +16 -56
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +149 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor.mjs +9 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +44 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-image.mjs +64 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-log.mjs +158 -0
- package/dist/assets/.agents/aidlc/scripts/lib/repo-head.mjs +22 -0
- package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +68 -2
- package/dist/assets/.agents/aidlc/scripts/lib/task-runtime-migrate.mjs +75 -0
- package/dist/assets/.agents/aidlc/scripts/lib/task-runtime.mjs +97 -0
- package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +168 -73
- package/dist/assets/.agents/aidlc/scripts/loop-gate.mjs +19 -7
- package/dist/assets/.agents/aidlc/scripts/loop.cmd +101 -0
- package/dist/assets/.agents/aidlc/scripts/loop.mjs +2 -1
- package/dist/assets/.agents/aidlc/scripts/loop.sh +71 -0
- package/dist/assets/.agents/aidlc/scripts/remote-state.mjs +10 -4
- package/dist/assets/.agents/aidlc/scripts/state.mjs +16 -8
- 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/changes.js +83 -0
- package/dist/assets/ui/common.js +664 -0
- package/dist/assets/ui/dashboard.css +636 -0
- package/dist/assets/ui/dashboard.js +77 -0
- package/dist/assets/ui/detail.js +159 -0
- package/dist/assets/ui/live-log.js +332 -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 +68 -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 +68 -0
- package/dist/src/cli/commands.d.ts +8 -0
- package/dist/src/cli/commands.js +159 -0
- package/dist/src/cli/loop-launcher.d.ts +47 -0
- package/dist/src/cli/loop-launcher.js +322 -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 +28 -191
- package/dist/src/gate.js +3 -2
- package/dist/src/installer.d.ts +14 -7
- package/dist/src/installer.js +42 -143
- 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 +142 -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 +55 -7
- package/dist/src/state.d.ts +16 -11
- package/dist/src/state.js +16 -11
- package/dist/src/task-ui.d.ts +33 -0
- package/dist/src/task-ui.js +33 -0
- package/dist/src/ui/assets.d.ts +14 -0
- package/dist/src/ui/assets.js +45 -0
- package/dist/src/ui/changes.d.ts +83 -0
- package/dist/src/ui/changes.js +498 -0
- package/dist/src/ui/containers.d.ts +71 -0
- package/dist/src/ui/containers.js +214 -0
- package/dist/src/ui/daemon.d.ts +75 -0
- package/dist/src/ui/daemon.js +232 -0
- package/dist/src/ui/data.d.ts +42 -0
- package/dist/src/ui/data.js +89 -0
- package/dist/src/ui/docs.d.ts +13 -0
- package/dist/src/ui/docs.js +39 -0
- package/dist/src/ui/files.d.ts +3 -0
- package/dist/src/ui/files.js +20 -0
- package/dist/src/ui/http.d.ts +14 -0
- package/dist/src/ui/http.js +80 -0
- package/dist/src/ui/live.d.ts +5 -0
- package/dist/src/ui/live.js +43 -0
- package/dist/src/ui/markdown.d.ts +2 -0
- package/dist/src/ui/markdown.js +127 -0
- package/dist/src/ui/options.d.ts +48 -0
- package/dist/src/ui/options.js +81 -0
- package/dist/src/ui/security.d.ts +34 -0
- package/dist/src/ui/security.js +38 -0
- package/dist/src/ui/server.d.ts +39 -0
- package/dist/src/ui/server.js +347 -0
- package/dist/src/ui/sse.d.ts +14 -0
- package/dist/src/ui/sse.js +281 -0
- package/dist/src/ui/views/board.d.ts +7 -0
- package/dist/src/ui/views/board.js +131 -0
- package/dist/src/ui/views/changes.d.ts +19 -0
- package/dist/src/ui/views/changes.js +154 -0
- package/dist/src/ui/views/components.d.ts +56 -0
- package/dist/src/ui/views/components.js +113 -0
- package/dist/src/ui/views/docs.d.ts +15 -0
- package/dist/src/ui/views/docs.js +64 -0
- package/dist/src/ui/views/layout.d.ts +44 -0
- package/dist/src/ui/views/layout.js +233 -0
- package/dist/src/ui/views/task.d.ts +25 -0
- package/dist/src/ui/views/task.js +320 -0
- package/dist/src/upgrade-board.js +10 -10
- package/dist/src/upgrade.d.ts +0 -1
- package/dist/src/upgrade.js +24 -85
- package/dist/src/workflow.d.ts +4 -0
- package/dist/src/workflow.js +36 -4
- package/docs/README.md +41 -22
- package/docs/architecture.md +101 -37
- package/docs/command-reference.md +289 -52
- package/docs/configuration.md +70 -30
- package/docs/coordination-service.md +91 -30
- package/docs/design-decisions.md +87 -0
- package/docs/development.md +124 -49
- package/docs/loop-local-run.md +228 -0
- package/docs/loop-runbook.md +214 -0
- package/docs/operating-workflow.md +55 -42
- package/docs/testing-and-release.md +72 -49
- package/docs/troubleshooting.md +221 -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
package/dist/src/installer.js
CHANGED
|
@@ -1,113 +1,23 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { chmodSync, 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
|
-
import { contentHash, coreWorkflowFiles, initialProjectFiles, manifestSpec, packageVersion } from "./workflow.js";
|
|
10
|
+
import { contentHash, coreWorkflowFiles, initialProjectFiles, isExecutableAsset, 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;
|
|
@@ -134,31 +44,15 @@ export const applyPlan = (root, plan) => {
|
|
|
134
44
|
const target = safeTarget(root, write.path);
|
|
135
45
|
mkdirSync(dirname(target), { recursive: true });
|
|
136
46
|
writeFileSync(target, write.content, "utf8");
|
|
47
|
+
if (isExecutableAsset(write.path) && process.platform !== "win32")
|
|
48
|
+
chmodSync(target, 0o755);
|
|
137
49
|
}
|
|
138
50
|
};
|
|
139
51
|
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
52
|
/** Maps one manifest inventory record onto its current spec and installed hash. */
|
|
158
53
|
const uninstallEntry = (key, item, currentSpecs) => {
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const owner = separator > 0 ? key.slice(separator + 1) : item.owner;
|
|
54
|
+
const { path, owner: keyedOwner } = splitInventoryKey(key);
|
|
55
|
+
const owner = keyedOwner ?? item.owner;
|
|
162
56
|
const spec = currentSpecs.find((candidate) => candidate.path === path && (candidate.strategy !== "managed-block" || candidate.owner === owner)) ?? { path, owner, strategy: item.strategy, content: "", ownershipClass: "managed" };
|
|
163
57
|
return { spec, hash: item.hash };
|
|
164
58
|
};
|
|
@@ -178,7 +72,7 @@ const stripManagedBlocks = (group, current) => {
|
|
|
178
72
|
const block = managedBlock(spec, next);
|
|
179
73
|
// A v0.2.1 single-marker block cannot be removed safely; retain ownership instead of orphaning it.
|
|
180
74
|
if (!block) {
|
|
181
|
-
if (next.includes(
|
|
75
|
+
if (next.includes(legacyManagedMarker(spec.owner)))
|
|
182
76
|
modified = spec;
|
|
183
77
|
continue;
|
|
184
78
|
}
|
|
@@ -200,7 +94,7 @@ const uninstallWholeFile = (group, exists, current) => {
|
|
|
200
94
|
return contentHash(current) === installedHash ? { ...spec, action: "delete", reason: "remove unchanged managed file" } : { ...spec, action: "preserve", reason: "managed file was modified" };
|
|
201
95
|
}
|
|
202
96
|
const { next, modified } = stripManagedBlocks(group, current);
|
|
203
|
-
const legacyResidue = group.find(({ spec }) => next.includes(
|
|
97
|
+
const legacyResidue = group.find(({ spec }) => next.includes(legacyManagedMarker(spec.owner)));
|
|
204
98
|
const ownerSpec = group[0].spec;
|
|
205
99
|
if (modified)
|
|
206
100
|
return { ...modified, action: "preserve", reason: "managed block was modified" };
|
|
@@ -233,45 +127,45 @@ export const planUninstall = (root) => {
|
|
|
233
127
|
plan.push(manifestDisposition(plan));
|
|
234
128
|
return plan;
|
|
235
129
|
};
|
|
236
|
-
|
|
237
|
-
export const status = (root) => {
|
|
130
|
+
export const statusData = (root) => {
|
|
238
131
|
const manifest = readManifest(root);
|
|
239
132
|
const legacyPath = join(resolve(root), ".agents/aidlc/manifest.json");
|
|
133
|
+
return {
|
|
134
|
+
workflow: manifest || existsSync(legacyPath) ? "installed" : "not installed",
|
|
135
|
+
installedVersion: manifest?.packageVersion ?? (existsSync(legacyPath) ? "legacy/unknown" : "none"),
|
|
136
|
+
schema: `${manifest?.schemaVersion ?? (existsSync(legacyPath) ? "legacy" : "none")}`,
|
|
137
|
+
detectedAgents: detectedAgents(root)
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
/** Reports installed workflow version, schema, and detected agents. */
|
|
141
|
+
export const status = (root) => {
|
|
142
|
+
const data = statusData(root);
|
|
240
143
|
return [
|
|
241
|
-
`workflow: ${
|
|
242
|
-
`installed version: ${
|
|
243
|
-
`schema: ${
|
|
244
|
-
`detected agents: ${detectedAgents
|
|
144
|
+
`workflow: ${data.workflow}`,
|
|
145
|
+
`installed version: ${data.installedVersion}`,
|
|
146
|
+
`schema: ${data.schema}`,
|
|
147
|
+
`detected agents: ${data.detectedAgents.join(", ") || "none"}`
|
|
245
148
|
].join("\n");
|
|
246
149
|
};
|
|
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
150
|
/** Manifest inventory paths whose on-disk content no longer matches the recorded owner hash. */
|
|
253
151
|
const modifiedManagedPaths = (root, manifest) => {
|
|
254
152
|
const modified = Object.entries(manifest.files).filter(([key, item]) => {
|
|
255
|
-
const target = safeTarget(root,
|
|
153
|
+
const target = safeTarget(root, splitInventoryKey(key).path);
|
|
256
154
|
if (!existsSync(target))
|
|
257
155
|
return true;
|
|
258
156
|
const spec = { owner: item.owner, strategy: item.strategy };
|
|
259
157
|
return managedContentHash(spec, readFileSync(target, "utf8")) !== item.hash;
|
|
260
158
|
});
|
|
261
|
-
return [...new Set(modified.map(([key]) =>
|
|
159
|
+
return [...new Set(modified.map(([key]) => splitInventoryKey(key).path))];
|
|
262
160
|
};
|
|
263
161
|
/** Validates the installed workflow manifest and local config/state. */
|
|
264
162
|
export const doctor = (root, strict = false) => {
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
163
|
+
const read = readManifestFile(root);
|
|
164
|
+
if (read.status === "missing")
|
|
267
165
|
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 {
|
|
166
|
+
if (read.status === "invalid")
|
|
273
167
|
return "ERROR: workflow manifest is invalid JSON.";
|
|
274
|
-
|
|
168
|
+
const parsed = read.value;
|
|
275
169
|
if (parsed.remoteUpdates || parsed.source !== "local-package-assets" || (parsed.managedBy !== "aidlc-kit" && parsed.managedBy !== "aidlc-workflow"))
|
|
276
170
|
return "ERROR: workflow manifest violates local-only asset policy.";
|
|
277
171
|
if (typeof parsed.schemaVersion === "number" && parsed.schemaVersion > 2)
|
|
@@ -291,3 +185,8 @@ export const doctor = (root, strict = false) => {
|
|
|
291
185
|
return `${strict ? "ERROR" : "WARN"}: managed integrity differs: ${modified.join(", ")}`;
|
|
292
186
|
return `OK: local workflow ${manifest.packageVersion} is valid; remote updates are disabled.`;
|
|
293
187
|
};
|
|
188
|
+
export const doctorData = (root, strict = false) => {
|
|
189
|
+
const message = doctor(root, strict);
|
|
190
|
+
const level = message.startsWith("ERROR:") ? "error" : message.startsWith("WARN:") ? "warn" : "ok";
|
|
191
|
+
return { level, message, strict };
|
|
192
|
+
};
|
package/dist/src/legacy.js
CHANGED
|
@@ -25,6 +25,7 @@ const instruction = (agent) => [
|
|
|
25
25
|
"Read `.agents/aidlc/orchestrator.md` and `.agents/data/state/BOARD.md` before starting non-trivial work. Use phase skills under `.agents/skills/`; do not fetch remote workflow content. Respect more-specific instructions already present in this repository.",
|
|
26
26
|
""
|
|
27
27
|
].join("\n");
|
|
28
|
+
/** Frozen v0.2.1 baseline payload — intentionally does NOT reuse managed-content markers: this string is a historical fact that must not track future marker-format changes. */
|
|
28
29
|
const block = (owner, body) => `<!-- aidlc-installer:${owner} -->\n${body}`;
|
|
29
30
|
export const legacyV021AdapterContents = Object.freeze({
|
|
30
31
|
"CLAUDE.md": block("claude", instruction("Claude Code")),
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import type { FileSpec } from "./model.js";
|
|
2
|
+
/** Canonical managed-block markers; every emitter and detector must build them through these helpers. */
|
|
3
|
+
export declare const managedStartMarker: (owner: string) => string;
|
|
4
|
+
export declare const managedEndMarker: (owner: string) => string;
|
|
5
|
+
/** Single-marker block emitted by the frozen v0.2.1 baseline; upgrade and uninstall still detect it. */
|
|
6
|
+
export declare const legacyManagedMarker: (owner: string) => string;
|
|
2
7
|
export declare const managedBlock: (spec: Pick<FileSpec, "owner" | "strategy">, content: string) => string | undefined;
|
|
3
8
|
export declare const mergeManagedBlock: (spec: FileSpec, current: string) => string;
|
|
9
|
+
/** Removes every block owned by `owner` from `current` and normalizes the leftover blank-line runs. */
|
|
10
|
+
export declare const removeManagedBlock: (current: string, owner: string) => string;
|
|
@@ -1,16 +1,27 @@
|
|
|
1
|
+
/** Canonical managed-block markers; every emitter and detector must build them through these helpers. */
|
|
2
|
+
export const managedStartMarker = (owner) => `<!-- aidlc-installer:${owner}:start -->`;
|
|
3
|
+
export const managedEndMarker = (owner) => `<!-- aidlc-installer:${owner}:end -->`;
|
|
4
|
+
/** Single-marker block emitted by the frozen v0.2.1 baseline; upgrade and uninstall still detect it. */
|
|
5
|
+
export const legacyManagedMarker = (owner) => `<!-- aidlc-installer:${owner} -->`;
|
|
1
6
|
export const managedBlock = (spec, content) => {
|
|
2
7
|
if (spec.strategy !== "managed-block")
|
|
3
8
|
return content;
|
|
4
|
-
const start =
|
|
5
|
-
const end =
|
|
9
|
+
const start = managedStartMarker(spec.owner);
|
|
10
|
+
const end = managedEndMarker(spec.owner);
|
|
6
11
|
return content.match(new RegExp(`${start}[\\s\\S]*?${end}`))?.[0];
|
|
7
12
|
};
|
|
8
13
|
export const mergeManagedBlock = (spec, current) => {
|
|
9
|
-
const start =
|
|
10
|
-
const end =
|
|
14
|
+
const start = managedStartMarker(spec.owner);
|
|
15
|
+
const end = managedEndMarker(spec.owner);
|
|
11
16
|
const block = spec.content.trimEnd();
|
|
12
17
|
const matches = new RegExp(`${start}[\\s\\S]*?${end}\\n?`, "g");
|
|
13
18
|
const withoutOwnedBlocks = current.replace(matches, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
14
19
|
const next = `${withoutOwnedBlocks}${withoutOwnedBlocks ? "\n\n" : ""}${block}`;
|
|
15
20
|
return `${next.trimEnd()}\n`;
|
|
16
21
|
};
|
|
22
|
+
/** Removes every block owned by `owner` from `current` and normalizes the leftover blank-line runs. */
|
|
23
|
+
export const removeManagedBlock = (current, owner) => {
|
|
24
|
+
const start = managedStartMarker(owner);
|
|
25
|
+
const end = managedEndMarker(owner);
|
|
26
|
+
return current.replace(new RegExp(`${start}[\\s\\S]*?${end}\\n?`, "g"), "").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
27
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { FileSpec, LoopConfig, ManifestInventoryItem, PlannedWrite, WorkflowManifest } from "./model.js";
|
|
2
|
+
/** Constructs manifest inventory keys (`path#owner` for shared managed blocks). The definition should live here; it stays in workflow.ts pending a cross-owner move. */
|
|
3
|
+
export { manifestKey } from "./workflow.js";
|
|
4
|
+
/** Raw outcome of reading the ownership manifest: missing on disk, unparseable, or parsed but unvalidated. */
|
|
5
|
+
export type ManifestFileRead = {
|
|
6
|
+
status: "missing";
|
|
7
|
+
} | {
|
|
8
|
+
status: "invalid";
|
|
9
|
+
} | {
|
|
10
|
+
status: "parsed";
|
|
11
|
+
value: Record<string, unknown>;
|
|
12
|
+
};
|
|
13
|
+
/** The single read+parse of .agents/aidlc/manifest.json that every manifest question goes through. */
|
|
14
|
+
export declare const readManifestFile: (root: string) => ManifestFileRead;
|
|
15
|
+
/** Strict schema-v2 ownership manifest; malformed or non-conforming content reads as "not installed". */
|
|
16
|
+
export declare const readManifest: (root: string) => WorkflowManifest | undefined;
|
|
17
|
+
/** Whether the on-disk manifest is owned by this package (any schema version). */
|
|
18
|
+
export declare const hasOwnedManifest: (root: string) => boolean;
|
|
19
|
+
/** Whether the on-disk manifest is a legacy aidlc-workflow v0.x install (schema 0 or 1). */
|
|
20
|
+
export declare const legacyManifest: (root: string) => boolean;
|
|
21
|
+
/** Splits a manifest inventory key into its path and, when present, its `#owner` suffix. */
|
|
22
|
+
export declare const splitInventoryKey: (key: string) => {
|
|
23
|
+
path: string;
|
|
24
|
+
owner?: string;
|
|
25
|
+
};
|
|
26
|
+
/** Managed prompt blocks share a path across owners, so inventory keys carry the owner. */
|
|
27
|
+
export declare const installedHashFor: (files: Record<string, ManifestInventoryItem> | undefined, spec: Pick<FileSpec, "path" | "owner" | "strategy">) => string | undefined;
|
|
28
|
+
/** Hash of the managed slice of a file: the owned block for managed-block strategies, the whole file otherwise. */
|
|
29
|
+
export declare const managedContentHash: (spec: Pick<FileSpec, "owner" | "strategy">, content: string) => string | undefined;
|
|
30
|
+
/** Plans one project/state-owned file: preserved verbatim when present, seeded from the package when absent. */
|
|
31
|
+
export declare const preserveOwnedFile: (target: string, spec: FileSpec) => PlannedWrite;
|
|
32
|
+
/** Caller-supplied project-path resolver so each flow keeps its own error persona (init refuses escapes and symlinks; upgrade refuses only escapes). */
|
|
33
|
+
export type ProjectPathResolver = (root: string, relative: string) => string;
|
|
34
|
+
/** Policy fields shared by both flows. */
|
|
35
|
+
export interface PlanPolicyBase {
|
|
36
|
+
/** Installed manifest to prove ownership against, when one exists. */
|
|
37
|
+
installed?: WorkflowManifest;
|
|
38
|
+
/** Project-path resolver carrying the caller's error persona. */
|
|
39
|
+
resolvePath: ProjectPathResolver;
|
|
40
|
+
}
|
|
41
|
+
/** Init: managed blocks merge unconditionally and --force may replace unmanaged files. */
|
|
42
|
+
export interface InitPlanPolicy extends PlanPolicyBase {
|
|
43
|
+
flow: "init";
|
|
44
|
+
force: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Upgrade: ownership is proven against installed hashes and the frozen v0.2.1 baselines. */
|
|
47
|
+
export interface UpgradePlanPolicy extends PlanPolicyBase {
|
|
48
|
+
flow: "upgrade";
|
|
49
|
+
/** True when no schema-2 manifest exists and a legacy v0.x install is being migrated. */
|
|
50
|
+
legacy: boolean;
|
|
51
|
+
/** Recognizes a v0.2.1 baseline for a spec's current managed-content hash; consulted only while `legacy` is set. */
|
|
52
|
+
legacyBaseline?: (spec: FileSpec, currentHash: string | undefined) => boolean;
|
|
53
|
+
}
|
|
54
|
+
/** Per-flow policy for the shared managed-file ladder; the union makes each flow's knobs explicit. */
|
|
55
|
+
export type PlanPolicy = InitPlanPolicy | UpgradePlanPolicy;
|
|
56
|
+
/**
|
|
57
|
+
* Plans managed files (and, for init, the manifest and project/state files) in
|
|
58
|
+
* order; later owners of a shared managed-block path merge into the earlier
|
|
59
|
+
* entry. Init finds the earlier entry by scanning the plan; upgrade tracks the
|
|
60
|
+
* first block entry per path so a conflicted first owner stays the single
|
|
61
|
+
* reportable entry for that path.
|
|
62
|
+
*/
|
|
63
|
+
export declare const planManagedWrites: (root: string, files: FileSpec[], policy: PlanPolicy) => PlannedWrite[];
|
|
64
|
+
/** Include an explicitly selected loop config in the same preview/transaction as setup. */
|
|
65
|
+
export declare const planLoopSelection: (plan: PlannedWrite[], selection?: LoopConfig) => void;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed-planning core shared by init and upgrade: the manifest-reader family,
|
|
3
|
+
* inventory-key handling, managed-content hash proofs, and the single
|
|
4
|
+
* create/skip/update/conflict ladder for managed files, driven by explicit
|
|
5
|
+
* per-flow policies so installation and upgrade keep their exact semantics.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { legacyManagedMarker, managedBlock, mergeManagedBlock } from "./managed-content.js";
|
|
10
|
+
import { validateLoopConfig } from "./profiles.js";
|
|
11
|
+
import { contentHash } from "./workflow.js";
|
|
12
|
+
/** Constructs manifest inventory keys (`path#owner` for shared managed blocks). The definition should live here; it stays in workflow.ts pending a cross-owner move. */
|
|
13
|
+
export { manifestKey } from "./workflow.js";
|
|
14
|
+
/** The single read+parse of .agents/aidlc/manifest.json that every manifest question goes through. */
|
|
15
|
+
export const readManifestFile = (root) => {
|
|
16
|
+
const path = join(resolve(root), ".agents/aidlc/manifest.json");
|
|
17
|
+
if (!existsSync(path))
|
|
18
|
+
return { status: "missing" };
|
|
19
|
+
try {
|
|
20
|
+
return { status: "parsed", value: JSON.parse(readFileSync(path, "utf8")) };
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return { status: "invalid" };
|
|
24
|
+
} /* intentional: an unreadable manifest is missing/invalid, never a crash */
|
|
25
|
+
};
|
|
26
|
+
/** Strict schema-v2 ownership manifest; malformed or non-conforming content reads as "not installed". */
|
|
27
|
+
export const readManifest = (root) => {
|
|
28
|
+
const read = readManifestFile(root);
|
|
29
|
+
if (read.status !== "parsed")
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
const value = read.value;
|
|
33
|
+
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));
|
|
34
|
+
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;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
} /* intentional: a malformed manifest is treated as "not installed" */
|
|
39
|
+
};
|
|
40
|
+
/** Whether the on-disk manifest is owned by this package (any schema version). */
|
|
41
|
+
export const hasOwnedManifest = (root) => {
|
|
42
|
+
const read = readManifestFile(root);
|
|
43
|
+
if (read.status !== "parsed")
|
|
44
|
+
return false;
|
|
45
|
+
try {
|
|
46
|
+
return read.value.managedBy === "aidlc-kit" || read.value.managedBy === "aidlc-workflow";
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
} /* intentional: a malformed/missing manifest is not owned */
|
|
51
|
+
};
|
|
52
|
+
/** Whether the on-disk manifest is a legacy aidlc-workflow v0.x install (schema 0 or 1). */
|
|
53
|
+
export const legacyManifest = (root) => {
|
|
54
|
+
const read = readManifestFile(root);
|
|
55
|
+
if (read.status !== "parsed")
|
|
56
|
+
return false;
|
|
57
|
+
try {
|
|
58
|
+
return read.value.managedBy === "aidlc-workflow" && (read.value.schemaVersion === 0 || read.value.schemaVersion === 1);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
} /* intentional: a malformed/missing manifest is not a legacy v0.x install */
|
|
63
|
+
};
|
|
64
|
+
/* --- Inventory keys and hash proofs --------------------------------------------- */
|
|
65
|
+
/** Splits a manifest inventory key into its path and, when present, its `#owner` suffix. */
|
|
66
|
+
export const splitInventoryKey = (key) => {
|
|
67
|
+
const separator = key.lastIndexOf("#");
|
|
68
|
+
return separator > 0 ? { path: key.slice(0, separator), owner: key.slice(separator + 1) } : { path: key };
|
|
69
|
+
};
|
|
70
|
+
/** Managed prompt blocks share a path across owners, so inventory keys carry the owner. */
|
|
71
|
+
export const installedHashFor = (files, spec) => {
|
|
72
|
+
if (!files)
|
|
73
|
+
return undefined;
|
|
74
|
+
const keyed = files[`${spec.path}#${spec.owner}`];
|
|
75
|
+
if (keyed)
|
|
76
|
+
return keyed.hash;
|
|
77
|
+
const legacy = files[spec.path];
|
|
78
|
+
return legacy && (spec.strategy !== "managed-block" || legacy.owner === spec.owner) ? legacy.hash : undefined;
|
|
79
|
+
};
|
|
80
|
+
/** Hash of the managed slice of a file: the owned block for managed-block strategies, the whole file otherwise. */
|
|
81
|
+
export const managedContentHash = (spec, content) => {
|
|
82
|
+
const managed = managedBlock(spec, content);
|
|
83
|
+
return managed === undefined ? undefined : contentHash(managed);
|
|
84
|
+
};
|
|
85
|
+
/* --- Owned-file preservation ------------------------------------------------------ */
|
|
86
|
+
/** Plans one project/state-owned file: preserved verbatim when present, seeded from the package when absent. */
|
|
87
|
+
export const preserveOwnedFile = (target, spec) => existsSync(target)
|
|
88
|
+
? { ...spec, content: readFileSync(target, "utf8"), action: "preserve", reason: `${spec.ownershipClass}-owned file` }
|
|
89
|
+
: { ...spec, action: "create", reason: `new ${spec.ownershipClass}-owned file` };
|
|
90
|
+
/* --- The one managed-file planning ladder -------------------------------------------- */
|
|
91
|
+
/**
|
|
92
|
+
* The single managed-file planning ladder shared by init and upgrade. The flows
|
|
93
|
+
* share the head rungs and then diverge; every difference is pinned below so
|
|
94
|
+
* each caller behaves exactly as it did before the extraction:
|
|
95
|
+
* - project/state specs: init plans preserve/create here; upgrade never passes
|
|
96
|
+
* them (its project files go through its own preserveProjectFiles pass).
|
|
97
|
+
* - fresh managed-block file: init merges into the empty file and says "new
|
|
98
|
+
* managed prompt"; upgrade copies spec.content verbatim and says "new managed
|
|
99
|
+
* file".
|
|
100
|
+
* - current content: init skips on whole-file equality ("already current");
|
|
101
|
+
* upgrade compares managed-content hashes ("managed content already current")
|
|
102
|
+
* and can still plan an update when only the surrounding layout changed.
|
|
103
|
+
* - manifest refresh and --force: init-only rungs (upgrade rewrites the
|
|
104
|
+
* manifest unconditionally through its own manifestSpec entry).
|
|
105
|
+
* - installed-hash proof: init compares the whole-file hash (only replace files
|
|
106
|
+
* reach this rung); upgrade compares the managed-content hash (block-scoped
|
|
107
|
+
* for shared blocks).
|
|
108
|
+
* - v0.2.1 baselines and the legacy single-marker conflict: upgrade-only rungs.
|
|
109
|
+
* - terminal conflict: init reports unprovable ownership; upgrade names the
|
|
110
|
+
* legacy-baseline or installed-hash mismatch.
|
|
111
|
+
*/
|
|
112
|
+
const planManagedFile = (root, spec, policy) => {
|
|
113
|
+
const target = policy.resolvePath(root, spec.path);
|
|
114
|
+
if (policy.flow === "init" && (spec.ownershipClass === "project" || spec.ownershipClass === "state"))
|
|
115
|
+
return preserveOwnedFile(target, spec);
|
|
116
|
+
if (policy.flow === "init" && spec.strategy === "managed-block") {
|
|
117
|
+
const current = existsSync(target) ? readFileSync(target, "utf8") : "";
|
|
118
|
+
const next = mergeManagedBlock(spec, current);
|
|
119
|
+
return { ...spec, content: next, action: existsSync(target) ? (next === current ? "skip" : "update") : "create", reason: existsSync(target) ? "managed prompt block" : "new managed prompt" };
|
|
120
|
+
}
|
|
121
|
+
if (!existsSync(target))
|
|
122
|
+
return { ...spec, action: "create", reason: "new managed file" };
|
|
123
|
+
const current = readFileSync(target, "utf8");
|
|
124
|
+
if (policy.flow === "init") {
|
|
125
|
+
if (current === spec.content)
|
|
126
|
+
return { ...spec, action: "skip", reason: "already current" };
|
|
127
|
+
if (spec.path === ".agents/aidlc/manifest.json" && policy.installed)
|
|
128
|
+
return { ...spec, action: "update", reason: "refresh manifest inventory" };
|
|
129
|
+
const installedHash = installedHashFor(policy.installed?.files, spec);
|
|
130
|
+
if (installedHash && contentHash(current) === installedHash)
|
|
131
|
+
return { ...spec, action: "update", reason: "unchanged managed file" };
|
|
132
|
+
if (policy.force)
|
|
133
|
+
return { ...spec, action: "update", reason: "--force permits replacement of an unmanaged file" };
|
|
134
|
+
return { ...spec, action: "conflict", reason: "managed ownership cannot be proven; use upgrade or review --force" };
|
|
135
|
+
}
|
|
136
|
+
const next = spec.strategy === "managed-block" ? mergeManagedBlock(spec, current) : spec.content;
|
|
137
|
+
const currentHash = managedContentHash(spec, current);
|
|
138
|
+
const nextHash = contentHash(spec.strategy === "managed-block" ? spec.content.trimEnd() : spec.content);
|
|
139
|
+
if (currentHash === nextHash)
|
|
140
|
+
return { ...spec, content: next, action: next === current ? "skip" : "update", reason: "managed content already current" };
|
|
141
|
+
const installedHash = installedHashFor(policy.installed?.files, spec);
|
|
142
|
+
if (installedHash && currentHash === installedHash)
|
|
143
|
+
return { ...spec, content: next, action: "update", reason: "unchanged managed file" };
|
|
144
|
+
if (policy.legacy && policy.legacyBaseline?.(spec, currentHash))
|
|
145
|
+
return { ...spec, content: next, action: "update", reason: "recognized v0.2.1 baseline" };
|
|
146
|
+
if (spec.strategy === "managed-block" && current.includes(legacyManagedMarker(spec.owner)))
|
|
147
|
+
return { ...spec, content: next, action: "conflict", reason: "managed prompt block was modified" };
|
|
148
|
+
return { ...spec, content: next, action: "conflict", reason: policy.legacy ? "legacy core differs from known baseline" : "managed file differs from installed hash" };
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Merges a later owner of a shared managed-block path into the already-planned
|
|
152
|
+
* entry. Upgrade proof-checks the later owner's installed block hash first so a
|
|
153
|
+
* user edit to a later adapter's block is reported instead of being silently
|
|
154
|
+
* overwritten; init has nothing to prove against and merges unconditionally,
|
|
155
|
+
* saying "new managed prompt" for a file that did not exist yet.
|
|
156
|
+
*/
|
|
157
|
+
const mergeSharedBlockOwner = (root, previous, spec, policy) => {
|
|
158
|
+
if (!["create", "update", "skip"].includes(previous.action))
|
|
159
|
+
return;
|
|
160
|
+
const target = policy.resolvePath(root, spec.path);
|
|
161
|
+
const current = existsSync(target) ? readFileSync(target, "utf8") : "";
|
|
162
|
+
if (policy.flow === "upgrade") {
|
|
163
|
+
const installedBlockHash = installedHashFor(policy.installed?.files, spec);
|
|
164
|
+
const currentBlockHash = managedContentHash(spec, current);
|
|
165
|
+
if (installedBlockHash !== undefined && currentBlockHash !== undefined && installedBlockHash !== currentBlockHash) {
|
|
166
|
+
previous.action = "conflict";
|
|
167
|
+
previous.reason = "managed prompt block was modified";
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
previous.content = mergeManagedBlock(spec, previous.content);
|
|
172
|
+
previous.action = existsSync(target) ? (previous.content === current ? "skip" : "update") : "create";
|
|
173
|
+
previous.reason = policy.flow === "init" && !existsSync(target) ? "new managed prompt" : "managed prompt block";
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Plans managed files (and, for init, the manifest and project/state files) in
|
|
177
|
+
* order; later owners of a shared managed-block path merge into the earlier
|
|
178
|
+
* entry. Init finds the earlier entry by scanning the plan; upgrade tracks the
|
|
179
|
+
* first block entry per path so a conflicted first owner stays the single
|
|
180
|
+
* reportable entry for that path.
|
|
181
|
+
*/
|
|
182
|
+
export const planManagedWrites = (root, files, policy) => {
|
|
183
|
+
const plan = [];
|
|
184
|
+
const firstBlocks = new Map();
|
|
185
|
+
for (const spec of files) {
|
|
186
|
+
const previous = spec.strategy === "managed-block"
|
|
187
|
+
? policy.flow === "init" ? plan.find((item) => item.path === spec.path && ["create", "update", "skip"].includes(item.action)) : firstBlocks.get(spec.path)
|
|
188
|
+
: undefined;
|
|
189
|
+
if (!previous) {
|
|
190
|
+
const write = planManagedFile(root, spec, policy);
|
|
191
|
+
plan.push(write);
|
|
192
|
+
if (policy.flow === "upgrade" && spec.strategy === "managed-block")
|
|
193
|
+
firstBlocks.set(spec.path, write);
|
|
194
|
+
}
|
|
195
|
+
else
|
|
196
|
+
mergeSharedBlockOwner(root, previous, spec, policy);
|
|
197
|
+
}
|
|
198
|
+
return plan;
|
|
199
|
+
};
|
|
200
|
+
/** Include an explicitly selected loop config in the same preview/transaction as setup. */
|
|
201
|
+
export const planLoopSelection = (plan, selection) => {
|
|
202
|
+
if (selection === undefined)
|
|
203
|
+
return;
|
|
204
|
+
const loop = validateLoopConfig(selection);
|
|
205
|
+
const config = plan.find((item) => item.path === ".agents/config.json");
|
|
206
|
+
if (!config || !["create", "preserve", "skip", "migrate"].includes(config.action))
|
|
207
|
+
throw new Error("Cannot change loop configuration while the project config has another planned change");
|
|
208
|
+
const parsed = JSON.parse(config.content);
|
|
209
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
210
|
+
throw new Error("Invalid project config");
|
|
211
|
+
const current = parsed;
|
|
212
|
+
if (JSON.stringify(current.loop) === JSON.stringify(loop))
|
|
213
|
+
return;
|
|
214
|
+
const previousAction = config.action;
|
|
215
|
+
current.loop = loop;
|
|
216
|
+
config.content = `${JSON.stringify(current, null, 2)}\n`;
|
|
217
|
+
config.action = previousAction === "create" || previousAction === "migrate" ? previousAction : "update";
|
|
218
|
+
config.reason = "explicit loop mode selection";
|
|
219
|
+
};
|
package/dist/src/mcp-setup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
+
import { defaultConfig, validateProjectConfig } from "./profiles.js";
|
|
3
4
|
export const planMcpSetup = (options) => {
|
|
4
5
|
const target = join(resolve(options.root), ".agents/aidlc-coordination");
|
|
5
6
|
const endpoint = options.deployment === "docker" ? "http://127.0.0.1:8787/mcp" : "https://replace-with-service.example/mcp";
|
|
@@ -24,8 +25,9 @@ export const applyMcpSetup = (options) => {
|
|
|
24
25
|
].join("\n");
|
|
25
26
|
writeFileSync(join(plan.target, ".env.example"), `${env}\n`, "utf8");
|
|
26
27
|
const configPath = join(resolve(options.root), ".agents/config.json");
|
|
27
|
-
const current = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf8")) : {
|
|
28
|
+
const current = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf8")) : { ...defaultConfig() };
|
|
28
29
|
current.mcp = plan.config;
|
|
30
|
+
validateProjectConfig(current);
|
|
29
31
|
writeFileSync(configPath, `${JSON.stringify(current, null, 2)}\n`, "utf8");
|
|
30
32
|
return plan;
|
|
31
33
|
};
|