@felixdotgo/aidlc-kit 0.0.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.
Files changed (93) hide show
  1. package/DEVELOPMENT.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +59 -0
  4. package/dist/assets/.agents/aidlc/conventions.md +68 -0
  5. package/dist/assets/.agents/aidlc/orchestrator.md +73 -0
  6. package/dist/assets/.agents/aidlc/phase-build.md +23 -0
  7. package/dist/assets/.agents/aidlc/phase-clarify.md +22 -0
  8. package/dist/assets/.agents/aidlc/phase-index.md +14 -0
  9. package/dist/assets/.agents/aidlc/phase-plan.md +21 -0
  10. package/dist/assets/.agents/aidlc/phase-wrap.md +16 -0
  11. package/dist/assets/.agents/aidlc/profiles/generic/profile.json +9 -0
  12. package/dist/assets/.agents/aidlc/profiles/git-submodules/profile.json +9 -0
  13. package/dist/assets/.agents/aidlc/profiles/single/profile.json +9 -0
  14. package/dist/assets/.agents/aidlc/profiles/workspace/profile.json +12 -0
  15. package/dist/assets/.agents/aidlc/rules/gate-check.md +16 -0
  16. package/dist/assets/.agents/aidlc/rules/stack-runtime.md +3 -0
  17. package/dist/assets/.agents/aidlc/schemas/adapter.schema.json +13 -0
  18. package/dist/assets/.agents/aidlc/schemas/config.schema.json +26 -0
  19. package/dist/assets/.agents/aidlc/schemas/manifest.schema.json +12 -0
  20. package/dist/assets/.agents/aidlc/schemas/profile.schema.json +12 -0
  21. package/dist/assets/.agents/aidlc/schemas/state.schema.json +108 -0
  22. package/dist/assets/.agents/aidlc/scripts/context.mjs +15 -0
  23. package/dist/assets/.agents/aidlc/scripts/gate-check.mjs +16 -0
  24. package/dist/assets/.agents/aidlc/scripts/gate-view.mjs +16 -0
  25. package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +238 -0
  26. package/dist/assets/.agents/aidlc/scripts/lib/runtime.mjs +2 -0
  27. package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +341 -0
  28. package/dist/assets/.agents/aidlc/scripts/render.mjs +24 -0
  29. package/dist/assets/.agents/aidlc/scripts/state.mjs +222 -0
  30. package/dist/assets/.agents/aidlc/scripts/task-next.mjs +27 -0
  31. package/dist/assets/.agents/aidlc/templates/design.md +15 -0
  32. package/dist/assets/.agents/aidlc/templates/intent.md +17 -0
  33. package/dist/assets/.agents/aidlc/templates/lesson.md +28 -0
  34. package/dist/assets/.agents/aidlc/templates/model-contract.md +41 -0
  35. package/dist/assets/.agents/aidlc/templates/workplan.md +9 -0
  36. package/dist/assets/.agents/skills/aidlc-build/SKILL.md +19 -0
  37. package/dist/assets/.agents/skills/aidlc-clarify/SKILL.md +17 -0
  38. package/dist/assets/.agents/skills/aidlc-index/SKILL.md +17 -0
  39. package/dist/assets/.agents/skills/aidlc-plan/SKILL.md +17 -0
  40. package/dist/assets/.agents/skills/aidlc-wrap/SKILL.md +17 -0
  41. package/dist/services/aidlc-coordination/Dockerfile +10 -0
  42. package/dist/services/aidlc-coordination/compose.yaml +25 -0
  43. package/dist/services/aidlc-coordination/package-lock.json +1154 -0
  44. package/dist/services/aidlc-coordination/package.json +20 -0
  45. package/dist/services/aidlc-coordination/src/coordination.mjs +68 -0
  46. package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +341 -0
  47. package/dist/services/aidlc-coordination/src/providers.mjs +23 -0
  48. package/dist/services/aidlc-coordination/src/repository.mjs +203 -0
  49. package/dist/services/aidlc-coordination/src/server.mjs +49 -0
  50. package/dist/services/aidlc-coordination/test/repository.test.mjs +211 -0
  51. package/dist/src/adapters.d.ts +7 -0
  52. package/dist/src/adapters.js +86 -0
  53. package/dist/src/cli.d.ts +2 -0
  54. package/dist/src/cli.js +192 -0
  55. package/dist/src/context.d.ts +14 -0
  56. package/dist/src/context.js +90 -0
  57. package/dist/src/gate.d.ts +40 -0
  58. package/dist/src/gate.js +176 -0
  59. package/dist/src/installer.d.ts +11 -0
  60. package/dist/src/installer.js +173 -0
  61. package/dist/src/ledger.d.ts +22 -0
  62. package/dist/src/ledger.js +119 -0
  63. package/dist/src/legacy.d.ts +2 -0
  64. package/dist/src/legacy.js +37 -0
  65. package/dist/src/managed-content.d.ts +3 -0
  66. package/dist/src/managed-content.js +16 -0
  67. package/dist/src/mcp-setup.d.ts +20 -0
  68. package/dist/src/mcp-setup.js +31 -0
  69. package/dist/src/model.d.ts +249 -0
  70. package/dist/src/model.js +1 -0
  71. package/dist/src/profiles.d.ts +28 -0
  72. package/dist/src/profiles.js +216 -0
  73. package/dist/src/project-migration.d.ts +2 -0
  74. package/dist/src/project-migration.js +144 -0
  75. package/dist/src/project-path.d.ts +2 -0
  76. package/dist/src/project-path.js +20 -0
  77. package/dist/src/state.d.ts +110 -0
  78. package/dist/src/state.js +764 -0
  79. package/dist/src/tracker.d.ts +29 -0
  80. package/dist/src/tracker.js +41 -0
  81. package/dist/src/upgrade.d.ts +11 -0
  82. package/dist/src/upgrade.js +270 -0
  83. package/dist/src/workflow.d.ts +7 -0
  84. package/dist/src/workflow.js +46 -0
  85. package/docs/README.md +31 -0
  86. package/docs/architecture.md +72 -0
  87. package/docs/command-reference.md +110 -0
  88. package/docs/configuration.md +72 -0
  89. package/docs/coordination-service.md +42 -0
  90. package/docs/development.md +106 -0
  91. package/docs/operating-workflow.md +87 -0
  92. package/docs/testing-and-release.md +113 -0
  93. package/package.json +53 -0
@@ -0,0 +1,29 @@
1
+ import type { TrackerConfig } from "./model.js";
2
+ export interface TrackerDiagnostic {
3
+ code: "TRACKER_DISABLED" | "TRACKER_AUTH" | "TRACKER_RATE_LIMIT" | "TRACKER_REQUEST";
4
+ message: string;
5
+ retryable: boolean;
6
+ }
7
+ export interface BacklogRequest {
8
+ method: "GET" | "PATCH";
9
+ path: string;
10
+ form?: Record<string, string | number>;
11
+ }
12
+ export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
13
+ export type TrackerResolution = {
14
+ enabled: false;
15
+ diagnostic: TrackerDiagnostic;
16
+ } | {
17
+ enabled: true;
18
+ config: TrackerConfig;
19
+ token: string;
20
+ };
21
+ export declare const resolveTracker: (config: TrackerConfig | undefined, environment?: NodeJS.ProcessEnv) => TrackerResolution;
22
+ export declare const executeBacklog: (resolution: TrackerResolution, request: BacklogRequest, fetcher?: FetchLike) => Promise<{
23
+ ok: true;
24
+ body: unknown;
25
+ } | {
26
+ ok: false;
27
+ diagnostic: TrackerDiagnostic;
28
+ }>;
29
+ export declare const gateUpdate: (config: TrackerConfig, issue: string, phase: "clarify" | "plan" | "build" | "wrap" | "done", idempotencyKey: string) => BacklogRequest;
@@ -0,0 +1,41 @@
1
+ export const resolveTracker = (config, environment = process.env) => {
2
+ if (!config)
3
+ return { enabled: false, diagnostic: { code: "TRACKER_DISABLED", message: "Tracker is not configured; Git-only mode is active.", retryable: false } };
4
+ const token = environment[config.tokenEnv];
5
+ if (!token)
6
+ return { enabled: false, diagnostic: { code: "TRACKER_DISABLED", message: `Backlog tracker is disabled because ${config.tokenEnv} is unavailable.`, retryable: false } };
7
+ return { enabled: true, config, token };
8
+ };
9
+ const form = (values) => {
10
+ const body = new URLSearchParams();
11
+ for (const [key, value] of Object.entries(values))
12
+ body.set(key, String(value));
13
+ return body;
14
+ };
15
+ export const executeBacklog = async (resolution, request, fetcher = fetch) => {
16
+ if (!resolution.enabled)
17
+ return { ok: false, diagnostic: resolution.diagnostic };
18
+ const response = await fetcher(`${resolution.config.spaceUrl}/api/v2/${request.path.replace(/^\//, "")}`, {
19
+ method: request.method,
20
+ headers: { Authorization: `Bearer ${resolution.token}`, ...(request.form ? { "Content-Type": "application/x-www-form-urlencoded" } : {}) },
21
+ body: request.form ? form(request.form) : undefined
22
+ });
23
+ if (response.status === 401)
24
+ return { ok: false, diagnostic: { code: "TRACKER_AUTH", message: "Backlog authentication failed; Git state was not changed.", retryable: false } };
25
+ if (response.status === 429)
26
+ return { ok: false, diagnostic: { code: "TRACKER_RATE_LIMIT", message: "Backlog rate limit reached; retry after the provider reset window.", retryable: true } };
27
+ if (!response.ok)
28
+ return { ok: false, diagnostic: { code: "TRACKER_REQUEST", message: `Backlog request failed with HTTP ${response.status}.`, retryable: response.status >= 500 } };
29
+ return { ok: true, body: await response.json() };
30
+ };
31
+ export const gateUpdate = (config, issue, phase, idempotencyKey) => {
32
+ const comment = `[aidlc:${idempotencyKey}] ${phase}`;
33
+ if (config.workflow === "waterfall") {
34
+ const statusId = config.mapping.statuses?.[phase];
35
+ if (!statusId)
36
+ throw new Error(`Backlog status mapping is required for Waterfall phase: ${phase}`);
37
+ return { method: "PATCH", path: `issues/${issue}`, form: { statusId, comment } };
38
+ }
39
+ const field = config.mapping.gateFieldId;
40
+ return { method: "PATCH", path: `issues/${issue}`, form: { ...(field ? { [`customField_${field}`]: phase } : {}), comment } };
41
+ };
@@ -0,0 +1,11 @@
1
+ import type { FileSpec, PlannedWrite, WorkflowState } from "./model.js";
2
+ export declare const migrateLegacyBoard: (root: string) => {
3
+ state: WorkflowState;
4
+ files: FileSpec[];
5
+ };
6
+ export declare const planUpgrade: (root: string) => PlannedWrite[];
7
+ export interface UpgradeResult {
8
+ backup: string;
9
+ changed: number;
10
+ }
11
+ export declare const applyUpgrade: (root: string, plan: PlannedWrite[]) => UpgradeResult;
@@ -0,0 +1,270 @@
1
+ import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { findAdapterForUpgrade, resolvedAdaptersForUpgrade } from "./adapters.js";
4
+ import { managedContentHash, readManifest } from "./installer.js";
5
+ import { legacyV021AdapterContents, legacyV021Hashes } from "./legacy.js";
6
+ import { planProjectLayoutMigration } from "./project-migration.js";
7
+ import { mergeManagedBlock } from "./managed-content.js";
8
+ import { resolveProjectPath, resolveProjectPathWithoutSymlinks } from "./project-path.js";
9
+ import { emptyState, prepareV4Migration, renderWorkplan, validateState } from "./state.js";
10
+ import { contentHash, coreWorkflowFiles, initialProjectFiles, manifestSpec } from "./workflow.js";
11
+ const safePath = (root, relative) => resolveProjectPath(root, relative, "Unsafe upgrade path");
12
+ const assertNoSymlinkPath = (root, relative) => { resolveProjectPathWithoutSymlinks(root, relative, "Unsafe upgrade path", "Refusing to upgrade symlink path"); };
13
+ const legacyManifest = (root) => {
14
+ const path = join(resolve(root), ".agents/aidlc/manifest.json");
15
+ if (!existsSync(path))
16
+ return false;
17
+ try {
18
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
19
+ return parsed.managedBy === "aidlc-workflow" && (parsed.schemaVersion === 0 || parsed.schemaVersion === 1);
20
+ }
21
+ catch {
22
+ 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 };
95
+ };
96
+ const planManaged = (root, spec, installedHash, legacy) => {
97
+ const target = safePath(root, spec.path);
98
+ if (!existsSync(target))
99
+ return { ...spec, action: "create", reason: "new managed file" };
100
+ const current = readFileSync(target, "utf8");
101
+ const next = spec.strategy === "managed-block" ? mergeManagedBlock(spec, current) : spec.content;
102
+ const currentHash = managedContentHash(spec, current);
103
+ const nextHash = contentHash(spec.strategy === "managed-block" ? spec.content.trimEnd() : spec.content);
104
+ if (currentHash === nextHash)
105
+ return { ...spec, content: next, action: next === current ? "skip" : "update", reason: "managed content already current" };
106
+ if (installedHash && currentHash === installedHash)
107
+ return { ...spec, content: next, action: "update", reason: "unchanged managed file" };
108
+ const legacyAdapter = legacyV021AdapterContents[spec.path];
109
+ const legacyAdapterHash = legacyAdapter ? contentHash(spec.strategy === "managed-block" ? legacyAdapter.trimEnd() : legacyAdapter) : undefined;
110
+ if (legacy && (legacyV021Hashes[spec.path] === currentHash || legacyAdapterHash === currentHash))
111
+ return { ...spec, content: next, action: "update", reason: "recognized v0.2.1 baseline" };
112
+ if (spec.strategy === "managed-block" && current.includes(`<!-- aidlc-installer:${spec.owner} -->`))
113
+ return { ...spec, content: next, action: "conflict", reason: "managed prompt block was modified" };
114
+ return { ...spec, content: next, action: "conflict", reason: legacy ? "legacy core differs from known baseline" : "managed file differs from installed hash" };
115
+ };
116
+ export const planUpgrade = (root) => {
117
+ const project = resolve(root);
118
+ const installed = readManifest(project);
119
+ const legacy = !installed && legacyManifest(project);
120
+ if (!installed && !legacy)
121
+ throw new Error("No owned AI-DLC installation found. Use init instead of upgrade.");
122
+ const upgradeAdapters = resolvedAdaptersForUpgrade();
123
+ const requested = installed?.adapters?.length ? installed.adapters : upgradeAdapters.filter((adapter) => adapter.detect(project)).map((adapter) => adapter.id);
124
+ const selected = requested.filter((id) => findAdapterForUpgrade(id));
125
+ const agentFiles = selected.flatMap((id) => findAdapterForUpgrade(id)?.files() ?? []).map((file) => ({ ...file, ownershipClass: "managed" }));
126
+ const managed = [...coreWorkflowFiles(), ...agentFiles];
127
+ const plan = managed.map((spec) => planManaged(project, spec, installed?.files[spec.path]?.hash, legacy));
128
+ if (installed) {
129
+ for (const [path, item] of Object.entries(installed.files)) {
130
+ if (managed.some((spec) => spec.path === path))
131
+ continue;
132
+ const target = safePath(project, path);
133
+ if (!existsSync(target))
134
+ continue;
135
+ const current = readFileSync(target, "utf8");
136
+ const hash = managedContentHash({ owner: item.owner, strategy: item.strategy }, current);
137
+ 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" });
138
+ }
139
+ }
140
+ for (const spec of initialProjectFiles()) {
141
+ if (legacy && spec.path === ".agents/data/state/aidlc-state.json" && !existsSync(safePath(project, spec.path)))
142
+ continue;
143
+ const target = safePath(project, spec.path);
144
+ 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
+ }
146
+ const canonicalState = plan.find((item) => item.path === ".agents/data/state/aidlc-state.json");
147
+ if (canonicalState && canonicalState.action === "preserve") {
148
+ const parsed = JSON.parse(canonicalState.content);
149
+ const normalized = validateState(parsed);
150
+ if (parsed.schemaVersion === 1 || parsed.schemaVersion === 2 || parsed.schemaVersion === 3) {
151
+ const migration = prepareV4Migration(normalized);
152
+ canonicalState.content = `${JSON.stringify(migration.state, null, 2)}\n`;
153
+ canonicalState.action = "migrate";
154
+ 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"}`;
155
+ for (const record of migration.records) {
156
+ assertNoSymlinkPath(project, record.path);
157
+ const target = safePath(project, record.path);
158
+ if (!existsSync(target))
159
+ plan.push({ path: record.path, owner: "aidlc-state", ownershipClass: "state", content: record.content, action: "migrate", reason: "write immutable terminal task record" });
160
+ else if (readFileSync(target, "utf8") !== record.content)
161
+ plan.push({ path: record.path, owner: "aidlc-state", ownershipClass: "state", content: record.content, action: "conflict", reason: "terminal task record conflicts with prior canonical content" });
162
+ }
163
+ const lessonIndex = plan.find((item) => item.path === ".agents/data/lessons/index.json");
164
+ if (!lessonIndex)
165
+ throw new Error("Lesson index plan is missing");
166
+ lessonIndex.content = `${JSON.stringify(migration.lessonIndex, null, 2)}\n`;
167
+ lessonIndex.action = "migrate";
168
+ lessonIndex.reason = "build derived lesson index from canonical task records";
169
+ }
170
+ }
171
+ planProjectLayoutMigration(project, plan);
172
+ if (legacy && !existsSync(safePath(project, ".agents/data/state/aidlc-state.json"))) {
173
+ const migration = migrateLegacyBoard(project);
174
+ const compact = prepareV4Migration(migration.state);
175
+ 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" });
176
+ for (const record of compact.records)
177
+ plan.push({ path: record.path, owner: "aidlc-state", ownershipClass: "state", content: record.content, action: "migrate", reason: "preserve legacy terminal task as immutable record" });
178
+ const lessonIndex = plan.find((item) => item.path === ".agents/data/lessons/index.json");
179
+ if (lessonIndex) {
180
+ lessonIndex.content = `${JSON.stringify(compact.lessonIndex, null, 2)}\n`;
181
+ lessonIndex.action = "migrate";
182
+ lessonIndex.reason = "build derived lesson index during legacy migration";
183
+ }
184
+ for (const file of migration.files)
185
+ plan.push({ ...file, action: "migrate", reason: "preserve legacy task prose in stable task directory" });
186
+ }
187
+ const boardPath = safePath(project, ".agents/data/state/BOARD.md");
188
+ if (existsSync(boardPath)) {
189
+ const stateItem = plan.find((item) => item.path === ".agents/data/state/aidlc-state.json");
190
+ if (!stateItem)
191
+ throw new Error("Canonical state must be present before removing legacy BOARD");
192
+ validateState(JSON.parse(stateItem.content));
193
+ 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" });
194
+ }
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
+ };
199
+ export const applyUpgrade = (root, plan) => {
200
+ const conflicts = plan.filter((item) => item.action === "conflict");
201
+ if (conflicts.length)
202
+ throw new Error(`Upgrade has ${conflicts.length} conflict(s); no files were written`);
203
+ const project = resolve(root);
204
+ const id = new Date().toISOString().replace(/[:.]/g, "-");
205
+ const backupRelative = `.agents/data/state/backups/upgrade-${id}`;
206
+ const backup = safePath(project, backupRelative);
207
+ const stage = safePath(project, `.agents/data/state/.upgrade-stage-${id}`);
208
+ const writes = plan.filter((item) => ["create", "update", "delete", "migrate"].includes(item.action));
209
+ mkdirSync(stage, { recursive: true });
210
+ const journal = [];
211
+ try {
212
+ for (const item of writes) {
213
+ const target = safePath(project, item.path);
214
+ assertNoSymlinkPath(project, item.path);
215
+ const existed = existsSync(target);
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"));
253
+ rmSync(stage, { recursive: true, force: true });
254
+ return { backup: backupRelative, changed: writes.length };
255
+ }
256
+ catch (error) {
257
+ for (const entry of journal.reverse()) {
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
+ }
267
+ rmSync(stage, { recursive: true, force: true });
268
+ throw error;
269
+ }
270
+ };
@@ -0,0 +1,7 @@
1
+ import type { AgentId, FileSpec, WorkflowManifest } from "./model.js";
2
+ export declare const contentHash: (content: string | Uint8Array) => string;
3
+ export declare const coreWorkflowFiles: () => FileSpec[];
4
+ export declare const initialProjectFiles: () => FileSpec[];
5
+ export declare const manifestFor: (files: FileSpec[], adapters: AgentId[]) => WorkflowManifest;
6
+ export declare const manifestSpec: (files: FileSpec[], adapters: AgentId[]) => FileSpec;
7
+ export declare const packageVersion: () => string;
@@ -0,0 +1,46 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readdirSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { defaultConfig } from "./profiles.js";
6
+ import { createLessonIndex, emptyMemoryRegistry, emptyState, lessonStateDigest } from "./state.js";
7
+ const owner = "aidlc-core";
8
+ const assetRoot = fileURLToPath(new URL("../assets/.agents", import.meta.url));
9
+ const packageJson = JSON.parse(readFileSync(fileURLToPath(new URL("../../package.json", import.meta.url)), "utf8"));
10
+ export const contentHash = (content) => createHash("sha256").update(content).digest("hex");
11
+ const collect = (directory, target) => readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
12
+ const source = join(directory, entry.name);
13
+ const destination = `${target}/${entry.name}`;
14
+ return entry.isDirectory() ? collect(source, destination) : [{ path: destination, content: readFileSync(source, "utf8"), owner, ownershipClass: "managed" }];
15
+ });
16
+ export const coreWorkflowFiles = () => [
17
+ ...collect(join(assetRoot, "aidlc"), ".agents/aidlc"),
18
+ ...collect(join(assetRoot, "skills"), ".agents/skills")
19
+ ];
20
+ export const initialProjectFiles = () => [
21
+ { path: ".agents/config.json", owner: "aidlc-project", ownershipClass: "project", content: `${JSON.stringify(defaultConfig(), null, 2)}\n` },
22
+ { path: ".agents/data/state/aidlc-state.json", owner: "aidlc-state", ownershipClass: "state", content: `${JSON.stringify(emptyState(), null, 2)}\n` },
23
+ { path: ".agents/data/lessons/index.json", owner: "aidlc-state", ownershipClass: "state", content: `${JSON.stringify(createLessonIndex([], lessonStateDigest(emptyState())), null, 2)}\n` },
24
+ { path: ".agents/data/memory/agentic-memory.json", owner: "aidlc-memory", ownershipClass: "project", content: `${JSON.stringify(emptyMemoryRegistry(), null, 2)}\n` }
25
+ ];
26
+ export const manifestFor = (files, adapters) => ({
27
+ schemaVersion: 2,
28
+ packageVersion: packageJson.version,
29
+ workflow: "AI-DLC",
30
+ source: "local-package-assets",
31
+ remoteUpdates: false,
32
+ managedBy: "aidlc-workflow",
33
+ adapters,
34
+ files: Object.fromEntries(files.filter((file) => file.ownershipClass === "managed" || !file.ownershipClass).map((file) => [file.path, {
35
+ hash: contentHash(file.strategy === "managed-block" ? file.content.trimEnd() : file.content),
36
+ owner: file.owner,
37
+ strategy: file.strategy ?? "replace"
38
+ }]))
39
+ });
40
+ export const manifestSpec = (files, adapters) => ({
41
+ path: ".agents/aidlc/manifest.json",
42
+ owner,
43
+ ownershipClass: "managed",
44
+ content: `${JSON.stringify(manifestFor(files, adapters), null, 2)}\n`
45
+ });
46
+ export const packageVersion = () => packageJson.version;
package/docs/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Documentation
2
+
3
+ Use this page to choose the guide that matches your role and next task.
4
+
5
+ ## For package users and operators
6
+
7
+ 1. Start with the repository [README](../README.md) to install the workflow.
8
+ 2. Read [Operating the workflow](./operating-workflow.md) before asking an agent to change a project.
9
+ 3. Use [Configuration](./configuration.md) to configure project rules, profiles, and Codex/Claude permissions.
10
+ 4. Keep [Command reference](./command-reference.md) nearby for safe CLI usage.
11
+ 5. Read [AI-DLC Coordination service](./coordination-service.md) before enabling remote canonical state.
12
+
13
+ ## For contributors and maintainers
14
+
15
+ 1. Begin with [Development](./development.md) for local setup and implementation boundaries.
16
+ 2. Use [Testing and release](./testing-and-release.md) for the verification and package-smoke workflow.
17
+ 3. Read [Architecture](./architecture.md) before changing package, installed-runtime, migration, or asset behaviour.
18
+
19
+ ## Documentation map
20
+
21
+ | Page | Audience | Purpose |
22
+ | --- | --- | --- |
23
+ | [Operating the workflow](./operating-workflow.md) | Operators | Lifecycle, task state, human gates, and blocked work. |
24
+ | [Configuration](./configuration.md) | Operators and maintainers | Project configuration, profile precedence, rules, and Codex/Claude permissions. |
25
+ | [Command reference](./command-reference.md) | Operators | Package CLI commands, flags, previews, and safety limits. |
26
+ | [Development](./development.md) | Contributors | Dependencies, source layout, local commands, and implementation rules. |
27
+ | [Testing and release](./testing-and-release.md) | Maintainers | Test strategy, evaluator, package smoke test, and release checks. |
28
+ | [Architecture](./architecture.md) | Contributors | Boundaries, dependency direction, and source-to-consumer flow. |
29
+ | [AI-DLC Coordination service](./coordination-service.md) | Operators | Opt-in service deployment, state authority, and credential safety. |
30
+
31
+ When a public command, configuration field, or installed layout changes, update the relevant guide and its validation in the same change.
@@ -0,0 +1,72 @@
1
+ # Architecture
2
+
3
+ `aidlc-kit` is a single Node.js/TypeScript package that installs static AI-DLC workflow assets into another project. The package CLI is used for installation and human-authorised maintenance; an installed project runs the dependency-free workflow scripts locally.
4
+
5
+ ## Source-to-consumer flow
6
+
7
+ ```text
8
+ assets/aidlc/ + skills/
9
+ │ build-assets.mjs
10
+ ▼
11
+ dist/assets/.agents/ + dist/src/
12
+ │ package CLI: init / upgrade
13
+ ▼
14
+ consumer project: .agents/aidlc/ + .agents/skills/ + project-owned .agents/data/
15
+ ```
16
+
17
+ The distinction matters:
18
+
19
+ - **Source layout** is this repository's editable `assets/aidlc/` and `skills/` tree.
20
+ - **Bundle layout** is generated under `dist/assets/.agents/` for package publication.
21
+ - **Installed layout** is written under a consumer project's `.agents/` directory.
22
+
23
+ `dist/` is generated. Source assets and installed assets must remain behaviourally aligned through the bundle and package-smoke tests.
24
+
25
+ ## Module boundaries
26
+
27
+ | Boundary | Modules | Responsibility |
28
+ | --- | --- | --- |
29
+ | CLI and installation | `src/cli.ts`, `src/installer.ts`, `src/workflow.ts`, `src/adapters.ts` | Parse commands, select adapters, plan deterministic writes, install managed assets, and expose local status/doctor checks. |
30
+ | Lifecycle domain | `src/model.ts`, `src/state.ts`, `src/gate.ts`, `src/context.ts` | Validate canonical state, calculate transitions and gate readiness, and build bounded phase packets. |
31
+ | Profiles and project integration | `src/profiles.ts`, `src/tracker.ts` | Resolve project configuration/profiles and optional tracker integration. |
32
+ | Migration | `src/upgrade.ts`, `src/project-migration.ts`, `src/legacy.ts` | Plan and apply safe legacy-data and version-layout migration with audit backups. |
33
+ | Shared file semantics | `src/project-path.ts`, `src/managed-content.ts` | Keep all paths inside the project, reject unsafe symlinks, and preserve managed-content ownership. |
34
+ | Development evaluator | `dev/evaluator/` | Run repository-only evaluation; never expose evaluator assets through the published package CLI. |
35
+
36
+ Dependencies flow inward: CLI, installation, and migration depend on lifecycle and shared helpers; lifecycle does not depend on installation; evaluator may provision an installation but is not part of published runtime behaviour.
37
+
38
+ ## Installed workflow runtime
39
+
40
+ The installed kernel consists of contracts, templates, schemas, profiles, and dependency-free scripts under `.agents/aidlc/`. Thin adapter instructions connect a selected coding tool to that kernel. Project customisation belongs outside the kernel:
41
+
42
+ | Location | Ownership | Purpose |
43
+ | --- | --- | --- |
44
+ | `.agents/aidlc/` | Package managed | Lifecycle contracts, schemas, and scripts. |
45
+ | `.agents/data/` | Project owned | Canonical state, task artifacts, derived indexes, memory, and evidence. |
46
+ | `.agents/project/` | Project owned | Local profiles, rules, and evaluation material. |
47
+
48
+ The state machine enforces the lifecycle; gates require explicit human approval; evidence is append-only. Installed scripts remain usable without an `aidlc-workflow` executable or a duplicate board file.
49
+
50
+ Installed entry points share a strict dependency-free argv parser. Parsing and command-shape validation precede root discovery, lock acquisition, and state reads. Explicit roots are validated project directories; implicit roots use nearest-ancestor `.agents/aidlc/` discovery and never fall back to writing beneath an arbitrary working directory.
51
+
52
+ ## Trust and safety boundaries
53
+
54
+ - Workflow assets are local package assets. The manifest records `remoteUpdates: false`; status and doctor do not consult a registry.
55
+ - Install/upgrade plans are conflict-first, path-contained, and reject unsafe symlink traversal.
56
+ - Human-only upgrade is an intentional boundary: interactive confirmation is required and agents must not run or discover upgrades.
57
+ - Only Codex and Claude Code adapters are installed. Codex uses trusted project-scoped `.codex/config.toml` and `.codex/rules/aidlc.rules`; Claude Code scopes lifecycle-script permission through its local settings and phase skills.
58
+ - Project commands are configured as executable-and-argument arrays rather than opaque shell strings.
59
+ - Canonical state, terminal records, and rendered workplans reject symlink traversal and use same-directory atomic replacement. Lock acquisition is guard-serialized, while release verifies the owner token directly so a contended guard cannot strand the owner's main lock.
60
+ - The compact master catalog remains archive authority. Recovery can replace only a valid, unreferenced terminal orphan tied by `id` and `createdAt` to the still-active catalog task; referenced, malformed, and identity-conflicting records fail closed.
61
+
62
+ ## Change impact guide
63
+
64
+ | If you change… | Also inspect… |
65
+ | --- | --- |
66
+ | A lifecycle/state rule | Installed scripts, schemas, gate/context rendering, runtime parity tests. |
67
+ | An asset or adapter | Asset builder, package inventory, installer, adapter tests, tarball smoke test. |
68
+ | A configuration/profile field | Validator, schema, effective-profile behaviour, documentation, and tests. |
69
+ | Migration semantics | Upgrade plan/apply logic, path safety, transaction rollback, and migration tests. |
70
+ | A public command | CLI parsing, README/setup guidance, command reference, and package tests. |
71
+
72
+ Use this guide with [Development](./development.md) and [Testing and release](./testing-and-release.md) before changing a cross-cutting boundary.
@@ -0,0 +1,110 @@
1
+ # Command reference
2
+
3
+ All package commands operate on local files. The examples run version `0.0.1` through npm; `npx` may ask permission to fetch that package on first use. Replace `.` with the target project path when needed. For a source checkout, see the [development guide](./development.md).
4
+
5
+ ## `init`
6
+
7
+ ```sh
8
+ npx --package @felixdotgo/aidlc-kit@0.0.1 aidlc-workflow init [path] [--agent <name[,name]> | --all] [--yes] [--dry-run] [--force]
9
+ ```
10
+
11
+ Plans the installation, prints a preview, and then writes the workflow only after confirmation (or `--yes`). `--dry-run` prints the same plan without writing. If exactly one installed agent is detected, it can be selected automatically. In an interactive terminal, choose multiple agents with the checkbox prompt: use arrow keys to move, Space to toggle, A to toggle all, and Enter to confirm. For scripts and non-interactive terminals, use `--agent <name[,name]>` or `--all`.
12
+
13
+ `--force` is for an intentional initial replacement of an unmanaged conflicting file. It is not an upgrade mechanism. If the project already has another workflow package version, use the human-run upgrade flow instead.
14
+
15
+ ## `status`
16
+
17
+ ```sh
18
+ npx --package @felixdotgo/aidlc-kit@0.0.1 aidlc-workflow status [path]
19
+ ```
20
+
21
+ Reports the installed version and local installation status. It does not query a registry.
22
+
23
+ ## `doctor`
24
+
25
+ ```sh
26
+ npx --package @felixdotgo/aidlc-kit@0.0.1 aidlc-workflow doctor [path] [--strict]
27
+ ```
28
+
29
+ Checks the local manifest, configuration, and canonical state. `--strict` turns compatibility warnings such as a legacy manifest into errors. It does not query a registry.
30
+
31
+ ## `profile validate`
32
+
33
+ ```sh
34
+ npx --package @felixdotgo/aidlc-kit@0.0.1 aidlc-workflow profile validate [path]
35
+ ```
36
+
37
+ Loads `.agents/config.json`, resolves its profile chain, and prints the effective profile order. Use it after changing `extends` or local profiles.
38
+
39
+ ## `uninstall`
40
+
41
+ ```sh
42
+ npx --package @felixdotgo/aidlc-kit@0.0.1 aidlc-workflow uninstall [path] [--yes] [--dry-run]
43
+ ```
44
+
45
+ Prints the removal plan before applying it. It removes eligible unchanged managed assets but preserves project configuration and state. Prefer `--dry-run` first when working in an existing project.
46
+
47
+ ## `mcp setup`
48
+
49
+ ```sh
50
+ npx --package @felixdotgo/aidlc-kit@0.0.1 aidlc-workflow mcp setup [path] [--dry-run] [--yes] [--storage sqlite|postgres] [--deployment docker|remote] [--bind localhost|network] [--workspace <id>] [--poll-ms <n>] [--enable]
51
+ ```
52
+
53
+ Previews the optional AI-DLC Coordination service configuration, then writes only after confirmation. Non-interactive use requires `--deployment`, `--storage`, `--bind`, and `--workspace`. It never starts Docker; `--enable` is explicit and remote mode has no local fallback. See [AI-DLC Coordination service](./coordination-service.md) for deployment and credential requirements.
54
+
55
+ ## `upgrade`
56
+
57
+ ```sh
58
+ npx --package @felixdotgo/aidlc-kit@0.0.1 aidlc-workflow upgrade [path] [--dry-run]
59
+ ```
60
+
61
+ Upgrade is deliberately human-only. The command rejects `--yes` and `--force`; applying it requires an interactive terminal and an exact typed confirmation of the package version. Interactive confirmations use the same styled terminal prompt as `init`. Agents must not run, preview, detect, or suggest package upgrades on their own.
62
+
63
+ The upgrade plan handles supported legacy `.aidlc/` project data transactionally: identical paths are deduplicated, differing content or unsafe entries stop the plan, and an audit backup supports rollback. Review the preview before an authorised human applies it.
64
+
65
+ ## Installed lifecycle scripts
66
+
67
+ After `init`, task lifecycle commands live in the target project, not in the package CLI:
68
+
69
+ ```sh
70
+ node .agents/aidlc/scripts/state.mjs task list --limit 20
71
+ node .agents/aidlc/scripts/state.mjs task find --query "<words>" --include-archive
72
+ node .agents/aidlc/scripts/state.mjs task show <task-id>
73
+ node .agents/aidlc/scripts/state.mjs task next <task-id>
74
+ node .agents/aidlc/scripts/task-next.mjs <task-id> --require-stop
75
+ node .agents/aidlc/scripts/gate-view.mjs <task-id>
76
+ ```
77
+
78
+ Installed lifecycle options may appear before or after positionals and accept both `--name value` and `--name=value`. Unknown, duplicate, command-irrelevant, missing-value, and surplus positional arguments are rejected before state I/O. Boolean options such as `--all`, `--include-archive`, and `--require-stop` do not accept values. When a literal value begins with `--`, use the equals form so it is not confused with a missing value.
79
+
80
+ An explicit `--root <path>` wins and must identify a directory containing `.agents/`. Without `--root`, the runtime walks upward from the current working directory and selects the nearest ancestor containing `.agents/aidlc/`. If no installed-workflow marker exists, the command fails without creating local state. Manual calls from a nested directory need an absolute or upward-relative script path so Node can locate the entry point. Runtime-generated actions always include both an absolute script path and an absolute `--root`, so they remain executable from any working directory.
81
+
82
+ Every `state.mjs` and `task-next.mjs` command prints one JSON envelope: `{ok: true, result, nextAction?}`. `result` carries the command payload (a mutated command nests it as `{task, ...extra}`; a read returns the read object), and `nextAction` is present whenever the command is task-scoped. Task-less commands (`memory list|promote|retire`, `lesson rebuild|search`, `state migrate`, `task list|find`) return no `nextAction`. A success envelope may additionally carry `warnings: [{code, message, hint?}]` when the mutation persisted but a derived step failed afterwards — `LESSON_INDEX_STALE` (lesson-index rebuild) or `RENDER_FAILED` (workplan render); the mutation is committed and the exit code stays `0`, so callers follow the warning hint instead of retrying the mutation, while the explicit repair commands (`lesson rebuild`, `render.mjs`) and `state migrate` still fail loudly. Failures print a one-line `{ok: false, error: {message, hint?}}` on stderr — never a stack trace. Exit codes are typed: `0` success, `1` expected validation or gate error, `2` the continuation guard, `3` an unexpected crash. Callers must execute `run_phase` actions immediately; a successful mutation is not permission to stop. In build, the action includes `itemId`, `remainingItems`, and an item-focused context command while work remains, and every embedded command carries an explicit `--root` so it runs from any working directory.
83
+
84
+ `task-next.mjs --require-stop` prints the current action plus a machine-readable `continuation` object in the stdout envelope — `{required, code, taskId, classification}` and, when work remains, `command`, `reason`, `itemId`, and `remainingItems` — and exits `2` when the classification is `run_phase`. The first stderr line is `CONTINUATION_REQUIRED: {json}` carrying the same task id, command, and reason (followed by an explicit "expected pause, not a failure" note); it is a local guard against a premature final response, and the caller must execute `continuation.command` instead of replying. It exits normally (`continuation.code = STOP_ALLOWED`) for a valid human gate, durable blocker, terminal outcome, or completion. The guard is detection at the package boundary: it cannot technically intercept a message a model host has already emitted, so hosts recover by running `task next <task-id>` in the next turn and executing its command. `task status ... --status blocked_on_user` fails instead of persisting when the current gate is not ready, and `gate-view.mjs` only renders tasks already in that validated status.
85
+
86
+ Human approval is a dedicated atomic operation:
87
+
88
+ ```sh
89
+ node .agents/aidlc/scripts/state.mjs gate approve <task-id> --gate <gate> --source "<explicit approval>"
90
+ ```
91
+
92
+ `--source` is audit provenance, not authentication or proof of consent. The host agent must invoke this command only in a later user turn that explicitly approves the gate currently presented.
93
+
94
+ `evidence add` accepts `spec`, `test`, `lint`, `review`, and `diagnostic`; it rejects `approval`. An evidence `--area` must belong to the task's affected areas, and multi-area test or lint evidence must name its area. `task status` accepts `active`, `blocked_on_user`, and `paused` (`done` is not a status command; finish with `task transition --to done` or `task archive`), and rejects `blocked_on_user` at wrap, which has no human gate. Every route out of a validated `blocked_on_user` wait, including `paused`, requires audited `--reason` and `--source` metadata and appends diagnostic evidence; entering `paused` from `active` requires the same audited `--mode audited --reason --source` triple and appends a `Paused while active` diagnostic, while resuming `paused → active` needs no audit metadata. Repair bounds are machine-enforced: the third failed verify record per area, or the second failed review record, in the current build flips `nextAction` to `blocked` and adds a `REPAIR_BOUND` error to the G2 gate check. Normal phase advancement uses `gate approve`, and wrap completion uses `task transition --to done` or `task archive <task-id> --reason "<reason>" --source "<source>"`, which applies the same transition checks and appends an `Archive to done` diagnostic. The low-level transition escape hatch is intentionally noisy, records diagnostic evidence, still applies all transition checks, and requires audit metadata:
95
+
96
+ ```sh
97
+ node .agents/aidlc/scripts/state.mjs task transition <task-id> --to <phase> --mode audited --reason "<reason>" --source "<source>"
98
+ ```
99
+
100
+ Self-transitions are rejected, and `task create` rejects an id that collides case-insensitively with an existing task or archive record. `task create` also fails while any other task is actionable (`nextAction` classification `run_phase`); the error lists the actionable task ids and the exact `--switch-from <ids>` acknowledgement. Passing that flag — only with explicit user direction to switch — records a resume-marker diagnostic on each acknowledged task and never pauses, closes, or otherwise mutates its lifecycle; the acknowledged task remains the authoritative resume target. After a G1 reopen, all non-deferred execution items return to `todo`; prior G2, verification, and review evidence cannot satisfy the new build boundary, and repair-bound counters reset with it.
101
+
102
+ State writes and generated workplans use symlink-checked atomic replacement. State-lock release is owner-token checked and never removes a replacement owner's lock. If a process stops after writing a terminal archive record but before committing the catalog, a retry replaces that orphan only when the persisted catalog still contains the same active task identity and does not reference an archive record; malformed, referenced, or identity-conflicting records fail closed.
103
+
104
+ Promoting an agent-memory candidate requires traceable source records and an explicit approver:
105
+
106
+ ```sh
107
+ node .agents/aidlc/scripts/state.mjs memory promote <memory-id> --summary "<summary>" --guidance "<guidance>" --source-task <task-id> --source-lesson <lesson-id> --approved-by "<human>"
108
+ ```
109
+
110
+ Use [Operating the workflow](./operating-workflow.md) for gates, approvals, handoffs, and task ownership.