@felixdotgo/aidlc-kit 0.0.1 → 0.1.1
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 +1 -1
- package/README.md +20 -9
- package/dist/assets/.agents/aidlc/conventions.md +8 -8
- package/dist/assets/.agents/aidlc/orchestrator.md +15 -10
- package/dist/assets/.agents/aidlc/phase-build.md +7 -7
- package/dist/assets/.agents/aidlc/phase-clarify.md +3 -3
- package/dist/assets/.agents/aidlc/phase-plan.md +6 -6
- package/dist/assets/.agents/aidlc/phase-wrap.md +1 -1
- package/dist/assets/.agents/aidlc/schemas/manifest.schema.json +1 -1
- package/dist/assets/.agents/aidlc/scripts/assignment.mjs +36 -0
- package/dist/assets/.agents/aidlc/scripts/context.mjs +2 -0
- package/dist/assets/.agents/aidlc/scripts/gate-check.mjs +2 -0
- package/dist/assets/.agents/aidlc/scripts/gate-view.mjs +2 -0
- package/dist/assets/.agents/aidlc/scripts/lib/assignment-store.mjs +181 -0
- package/dist/assets/.agents/aidlc/scripts/lib/authority.mjs +3 -0
- package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +13 -1
- package/dist/assets/.agents/aidlc/scripts/lib/feature-store.mjs +70 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +220 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +183 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +100 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +58 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +320 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +284 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +51 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-lead-artifacts.mjs +63 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-lead-broker.mjs +53 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-lead-local-broker.mjs +44 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-lead-sandbox.mjs +88 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-lease.mjs +89 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-local-gate-receipt.mjs +32 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +162 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-planner.mjs +13 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +260 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-local.mjs +131 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +55 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor.mjs +101 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +32 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-snapshot.mjs +66 -0
- package/dist/assets/.agents/aidlc/scripts/lib/remote-state-client.mjs +112 -0
- package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +41 -6
- package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +90 -0
- package/dist/assets/.agents/aidlc/scripts/loop-gate.mjs +58 -0
- package/dist/assets/.agents/aidlc/scripts/loop.mjs +36 -0
- package/dist/assets/.agents/aidlc/scripts/remote-state.mjs +311 -0
- package/dist/assets/.agents/aidlc/scripts/render.mjs +2 -0
- package/dist/assets/.agents/aidlc/scripts/state.mjs +71 -11
- package/dist/assets/.agents/aidlc/scripts/task-next.mjs +2 -0
- package/dist/assets/.agents/aidlc/templates/model-contract.md +3 -3
- package/dist/assets/.agents/aidlc/templates/workplan.md +2 -2
- package/dist/assets/.agents/skills/aidlc-build/SKILL.md +2 -2
- package/dist/assets/.agents/skills/aidlc-clarify/SKILL.md +2 -2
- package/dist/assets/.agents/skills/aidlc-plan/SKILL.md +2 -2
- package/dist/assets/.agents/skills/aidlc-wrap/SKILL.md +1 -1
- package/dist/services/aidlc-coordination/.env.fleet.example +11 -0
- package/dist/services/aidlc-coordination/compose.yaml +23 -2
- package/dist/services/aidlc-coordination/package-lock.json +1 -0
- package/dist/services/aidlc-coordination/package.json +2 -1
- package/dist/services/aidlc-coordination/src/coordination.mjs +1 -1
- package/dist/services/aidlc-coordination/src/fleet-client.mjs +36 -0
- package/dist/services/aidlc-coordination/src/fleet-run.mjs +18 -0
- package/dist/services/aidlc-coordination/src/fleet.mjs +233 -0
- package/dist/services/aidlc-coordination/src/git-gateway.mjs +50 -0
- package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +41 -6
- package/dist/services/aidlc-coordination/src/repository.mjs +167 -4
- package/dist/services/aidlc-coordination/src/server.mjs +53 -9
- package/dist/services/aidlc-coordination/test/fleet.test.mjs +351 -0
- package/dist/services/aidlc-coordination/test/git-gateway.test.mjs +75 -0
- package/dist/services/aidlc-coordination/test/supervisor-fencing.test.mjs +59 -0
- package/dist/src/adapters.js +32 -5
- package/dist/src/cli.js +146 -70
- package/dist/src/context.js +2 -0
- package/dist/src/gate.d.ts +1 -0
- package/dist/src/gate.js +51 -38
- package/dist/src/installer.d.ts +10 -1
- package/dist/src/installer.js +157 -37
- package/dist/src/managed-content.js +1 -1
- package/dist/src/model.d.ts +16 -1
- package/dist/src/model.js +1 -1
- package/dist/src/profiles.d.ts +9 -1
- package/dist/src/profiles.js +37 -3
- package/dist/src/project-migration.d.ts +1 -0
- package/dist/src/project-migration.js +7 -0
- package/dist/src/state/archive.d.ts +17 -0
- package/dist/src/state/archive.js +60 -0
- package/dist/src/state/items.d.ts +25 -0
- package/dist/src/state/items.js +146 -0
- package/dist/src/state/lessons.d.ts +7 -0
- package/dist/src/state/lessons.js +67 -0
- package/dist/src/state/lifecycle.d.ts +5 -0
- package/dist/src/state/lifecycle.js +78 -0
- package/dist/src/state/lock.d.ts +7 -0
- package/dist/src/state/lock.js +124 -0
- package/dist/src/state/memory.d.ts +8 -0
- package/dist/src/state/memory.js +88 -0
- package/dist/src/state/render.d.ts +26 -0
- package/dist/src/state/render.js +29 -0
- package/dist/src/state/shared.d.ts +29 -0
- package/dist/src/state/shared.js +71 -0
- package/dist/src/state/store.d.ts +4 -0
- package/dist/src/state/store.js +81 -0
- package/dist/src/state/transitions.d.ts +21 -0
- package/dist/src/state/transitions.js +93 -0
- package/dist/src/state/validate.d.ts +4 -0
- package/dist/src/state/validate.js +100 -0
- package/dist/src/state.d.ts +15 -110
- package/dist/src/state.js +15 -764
- package/dist/src/tracker.d.ts +7 -0
- package/dist/src/tracker.js +3 -0
- package/dist/src/upgrade-board.d.ts +5 -0
- package/dist/src/upgrade-board.js +89 -0
- package/dist/src/upgrade.d.ts +5 -6
- package/dist/src/upgrade.js +211 -193
- package/dist/src/workflow.d.ts +1 -0
- package/dist/src/workflow.js +3 -2
- package/docs/README.md +4 -4
- package/docs/architecture.md +10 -2
- package/docs/command-reference.md +72 -11
- package/docs/configuration.md +26 -4
- package/docs/coordination-service.md +59 -3
- package/docs/development.md +16 -2
- package/docs/operating-workflow.md +53 -14
- package/docs/testing-and-release.md +53 -10
- package/package.json +3 -3
package/dist/src/tracker.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backlog tracker integration: resolves tracker credentials from the environment
|
|
3
|
+
* and builds the HTTP requests used to mirror lifecycle phase transitions.
|
|
4
|
+
*/
|
|
1
5
|
import type { TrackerConfig } from "./model.js";
|
|
2
6
|
export interface TrackerDiagnostic {
|
|
3
7
|
code: "TRACKER_DISABLED" | "TRACKER_AUTH" | "TRACKER_RATE_LIMIT" | "TRACKER_REQUEST";
|
|
@@ -18,7 +22,9 @@ export type TrackerResolution = {
|
|
|
18
22
|
config: TrackerConfig;
|
|
19
23
|
token: string;
|
|
20
24
|
};
|
|
25
|
+
/** Resolves a tracker configuration into either an enabled resolution (with token) or a disabled diagnostic. */
|
|
21
26
|
export declare const resolveTracker: (config: TrackerConfig | undefined, environment?: NodeJS.ProcessEnv) => TrackerResolution;
|
|
27
|
+
/** Executes a single Backlog request, mapping HTTP failures to retryable diagnostics. */
|
|
22
28
|
export declare const executeBacklog: (resolution: TrackerResolution, request: BacklogRequest, fetcher?: FetchLike) => Promise<{
|
|
23
29
|
ok: true;
|
|
24
30
|
body: unknown;
|
|
@@ -26,4 +32,5 @@ export declare const executeBacklog: (resolution: TrackerResolution, request: Ba
|
|
|
26
32
|
ok: false;
|
|
27
33
|
diagnostic: TrackerDiagnostic;
|
|
28
34
|
}>;
|
|
35
|
+
/** Builds the Backlog PATCH request that mirrors a lifecycle phase transition for an issue. */
|
|
29
36
|
export declare const gateUpdate: (config: TrackerConfig, issue: string, phase: "clarify" | "plan" | "build" | "wrap" | "done", idempotencyKey: string) => BacklogRequest;
|
package/dist/src/tracker.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Resolves a tracker configuration into either an enabled resolution (with token) or a disabled diagnostic. */
|
|
1
2
|
export const resolveTracker = (config, environment = process.env) => {
|
|
2
3
|
if (!config)
|
|
3
4
|
return { enabled: false, diagnostic: { code: "TRACKER_DISABLED", message: "Tracker is not configured; Git-only mode is active.", retryable: false } };
|
|
@@ -12,6 +13,7 @@ const form = (values) => {
|
|
|
12
13
|
body.set(key, String(value));
|
|
13
14
|
return body;
|
|
14
15
|
};
|
|
16
|
+
/** Executes a single Backlog request, mapping HTTP failures to retryable diagnostics. */
|
|
15
17
|
export const executeBacklog = async (resolution, request, fetcher = fetch) => {
|
|
16
18
|
if (!resolution.enabled)
|
|
17
19
|
return { ok: false, diagnostic: resolution.diagnostic };
|
|
@@ -28,6 +30,7 @@ export const executeBacklog = async (resolution, request, fetcher = fetch) => {
|
|
|
28
30
|
return { ok: false, diagnostic: { code: "TRACKER_REQUEST", message: `Backlog request failed with HTTP ${response.status}.`, retryable: response.status >= 500 } };
|
|
29
31
|
return { ok: true, body: await response.json() };
|
|
30
32
|
};
|
|
33
|
+
/** Builds the Backlog PATCH request that mirrors a lifecycle phase transition for an issue. */
|
|
31
34
|
export const gateUpdate = (config, issue, phase, idempotencyKey) => {
|
|
32
35
|
const comment = `[aidlc:${idempotencyKey}] ${phase}`;
|
|
33
36
|
if (config.workflow === "waterfall") {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy v0.2.x BOARD.md migration. Parses the markdown task table into
|
|
3
|
+
* canonical lifecycle state and preserves each legacy task's prose files in
|
|
4
|
+
* the stable artifact layout so no legacy content is lost.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
|
+
import { resolveProjectPath } from "./project-path.js";
|
|
9
|
+
import { emptyState, renderWorkplan, validateState } from "./state.js";
|
|
10
|
+
const safePath = (root, relative) => resolveProjectPath(root, relative, "Unsafe upgrade path");
|
|
11
|
+
const parseFrontmatter = (content) => {
|
|
12
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
13
|
+
if (!match)
|
|
14
|
+
return {};
|
|
15
|
+
return Object.fromEntries(match[1].split("\n").flatMap((line) => {
|
|
16
|
+
const separator = line.indexOf(":");
|
|
17
|
+
return separator < 0 ? [] : [[line.slice(0, separator).trim(), line.slice(separator + 1).trim().replace(/^['"]|['"]$/g, "")]];
|
|
18
|
+
}));
|
|
19
|
+
};
|
|
20
|
+
const cells = (line) => line.split("|").slice(1, -1).map((cell) => cell.trim().replace(/^`|`$/g, ""));
|
|
21
|
+
/* Approval evidence is rebuilt from the row's phase: everything beyond plan implies G0 passed, etc. */
|
|
22
|
+
const legacyEvidence = (phase, recordedAt) => {
|
|
23
|
+
const evidence = [];
|
|
24
|
+
if (["plan", "build", "wrap", "done"].includes(phase))
|
|
25
|
+
evidence.push({ kind: "approval", gate: "G0_confirm", source: "legacy state migration", result: "pass", recordedAt });
|
|
26
|
+
if (["build", "wrap", "done"].includes(phase))
|
|
27
|
+
evidence.push({ kind: "approval", gate: "G1_review", source: "legacy state migration", result: "pass", recordedAt });
|
|
28
|
+
return evidence;
|
|
29
|
+
};
|
|
30
|
+
/** Maps one legacy BOARD row to a canonical task, copying its prose files into the stable artifact layout. */
|
|
31
|
+
const taskFromLegacyRow = (root, files, row, recordedAt) => {
|
|
32
|
+
const [id, title, phaseRaw, gateRaw, statusRaw, branch, areasRaw, doc] = row;
|
|
33
|
+
const oldDoc = safePath(root, doc);
|
|
34
|
+
const source = existsSync(oldDoc) ? readFileSync(oldDoc, "utf8") : "";
|
|
35
|
+
const frontmatter = parseFrontmatter(source);
|
|
36
|
+
const stableDir = `.agents/data/tasks/${id}`;
|
|
37
|
+
const prosePath = `${stableDir}/intent-design.md`;
|
|
38
|
+
const workplanPath = `${stableDir}/workplan.md`;
|
|
39
|
+
if (source)
|
|
40
|
+
files.push({ path: prosePath, owner: "aidlc-state", ownershipClass: "state", content: source });
|
|
41
|
+
const oldWorkplan = doc.replace(/\.md$/, ".workplan.md");
|
|
42
|
+
if (existsSync(safePath(root, oldWorkplan)))
|
|
43
|
+
files.push({ path: `${stableDir}/legacy-workplan.md`, owner: "aidlc-state", ownershipClass: "state", content: readFileSync(safePath(root, oldWorkplan), "utf8") });
|
|
44
|
+
const phase = ["clarify", "plan", "build", "wrap", "done"].includes(phaseRaw) ? phaseRaw : "clarify";
|
|
45
|
+
const gate = ["none", "G0_confirm", "G1_review", "G2_codereview"].includes(gateRaw) ? gateRaw : "G0_confirm";
|
|
46
|
+
return {
|
|
47
|
+
id,
|
|
48
|
+
title,
|
|
49
|
+
type: (["feature", "bug", "refactor", "infra"].includes(frontmatter.type) ? frontmatter.type : "infra"),
|
|
50
|
+
phase,
|
|
51
|
+
gate,
|
|
52
|
+
status: (["active", "blocked_on_user", "paused", "done"].includes(statusRaw) ? statusRaw : "paused"),
|
|
53
|
+
language: frontmatter.language === "en" ? "en" : "vi",
|
|
54
|
+
risk: "normal",
|
|
55
|
+
areas: areasRaw.split(",").map((area) => area.trim()).filter(Boolean),
|
|
56
|
+
branch: branch || "—",
|
|
57
|
+
artifacts: { intent: source ? prosePath : undefined, design: source ? prosePath : undefined, workplan: workplanPath },
|
|
58
|
+
decisions: [],
|
|
59
|
+
tasks: [],
|
|
60
|
+
evidence: [...legacyEvidence(phase, recordedAt), { kind: "diagnostic", source: "legacy state migration", result: "pass", detail: `Original artifacts preserved at ${doc}`, recordedAt }],
|
|
61
|
+
createdAt: recordedAt,
|
|
62
|
+
updatedAt: recordedAt
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
export const migrateLegacyBoard = (root) => {
|
|
66
|
+
const boardPath = join(resolve(root), ".agents/data/state/BOARD.md");
|
|
67
|
+
if (!existsSync(boardPath))
|
|
68
|
+
return { state: emptyState(), files: [] };
|
|
69
|
+
const content = readFileSync(boardPath, "utf8");
|
|
70
|
+
const state = emptyState();
|
|
71
|
+
const files = [];
|
|
72
|
+
let migratedRows = 0;
|
|
73
|
+
for (const line of content.split("\n")) {
|
|
74
|
+
if (!line.startsWith("|") || /^\|[- ]+\|/.test(line) || line.includes("| id |"))
|
|
75
|
+
continue;
|
|
76
|
+
const row = cells(line);
|
|
77
|
+
if (row.length < 8)
|
|
78
|
+
throw new Error("Legacy BOARD contains a malformed task row; refusing lossy migration");
|
|
79
|
+
migratedRows += 1;
|
|
80
|
+
const task = taskFromLegacyRow(root, files, row, new Date().toISOString());
|
|
81
|
+
state.tasks[task.id] = task;
|
|
82
|
+
}
|
|
83
|
+
if (!migratedRows && !content.includes("_No tasks in flight._"))
|
|
84
|
+
throw new Error("Legacy BOARD contains no recognizable task rows; refusing lossy migration");
|
|
85
|
+
for (const task of Object.values(state.tasks))
|
|
86
|
+
files.push({ path: task.artifacts.workplan, owner: "aidlc-state", ownershipClass: "state", content: renderWorkplan(task) });
|
|
87
|
+
validateState(state);
|
|
88
|
+
return { state, files };
|
|
89
|
+
};
|
package/dist/src/upgrade.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
};
|
|
6
|
-
export declare const planUpgrade: (root: string) => PlannedWrite[];
|
|
1
|
+
import type { LoopConfig, PlannedWrite } from "./model.js";
|
|
2
|
+
export { migrateLegacyBoard } from "./upgrade-board.js";
|
|
3
|
+
/** Plans an upgrade transaction from the installed manifest to the current package. */
|
|
4
|
+
export declare const planUpgrade: (root: string, loop?: LoopConfig) => PlannedWrite[];
|
|
7
5
|
export interface UpgradeResult {
|
|
8
6
|
backup: string;
|
|
9
7
|
changed: number;
|
|
10
8
|
}
|
|
9
|
+
/** Applies a planned upgrade transaction with backup and rollback on failure. */
|
|
11
10
|
export declare const applyUpgrade: (root: string, plan: PlannedWrite[]) => UpgradeResult;
|
package/dist/src/upgrade.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { findAdapterForUpgrade, resolvedAdaptersForUpgrade } from "./adapters.js";
|
|
4
|
-
import { managedContentHash, readManifest } from "./installer.js";
|
|
4
|
+
import { installedHashFor, managedContentHash, planLoopSelection, readManifest } from "./installer.js";
|
|
5
5
|
import { legacyV021AdapterContents, legacyV021Hashes } from "./legacy.js";
|
|
6
6
|
import { planProjectLayoutMigration } from "./project-migration.js";
|
|
7
7
|
import { mergeManagedBlock } from "./managed-content.js";
|
|
8
8
|
import { resolveProjectPath, resolveProjectPathWithoutSymlinks } from "./project-path.js";
|
|
9
|
-
import {
|
|
9
|
+
import { prepareV4Migration, validateState } from "./state.js";
|
|
10
10
|
import { contentHash, coreWorkflowFiles, initialProjectFiles, manifestSpec } from "./workflow.js";
|
|
11
|
+
import { migrateLegacyBoard } from "./upgrade-board.js";
|
|
12
|
+
export { migrateLegacyBoard } from "./upgrade-board.js";
|
|
11
13
|
const safePath = (root, relative) => resolveProjectPath(root, relative, "Unsafe upgrade path");
|
|
12
14
|
const assertNoSymlinkPath = (root, relative) => { resolveProjectPathWithoutSymlinks(root, relative, "Unsafe upgrade path", "Refusing to upgrade symlink path"); };
|
|
13
15
|
const legacyManifest = (root) => {
|
|
@@ -20,79 +22,9 @@ const legacyManifest = (root) => {
|
|
|
20
22
|
}
|
|
21
23
|
catch {
|
|
22
24
|
return false;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
const parseFrontmatter = (content) => {
|
|
26
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
27
|
-
if (!match)
|
|
28
|
-
return {};
|
|
29
|
-
return Object.fromEntries(match[1].split("\n").flatMap((line) => {
|
|
30
|
-
const separator = line.indexOf(":");
|
|
31
|
-
return separator < 0 ? [] : [[line.slice(0, separator).trim(), line.slice(separator + 1).trim().replace(/^['"]|['"]$/g, "")]];
|
|
32
|
-
}));
|
|
33
|
-
};
|
|
34
|
-
const cells = (line) => line.split("|").slice(1, -1).map((cell) => cell.trim().replace(/^`|`$/g, ""));
|
|
35
|
-
export const migrateLegacyBoard = (root) => {
|
|
36
|
-
const boardPath = join(resolve(root), ".agents/data/state/BOARD.md");
|
|
37
|
-
if (!existsSync(boardPath))
|
|
38
|
-
return { state: emptyState(), files: [] };
|
|
39
|
-
const state = emptyState();
|
|
40
|
-
const files = [];
|
|
41
|
-
const boardContent = readFileSync(boardPath, "utf8");
|
|
42
|
-
let migratedRows = 0;
|
|
43
|
-
for (const line of boardContent.split("\n")) {
|
|
44
|
-
if (!line.startsWith("|") || /^\|[- ]+\|/.test(line) || line.includes("| id |"))
|
|
45
|
-
continue;
|
|
46
|
-
const row = cells(line);
|
|
47
|
-
if (row.length < 8)
|
|
48
|
-
throw new Error("Legacy BOARD contains a malformed task row; refusing lossy migration");
|
|
49
|
-
const [id, title, phaseRaw, gateRaw, statusRaw, branch, areasRaw, doc] = row;
|
|
50
|
-
migratedRows += 1;
|
|
51
|
-
const oldDoc = safePath(root, doc);
|
|
52
|
-
const source = existsSync(oldDoc) ? readFileSync(oldDoc, "utf8") : "";
|
|
53
|
-
const frontmatter = parseFrontmatter(source);
|
|
54
|
-
const stableDir = `.agents/data/tasks/${id}`;
|
|
55
|
-
const prosePath = `${stableDir}/intent-design.md`;
|
|
56
|
-
const workplanPath = `${stableDir}/workplan.md`;
|
|
57
|
-
if (source)
|
|
58
|
-
files.push({ path: prosePath, owner: "aidlc-state", ownershipClass: "state", content: source });
|
|
59
|
-
const oldWorkplan = doc.replace(/\.md$/, ".workplan.md");
|
|
60
|
-
if (existsSync(safePath(root, oldWorkplan)))
|
|
61
|
-
files.push({ path: `${stableDir}/legacy-workplan.md`, owner: "aidlc-state", ownershipClass: "state", content: readFileSync(safePath(root, oldWorkplan), "utf8") });
|
|
62
|
-
const phase = ["clarify", "plan", "build", "wrap", "done"].includes(phaseRaw) ? phaseRaw : "clarify";
|
|
63
|
-
const gate = ["none", "G0_confirm", "G1_review", "G2_codereview"].includes(gateRaw) ? gateRaw : "G0_confirm";
|
|
64
|
-
const evidence = [];
|
|
65
|
-
const now = new Date().toISOString();
|
|
66
|
-
if (["plan", "build", "wrap", "done"].includes(phase))
|
|
67
|
-
evidence.push({ kind: "approval", gate: "G0_confirm", source: "legacy state migration", result: "pass", recordedAt: now });
|
|
68
|
-
if (["build", "wrap", "done"].includes(phase))
|
|
69
|
-
evidence.push({ kind: "approval", gate: "G1_review", source: "legacy state migration", result: "pass", recordedAt: now });
|
|
70
|
-
state.tasks[id] = {
|
|
71
|
-
id,
|
|
72
|
-
title,
|
|
73
|
-
type: (["feature", "bug", "refactor", "infra"].includes(frontmatter.type) ? frontmatter.type : "infra"),
|
|
74
|
-
phase,
|
|
75
|
-
gate,
|
|
76
|
-
status: (["active", "blocked_on_user", "paused", "done"].includes(statusRaw) ? statusRaw : "paused"),
|
|
77
|
-
language: frontmatter.language === "en" ? "en" : "vi",
|
|
78
|
-
risk: "normal",
|
|
79
|
-
areas: areasRaw.split(",").map((area) => area.trim()).filter(Boolean),
|
|
80
|
-
branch: branch || "—",
|
|
81
|
-
artifacts: { intent: source ? prosePath : undefined, design: source ? prosePath : undefined, workplan: workplanPath },
|
|
82
|
-
decisions: [],
|
|
83
|
-
tasks: [],
|
|
84
|
-
evidence: [...evidence, { kind: "diagnostic", source: "legacy state migration", result: "pass", detail: `Original artifacts preserved at ${doc}`, recordedAt: now }],
|
|
85
|
-
createdAt: now,
|
|
86
|
-
updatedAt: now
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
if (!migratedRows && !boardContent.includes("_No tasks in flight._"))
|
|
90
|
-
throw new Error("Legacy BOARD contains no recognizable task rows; refusing lossy migration");
|
|
91
|
-
for (const task of Object.values(state.tasks))
|
|
92
|
-
files.push({ path: task.artifacts.workplan, owner: "aidlc-state", ownershipClass: "state", content: renderWorkplan(task) });
|
|
93
|
-
validateState(state);
|
|
94
|
-
return { state, files };
|
|
25
|
+
} /* intentional: a malformed/missing manifest is not a legacy v0.x install */
|
|
95
26
|
};
|
|
27
|
+
/* --- Managed file planning -------------------------------------------------- */
|
|
96
28
|
const planManaged = (root, spec, installedHash, legacy) => {
|
|
97
29
|
const target = safePath(root, spec.path);
|
|
98
30
|
if (!existsSync(target))
|
|
@@ -113,89 +45,221 @@ const planManaged = (root, spec, installedHash, legacy) => {
|
|
|
113
45
|
return { ...spec, content: next, action: "conflict", reason: "managed prompt block was modified" };
|
|
114
46
|
return { ...spec, content: next, action: "conflict", reason: legacy ? "legacy core differs from known baseline" : "managed file differs from installed hash" };
|
|
115
47
|
};
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
48
|
+
/** Merges a later owner of a shared managed-block path into the already-emitted plan entry. */
|
|
49
|
+
const mergeSharedBlockOwner = (project, previous, spec, installed) => {
|
|
50
|
+
if (!["create", "update", "skip"].includes(previous.action))
|
|
51
|
+
return;
|
|
52
|
+
const target = safePath(project, spec.path);
|
|
53
|
+
const current = existsSync(target) ? readFileSync(target, "utf8") : "";
|
|
54
|
+
// Every owner block is checked before merging so a user edit to a later
|
|
55
|
+
// adapter's block is reported instead of being silently overwritten.
|
|
56
|
+
const installedBlockHash = installedHashFor(installed?.files, spec);
|
|
57
|
+
const currentBlockHash = managedContentHash(spec, current);
|
|
58
|
+
if (installedBlockHash !== undefined && currentBlockHash !== undefined && installedBlockHash !== currentBlockHash) {
|
|
59
|
+
previous.action = "conflict";
|
|
60
|
+
previous.reason = "managed prompt block was modified";
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
previous.content = mergeManagedBlock(spec, previous.content);
|
|
64
|
+
previous.action = existsSync(target) ? (previous.content === current ? "skip" : "update") : "create";
|
|
65
|
+
previous.reason = "managed prompt block";
|
|
66
|
+
};
|
|
67
|
+
/** Plans managed replacements and shared managed-block writes across every owner. */
|
|
68
|
+
const planManagedFiles = (project, managed, installed, legacy) => {
|
|
69
|
+
const plan = [];
|
|
70
|
+
const mergedBlocks = new Map();
|
|
71
|
+
for (const spec of managed) {
|
|
72
|
+
const installedHash = installedHashFor(installed?.files, spec);
|
|
73
|
+
if (spec.strategy !== "managed-block") {
|
|
74
|
+
plan.push(planManaged(project, spec, installedHash, legacy));
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const previous = mergedBlocks.get(spec.path);
|
|
78
|
+
if (!previous) {
|
|
79
|
+
const write = planManaged(project, spec, installedHash, legacy);
|
|
80
|
+
plan.push(write);
|
|
81
|
+
mergedBlocks.set(spec.path, write);
|
|
82
|
+
continue;
|
|
138
83
|
}
|
|
84
|
+
mergeSharedBlockOwner(project, previous, spec, installed);
|
|
139
85
|
}
|
|
86
|
+
return plan;
|
|
87
|
+
};
|
|
88
|
+
/* --- Obsolete files and project-owned files ---------------------------------- */
|
|
89
|
+
/** Deletes obsolete managed files tracked by the installed manifest that no longer ship in this release. */
|
|
90
|
+
const planObsoleteRemovals = (project, plan, installed, managed) => {
|
|
91
|
+
for (const [key, item] of Object.entries(installed.files)) {
|
|
92
|
+
const path = key.includes("#") ? key.slice(0, key.lastIndexOf("#")) : key;
|
|
93
|
+
// Shared prompt blocks are upgraded through the merged plan above; never delete their whole path here.
|
|
94
|
+
if (item.strategy === "managed-block")
|
|
95
|
+
continue;
|
|
96
|
+
if (managed.some((spec) => spec.path === path))
|
|
97
|
+
continue;
|
|
98
|
+
const target = safePath(project, path);
|
|
99
|
+
if (!existsSync(target))
|
|
100
|
+
continue;
|
|
101
|
+
const current = readFileSync(target, "utf8");
|
|
102
|
+
const hash = managedContentHash({ owner: item.owner, strategy: item.strategy }, current);
|
|
103
|
+
plan.push({ path, owner: item.owner, strategy: item.strategy, ownershipClass: "managed", content: "", action: hash === item.hash ? "delete" : "conflict", reason: hash === item.hash ? "obsolete unchanged managed file" : "obsolete managed file was modified" });
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const preserveProjectFiles = (project, plan, legacy) => {
|
|
140
107
|
for (const spec of initialProjectFiles()) {
|
|
141
108
|
if (legacy && spec.path === ".agents/data/state/aidlc-state.json" && !existsSync(safePath(project, spec.path)))
|
|
142
109
|
continue;
|
|
143
110
|
const target = safePath(project, spec.path);
|
|
144
111
|
plan.push(existsSync(target) ? { ...spec, content: readFileSync(target, "utf8"), action: "preserve", reason: `${spec.ownershipClass}-owned file` } : { ...spec, action: "create", reason: `new ${spec.ownershipClass}-owned file` });
|
|
145
112
|
}
|
|
113
|
+
};
|
|
114
|
+
/* --- Canonical state and legacy BOARD migration -------------------------------- */
|
|
115
|
+
/** Migrates a preserved canonical state file (schema v1–v3) to v4 with immutable archived-task records. */
|
|
116
|
+
const migrateCanonicalState = (project, plan, canonicalState) => {
|
|
117
|
+
if (canonicalState.action !== "preserve")
|
|
118
|
+
return;
|
|
119
|
+
const parsed = JSON.parse(canonicalState.content);
|
|
120
|
+
const normalized = validateState(parsed);
|
|
121
|
+
if (parsed.schemaVersion !== 1 && parsed.schemaVersion !== 2 && parsed.schemaVersion !== 3)
|
|
122
|
+
return;
|
|
123
|
+
const migration = prepareV4Migration(normalized);
|
|
124
|
+
canonicalState.content = `${JSON.stringify(migration.state, null, 2)}\n`;
|
|
125
|
+
canonicalState.action = "migrate";
|
|
126
|
+
canonicalState.reason = `migrate canonical state schema v${parsed.schemaVersion} to v4 compact catalog${migration.stamped.length ? ` and stamp pre-existing ready G2 waits: ${migration.stamped.join(", ")}` : " without changing task content"}`;
|
|
127
|
+
for (const record of migration.records) {
|
|
128
|
+
assertNoSymlinkPath(project, record.path);
|
|
129
|
+
const target = safePath(project, record.path);
|
|
130
|
+
if (!existsSync(target))
|
|
131
|
+
plan.push({ path: record.path, owner: "aidlc-state", ownershipClass: "state", content: record.content, action: "migrate", reason: "write immutable terminal task record" });
|
|
132
|
+
else if (readFileSync(target, "utf8") !== record.content)
|
|
133
|
+
plan.push({ path: record.path, owner: "aidlc-state", ownershipClass: "state", content: record.content, action: "conflict", reason: "terminal task record conflicts with prior canonical content" });
|
|
134
|
+
}
|
|
135
|
+
const lessonIndex = plan.find((item) => item.path === ".agents/data/lessons/index.json");
|
|
136
|
+
if (!lessonIndex)
|
|
137
|
+
throw new Error("Lesson index plan is missing");
|
|
138
|
+
lessonIndex.content = `${JSON.stringify(migration.lessonIndex, null, 2)}\n`;
|
|
139
|
+
lessonIndex.action = "migrate";
|
|
140
|
+
lessonIndex.reason = "build derived lesson index from canonical task records";
|
|
141
|
+
};
|
|
142
|
+
/** Seeds the plan from a legacy BOARD.md when no canonical state file exists yet. */
|
|
143
|
+
const migrateLegacyState = (project, plan, legacy) => {
|
|
144
|
+
if (!legacy || existsSync(safePath(project, ".agents/data/state/aidlc-state.json")))
|
|
145
|
+
return;
|
|
146
|
+
const migration = migrateLegacyBoard(project);
|
|
147
|
+
const compact = prepareV4Migration(migration.state);
|
|
148
|
+
plan.push({ path: ".agents/data/state/aidlc-state.json", owner: "aidlc-state", ownershipClass: "state", content: `${JSON.stringify(compact.state, null, 2)}\n`, action: "migrate", reason: "migrate legacy BOARD to canonical v4 state" });
|
|
149
|
+
for (const record of compact.records)
|
|
150
|
+
plan.push({ path: record.path, owner: "aidlc-state", ownershipClass: "state", content: record.content, action: "migrate", reason: "preserve legacy terminal task as immutable record" });
|
|
151
|
+
const lessonIndex = plan.find((item) => item.path === ".agents/data/lessons/index.json");
|
|
152
|
+
if (lessonIndex) {
|
|
153
|
+
lessonIndex.content = `${JSON.stringify(compact.lessonIndex, null, 2)}\n`;
|
|
154
|
+
lessonIndex.action = "migrate";
|
|
155
|
+
lessonIndex.reason = "build derived lesson index during legacy migration";
|
|
156
|
+
}
|
|
157
|
+
for (const file of migration.files)
|
|
158
|
+
plan.push({ ...file, action: "migrate", reason: "preserve legacy task prose in stable task directory" });
|
|
159
|
+
};
|
|
160
|
+
/** Removes the duplicate BOARD.md projection only when the planned canonical state validates. */
|
|
161
|
+
const removeLegacyBoard = (project, plan) => {
|
|
162
|
+
const boardPath = safePath(project, ".agents/data/state/BOARD.md");
|
|
163
|
+
if (!existsSync(boardPath))
|
|
164
|
+
return;
|
|
165
|
+
const stateItem = plan.find((item) => item.path === ".agents/data/state/aidlc-state.json");
|
|
166
|
+
if (!stateItem)
|
|
167
|
+
throw new Error("Canonical state must be present before removing legacy BOARD");
|
|
168
|
+
validateState(JSON.parse(stateItem.content));
|
|
169
|
+
plan.push({ path: ".agents/data/state/BOARD.md", owner: "aidlc-state", ownershipClass: "state", content: "", action: "delete", reason: "remove duplicate lifecycle-state projection after canonical JSON validation" });
|
|
170
|
+
};
|
|
171
|
+
/* --- planUpgrade orchestration ------------------------------------------------ */
|
|
172
|
+
/** Selects which adapters to upgrade: manifest-registered ones, else auto-detected installable ones. */
|
|
173
|
+
const selectedUpgradeAgents = (project, installed) => {
|
|
174
|
+
const upgradeAdapters = resolvedAdaptersForUpgrade();
|
|
175
|
+
const requested = installed?.adapters?.length ? installed.adapters : upgradeAdapters.filter((adapter) => adapter.detect(project)).map((adapter) => adapter.id);
|
|
176
|
+
return requested.filter((id) => findAdapterForUpgrade(id));
|
|
177
|
+
};
|
|
178
|
+
const agentUpgradeFiles = (selected) => selected.flatMap((id) => findAdapterForUpgrade(id)?.files() ?? []).map((file) => ({ ...file, ownershipClass: "managed" }));
|
|
179
|
+
/** Plans an upgrade transaction from the installed manifest to the current package. */
|
|
180
|
+
export const planUpgrade = (root, loop) => {
|
|
181
|
+
const project = resolve(root);
|
|
182
|
+
const installed = readManifest(project);
|
|
183
|
+
const legacy = !installed && legacyManifest(project);
|
|
184
|
+
if (!installed && !legacy)
|
|
185
|
+
throw new Error("No owned AI-DLC installation found. Use init instead of upgrade.");
|
|
186
|
+
const selected = selectedUpgradeAgents(project, installed);
|
|
187
|
+
const managed = [...coreWorkflowFiles(), ...agentUpgradeFiles(selected)];
|
|
188
|
+
const plan = planManagedFiles(project, managed, installed, legacy);
|
|
189
|
+
if (installed)
|
|
190
|
+
planObsoleteRemovals(project, plan, installed, managed);
|
|
191
|
+
preserveProjectFiles(project, plan, legacy);
|
|
146
192
|
const canonicalState = plan.find((item) => item.path === ".agents/data/state/aidlc-state.json");
|
|
147
|
-
if (canonicalState
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
193
|
+
if (canonicalState)
|
|
194
|
+
migrateCanonicalState(project, plan, canonicalState);
|
|
195
|
+
planProjectLayoutMigration(project, plan);
|
|
196
|
+
migrateLegacyState(project, plan, legacy);
|
|
197
|
+
removeLegacyBoard(project, plan);
|
|
198
|
+
plan.push({ ...manifestSpec(managed, selected), action: "update", reason: legacy ? "migrate manifest to schema v2" : "update manifest inventory" });
|
|
199
|
+
planLoopSelection(plan, loop);
|
|
200
|
+
return plan;
|
|
201
|
+
};
|
|
202
|
+
/** Copies each affected original into the backup and stages the new content outside the live tree. */
|
|
203
|
+
const stageUpgradedWrites = (project, writes, backup, stage, journal) => {
|
|
204
|
+
for (const item of writes) {
|
|
205
|
+
const target = safePath(project, item.path);
|
|
206
|
+
assertNoSymlinkPath(project, item.path);
|
|
207
|
+
const existed = existsSync(target);
|
|
208
|
+
journal.push({ path: item.path, existed, action: item.action });
|
|
209
|
+
if (existed) {
|
|
210
|
+
const destination = join(backup, item.path);
|
|
211
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
212
|
+
copyFileSync(target, destination);
|
|
213
|
+
}
|
|
214
|
+
if (item.action !== "delete") {
|
|
215
|
+
const staged = join(stage, item.path);
|
|
216
|
+
mkdirSync(dirname(staged), { recursive: true });
|
|
217
|
+
writeFileSync(staged, item.contentEncoding === "base64" ? Buffer.from(item.content, "base64") : item.content);
|
|
218
|
+
if (item.path.endsWith(".json") && item.contentEncoding !== "base64")
|
|
219
|
+
JSON.parse(item.content);
|
|
169
220
|
}
|
|
170
221
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
222
|
+
};
|
|
223
|
+
const writeUpgradeJournal = (backup, journal) => {
|
|
224
|
+
mkdirSync(backup, { recursive: true });
|
|
225
|
+
writeFileSync(join(backup, "journal.json"), `${JSON.stringify(journal, null, 2)}\n`, "utf8");
|
|
226
|
+
};
|
|
227
|
+
const commitStagedWrites = (project, writes, stage) => {
|
|
228
|
+
for (const item of writes) {
|
|
229
|
+
const target = safePath(project, item.path);
|
|
230
|
+
if (item.action === "delete")
|
|
231
|
+
rmSync(target, { force: true });
|
|
232
|
+
else {
|
|
233
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
234
|
+
if (existsSync(target))
|
|
235
|
+
rmSync(target, { force: true });
|
|
236
|
+
renameSync(join(stage, item.path), target);
|
|
183
237
|
}
|
|
184
|
-
for (const file of migration.files)
|
|
185
|
-
plan.push({ ...file, action: "migrate", reason: "preserve legacy task prose in stable task directory" });
|
|
186
238
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
239
|
+
};
|
|
240
|
+
const pruneEmptyDirectories = (directory) => {
|
|
241
|
+
if (!existsSync(directory) || !lstatSync(directory).isDirectory())
|
|
242
|
+
return;
|
|
243
|
+
for (const entry of readdirSync(directory, { withFileTypes: true }))
|
|
244
|
+
if (entry.isDirectory())
|
|
245
|
+
pruneEmptyDirectories(join(directory, entry.name));
|
|
246
|
+
if (readdirSync(directory).length === 0)
|
|
247
|
+
rmdirSync(directory);
|
|
248
|
+
};
|
|
249
|
+
/** Restores the journaled originals so a failed upgrade leaves no partial state. */
|
|
250
|
+
const restoreFromJournal = (project, journal, backup) => {
|
|
251
|
+
for (const entry of journal.reverse()) {
|
|
252
|
+
const target = safePath(project, entry.path);
|
|
253
|
+
const source = join(backup, entry.path);
|
|
254
|
+
if (entry.existed && existsSync(source)) {
|
|
255
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
256
|
+
copyFileSync(source, target);
|
|
257
|
+
}
|
|
258
|
+
else if (!entry.existed)
|
|
259
|
+
rmSync(target, { force: true });
|
|
194
260
|
}
|
|
195
|
-
const manifest = manifestSpec(managed, selected);
|
|
196
|
-
plan.push({ ...manifest, action: "update", reason: legacy ? "migrate manifest to schema v2" : "update manifest inventory" });
|
|
197
|
-
return plan;
|
|
198
261
|
};
|
|
262
|
+
/** Applies a planned upgrade transaction with backup and rollback on failure. */
|
|
199
263
|
export const applyUpgrade = (root, plan) => {
|
|
200
264
|
const conflicts = plan.filter((item) => item.action === "conflict");
|
|
201
265
|
if (conflicts.length)
|
|
@@ -209,61 +273,15 @@ export const applyUpgrade = (root, plan) => {
|
|
|
209
273
|
mkdirSync(stage, { recursive: true });
|
|
210
274
|
const journal = [];
|
|
211
275
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
journal.push({ path: item.path, existed, action: item.action });
|
|
217
|
-
if (existed) {
|
|
218
|
-
const destination = join(backup, item.path);
|
|
219
|
-
mkdirSync(dirname(destination), { recursive: true });
|
|
220
|
-
copyFileSync(target, destination);
|
|
221
|
-
}
|
|
222
|
-
if (item.action !== "delete") {
|
|
223
|
-
const staged = join(stage, item.path);
|
|
224
|
-
mkdirSync(dirname(staged), { recursive: true });
|
|
225
|
-
writeFileSync(staged, item.contentEncoding === "base64" ? Buffer.from(item.content, "base64") : item.content);
|
|
226
|
-
if (item.path.endsWith(".json") && item.contentEncoding !== "base64")
|
|
227
|
-
JSON.parse(item.content);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
mkdirSync(backup, { recursive: true });
|
|
231
|
-
writeFileSync(join(backup, "journal.json"), `${JSON.stringify(journal, null, 2)}\n`, "utf8");
|
|
232
|
-
for (const item of writes) {
|
|
233
|
-
const target = safePath(project, item.path);
|
|
234
|
-
if (item.action === "delete")
|
|
235
|
-
rmSync(target, { force: true });
|
|
236
|
-
else {
|
|
237
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
238
|
-
if (existsSync(target))
|
|
239
|
-
rmSync(target, { force: true });
|
|
240
|
-
renameSync(join(stage, item.path), target);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
const pruneEmpty = (directory) => {
|
|
244
|
-
if (!existsSync(directory) || !lstatSync(directory).isDirectory())
|
|
245
|
-
return;
|
|
246
|
-
for (const entry of readdirSync(directory, { withFileTypes: true }))
|
|
247
|
-
if (entry.isDirectory())
|
|
248
|
-
pruneEmpty(join(directory, entry.name));
|
|
249
|
-
if (readdirSync(directory).length === 0)
|
|
250
|
-
rmdirSync(directory);
|
|
251
|
-
};
|
|
252
|
-
pruneEmpty(safePath(project, ".aidlc"));
|
|
276
|
+
stageUpgradedWrites(project, writes, backup, stage, journal);
|
|
277
|
+
writeUpgradeJournal(backup, journal);
|
|
278
|
+
commitStagedWrites(project, writes, stage);
|
|
279
|
+
pruneEmptyDirectories(safePath(project, ".aidlc"));
|
|
253
280
|
rmSync(stage, { recursive: true, force: true });
|
|
254
281
|
return { backup: backupRelative, changed: writes.length };
|
|
255
282
|
}
|
|
256
283
|
catch (error) {
|
|
257
|
-
|
|
258
|
-
const target = safePath(project, entry.path);
|
|
259
|
-
const source = join(backup, entry.path);
|
|
260
|
-
if (entry.existed && existsSync(source)) {
|
|
261
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
262
|
-
copyFileSync(source, target);
|
|
263
|
-
}
|
|
264
|
-
else if (!entry.existed)
|
|
265
|
-
rmSync(target, { force: true });
|
|
266
|
-
}
|
|
284
|
+
restoreFromJournal(project, journal, backup);
|
|
267
285
|
rmSync(stage, { recursive: true, force: true });
|
|
268
286
|
throw error;
|
|
269
287
|
}
|
package/dist/src/workflow.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { AgentId, FileSpec, WorkflowManifest } from "./model.js";
|
|
|
2
2
|
export declare const contentHash: (content: string | Uint8Array) => string;
|
|
3
3
|
export declare const coreWorkflowFiles: () => FileSpec[];
|
|
4
4
|
export declare const initialProjectFiles: () => FileSpec[];
|
|
5
|
+
export declare const manifestKey: (spec: Pick<FileSpec, "path" | "owner" | "strategy">) => string;
|
|
5
6
|
export declare const manifestFor: (files: FileSpec[], adapters: AgentId[]) => WorkflowManifest;
|
|
6
7
|
export declare const manifestSpec: (files: FileSpec[], adapters: AgentId[]) => FileSpec;
|
|
7
8
|
export declare const packageVersion: () => string;
|