@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,222 @@
1
+ #!/usr/bin/env node
2
+ import { acquireStateLock, approveAndAdvance, checkGate, closeTask, failEntry, handoffTask, listTaskSummaries, loadMemoryRegistry, loadState, loadTask, migrateState, nextAction, option, parseArguments, promoteAgenticMemory, rebuildLessonIndex, recordLesson, recordNoLessons, renderViews, reopenTask, retireAgenticMemory, rootOption, saveState, searchLessons, supersedeTask, transitionTask, validateArguments } from "./lib/runtime.mjs";
3
+
4
+ try {
5
+ const raw = process.argv.slice(2);
6
+ const valueOptions = ["--root", "--status", "--limit", "--cursor", "--query", "--title", "--type", "--language", "--risk", "--area", "--mode", "--reason", "--source", "--to", "--kind", "--successor", "--label", "--resolution", "--result", "--gate", "--detail", "--summary", "--prevention", "--example", "--promotion", "--guidance", "--phase", "--priority", "--source-task", "--source-lesson", "--approved-by", "--switch-from"];
7
+ const parsed = parseArguments(raw, { valueOptions, booleanOptions: ["--include-archive"] });
8
+ const args = parsed.positionals;
9
+ const [group, action, rawId] = args;
10
+ const id = rawId;
11
+ const schemas = {
12
+ "state migrate": { minPositionals: 2 },
13
+ "task show": { minPositionals: 2, maxPositionals: 3, valueOptions: ["--status", "--limit", "--cursor"], booleanOptions: ["--include-archive"] },
14
+ "task list": { minPositionals: 2, valueOptions: ["--status", "--limit", "--cursor"], booleanOptions: ["--include-archive"] },
15
+ "task find": { minPositionals: 2, valueOptions: ["--query", "--status", "--limit", "--cursor"], booleanOptions: ["--include-archive"], requiredOptions: ["--query"] },
16
+ "task next": { minPositionals: 3 },
17
+ "task create": { minPositionals: 3, valueOptions: ["--title", "--type", "--language", "--risk", "--area", "--switch-from"], requiredOptions: ["--title"] },
18
+ "task transition": { minPositionals: 3, valueOptions: ["--mode", "--reason", "--source", "--to"], requiredOptions: ["--mode", "--reason", "--source", "--to"], usage: "task transition requires --mode audited, --reason, --source, and --to" },
19
+ "task archive": { minPositionals: 3, valueOptions: ["--reason", "--source"], requiredOptions: ["--reason", "--source"] },
20
+ "task handoff": { minPositionals: 3, valueOptions: ["--kind", "--reason", "--source"], requiredOptions: ["--kind", "--reason", "--source"] },
21
+ "task close": { minPositionals: 3, valueOptions: ["--reason", "--source"], requiredOptions: ["--reason", "--source"] },
22
+ "task supersede": { minPositionals: 3, valueOptions: ["--successor", "--reason", "--source"], requiredOptions: ["--successor", "--reason", "--source"] },
23
+ "task reopen": { minPositionals: 3, valueOptions: ["--to", "--reason", "--source"], requiredOptions: ["--to", "--reason", "--source"] },
24
+ "task status": { minPositionals: 3, valueOptions: ["--status", "--mode", "--reason", "--source"], requiredOptions: ["--status"] },
25
+ "task item": { minPositionals: 4, valueOptions: ["--status", "--label"], requiredOptions: ["--status"] },
26
+ "decision set": { minPositionals: 4, valueOptions: ["--status", "--label", "--resolution"], requiredOptions: ["--status"] },
27
+ "evidence add": { minPositionals: 3, valueOptions: ["--kind", "--result", "--gate", "--area", "--source", "--detail"], requiredOptions: ["--kind", "--result"] },
28
+ "lesson record": { minPositionals: 4, valueOptions: ["--area", "--summary", "--prevention", "--example", "--promotion", "--source"] },
29
+ "lesson none": { minPositionals: 3, valueOptions: ["--reason", "--source"], requiredOptions: ["--reason", "--source"] },
30
+ "lesson rebuild": { minPositionals: 2 },
31
+ "lesson search": { minPositionals: 2, valueOptions: ["--query", "--area", "--limit"] },
32
+ "memory list": { minPositionals: 2 },
33
+ "memory promote": { minPositionals: 3, valueOptions: ["--summary", "--guidance", "--area", "--phase", "--priority", "--source-task", "--source-lesson", "--approved-by"], requiredOptions: ["--summary", "--guidance", "--source-task", "--source-lesson", "--approved-by"] },
34
+ "memory retire": { minPositionals: 3, valueOptions: ["--reason", "--approved-by"], requiredOptions: ["--reason", "--approved-by"] },
35
+ "gate approve": { minPositionals: 3, valueOptions: ["--gate", "--source"], requiredOptions: ["--gate", "--source"] }
36
+ };
37
+ const usage = "Usage: state.mjs state migrate | task create|show|list|find|next|status|item|transition|archive|handoff|close|supersede|reopen | decision set | evidence add | lesson record|none|search|rebuild | memory list|promote|retire | gate approve";
38
+ const schema = schemas[`${group ?? ""} ${action ?? ""}`];
39
+ if (!schema) throw new Error(usage);
40
+ validateArguments(parsed, { ...schema, maxPositionals: schema.maxPositionals ?? schema.minPositionals, usage: schema.usage ?? usage });
41
+ const root = rootOption(parsed);
42
+ const releaseStateLock = acquireStateLock(root);
43
+ let released = false;
44
+ const releaseOnce = () => { if (!released) { releaseStateLock(); released = true; } };
45
+ process.once("exit", releaseOnce);
46
+ try {
47
+ const state = loadState(root);
48
+ const now = () => new Date().toISOString();
49
+ const numberOption = (name, fallback) => {
50
+ const rawValue = option(parsed, name);
51
+ if (rawValue === undefined) return fallback;
52
+ const value = Number(rawValue);
53
+ if (!Number.isInteger(value) || value < 0) throw new Error(`${name} must be a non-negative integer`);
54
+ return value;
55
+ };
56
+ const summaryOptions = (query) => ({
57
+ includeArchive: parsed.flags.has("--include-archive"),
58
+ statuses: option(parsed, "--status")?.split(",").map((item) => item.trim()).filter(Boolean),
59
+ query,
60
+ limit: numberOption("--limit", 20),
61
+ cursor: numberOption("--cursor", 0)
62
+ });
63
+ const warnings = [];
64
+ const derivedStep = (code, hint, step) => { try { step(); } catch (error) { warnings.push({ code, message: error instanceof Error ? error.message : String(error), hint }); } };
65
+ const persist = (tasks, rebuildLessons = false) => {
66
+ const archived = saveState(root, state);
67
+ if (rebuildLessons || archived.length) derivedStep("LESSON_INDEX_STALE", "restore the damaged archive record from git history, then run state.mjs lesson rebuild", () => rebuildLessonIndex(root, state));
68
+ derivedStep("RENDER_FAILED", "fix the reported path, then run render.mjs <task-id>", () => renderViews(root, state, tasks.filter(Boolean)));
69
+ };
70
+ const emit = (result, task) => console.log(JSON.stringify({ ok: true, ...(result === undefined ? {} : { result }), ...(warnings.length ? { warnings } : {}), ...(task ? { nextAction: nextAction(task, root) } : {}) }, null, 2));
71
+ const respond = (task, result = {}) => emit({ task, ...result }, task);
72
+
73
+ if (group === "state" && action === "migrate") {
74
+ emit(migrateState(root, state));
75
+ } else if (group === "task" && action === "show") {
76
+ if (id) { const shown = loadTask(root, id, state) ?? null; emit(shown, shown ?? undefined); }
77
+ else emit(listTaskSummaries(state, summaryOptions()));
78
+ } else if (group === "task" && action === "list") {
79
+ emit(listTaskSummaries(state, summaryOptions()));
80
+ } else if (group === "task" && action === "find") {
81
+ const query = option(parsed, "--query");
82
+ if (!query) throw new Error("task find requires --query");
83
+ emit(listTaskSummaries(state, summaryOptions(query)));
84
+ } else if (group === "task" && action === "next") {
85
+ const task = loadTask(root, id, state);
86
+ if (!task) throw new Error(`Unknown task: ${id}`);
87
+ emit({ id: task.id, phase: task.phase, gate: task.gate, status: task.status }, task);
88
+ } else if (group === "task" && action === "create") {
89
+ const title = option(parsed, "--title");
90
+ if (!id || !title) throw new Error("task create requires <task-id> and --title");
91
+ if (loadTask(root, id, state)) throw new Error(`Task already exists: ${id}; use task next ${id} to resume it`);
92
+ const collision = [...Object.keys(state.tasks), ...Object.keys(state.archive ?? {})].find((existing) => existing.toLowerCase() === id.toLowerCase());
93
+ if (collision) throw new Error(`Task id collides case-insensitively with existing task: ${collision}`);
94
+ const actionable = Object.values(state.tasks).filter((existing) => nextAction(existing, root).classification === "run_phase").map((existing) => existing.id).sort();
95
+ const actionableByLowercase = new Map(actionable.map((existing) => [existing.toLowerCase(), existing]));
96
+ const acknowledgedTokens = [...new Set((option(parsed, "--switch-from") ?? "").split(",").map((item) => item.trim()).filter(Boolean))];
97
+ const unknownAcknowledgement = acknowledgedTokens.filter((ack) => !actionableByLowercase.has(ack.toLowerCase()));
98
+ if (unknownAcknowledgement.length) throw new Error(`--switch-from must name only actionable task(s); not actionable: ${unknownAcknowledgement.join(", ")}`);
99
+ const acknowledged = [...new Set(acknowledgedTokens.map((ack) => actionableByLowercase.get(ack.toLowerCase())))];
100
+ const stranded = actionable.filter((existing) => !acknowledged.includes(existing));
101
+ if (stranded.length) throw new Error(`Task create is blocked while other work is actionable: ${stranded.join(", ")}. With explicit user direction to switch, rerun with --switch-from ${actionable.join(",")}; acknowledged tasks keep their state and remain the resume target`);
102
+ const recordedAt = now();
103
+ const task = state.tasks[id] = {
104
+ id, title, type: option(parsed, "--type") ?? "infra", phase: "clarify", gate: "G0_confirm", status: "active",
105
+ language: option(parsed, "--language") === "en" ? "en" : "vi", risk: option(parsed, "--risk") ?? "normal",
106
+ areas: (option(parsed, "--area") ?? "root").split(",").map((area) => area.trim()).filter(Boolean), branch: "—",
107
+ artifacts: { intent: `.agents/data/tasks/${id}/intent.md`, design: `.agents/data/tasks/${id}/design.md`, workplan: `.agents/data/tasks/${id}/workplan.md` },
108
+ decisions: [], tasks: [], evidence: [], createdAt: recordedAt, updatedAt: recordedAt
109
+ };
110
+ const acknowledgedTasks = acknowledged.map((ack) => state.tasks[ack]);
111
+ for (const prior of acknowledgedTasks) {
112
+ prior.evidence.push({ kind: "diagnostic", result: "pass", source: `task create ${id}`, detail: `Switch acknowledged: created ${id} while ${prior.id} was actionable; ${prior.id} keeps its state — resume with task next ${prior.id}`, recordedAt });
113
+ prior.updatedAt = recordedAt;
114
+ }
115
+ persist([task, ...acknowledgedTasks]); respond(task);
116
+ } else if (group === "task" && action === "transition") {
117
+ const mode = option(parsed, "--mode"); const reason = option(parsed, "--reason"); const source = option(parsed, "--source");
118
+ if (mode !== "audited" || !reason?.trim() || !source?.trim()) throw new Error("task transition requires --mode audited, --reason, and --source");
119
+ const from = state.tasks[id]?.phase; const target = option(parsed, "--to"); const task = transitionTask(state, id, target);
120
+ task.evidence.push({ kind: "diagnostic", result: "pass", source, detail: `Audited transition ${from} → ${target}: ${reason}`, recordedAt: now() });
121
+ persist([task]); respond(task);
122
+ } else if (group === "task" && action === "archive") {
123
+ const reason = option(parsed, "--reason"); const source = option(parsed, "--source");
124
+ if (!reason?.trim() || !source?.trim()) throw new Error("task archive requires --reason and --source");
125
+ const task = transitionTask(state, id, "done");
126
+ task.evidence.push({ kind: "diagnostic", result: "pass", source, detail: `Archive to done: ${reason}`, recordedAt: now() });
127
+ persist([task], true); respond(task);
128
+ } else if (group === "task" && action === "handoff") {
129
+ const task = handoffTask(state, id, option(parsed, "--kind"), option(parsed, "--reason"), option(parsed, "--source"));
130
+ persist([task]); respond(task);
131
+ } else if (group === "task" && action === "close") {
132
+ const task = closeTask(state, id, option(parsed, "--reason"), option(parsed, "--source"));
133
+ persist([task]); respond(task);
134
+ } else if (group === "task" && action === "supersede") {
135
+ const task = supersedeTask(state, id, option(parsed, "--successor"), option(parsed, "--reason"), option(parsed, "--source")); const successor = state.tasks[task.successorTaskId];
136
+ persist([task, successor]); respond(task, { successor });
137
+ } else if (group === "task" && action === "reopen") {
138
+ const task = reopenTask(state, id, option(parsed, "--to"), option(parsed, "--reason"), option(parsed, "--source"));
139
+ persist([task]); respond(task);
140
+ } else if (group === "task" && action === "status") {
141
+ const task = state.tasks[id]; const status = option(parsed, "--status");
142
+ if (!task || !["active", "blocked_on_user", "paused"].includes(status)) throw new Error("task status requires an active task id and --status active|blocked_on_user|paused; finishing a task uses task transition --to done or task archive");
143
+ const leavingGateWait = task.status === "blocked_on_user" && status !== "blocked_on_user";
144
+ const recoveringGatelessWrap = leavingGateWait && task.phase === "wrap" && task.gate === "none" && status === "active";
145
+ if (leavingGateWait && !recoveringGatelessWrap) {
146
+ const mode = option(parsed, "--mode"); const reason = option(parsed, "--reason"); const cancelSource = option(parsed, "--source");
147
+ if (mode !== "audited" || !reason?.trim() || !cancelSource?.trim()) throw new Error("Cancelling a human-gate wait requires --mode audited, --reason, and --source (e.g. the user's rejection message)");
148
+ task.evidence.push({ kind: "diagnostic", result: "pass", source: cancelSource, detail: `Cancelled gate wait at ${task.gate}: ${reason}`, recordedAt: now() });
149
+ }
150
+ const enteringPause = status === "paused" && task.status === "active";
151
+ if (enteringPause) {
152
+ const mode = option(parsed, "--mode"); const reason = option(parsed, "--reason"); const pauseSource = option(parsed, "--source");
153
+ if (mode !== "audited" || !reason?.trim() || !pauseSource?.trim()) throw new Error("Pausing an active task requires --mode audited, --reason, and --source");
154
+ task.evidence.push({ kind: "diagnostic", result: "pass", source: pauseSource, detail: `Paused while active: ${reason}`, recordedAt: now() });
155
+ }
156
+ if (status === "blocked_on_user") {
157
+ if (task.gate === "none") throw new Error("Phase wrap has no human gate; continue wrap and finish with task transition --to done or task archive");
158
+ const errors = checkGate(root, state, id, task.gate).filter((item) => item.level === "ERROR");
159
+ if (errors.length) throw new Error(`Gate is not ready: ${errors.map((item) => `${item.code}: ${item.message}`).join("; ")}`);
160
+ }
161
+ if (leavingGateWait) delete task.legacyG2Wait;
162
+ task.status = status; task.updatedAt = now(); persist([task]); respond(task);
163
+ } else if (group === "task" && action === "item") {
164
+ const itemId = args[3]; const task = state.tasks[id]; const status = option(parsed, "--status");
165
+ if (!task || !itemId || !["todo", "in_progress", "done", "deferred"].includes(status)) throw new Error("task item requires active task id, item id, and valid --status");
166
+ const item = task.tasks.find((entry) => entry.id === itemId);
167
+ if (item) { item.status = status; if (parsed.present.has("--label")) item.label = option(parsed, "--label"); }
168
+ else task.tasks.push({ id: itemId, label: option(parsed, "--label") ?? itemId, status });
169
+ task.updatedAt = now(); persist([task]); respond(task);
170
+ } else if (group === "decision" && action === "set") {
171
+ const decisionId = args[3]; const task = state.tasks[id]; const status = option(parsed, "--status");
172
+ if (!task || !decisionId || !["unresolved", "approved", "changed", "dropped"].includes(status)) throw new Error("decision set requires active task id, decision id, and valid --status");
173
+ const current = task.decisions.find((entry) => entry.id === decisionId);
174
+ if (current) { current.status = status; if (parsed.present.has("--label")) current.label = option(parsed, "--label"); if (parsed.present.has("--resolution")) current.resolution = option(parsed, "--resolution"); }
175
+ else task.decisions.push({ id: decisionId, label: option(parsed, "--label") ?? decisionId, status, resolution: option(parsed, "--resolution") });
176
+ task.updatedAt = now(); persist([task]); respond(task);
177
+ } else if (group === "evidence" && action === "add") {
178
+ const task = state.tasks[id]; const kind = option(parsed, "--kind"); const result = option(parsed, "--result");
179
+ if (!task || !["spec", "test", "lint", "review", "diagnostic"].includes(kind) || !["pass", "fail", "skip"].includes(result)) throw new Error("evidence add requires active task id, non-approval --kind, and valid --result; approvals use gate approve");
180
+ const area = option(parsed, "--area");
181
+ if (area && !task.areas.includes(area)) throw new Error(`Evidence area must belong to task.areas: ${area}`);
182
+ if (["test", "lint"].includes(kind) && task.areas.length > 1 && !area) throw new Error("Test and lint evidence for multi-area tasks requires --area");
183
+ task.evidence.push({ kind, gate: option(parsed, "--gate"), area, result, source: option(parsed, "--source") ?? "local Node.js script", detail: option(parsed, "--detail"), recordedAt: now() });
184
+ task.updatedAt = now(); persist([task]); respond(task);
185
+ } else if (group === "lesson" && action === "record") {
186
+ const lessonId = args[3]; const task = state.tasks[id];
187
+ if (!task || !lessonId) throw new Error("lesson record requires active task id and lesson id");
188
+ const source = option(parsed, "--source");
189
+ const lesson = recordLesson(task, { id: lessonId, areas: (option(parsed, "--area") ?? task.areas.join(",")).split(",").map((item) => item.trim()).filter(Boolean), summary: option(parsed, "--summary"), prevention: option(parsed, "--prevention"), example: option(parsed, "--example"), promotion: option(parsed, "--promotion") ?? "not promoted", source });
190
+ persist([task], true); respond(task, { lesson });
191
+ } else if (group === "lesson" && action === "none") {
192
+ const task = state.tasks[id]; if (!task) throw new Error("lesson none requires active task id");
193
+ recordNoLessons(task, option(parsed, "--reason"), option(parsed, "--source")); persist([task], true); respond(task, { lessonDisposition: task.lessonDisposition });
194
+ } else if (group === "lesson" && action === "rebuild") {
195
+ emit(rebuildLessonIndex(root, state));
196
+ } else if (group === "lesson" && action === "search") {
197
+ emit(searchLessons(root, state, option(parsed, "--query") ?? "", (option(parsed, "--area") ?? "").split(",").map((item) => item.trim()).filter(Boolean), numberOption("--limit", 5)));
198
+ } else if (group === "memory" && action === "list") {
199
+ emit(loadMemoryRegistry(root));
200
+ } else if (group === "memory" && action === "promote") {
201
+ const entry = promoteAgenticMemory(root, {
202
+ id: args[2], summary: option(parsed, "--summary"), guidance: option(parsed, "--guidance"),
203
+ areas: (option(parsed, "--area") ?? "*").split(",").map((item) => item.trim()).filter(Boolean),
204
+ phases: (option(parsed, "--phase") ?? "*").split(",").map((item) => item.trim()).filter(Boolean),
205
+ priority: numberOption("--priority", 50), sourceTaskId: option(parsed, "--source-task"), sourceLessonId: option(parsed, "--source-lesson"), approvedBy: option(parsed, "--approved-by"), approvedAt: now()
206
+ });
207
+ emit(entry);
208
+ } else if (group === "memory" && action === "retire") {
209
+ const entry = retireAgenticMemory(root, args[2], option(parsed, "--reason"), option(parsed, "--approved-by"));
210
+ emit(entry);
211
+ } else if (group === "gate" && action === "approve") {
212
+ const gate = option(parsed, "--gate"); const source = option(parsed, "--source");
213
+ if (!id || !gate || !source) throw new Error("gate approve requires active task id, --gate, and explicit --source");
214
+ const outcome = approveAndAdvance(root, state, id, gate, source); persist([outcome.task]); emit({ task: outcome.task, idempotent: outcome.idempotent }, outcome.task);
215
+ } else {
216
+ throw new Error("Usage: state.mjs state migrate | task create|show|list|find|next|status|item|transition|archive|handoff|close|supersede|reopen | decision set | evidence add | lesson record|none|search|rebuild | memory list|promote|retire | gate approve");
217
+ }
218
+ } finally {
219
+ process.off("exit", releaseOnce);
220
+ releaseOnce();
221
+ }
222
+ } catch (error) { failEntry(error); }
@@ -0,0 +1,27 @@
1
+ import { failEntry, loadState, loadTask, nextAction, parseArguments, rootOption, validateArguments } from "./lib/runtime.mjs";
2
+
3
+ try {
4
+ const raw = process.argv.slice(2);
5
+ const parsed = parseArguments(raw, { valueOptions: ["--root"], booleanOptions: ["--require-stop"] });
6
+ validateArguments(parsed, { minPositionals: 1, booleanOptions: ["--require-stop"], usage: "Usage: task-next.mjs <task-id> [--root <path>] [--require-stop]" });
7
+ const root = rootOption(parsed);
8
+ const [id] = parsed.positionals;
9
+ const task = loadTask(root, id, loadState(root));
10
+ if (!task) throw new Error(`Unknown task: ${id}`);
11
+ const action = nextAction(task, root);
12
+ const requireStop = parsed.flags.has("--require-stop");
13
+ const required = action.classification === "run_phase";
14
+ const continuation = requireStop ? {
15
+ required,
16
+ code: required ? "CONTINUATION_REQUIRED" : "STOP_ALLOWED",
17
+ taskId: task.id,
18
+ classification: action.classification,
19
+ ...(required ? { command: action.command, reason: action.reason, ...(action.itemId !== undefined ? { itemId: action.itemId } : {}), ...(action.remainingItems !== undefined ? { remainingItems: action.remainingItems } : {}) } : {})
20
+ } : undefined;
21
+ console.log(JSON.stringify({ ok: true, result: { id: task.id, phase: task.phase, gate: task.gate, status: task.status }, nextAction: action, ...(continuation ? { continuation } : {}) }, null, 2));
22
+ if (requireStop && required) {
23
+ console.error(`CONTINUATION_REQUIRED: ${JSON.stringify({ taskId: task.id, command: action.command, reason: action.reason })}`);
24
+ console.error("note: exit code 2 is the continuation guard working as intended (an expected pause, not a failure); execute the command above instead of replying.");
25
+ process.exitCode = 2;
26
+ }
27
+ } catch (error) { failEntry(error); }
@@ -0,0 +1,15 @@
1
+ # Design — <Title>
2
+
3
+ ## 🧩 Solution per affected area
4
+ - `<area>` — <approach, target, and reuse candidate>
5
+
6
+ ## 📌 Spec traceability
7
+ | design element | source | exact quote / approved decision |
8
+ |----------------|--------|---------------------------------|
9
+ | <quantified rule or contract> | <path:line, rule ID, or user decision> | <exact source> |
10
+
11
+ ## 🔗 Cross-service contracts
12
+ - <contract or `none`>
13
+
14
+ ## ⚠️ Risks / edge cases
15
+ - <risk and mitigation>
@@ -0,0 +1,17 @@
1
+ # <Title>
2
+
3
+ ## 📋 Problem
4
+ <Restate the goal and why it matters.>
5
+
6
+ ## 🗺️ Affected areas
7
+ - `<area>` — <why> — spec: <indexed source or `none indexed`>
8
+
9
+ ## 💭 Assumptions
10
+ - <assumption>
11
+
12
+ ## ❓ Open questions
13
+ 1. <question> — *default:* <safe default>
14
+
15
+ ## 🎯 Scope
16
+ **In:** <decidable scope>
17
+ **🚫 Out:** <explicit exclusions>
@@ -0,0 +1,28 @@
1
+ # Structured lesson contract
2
+
3
+ Lessons are canonical task data with task provenance. Project-wide advisory memory is a separate, explicitly approved registry; it never rewrites lifecycle state or project rules.
4
+
5
+ Required fields: `id`, `taskId`, `areas`, `summary`, `prevention`, `example`, `promotion`, `source`, `recordedAt`.
6
+
7
+ ```sh
8
+ node .agents/aidlc/scripts/state.mjs lesson record <task-id> <lesson-id> \
9
+ --area <areas> --summary <summary> --prevention <rule> \
10
+ --example <example> --promotion <status-or-target> --source <artifact-or-evidence>
11
+ ```
12
+
13
+ If honest review finds no durable lesson:
14
+
15
+ ```sh
16
+ node .agents/aidlc/scripts/state.mjs lesson none <task-id> --reason <reason> --source <source>
17
+ ```
18
+
19
+ `lesson search` reads the digest-checked derived index; `lesson rebuild` recreates it from canonical task records. Promote only an existing source lesson with explicit human approval:
20
+
21
+ ```sh
22
+ node .agents/aidlc/scripts/state.mjs memory promote <memory-id> \
23
+ --summary <summary> --guidance <actionable-guidance> --area <areas> \
24
+ --phase <phases-or-*> --priority <0-100> --source-task <task-id> \
25
+ --source-lesson <lesson-id> --approved-by <explicit-human-approval>
26
+ ```
27
+
28
+ Retirement is also audited: `state.mjs memory retire <memory-id> --reason <reason> --approved-by <explicit-human-approval>`. Context packets load only matching registry entries after project rules and only when budget remains.
@@ -0,0 +1,41 @@
1
+ # Model collaboration contract
2
+
3
+ Use this managed baseline with any AI model. It standardizes communication; it does not replace canonical state, executable verification, human gates, or approved task decisions. Project-specific additions belong in `.agents/project/rules/`.
4
+
5
+ ## COSTARS — task context
6
+
7
+ Express the applicable parts of a task in this order:
8
+
9
+ 1. **Context** — current phase, task state, relevant files/spec anchors, constraints, and known risks.
10
+ 2. **Objective** — the concrete result to produce and the boundary of responsibility.
11
+ 3. **Style** — required reasoning/implementation style, such as evidence-first or minimal diff.
12
+ 4. **Tone** — concise, direct, and calibrated to the intended reader.
13
+ 5. **Audience** — human reviewer, implementer, or another model; state their assumed context.
14
+ 6. **Response format** — exact headings, structured data, file paths, or commands expected.
15
+ 7. **Success criteria** — observable acceptance conditions, required evidence, and when to stop/escalate.
16
+
17
+ Omit fields that the active phase packet already makes explicit; never invent a spec, approval, or passing result to complete the format.
18
+
19
+ ## Phase mapping
20
+
21
+ | phase | required COSTARS emphasis | output |
22
+ |-------|---------------------------|--------|
23
+ | clarify | Context, Objective, Audience, Success criteria | intent, assumptions, open questions, scope |
24
+ | plan | Context, Objective, Response format, Success criteria | design, explicit decisions, workplan, verification plan |
25
+ | build | Context, Objective, Style, Success criteria | smallest approved diff and executable verification evidence |
26
+
27
+ Use CRITICS during clarify or plan only when risk is elevated, requirements are ambiguous, or models disagree.
28
+
29
+ ## CRITICS — adversarial review
30
+
31
+ Before recording passing review evidence for G2, report these sections against the approved decisions, cited anchors, and task diff:
32
+
33
+ 1. **Criteria** — acceptance criteria, invariants, and exact spec anchors checked.
34
+ 2. **Risks** — regression, safety, compatibility, and unverified assumptions.
35
+ 3. **Issues** — each finding with severity (`critical`, `high`, `medium`, `low`) and evidence; state `none found` when applicable.
36
+ 4. **Tests / evidence** — commands or inspections run, results, and residual gaps.
37
+ 5. **Improvements** — required fixes or explicitly deferred follow-ups, each tied to an issue.
38
+ 6. **Conclusion** — `pass`, `needs changes`, or `escalate`; a pass requires no unresolved critical/high issue.
39
+ 7. **Stop / escalate** — reopen G1 for a structural/spec conflict; otherwise defer to the human gate when authority is needed.
40
+
41
+ CRITICS is a review record, not approval. Only the state machine, recorded evidence, and human G2 approval advance the task.
@@ -0,0 +1,9 @@
1
+ # Workplan — <Title> (`<task-id>`)
2
+
3
+ > Review artifact derived from `.agents/data/state/aidlc-state.json`. Resolve decisions and update progress with `node .agents/aidlc/scripts/state.mjs`, then run `node .agents/aidlc/scripts/render.mjs`.
4
+
5
+ ## 🧩 Decisions (Gate G1 — approve before build)
6
+ - <generated decision state>
7
+
8
+ ## 🧩 Tasks (Gate G2 — build execution)
9
+ - <generated task state>
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: aidlc-build
3
+ description: "AI-DLC phase 3 (Gate G2): after explicit G1 plan approval, implement the canonical work items, run the narrowest tests/lint, then run code-review. Triggers when the AI-DLC Orchestrator routes an approved post-G1 task to build. Ends at G2 for explicit user code review."
4
+ ---
5
+
6
+ # AI-DLC · Build (Gate G2) — adapter
7
+
8
+ This skill is a thin Claude Code adapter. The canonical playbook is tool-agnostic and lives in the repo:
9
+
10
+ **Read `.agents/aidlc/phase-build.md` and execute it exactly.**
11
+
12
+ Completing a canonical item is progress, not a stopping point. Execute the mutation's returned `nextAction` immediately, loop across remaining items, and run `task-next.mjs <task-id> --require-stop` before any final response.
13
+
14
+ - Shared conventions (canonical state, gates, continuation, evidence): `.agents/aidlc/conventions.md`
15
+ - Common rules & orchestrator: `.agents/aidlc/orchestrator.md`
16
+ - Templates: `.agents/aidlc/templates/` · Stack rules: `.agents/aidlc/rules/`
17
+ - Lifecycle state: `.agents/data/state/aidlc-state.json` · discovery indexes: `.agents/data/index/repo-map.md`, `.agents/data/index/specs-index.md`
18
+
19
+ Do not re-author content from memory — the canonical files are the source of truth.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: aidlc-clarify
3
+ description: "AI-DLC phase 1 (Gate G0): turn a raw request into a confirmed intent brief — restate the problem, map affected submodules + specs, list assumptions and open questions, fix scope. Cheap: reads only the AI-DLC index, no deep code dive. Triggers: a NEW feature/bug/change request when the AI-DLC Orchestrator routes to clarify. Ends by STOPPING for user confirmation (G0)."
4
+ ---
5
+
6
+ # AI-DLC · Clarify (Gate G0) — adapter
7
+
8
+ This skill is a thin Claude Code adapter. The canonical playbook is tool-agnostic and lives in the repo:
9
+
10
+ **Read `.agents/aidlc/phase-clarify.md` and execute it exactly.**
11
+
12
+ - Shared conventions (canonical state, gates, continuation, evidence): `.agents/aidlc/conventions.md`
13
+ - Common rules & orchestrator: `.agents/aidlc/orchestrator.md`
14
+ - Templates: `.agents/aidlc/templates/` · Stack rules: `.agents/aidlc/rules/`
15
+ - Lifecycle state: `.agents/data/state/aidlc-state.json` · discovery indexes: `.agents/data/index/repo-map.md`, `.agents/data/index/specs-index.md`
16
+
17
+ Do not re-author content from memory — the canonical files are the source of truth.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: aidlc-index
3
+ description: "Build or refresh the AI-DLC index — a compact repo-map and spec-index used by every other AI-DLC phase to locate relevant code and docs cheaply. Run when .agents/data/index/repo-map.md is missing, or whenever submodules, stacks, or spec files change. Triggers: 'aidlc index', 'refresh repo map', 'rebuild spec index', 'index the specs'. Normally invoked automatically by the AI-DLC Orchestrator, not by the user."
4
+ ---
5
+
6
+ # AI-DLC · Index — adapter
7
+
8
+ This skill is a thin Claude Code adapter. The canonical playbook is tool-agnostic and lives in the repo:
9
+
10
+ **Read `.agents/aidlc/phase-index.md` and execute it exactly.**
11
+
12
+ - Shared conventions (canonical state, gates, continuation, evidence): `.agents/aidlc/conventions.md`
13
+ - Common rules & orchestrator: `.agents/aidlc/orchestrator.md`
14
+ - Templates: `.agents/aidlc/templates/` · Stack rules: `.agents/aidlc/rules/`
15
+ - Lifecycle state: `.agents/data/state/aidlc-state.json` · discovery indexes: `.agents/data/index/repo-map.md`, `.agents/data/index/specs-index.md`
16
+
17
+ Do not re-author content from memory — the canonical files are the source of truth.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: aidlc-plan
3
+ description: "AI-DLC phase 2 (Gate G1): after intent is confirmed, run bounded discovery, then produce a detailed design, canonical decisions, and a per-submodule workplan. Triggers when the AI-DLC Orchestrator routes a confirmed post-G0 task to planning. Ends at the executable G1 plan review for explicit user approval."
4
+ ---
5
+
6
+ # AI-DLC · Plan (Gate G1) — adapter
7
+
8
+ This skill is a thin Claude Code adapter. The canonical playbook is tool-agnostic and lives in the repo:
9
+
10
+ **Read `.agents/aidlc/phase-plan.md` and execute it exactly.**
11
+
12
+ - Shared conventions (canonical state, gates, continuation, evidence): `.agents/aidlc/conventions.md`
13
+ - Common rules & orchestrator: `.agents/aidlc/orchestrator.md`
14
+ - Templates: `.agents/aidlc/templates/` · Stack rules: `.agents/aidlc/rules/`
15
+ - Lifecycle state: `.agents/data/state/aidlc-state.json` · discovery indexes: `.agents/data/index/repo-map.md`, `.agents/data/index/specs-index.md`
16
+
17
+ Do not re-author content from memory — the canonical files are the source of truth.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: aidlc-wrap
3
+ description: "AI-DLC phase 4 (post-G2): after the user approves the code, follow project delivery policy, capture lessons and residual risk, then close canonical task state."
4
+ ---
5
+
6
+ # AI-DLC · Wrap — adapter
7
+
8
+ This skill is a thin Claude Code adapter. The canonical playbook is tool-agnostic and lives in the repo:
9
+
10
+ **Read `.agents/aidlc/phase-wrap.md` and execute it exactly.**
11
+
12
+ - Shared conventions (canonical state, gates, continuation, evidence): `.agents/aidlc/conventions.md`
13
+ - Common rules & orchestrator: `.agents/aidlc/orchestrator.md`
14
+ - Templates: `.agents/aidlc/templates/` · Stack rules: `.agents/aidlc/rules/`
15
+ - Lifecycle state: `.agents/data/state/aidlc-state.json` · discovery indexes: `.agents/data/index/repo-map.md`, `.agents/data/index/specs-index.md`
16
+
17
+ Do not re-author content from memory — the canonical files are the source of truth.
@@ -0,0 +1,10 @@
1
+ FROM node:24-bookworm-slim
2
+ WORKDIR /app
3
+ RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ && rm -rf /var/lib/apt/lists/*
4
+ COPY package.json package-lock.json ./
5
+ RUN npm ci --omit=dev
6
+ COPY src ./src
7
+ RUN mkdir -p /data
8
+ ENV MCP_STATE_HOST=0.0.0.0 MCP_STATE_PORT=8787 MCP_STATE_SQLITE_PATH=/data/mcp-state.db
9
+ EXPOSE 8787
10
+ CMD ["node", "src/server.mjs"]
@@ -0,0 +1,25 @@
1
+ services:
2
+ aidlc-coordination:
3
+ build: .
4
+ ports:
5
+ - "127.0.0.1:8787:8787"
6
+ volumes:
7
+ - aidlc-coordination-data:/data
8
+ environment:
9
+ MCP_STATE_DATABASE: sqlite
10
+ MCP_STATE_SQLITE_PATH: /data/mcp-state.db
11
+ MCP_STATE_AUTH_TOKEN: ${MCP_STATE_AUTH_TOKEN:?set MCP_STATE_AUTH_TOKEN in .env before starting the service}
12
+
13
+ postgres:
14
+ image: postgres:16-alpine
15
+ profiles: [postgres]
16
+ environment:
17
+ POSTGRES_DB: aidlc
18
+ POSTGRES_USER: aidlc
19
+ POSTGRES_PASSWORD: change-me
20
+ volumes:
21
+ - postgres-data:/var/lib/postgresql/data
22
+
23
+ volumes:
24
+ aidlc-coordination-data:
25
+ postgres-data: