@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,108 @@
1
+ {
2
+ "title": "AI-DLC canonical state",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "required": ["schemaVersion", "tasks", "archive"],
6
+ "properties": {
7
+ "schemaVersion": { "const": 4 },
8
+ "tasks": { "type": "object", "additionalProperties": { "$ref": "#/$defs/task" } },
9
+ "archive": { "type": "object", "additionalProperties": { "$ref": "#/$defs/archiveSummary" } }
10
+ },
11
+ "$defs": {
12
+ "task": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["id", "title", "type", "phase", "gate", "status", "language", "risk", "areas", "branch", "artifacts", "decisions", "tasks", "evidence", "createdAt", "updatedAt"],
16
+ "properties": {
17
+ "id": { "type": "string" },
18
+ "title": { "type": "string" },
19
+ "type": { "enum": ["feature", "bug", "refactor", "infra"] },
20
+ "phase": { "enum": ["clarify", "plan", "build", "wrap", "done"] },
21
+ "gate": { "enum": ["none", "G0_confirm", "G1_review", "G2_codereview"] },
22
+ "status": { "enum": ["active", "blocked_on_user", "paused", "done", "closed", "superseded"] },
23
+ "language": { "enum": ["vi", "en"] },
24
+ "risk": { "enum": ["low", "normal", "high", "regulated"] },
25
+ "areas": { "type": "array", "minItems": 1, "items": { "type": "string" } },
26
+ "branch": { "type": "string" },
27
+ "artifacts": { "type": "object" },
28
+ "decisions": { "type": "array", "items": { "$ref": "#/$defs/decision" } },
29
+ "tasks": { "type": "array", "items": { "$ref": "#/$defs/executionTask" } },
30
+ "evidence": { "type": "array", "items": { "$ref": "#/$defs/evidence" } },
31
+ "lessons": { "type": "array", "items": { "$ref": "#/$defs/lesson" } },
32
+ "lessonDisposition": { "$ref": "#/$defs/lessonDisposition" },
33
+ "handoff": { "$ref": "#/$defs/handoff" },
34
+ "closure": { "$ref": "#/$defs/closure" },
35
+ "legacyG2Wait": { "$ref": "#/$defs/legacyG2Wait" },
36
+ "predecessorTaskId": { "type": "string", "minLength": 1 },
37
+ "successorTaskId": { "type": "string", "minLength": 1 },
38
+ "createdAt": { "type": "string" },
39
+ "updatedAt": { "type": "string" }
40
+ },
41
+ "allOf": [
42
+ { "if": { "properties": { "phase": { "const": "done" } }, "required": ["phase"] }, "then": { "properties": { "status": { "const": "done" } } } },
43
+ { "if": { "properties": { "status": { "const": "done" } }, "required": ["status"] }, "then": { "properties": { "phase": { "const": "done" } } } },
44
+ { "if": { "properties": { "status": { "enum": ["closed", "superseded"] } }, "required": ["status"] }, "then": { "required": ["closure"] }, "else": { "not": { "required": ["closure"] } } },
45
+ { "if": { "properties": { "status": { "const": "superseded" } }, "required": ["status"] }, "then": { "required": ["successorTaskId"] }, "else": { "not": { "required": ["successorTaskId"] } } },
46
+ { "if": { "required": ["handoff"] }, "then": { "properties": { "status": { "const": "paused" } } } },
47
+ { "if": { "required": ["legacyG2Wait"] }, "then": { "properties": { "phase": { "const": "build" }, "gate": { "const": "G2_codereview" }, "status": { "const": "blocked_on_user" } } } }
48
+ ]
49
+ },
50
+ "archiveSummary": {
51
+ "type": "object",
52
+ "additionalProperties": false,
53
+ "required": ["id", "title", "type", "phase", "gate", "status", "risk", "areas", "record", "digest", "lessonCount", "createdAt", "updatedAt"],
54
+ "properties": {
55
+ "id": { "type": "string" },
56
+ "title": { "type": "string" },
57
+ "type": { "enum": ["feature", "bug", "refactor", "infra"] },
58
+ "phase": { "enum": ["clarify", "plan", "build", "wrap", "done"] },
59
+ "gate": { "enum": ["none", "G0_confirm", "G1_review", "G2_codereview"] },
60
+ "status": { "enum": ["done", "closed", "superseded"] },
61
+ "risk": { "enum": ["low", "normal", "high", "regulated"] },
62
+ "areas": { "type": "array", "minItems": 1, "items": { "type": "string" } },
63
+ "record": { "type": "string", "pattern": "^\\.agents/data/state/archive/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\\.json$" },
64
+ "digest": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
65
+ "lessonCount": { "type": "integer", "minimum": 0 },
66
+ "predecessorTaskId": { "type": "string", "minLength": 1 },
67
+ "successorTaskId": { "type": "string", "minLength": 1 },
68
+ "createdAt": { "type": "string" },
69
+ "updatedAt": { "type": "string" }
70
+ }
71
+ },
72
+ "handoff": {
73
+ "type": "object",
74
+ "additionalProperties": false,
75
+ "required": ["kind", "reason", "source", "recordedAt"],
76
+ "properties": {
77
+ "kind": { "enum": ["repair_exhausted", "review_exhausted", "g2_failed", "release_failed", "structural_change", "other"] },
78
+ "reason": { "type": "string", "minLength": 1 },
79
+ "source": { "type": "string", "minLength": 1 },
80
+ "recordedAt": { "type": "string" }
81
+ }
82
+ },
83
+ "closure": {
84
+ "type": "object",
85
+ "additionalProperties": false,
86
+ "required": ["reason", "source", "recordedAt"],
87
+ "properties": {
88
+ "reason": { "type": "string", "minLength": 1 },
89
+ "source": { "type": "string", "minLength": 1 },
90
+ "recordedAt": { "type": "string" }
91
+ }
92
+ },
93
+ "legacyG2Wait": {
94
+ "type": "object",
95
+ "additionalProperties": false,
96
+ "required": ["migratedAt", "source"],
97
+ "properties": {
98
+ "migratedAt": { "type": "string" },
99
+ "source": { "type": "string", "minLength": 1 }
100
+ }
101
+ },
102
+ "lesson": { "type": "object", "additionalProperties": false, "required": ["id", "taskId", "areas", "summary", "prevention", "example", "promotion", "source", "recordedAt"], "properties": { "id": { "type": "string" }, "taskId": { "type": "string" }, "areas": { "type": "array", "minItems": 1, "items": { "type": "string" } }, "summary": { "type": "string", "minLength": 1 }, "prevention": { "type": "string", "minLength": 1 }, "example": { "type": "string", "minLength": 1 }, "promotion": { "type": "string", "minLength": 1 }, "source": { "type": "string", "minLength": 1 }, "recordedAt": { "type": "string" } } },
103
+ "lessonDisposition": { "type": "object", "additionalProperties": false, "required": ["status", "source", "recordedAt"], "properties": { "status": { "enum": ["captured", "none"] }, "reason": { "type": "string", "minLength": 1 }, "source": { "type": "string", "minLength": 1 }, "recordedAt": { "type": "string" } } },
104
+ "decision": { "type": "object", "required": ["id", "label", "status"], "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "status": { "enum": ["unresolved", "approved", "changed", "dropped"] }, "resolution": { "type": "string" } } },
105
+ "executionTask": { "type": "object", "required": ["id", "label", "status"], "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "status": { "enum": ["todo", "in_progress", "done", "deferred"] } } },
106
+ "evidence": { "type": "object", "required": ["kind", "source", "result", "recordedAt"], "properties": { "kind": { "enum": ["approval", "spec", "test", "lint", "review", "diagnostic"] }, "gate": { "enum": ["none", "G0_confirm", "G1_review", "G2_codereview"] }, "area": { "type": "string" }, "source": { "type": "string" }, "result": { "enum": ["pass", "fail", "skip"] }, "detail": { "type": "string" }, "recordedAt": { "type": "string" } } }
107
+ }
108
+ }
@@ -0,0 +1,15 @@
1
+ import { compileContext, failEntry, loadState, loadTask, option, parseArguments, rootOption, validateArguments } from "./lib/runtime.mjs";
2
+
3
+ try {
4
+ const raw = process.argv.slice(2);
5
+ const parsed = parseArguments(raw, { valueOptions: ["--root", "--phase", "--mode", "--item", "--format"] });
6
+ validateArguments(parsed, { minPositionals: 1, valueOptions: ["--phase", "--mode", "--item", "--format"], usage: "Usage: context.mjs <task-id> [--phase <phase>] [--mode <mode>] [--item <item-id>] [--format <format>] [--root <path>]" });
7
+ const root = rootOption(parsed);
8
+ const [id] = parsed.positionals;
9
+ const state = loadState(root);
10
+ const task = loadTask(root, id, state);
11
+ if (!task) throw new Error(`Unknown task: ${id}`);
12
+ const packet = compileContext(root, task, option(parsed, "--phase") ?? task.phase, { mode: option(parsed, "--mode") ?? "standard", itemId: option(parsed, "--item") });
13
+ if (option(parsed, "--format") === "json") console.log(JSON.stringify(packet, null, 2));
14
+ else console.log(`${packet.content}\n\n---\nchars: ${packet.chars} · estimated tokens: ${packet.estimatedTokens} · omitted rules: ${packet.omittedRules.length}`);
15
+ } catch (error) { failEntry(error); }
@@ -0,0 +1,16 @@
1
+ import { checkGate, failEntry, formatDiagnostics, loadState, option, parseArguments, rootOption, validateArguments } from "./lib/runtime.mjs";
2
+
3
+ try {
4
+ const raw = process.argv.slice(2);
5
+ const parsed = parseArguments(raw, { valueOptions: ["--root", "--gate"] });
6
+ validateArguments(parsed, { minPositionals: 1, valueOptions: ["--gate"], usage: "Usage: gate-check.mjs <task-id> [--gate <gate>] [--root <path>]" });
7
+ const root = rootOption(parsed);
8
+ const [id] = parsed.positionals;
9
+ const state = loadState(root);
10
+ const task = state.tasks[id];
11
+ const gate = option(parsed, "--gate") ?? task?.gate;
12
+ if (!gate) throw new Error(`Unknown task: ${id}; pass --gate to check a gate contract without task state`);
13
+ const diagnostics = checkGate(root, state, id, gate);
14
+ console.log(formatDiagnostics(diagnostics));
15
+ if (diagnostics.some((item) => item.level === "ERROR")) process.exitCode = 1;
16
+ } catch (error) { failEntry(error); }
@@ -0,0 +1,16 @@
1
+ import { checkGate, failEntry, formatGateView, loadState, option, parseArguments, rootOption, validateArguments } from "./lib/runtime.mjs";
2
+
3
+ try {
4
+ const raw = process.argv.slice(2);
5
+ const parsed = parseArguments(raw, { valueOptions: ["--root", "--format"] });
6
+ validateArguments(parsed, { minPositionals: 1, valueOptions: ["--format"], usage: "Usage: gate-view.mjs <task-id> [--format <format>] [--root <path>]" });
7
+ const root = rootOption(parsed);
8
+ const [id] = parsed.positionals;
9
+ const state = loadState(root);
10
+ const task = state.tasks[id];
11
+ if (!task) throw new Error(`Unknown active task: ${id}`);
12
+ if (task.status !== "blocked_on_user") throw new Error(`Task must be blocked_on_user before presenting ${task.gate}`);
13
+ const format = option(parsed, "--format") ?? "markdown";
14
+ if (!["markdown", "plain", "json"].includes(format)) throw new Error("--format must be markdown, plain, or json");
15
+ console.log(formatGateView(task, checkGate(root, state, id, task.gate), format).trimEnd());
16
+ } catch (error) { failEntry(error); }
@@ -0,0 +1,238 @@
1
+ import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
2
+ import { dirname, isAbsolute, join, normalize, resolve } from "node:path";
3
+ import { loadMemoryRegistry, nextAction, selectAgenticMemory } from "./store.mjs";
4
+
5
+ const separator = process.platform === "win32" ? "\\" : "/";
6
+
7
+ export const parseArguments = (args, { valueOptions = [], booleanOptions = [] } = {}) => {
8
+ const valueNames = new Set(valueOptions);
9
+ const booleanNames = new Set(booleanOptions);
10
+ const positionals = [];
11
+ const values = new Map();
12
+ const flags = new Set();
13
+ const present = new Set();
14
+ let positionalOnly = false;
15
+ for (let index = 0; index < args.length; index += 1) {
16
+ const token = args[index];
17
+ if (positionalOnly || !token.startsWith("--")) {
18
+ positionals.push(token);
19
+ continue;
20
+ }
21
+ if (token === "--") {
22
+ positionalOnly = true;
23
+ continue;
24
+ }
25
+ const separatorIndex = token.indexOf("=");
26
+ const name = separatorIndex < 0 ? token : token.slice(0, separatorIndex);
27
+ const inlineValue = separatorIndex < 0 ? undefined : token.slice(separatorIndex + 1);
28
+ if (!valueNames.has(name) && !booleanNames.has(name)) throw new Error(`Unknown option: ${name}`);
29
+ if (present.has(name)) throw new Error(`Duplicate option: ${name}`);
30
+ present.add(name);
31
+ if (booleanNames.has(name)) {
32
+ if (inlineValue !== undefined) throw new Error(`${name} does not accept a value`);
33
+ flags.add(name);
34
+ continue;
35
+ }
36
+ const value = inlineValue === undefined ? args[++index] : inlineValue;
37
+ if (value === undefined || value === "" || (inlineValue === undefined && value.startsWith("--"))) throw new Error(`${name} requires a value`);
38
+ values.set(name, value);
39
+ }
40
+ return { positionals, values, flags, present };
41
+ };
42
+
43
+ export const option = (parsed, name) => parsed.values.get(name);
44
+
45
+ export const validateArguments = (parsed, { minPositionals, maxPositionals = minPositionals, valueOptions = [], booleanOptions = [], requiredOptions = [], usage = "Invalid arguments" }) => {
46
+ const allowed = new Set(["--root", ...valueOptions, ...booleanOptions]);
47
+ for (const name of parsed.present) if (!allowed.has(name)) throw new Error(`${name} is not valid for this command`);
48
+ if (parsed.positionals.length < minPositionals || parsed.positionals.length > maxPositionals) throw new Error(usage);
49
+ for (const name of requiredOptions) if (!parsed.present.has(name)) throw new Error(`${usage}: ${name} is required`);
50
+ return parsed;
51
+ };
52
+
53
+ export const failEntry = (error) => {
54
+ const expected = error instanceof Error && error.name === "Error";
55
+ const payload = { ok: false, error: { message: error instanceof Error ? error.message : String(error), ...(error?.hint ? { hint: error.hint } : {}) } };
56
+ console.error(JSON.stringify(payload));
57
+ process.exitCode = expected ? 1 : 3;
58
+ };
59
+
60
+ export const rootOption = (parsed) => {
61
+ const explicit = option(parsed, "--root");
62
+ if (explicit !== undefined) {
63
+ const project = resolve(explicit);
64
+ const marker = join(project, ".agents");
65
+ if (!existsSync(marker) || !lstatSync(marker).isDirectory()) throw new Error(`Explicit project root must contain .agents: ${project}`);
66
+ return project;
67
+ }
68
+ let candidate = resolve(process.cwd());
69
+ while (true) {
70
+ const marker = join(candidate, ".agents/aidlc");
71
+ if (existsSync(marker) && lstatSync(marker).isDirectory()) return candidate;
72
+ const parent = dirname(candidate);
73
+ if (parent === candidate) throw new Error("Project root not found; use --root to select an installed project");
74
+ candidate = parent;
75
+ }
76
+ };
77
+
78
+ const safeRelative = (path, label) => {
79
+ const cleaned = normalize(path);
80
+ if (isAbsolute(cleaned) || cleaned === ".." || cleaned.startsWith(`..${separator}`)) throw new Error(`${label} must stay inside the project`);
81
+ return cleaned;
82
+ };
83
+
84
+ const object = (value, label) => {
85
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
86
+ return value;
87
+ };
88
+
89
+ const strings = (value, label) => {
90
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new Error(`${label} must be a string array`);
91
+ return value;
92
+ };
93
+
94
+ const parseJson = (content, label) => {
95
+ try { return JSON.parse(content); }
96
+ catch (error) { throw new Error(`${label} is corrupted or unreadable: ${error instanceof Error ? error.message : String(error)}`); }
97
+ };
98
+
99
+ const command = (value, label) => {
100
+ const item = object(value, label);
101
+ if (typeof item.command !== "string" || !item.command || isAbsolute(item.command)) throw new Error(`${label}.command must be a non-absolute executable name`);
102
+ return { command: item.command, args: strings(item.args ?? [], `${label}.args`) };
103
+ };
104
+
105
+ const commands = (value, label) => value === undefined ? {} : Object.fromEntries(Object.entries(object(value, label)).map(([key, item]) => [key, command(item, `${label}.${key}`)]));
106
+
107
+ const validateProfile = (value, label) => {
108
+ const item = object(value, label);
109
+ const idPattern = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/;
110
+ if (item.schemaVersion !== 1 || typeof item.id !== "string" || !idPattern.test(item.id) || typeof item.topology !== "string" || !idPattern.test(item.topology)) throw new Error(`${label} has an unsupported schema, id, or topology`);
111
+ const discovery = item.discovery === undefined ? undefined : object(item.discovery, `${label}.discovery`);
112
+ const specs = item.specs === undefined ? undefined : object(item.specs, `${label}.specs`);
113
+ const rules = item.rules === undefined ? undefined : object(item.rules, `${label}.rules`);
114
+ return {
115
+ schemaVersion: 1, id: item.id, extends: item.extends === undefined ? [] : strings(item.extends, `${label}.extends`), topology: item.topology,
116
+ discovery: discovery ? { roots: strings(discovery.roots ?? [], `${label}.discovery.roots`).map((path) => safeRelative(path, `${label}.discovery.roots`)), workspaceMarkers: strings(discovery.workspaceMarkers ?? [], `${label}.discovery.workspaceMarkers`).map((path) => safeRelative(path, `${label}.discovery.workspaceMarkers`)) } : undefined,
117
+ specs: specs ? { roots: strings(specs.roots ?? [], `${label}.specs.roots`).map((path) => safeRelative(path, `${label}.specs.roots`)) } : undefined,
118
+ commands: commands(item.commands, `${label}.commands`), rules: rules ? { include: strings(rules.include ?? [], `${label}.rules.include`).map((path) => safeRelative(path, `${label}.rules.include`)) } : undefined
119
+ };
120
+ };
121
+
122
+ const profile = (id, markers = []) => ({ schemaVersion: 1, id: `topology/${id}`, topology: id, discovery: { roots: ["."], workspaceMarkers: markers }, specs: { roots: [] }, commands: {}, rules: { include: [] } });
123
+ const builtInProfiles = {
124
+ "topology/generic": profile("generic"),
125
+ "topology/single": profile("single"),
126
+ "topology/workspace": profile("workspace", ["package.json", "pnpm-workspace.yaml", "lerna.json"]),
127
+ "topology/git-submodules": profile("git-submodules", [".gitmodules"])
128
+ };
129
+
130
+ const profileAlias = (id) => builtInProfiles[id] ? id : builtInProfiles[`topology/${id}`] ? `topology/${id}` : id;
131
+
132
+ const loadConfig = (root) => {
133
+ const path = join(resolve(root), ".agents/config.json");
134
+ const item = existsSync(path) ? object(parseJson(readFileSync(path, "utf8"), "Project config"), ".agents/config.json") : {};
135
+ if (item.schemaVersion !== undefined && ![1, 2, 3].includes(item.schemaVersion)) throw new Error("Unsupported project config schema");
136
+ const risk = object(item.risk ?? {}, "config.risk");
137
+ const context = object(item.context ?? {}, "config.context");
138
+ const riskDefault = String(risk.default ?? "normal");
139
+ if (!["low", "normal", "high", "regulated"].includes(riskDefault)) throw new Error("Unsupported project risk level");
140
+ const maxChars = Number(context.maxChars ?? 16000);
141
+ if (!Number.isInteger(maxChars) || maxChars < 4000 || maxChars > 128000) throw new Error("context.maxChars must be an integer from 4000 to 128000");
142
+ return {
143
+ extends: strings(item.extends ?? ["topology/single"], "config.extends"),
144
+ specs: { roots: strings(object(item.specs ?? {}, "config.specs").roots ?? [], "config.specs.roots").map((entry) => safeRelative(entry, "config.specs.roots")) },
145
+ commands: commands(item.commands, "config.commands"),
146
+ rules: { include: strings(object(item.rules ?? {}, "config.rules").include ?? [], "config.rules.include").map((entry) => safeRelative(entry, "config.rules.include")) },
147
+ risk: { default: riskDefault }, context: { maxChars },
148
+ mcp: item.mcp?.enabled === true ? { enabled: true, endpoint: typeof item.mcp.endpoint === "string" ? item.mcp.endpoint : undefined, workspace: typeof item.mcp.workspace === "string" ? item.mcp.workspace : undefined, tokenEnv: typeof item.mcp.tokenEnv === "string" ? item.mcp.tokenEnv : undefined, pollMs: Number.isInteger(item.mcp.pollMs) ? item.mcp.pollMs : undefined, providers: Array.isArray(item.mcp.providers) ? item.mcp.providers.filter((provider) => ["jira", "trello", "github-issues"].includes(provider)) : [] } : { enabled: false }
149
+ };
150
+ };
151
+
152
+ const loadProfile = (root, id) => {
153
+ const resolvedId = profileAlias(id);
154
+ if (builtInProfiles[resolvedId]) return builtInProfiles[resolvedId];
155
+ const path = join(resolve(root), ".agents/project/profiles", safeRelative(id, "profile id"), "profile.json");
156
+ if (!existsSync(path)) throw new Error(`Unknown profile: ${id}`);
157
+ if (lstatSync(path).isSymbolicLink() || !realpathSync(path).startsWith(`${realpathSync(resolve(root))}${separator}`)) throw new Error(`Profile escapes the project: ${id}`);
158
+ const value = validateProfile(parseJson(readFileSync(path, "utf8"), `Profile ${id}`), id);
159
+ if (value.id !== id) throw new Error(`Invalid profile id: ${id}`);
160
+ return value;
161
+ };
162
+
163
+ const resolveProfiles = (root, ids) => {
164
+ const resolved = [];
165
+ const visiting = new Set();
166
+ const visit = (id) => {
167
+ const resolvedId = profileAlias(id);
168
+ if (resolved.some((entry) => entry.id === resolvedId)) return;
169
+ if (visiting.has(resolvedId)) throw new Error(`Profile cycle detected at ${resolvedId}`);
170
+ visiting.add(resolvedId);
171
+ const current = loadProfile(root, resolvedId);
172
+ for (const parent of current.extends ?? []) visit(parent);
173
+ visiting.delete(resolvedId);
174
+ resolved.push(current);
175
+ };
176
+ ids.forEach(visit);
177
+ return resolved;
178
+ };
179
+
180
+ const stable = (values) => [...new Set(values)];
181
+ const resolveEffectiveConfig = (root, config) => {
182
+ const profiles = resolveProfiles(root, config.extends);
183
+ return { profiles, discovery: { roots: stable(profiles.flatMap((item) => item.discovery?.roots ?? ["."])), workspaceMarkers: stable(profiles.flatMap((item) => item.discovery?.workspaceMarkers ?? [])) }, specs: { roots: stable([...profiles.flatMap((item) => item.specs?.roots ?? []), ...config.specs.roots]) }, commands: Object.assign({}, ...profiles.map((item) => item.commands ?? {}), config.commands), rules: { include: stable([...profiles.flatMap((item) => item.rules?.include ?? []), ...config.rules.include]) }, risk: config.risk, context: config.context, mcp: config.mcp };
184
+ };
185
+
186
+ const includedRuleFiles = (root, patterns) => {
187
+ const project = resolve(root);
188
+ const realProject = realpathSync(project);
189
+ const inside = (path) => !lstatSync(path).isSymbolicLink() && realpathSync(path).startsWith(`${realProject}${separator}`);
190
+ const files = new Set();
191
+ for (const pattern of patterns) {
192
+ const safe = safeRelative(pattern, "rule include");
193
+ if (safe.endsWith("/*.md")) {
194
+ const directory = join(project, safe.slice(0, -5));
195
+ if (existsSync(directory) && inside(directory)) for (const entry of readdirSync(directory)) {
196
+ const path = join(directory, entry);
197
+ if (entry.endsWith(".md") && existsSync(path) && inside(path)) files.add(path);
198
+ }
199
+ } else {
200
+ const target = join(project, safe);
201
+ if (existsSync(target) && inside(target)) files.add(target);
202
+ }
203
+ }
204
+ return [...files].sort();
205
+ };
206
+
207
+ const requiredEvidence = (task) => task.evidence.filter((item, index, entries) => {
208
+ const key = `${item.kind}:${item.area ?? ""}:${item.gate ?? ""}`;
209
+ return !entries.slice(index + 1).some((candidate) => `${candidate.kind}:${candidate.area ?? ""}:${candidate.gate ?? ""}` === key);
210
+ }).map(({ detail, ...item }) => item);
211
+
212
+ export const compileContext = (root, task, phase = task.phase, options = {}) => {
213
+ if (!["clarify", "plan", "build", "wrap", "done"].includes(phase)) throw new Error(`Unsupported phase: ${phase}`);
214
+ const config = loadConfig(root);
215
+ const effective = resolveEffectiveConfig(root, config);
216
+ const profiles = effective.profiles;
217
+ const rules = includedRuleFiles(root, effective.rules.include);
218
+ if (options.itemId && !task.tasks.some((item) => item.id === options.itemId)) throw new Error(`Unknown task item: ${options.itemId}`);
219
+ const compact = JSON.stringify({ id: task.id, title: task.title, type: task.type, phase: task.phase, gate: task.gate, status: task.status, risk: task.risk, areas: task.areas, handoff: task.handoff, closure: task.closure, predecessorTaskId: task.predecessorTaskId, successorTaskId: task.successorTaskId, decisions: task.decisions.map(({ id, label, status }) => ({ id, label, status })), tasks: options.itemId ? task.tasks.filter((item) => item.id === options.itemId) : task.tasks, evidence: requiredEvidence(task), lessonDisposition: task.lessonDisposition, artifacts: task.artifacts }, null, 2);
220
+ const actionableItems = options.itemId ? task.tasks.filter((item) => item.status === "in_progress" || item.status === "todo").map((item) => item.id) : [];
221
+ const invariants = [options.itemId ? `- Focused item context does not narrow the workplan: actionable items are ${actionableItems.join(", ") || "none"}; after this item, execute nextAction immediately.` : "", "- Continue after a non-terminal transition or item/evidence mutation; yield only at a human gate, real blocker, or completion.", "- Item completion is progress for commentary, never a final response while nextAction is run_phase.", "- Never omit approved decisions, spec anchors, safety constraints, or applicable verification evidence.", "- Agents may run configured build/test/lint commands, but workflow installation and upgrades remain human-only npm/npx operations."].filter(Boolean).join("\n");
222
+ const phasePath = join(resolve(root), ".agents/aidlc", phase === "done" ? "phase-wrap.md" : `phase-${phase}.md`);
223
+ const phaseContract = readFileSync(phasePath, "utf8");
224
+ let content = [`# AI-DLC phase packet — ${phase}`, `mode: ${options.mode ?? "standard"}${options.itemId ? ` · item: ${options.itemId}` : ""}`, "## Next action / stop contract", JSON.stringify(nextAction(task, root), null, 2), "## Phase contract", phaseContract, "## Canonical task state", compact, "## Resolved profiles", profiles.map((item) => JSON.stringify({ id: item.id, topology: item.topology, discovery: item.discovery, specs: item.specs, commands: item.commands })).join("\n"), "## Effective project configuration", JSON.stringify({ discovery: effective.discovery, specs: effective.specs, commands: effective.commands, risk: effective.risk, context: effective.context }, null, 2), "## Invariants", invariants].join("\n\n");
225
+ if (content.length > config.context.maxChars) throw new Error("Context budget is too small for the full phase contract, next action, canonical task state, and mandatory invariants");
226
+ const omittedRules = [];
227
+ for (const path of rules) {
228
+ const relative = path.slice(resolve(root).length + 1);
229
+ const block = `\n\n## Project rule — ${relative}\n${readFileSync(path, "utf8")}`;
230
+ if (content.length + block.length <= config.context.maxChars) content += block;
231
+ else omittedRules.push(relative);
232
+ }
233
+ try {
234
+ const memory = selectAgenticMemory(loadMemoryRegistry(root), task.areas, phase === "done" ? "wrap" : phase, config.context.maxChars - content.length - "\n\n## Advisory project memory\n".length);
235
+ if (memory.length) content += `\n\n## Advisory project memory\n${memory.map((entry) => `- ${entry.id} — ${entry.summary} — guidance: ${entry.guidance} — source: ${entry.sourceTaskId}/${entry.sourceLessonId}`).join("\n")}`;
236
+ } catch (error) { omittedRules.push(`memory:unavailable:${error instanceof Error ? error.message : String(error)}`); }
237
+ return { content, chars: content.length, estimatedTokens: Math.ceil(content.length / 4), omittedRules };
238
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./store.mjs";
2
+ export { compileContext, failEntry, option, parseArguments, rootOption, validateArguments } from "./context-runtime.mjs";