@felixdotgo/aidlc-kit 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEVELOPMENT.md +14 -13
- package/README.md +37 -43
- package/dist/assets/.agents/aidlc/docker/Dockerfile.claude +7 -0
- package/dist/assets/.agents/aidlc/docker/Dockerfile.codex +9 -0
- package/dist/assets/.agents/aidlc/docker/Dockerfile.opencode +8 -0
- package/dist/assets/.agents/aidlc/orchestrator.md +2 -2
- package/dist/assets/.agents/aidlc/phase-clarify.md +1 -1
- package/dist/assets/.agents/aidlc/phase-plan.md +2 -2
- package/dist/assets/.agents/aidlc/schemas/config.schema.json +2 -1
- package/dist/assets/.agents/aidlc/schemas/state.schema.json +2 -0
- package/dist/assets/.agents/aidlc/scripts/lib/assignment-store.mjs +94 -48
- package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +5 -4
- package/dist/assets/.agents/aidlc/scripts/lib/loop-authority.mjs +261 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-commit-message.mjs +45 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +206 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +100 -14
- package/dist/assets/.agents/aidlc/scripts/lib/loop-env.mjs +279 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +105 -32
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +17 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +24 -7
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +42 -16
- package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +20 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-holder.mjs +376 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +33 -7
- package/dist/assets/.agents/aidlc/scripts/lib/loop-lease.mjs +67 -61
- package/dist/assets/.agents/aidlc/scripts/lib/loop-leases.mjs +268 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +116 -28
- package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +114 -12
- package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +117 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-local.mjs +16 -56
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +149 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor.mjs +9 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +44 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-image.mjs +64 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-log.mjs +158 -0
- package/dist/assets/.agents/aidlc/scripts/lib/repo-head.mjs +22 -0
- package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +68 -2
- package/dist/assets/.agents/aidlc/scripts/lib/task-runtime-migrate.mjs +75 -0
- package/dist/assets/.agents/aidlc/scripts/lib/task-runtime.mjs +97 -0
- package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +168 -73
- package/dist/assets/.agents/aidlc/scripts/loop-gate.mjs +19 -7
- package/dist/assets/.agents/aidlc/scripts/loop.cmd +101 -0
- package/dist/assets/.agents/aidlc/scripts/loop.mjs +2 -1
- package/dist/assets/.agents/aidlc/scripts/loop.sh +71 -0
- package/dist/assets/.agents/aidlc/scripts/remote-state.mjs +10 -4
- package/dist/assets/.agents/aidlc/scripts/state.mjs +16 -8
- package/dist/assets/.agents/aidlc/templates/design.md +9 -0
- package/dist/assets/.agents/aidlc/templates/intent.md +4 -0
- package/dist/assets/.agents/aidlc/templates/workplan.md +1 -1
- package/dist/assets/.agents/skills/aidlc-plan/SKILL.md +1 -1
- package/dist/assets/ui/bulma.min.css +3 -0
- package/dist/assets/ui/changes.js +83 -0
- package/dist/assets/ui/common.js +664 -0
- package/dist/assets/ui/dashboard.css +636 -0
- package/dist/assets/ui/dashboard.js +77 -0
- package/dist/assets/ui/detail.js +159 -0
- package/dist/assets/ui/live-log.js +332 -0
- package/dist/services/aidlc-coordination/Dockerfile +11 -3
- package/dist/services/aidlc-coordination/compose.yaml +12 -0
- package/dist/services/aidlc-coordination/package-lock.json +3 -1
- package/dist/services/aidlc-coordination/package.json +9 -4
- package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +68 -2
- package/dist/services/aidlc-coordination/src/repository.mjs +66 -0
- package/dist/services/aidlc-coordination/src/server.mjs +9 -3
- package/dist/services/aidlc-coordination/test/fleet.test.mjs +21 -0
- package/dist/services/aidlc-coordination/test/server-health-auth.test.mjs +67 -0
- package/dist/src/adapters.d.ts +0 -3
- package/dist/src/adapters.js +3 -5
- package/dist/src/cli/argv.d.ts +12 -0
- package/dist/src/cli/argv.js +68 -0
- package/dist/src/cli/commands.d.ts +8 -0
- package/dist/src/cli/commands.js +159 -0
- package/dist/src/cli/loop-launcher.d.ts +47 -0
- package/dist/src/cli/loop-launcher.js +322 -0
- package/dist/src/cli/options.d.ts +25 -0
- package/dist/src/cli/options.js +32 -0
- package/dist/src/cli/prompt.d.ts +9 -0
- package/dist/src/cli/prompt.js +66 -0
- package/dist/src/cli.js +28 -191
- package/dist/src/gate.js +3 -2
- package/dist/src/installer.d.ts +14 -7
- package/dist/src/installer.js +42 -143
- package/dist/src/legacy.js +1 -0
- package/dist/src/managed-content.d.ts +7 -0
- package/dist/src/managed-content.js +15 -4
- package/dist/src/managed-planning.d.ts +65 -0
- package/dist/src/managed-planning.js +219 -0
- package/dist/src/mcp-setup.js +3 -1
- package/dist/src/model/config-contracts.d.ts +65 -0
- package/dist/src/model/config-contracts.js +1 -0
- package/dist/src/model/install-contracts.d.ts +29 -0
- package/dist/src/model/install-contracts.js +1 -0
- package/dist/src/model/lifecycle-enums.d.ts +13 -0
- package/dist/src/model/lifecycle-enums.js +1 -0
- package/dist/src/model/loop.d.ts +20 -0
- package/dist/src/model/loop.js +1 -0
- package/dist/src/model/manifest.d.ts +16 -0
- package/dist/src/model/manifest.js +1 -0
- package/dist/src/model/records.d.ts +142 -0
- package/dist/src/model/records.js +1 -0
- package/dist/src/model.d.ts +11 -264
- package/dist/src/model.js +1 -1
- package/dist/src/profiles.d.ts +2 -0
- package/dist/src/profiles.js +53 -9
- package/dist/src/state/archive-record.d.ts +9 -0
- package/dist/src/state/archive-record.js +26 -0
- package/dist/src/state/commands.d.ts +18 -0
- package/dist/src/state/commands.js +27 -0
- package/dist/src/state/domain.d.ts +9 -0
- package/dist/src/state/domain.js +9 -0
- package/dist/src/state/guards.d.ts +7 -0
- package/dist/src/state/guards.js +17 -0
- package/dist/src/state/io.d.ts +10 -0
- package/dist/src/state/io.js +35 -0
- package/dist/src/state/items.d.ts +3 -4
- package/dist/src/state/items.js +58 -57
- package/dist/src/state/lessons.js +14 -7
- package/dist/src/state/lifecycle.js +2 -2
- package/dist/src/state/lock.js +7 -6
- package/dist/src/state/memory.js +24 -12
- package/dist/src/state/{archive.js → migrate.js} +7 -7
- package/dist/src/state/partition.d.ts +15 -0
- package/dist/src/state/partition.js +90 -0
- package/dist/src/state/paths.d.ts +11 -0
- package/dist/src/state/paths.js +12 -0
- package/dist/src/state/render.js +9 -6
- package/dist/src/state/store.js +9 -15
- package/dist/src/state/transitions.js +9 -7
- package/dist/src/state/validate.js +55 -7
- package/dist/src/state.d.ts +16 -11
- package/dist/src/state.js +16 -11
- package/dist/src/task-ui.d.ts +33 -0
- package/dist/src/task-ui.js +33 -0
- package/dist/src/ui/assets.d.ts +14 -0
- package/dist/src/ui/assets.js +45 -0
- package/dist/src/ui/changes.d.ts +83 -0
- package/dist/src/ui/changes.js +498 -0
- package/dist/src/ui/containers.d.ts +71 -0
- package/dist/src/ui/containers.js +214 -0
- package/dist/src/ui/daemon.d.ts +75 -0
- package/dist/src/ui/daemon.js +232 -0
- package/dist/src/ui/data.d.ts +42 -0
- package/dist/src/ui/data.js +89 -0
- package/dist/src/ui/docs.d.ts +13 -0
- package/dist/src/ui/docs.js +39 -0
- package/dist/src/ui/files.d.ts +3 -0
- package/dist/src/ui/files.js +20 -0
- package/dist/src/ui/http.d.ts +14 -0
- package/dist/src/ui/http.js +80 -0
- package/dist/src/ui/live.d.ts +5 -0
- package/dist/src/ui/live.js +43 -0
- package/dist/src/ui/markdown.d.ts +2 -0
- package/dist/src/ui/markdown.js +127 -0
- package/dist/src/ui/options.d.ts +48 -0
- package/dist/src/ui/options.js +81 -0
- package/dist/src/ui/security.d.ts +34 -0
- package/dist/src/ui/security.js +38 -0
- package/dist/src/ui/server.d.ts +39 -0
- package/dist/src/ui/server.js +347 -0
- package/dist/src/ui/sse.d.ts +14 -0
- package/dist/src/ui/sse.js +281 -0
- package/dist/src/ui/views/board.d.ts +7 -0
- package/dist/src/ui/views/board.js +131 -0
- package/dist/src/ui/views/changes.d.ts +19 -0
- package/dist/src/ui/views/changes.js +154 -0
- package/dist/src/ui/views/components.d.ts +56 -0
- package/dist/src/ui/views/components.js +113 -0
- package/dist/src/ui/views/docs.d.ts +15 -0
- package/dist/src/ui/views/docs.js +64 -0
- package/dist/src/ui/views/layout.d.ts +44 -0
- package/dist/src/ui/views/layout.js +233 -0
- package/dist/src/ui/views/task.d.ts +25 -0
- package/dist/src/ui/views/task.js +320 -0
- package/dist/src/upgrade-board.js +10 -10
- package/dist/src/upgrade.d.ts +0 -1
- package/dist/src/upgrade.js +24 -85
- package/dist/src/workflow.d.ts +4 -0
- package/dist/src/workflow.js +36 -4
- package/docs/README.md +41 -22
- package/docs/architecture.md +101 -37
- package/docs/command-reference.md +289 -52
- package/docs/configuration.md +70 -30
- package/docs/coordination-service.md +91 -30
- package/docs/design-decisions.md +87 -0
- package/docs/development.md +124 -49
- package/docs/loop-local-run.md +228 -0
- package/docs/loop-runbook.md +214 -0
- package/docs/operating-workflow.md +55 -42
- package/docs/testing-and-release.md +72 -49
- package/docs/troubleshooting.md +221 -0
- package/package.json +4 -1
- package/dist/src/ledger.d.ts +0 -22
- package/dist/src/ledger.js +0 -119
- package/dist/src/state/shared.d.ts +0 -29
- package/dist/src/state/shared.js +0 -71
- package/dist/src/tracker.d.ts +0 -36
- package/dist/src/tracker.js +0 -44
- /package/dist/src/state/{archive.d.ts → migrate.d.ts} +0 -0
package/dist/src/state/memory.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { idPattern, iso, text } from "./guards.js";
|
|
2
|
+
import { loadJsonOr, saveJson } from "./io.js";
|
|
3
|
+
import { memoryRegistryPathRelative } from "./paths.js";
|
|
2
4
|
import { loadState, loadTask } from "./store.js";
|
|
3
|
-
import { assertNoSymlinkPath, idPattern, iso, memoryRegistryPath, parseJsonFile, text, writeAtomic } from "./shared.js";
|
|
4
5
|
const memoryPhases = ["clarify", "plan", "build", "wrap", "*"];
|
|
5
6
|
const validateMemoryEntry = (entry) => {
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
const id = entry?.id ?? "unknown";
|
|
8
|
+
if (!entry)
|
|
9
|
+
throw new Error(`Invalid agentic memory entry: ${id}`);
|
|
10
|
+
if (!idPattern.test(entry.id) || !text(entry.summary) || !text(entry.guidance))
|
|
11
|
+
throw new Error(`Invalid agentic memory entry: ${id}`);
|
|
12
|
+
if (!Array.isArray(entry.areas) || !entry.areas.length || entry.areas.some((area) => !text(area)))
|
|
13
|
+
throw new Error(`Invalid agentic memory entry: ${id}`);
|
|
14
|
+
if (!Array.isArray(entry.phases) || !entry.phases.length || entry.phases.some((phase) => !memoryPhases.includes(phase)))
|
|
15
|
+
throw new Error(`Invalid agentic memory entry: ${id}`);
|
|
16
|
+
if (!Number.isInteger(entry.priority) || entry.priority < 0 || entry.priority > 100)
|
|
17
|
+
throw new Error(`Invalid agentic memory entry: ${id}`);
|
|
18
|
+
if (!idPattern.test(entry.sourceTaskId) || !idPattern.test(entry.sourceLessonId) || !text(entry.approvedBy) || !iso(entry.approvedAt))
|
|
19
|
+
throw new Error(`Invalid agentic memory entry: ${id}`);
|
|
8
20
|
};
|
|
9
21
|
export const emptyMemoryRegistry = () => ({ schemaVersion: 1, entries: [], retired: [] });
|
|
10
22
|
export const validateMemoryRegistry = (value) => {
|
|
@@ -30,19 +42,17 @@ export const validateMemoryRegistry = (value) => {
|
|
|
30
42
|
}
|
|
31
43
|
return registry;
|
|
32
44
|
};
|
|
33
|
-
const
|
|
34
|
-
export const loadMemoryRegistry = (root) => {
|
|
35
|
-
assertNoSymlinkPath(root, registryPathRelative);
|
|
36
|
-
return existsSync(memoryRegistryPath(root)) ? validateMemoryRegistry(parseJsonFile(memoryRegistryPath(root), "Agentic memory registry")) : emptyMemoryRegistry();
|
|
37
|
-
};
|
|
45
|
+
export const loadMemoryRegistry = (root) => loadJsonOr(root, memoryRegistryPathRelative, validateMemoryRegistry, emptyMemoryRegistry, "Agentic memory registry");
|
|
38
46
|
export const saveMemoryRegistry = (root, registry) => {
|
|
39
47
|
const normalized = validateMemoryRegistry(registry);
|
|
40
|
-
|
|
41
|
-
writeAtomic(memoryRegistryPath(root), `${JSON.stringify(normalized, null, 2)}\n`);
|
|
48
|
+
saveJson(root, memoryRegistryPathRelative, normalized);
|
|
42
49
|
};
|
|
43
50
|
export const selectAgenticMemory = (registry, areas, phase, maxChars) => {
|
|
44
51
|
const areaSet = new Set(areas.map((area) => area.toLocaleLowerCase()));
|
|
45
|
-
const matches = registry.entries
|
|
52
|
+
const matches = registry.entries
|
|
53
|
+
.filter((entry) => entry.phases.includes("*") || entry.phases.includes(phase))
|
|
54
|
+
.filter((entry) => entry.areas.includes("*") || entry.areas.some((area) => areaSet.has(area.toLocaleLowerCase())))
|
|
55
|
+
.sort((left, right) => right.priority - left.priority || left.id.localeCompare(right.id));
|
|
46
56
|
const selected = [];
|
|
47
57
|
let used = 0;
|
|
48
58
|
for (const entry of matches) {
|
|
@@ -81,6 +91,8 @@ export const retireAgenticMemory = (root, id, reason, retiredBy, retiredAt = new
|
|
|
81
91
|
if (index < 0)
|
|
82
92
|
throw new Error(`Unknown active agentic memory entry: ${id}`);
|
|
83
93
|
const [entry] = registry.entries.splice(index, 1);
|
|
94
|
+
if (!entry)
|
|
95
|
+
throw new Error(`Unknown active agentic memory entry: ${id}`);
|
|
84
96
|
registry.retired.push({ ...entry, retiredBy, retiredAt, reason });
|
|
85
97
|
registry.retired.sort((left, right) => right.retiredAt.localeCompare(left.retiredAt) || left.id.localeCompare(right.id));
|
|
86
98
|
saveMemoryRegistry(root, registry);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { buildArchiveRecord } from "./archive-record.js";
|
|
3
|
+
import { legacyG2WaitSource, terminal } from "./domain.js";
|
|
4
|
+
import { safeResolvedPath, writeAtomic } from "./io.js";
|
|
3
5
|
import { createLessonIndex, lessonStateDigest, rebuildLessonIndex } from "./lessons.js";
|
|
4
6
|
import { hasReview, hasVerification } from "./transitions.js";
|
|
5
7
|
import { loadState, saveState } from "./store.js";
|
|
6
8
|
import { validateState } from "./validate.js";
|
|
7
|
-
import { archiveRecordPath, assertNoSymlinkPath, legacyG2WaitSource, taskRecordContent, terminal, writeAtomic, archivedSummary } from "./shared.js";
|
|
8
9
|
/** Marks pre-existing ready G2 waits during migration; the marker is the only exemption from repair-bound enforcement. */
|
|
9
10
|
export const stampLegacyG2Waits = (state) => {
|
|
10
11
|
const stamped = [];
|
|
@@ -28,9 +29,9 @@ export const prepareV4Migration = (value) => {
|
|
|
28
29
|
if (!terminal(task))
|
|
29
30
|
next.tasks[task.id] = task;
|
|
30
31
|
else {
|
|
31
|
-
const content =
|
|
32
|
-
next.archive[task.id] =
|
|
33
|
-
records.push({ path:
|
|
32
|
+
const { content, summary } = buildArchiveRecord(task);
|
|
33
|
+
next.archive[task.id] = summary;
|
|
34
|
+
records.push({ path: summary.record, content });
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
for (const item of Object.values(source.archive ?? {}))
|
|
@@ -45,8 +46,7 @@ export const migrateState = (root, state = loadState(root)) => {
|
|
|
45
46
|
return { migrated: false, archived: [], stamped: [] };
|
|
46
47
|
const migration = prepareV4Migration(state);
|
|
47
48
|
for (const record of migration.records) {
|
|
48
|
-
|
|
49
|
-
const path = join(resolve(root), record.path);
|
|
49
|
+
const path = safeResolvedPath(root, record.path);
|
|
50
50
|
if (existsSync(path)) {
|
|
51
51
|
if (readFileSync(path, "utf8") !== record.content)
|
|
52
52
|
throw new Error(`Archived task record conflicts: ${record.path}`);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Diagnostic, TaskState } from "../model.js";
|
|
2
|
+
type Item = TaskState["tasks"][number];
|
|
3
|
+
export declare const pathOverlaps: (left: string, right: string) => boolean;
|
|
4
|
+
export declare const pathsOverlap: (left?: readonly string[], right?: readonly string[]) => boolean;
|
|
5
|
+
/** Pairs of open (not done/deferred) items that are not ordered by `dependsOn`. */
|
|
6
|
+
export declare const concurrentItemPairs: (task: TaskState) => Array<[Item, Item]>;
|
|
7
|
+
/** Approve-plan partition check. `strict` (enabled loop) makes findings ERRORs; otherwise WARNs. */
|
|
8
|
+
export declare const partitionDiagnostics: (task: TaskState, { strict }?: {
|
|
9
|
+
strict?: boolean;
|
|
10
|
+
}) => Diagnostic[];
|
|
11
|
+
/** Strict when `loop.mode` is local/fleet or inside a fleet-enabled coordination service; a missing config is `off`; an unreadable one fails closed. */
|
|
12
|
+
export declare const loopPartitionStrict: (root: string | undefined) => boolean;
|
|
13
|
+
/** ` — owns: a, b · after: x` suffix for a rendered workplan line. */
|
|
14
|
+
export declare const workplanScope: (item: Item) => string;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel work partition checks, mirroring `assets/aidlc/scripts/lib/store.mjs`
|
|
3
|
+
* (`pathOverlaps`, `pathsOverlap`, `concurrentItemPairs`, `partitionDiagnostics`,
|
|
4
|
+
* `loopPartitionStrict`). Owned paths overlap by path segment (`a` overlaps
|
|
5
|
+
* `a/b`, not `ab`); two open items can run concurrently unless one is a direct
|
|
6
|
+
* or transitive dependency of the other.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
+
import { join, resolve } from "node:path";
|
|
10
|
+
const ownedPathKey = (path) => String(path).replace(/\/+$/, "");
|
|
11
|
+
export const pathOverlaps = (left, right) => {
|
|
12
|
+
const a = ownedPathKey(left);
|
|
13
|
+
const b = ownedPathKey(right);
|
|
14
|
+
return a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
|
|
15
|
+
};
|
|
16
|
+
export const pathsOverlap = (left = [], right = []) => left.some((a) => right.some((b) => pathOverlaps(a, b)));
|
|
17
|
+
const itemAncestors = (task) => {
|
|
18
|
+
const byId = new Map(task.tasks.map((item) => [item.id, item]));
|
|
19
|
+
const memo = new Map();
|
|
20
|
+
const walk = (id, trail = new Set()) => {
|
|
21
|
+
const cached = memo.get(id);
|
|
22
|
+
if (cached)
|
|
23
|
+
return cached;
|
|
24
|
+
const found = new Set();
|
|
25
|
+
if (trail.has(id))
|
|
26
|
+
return found;
|
|
27
|
+
trail.add(id);
|
|
28
|
+
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
29
|
+
if (!byId.has(dep))
|
|
30
|
+
continue;
|
|
31
|
+
found.add(dep);
|
|
32
|
+
for (const deeper of walk(dep, trail))
|
|
33
|
+
found.add(deeper);
|
|
34
|
+
}
|
|
35
|
+
trail.delete(id);
|
|
36
|
+
memo.set(id, found);
|
|
37
|
+
return found;
|
|
38
|
+
};
|
|
39
|
+
return (id) => walk(id);
|
|
40
|
+
};
|
|
41
|
+
/** Pairs of open (not done/deferred) items that are not ordered by `dependsOn`. */
|
|
42
|
+
export const concurrentItemPairs = (task) => {
|
|
43
|
+
const open = task.tasks.filter((item) => item.status !== "done" && item.status !== "deferred");
|
|
44
|
+
const ancestors = itemAncestors(task);
|
|
45
|
+
const pairs = [];
|
|
46
|
+
for (let i = 0; i < open.length; i += 1)
|
|
47
|
+
for (let j = i + 1; j < open.length; j += 1) {
|
|
48
|
+
const a = open[i];
|
|
49
|
+
const b = open[j];
|
|
50
|
+
if (!ancestors(a.id).has(b.id) && !ancestors(b.id).has(a.id))
|
|
51
|
+
pairs.push([a, b]);
|
|
52
|
+
}
|
|
53
|
+
return pairs;
|
|
54
|
+
};
|
|
55
|
+
/** Approve-plan partition check. `strict` (enabled loop) makes findings ERRORs; otherwise WARNs. */
|
|
56
|
+
export const partitionDiagnostics = (task, { strict = true } = {}) => {
|
|
57
|
+
const level = strict ? "ERROR" : "WARN";
|
|
58
|
+
const diagnostics = [];
|
|
59
|
+
const unscoped = task.tasks.filter((item) => item.status !== "done" && item.status !== "deferred" && !(Array.isArray(item.ownedPaths) && item.ownedPaths.length));
|
|
60
|
+
if (unscoped.length)
|
|
61
|
+
diagnostics.push({ level, code: "PARTITION_SCOPE", message: `Build items need owned paths (--owns) for parallel workers: ${unscoped.map((item) => item.id).join(", ")}` });
|
|
62
|
+
for (const [a, b] of concurrentItemPairs(task)) {
|
|
63
|
+
const shared = (a.ownedPaths ?? []).filter((path) => (b.ownedPaths ?? []).some((other) => pathOverlaps(path, other)));
|
|
64
|
+
if (shared.length)
|
|
65
|
+
diagnostics.push({ level, code: "PARTITION_OVERLAP", message: `Items ${a.id} and ${b.id} can run concurrently but both own ${shared.join(", ")}; split the paths or add --depends-on` });
|
|
66
|
+
}
|
|
67
|
+
return diagnostics;
|
|
68
|
+
};
|
|
69
|
+
/** Strict when `loop.mode` is local/fleet or inside a fleet-enabled coordination service; a missing config is `off`; an unreadable one fails closed. */
|
|
70
|
+
export const loopPartitionStrict = (root) => {
|
|
71
|
+
if (process.env["MCP_FLEET_ENABLED"] === "1")
|
|
72
|
+
return true;
|
|
73
|
+
if (root === undefined)
|
|
74
|
+
return false;
|
|
75
|
+
const path = join(resolve(root), ".agents", "config.json");
|
|
76
|
+
if (!existsSync(path))
|
|
77
|
+
return false;
|
|
78
|
+
try {
|
|
79
|
+
const mode = JSON.parse(readFileSync(path, "utf8"))?.loop?.mode;
|
|
80
|
+
return mode === "local" || mode === "fleet";
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
/** ` — owns: a, b · after: x` suffix for a rendered workplan line. */
|
|
87
|
+
export const workplanScope = (item) => {
|
|
88
|
+
const parts = [item.ownedPaths?.length ? `owns: ${item.ownedPaths.join(", ")}` : "", item.dependsOn?.length ? `after: ${item.dependsOn.join(", ")}` : ""].filter(Boolean);
|
|
89
|
+
return parts.length ? ` — ${parts.join(" · ")}` : "";
|
|
90
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const statePathRelative = ".agents/data/state/aidlc-state.json";
|
|
2
|
+
export declare const lessonIndexPathRelative = ".agents/data/lessons/index.json";
|
|
3
|
+
export declare const memoryRegistryPathRelative = ".agents/data/memory/agentic-memory.json";
|
|
4
|
+
export declare const stateLockPathRelative = ".agents/data/state/.aidlc-state.lock";
|
|
5
|
+
export declare const stateLockGuardPathRelative = ".agents/data/state/.aidlc-state.lock.guard";
|
|
6
|
+
export declare const statePath: (root: string) => string;
|
|
7
|
+
export declare const lessonIndexPath: (root: string) => string;
|
|
8
|
+
export declare const memoryRegistryPath: (root: string) => string;
|
|
9
|
+
export declare const stateLockPath: (root: string) => string;
|
|
10
|
+
export declare const stateLockGuardPath: (root: string) => string;
|
|
11
|
+
export declare const archiveRecordPath: (id: string) => string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
export const statePathRelative = ".agents/data/state/aidlc-state.json";
|
|
3
|
+
export const lessonIndexPathRelative = ".agents/data/lessons/index.json";
|
|
4
|
+
export const memoryRegistryPathRelative = ".agents/data/memory/agentic-memory.json";
|
|
5
|
+
export const stateLockPathRelative = ".agents/data/state/.aidlc-state.lock";
|
|
6
|
+
export const stateLockGuardPathRelative = ".agents/data/state/.aidlc-state.lock.guard";
|
|
7
|
+
export const statePath = (root) => join(resolve(root), statePathRelative);
|
|
8
|
+
export const lessonIndexPath = (root) => join(resolve(root), lessonIndexPathRelative);
|
|
9
|
+
export const memoryRegistryPath = (root) => join(resolve(root), memoryRegistryPathRelative);
|
|
10
|
+
export const stateLockPath = (root) => join(resolve(root), stateLockPathRelative);
|
|
11
|
+
export const stateLockGuardPath = (root) => join(resolve(root), stateLockGuardPathRelative);
|
|
12
|
+
export const archiveRecordPath = (id) => `.agents/data/state/archive/${id}.json`;
|
package/dist/src/state/render.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tokens } from "./guards.js";
|
|
2
|
+
import { safeResolvedPath, writeAtomic } from "./io.js";
|
|
2
3
|
import { loadState } from "./store.js";
|
|
3
|
-
import {
|
|
4
|
+
import { workplanScope } from "./partition.js";
|
|
4
5
|
export const listTaskSummaries = (state, options = {}) => {
|
|
5
6
|
const active = Object.values(state.tasks).map((task) => ({ id: task.id, title: task.title, phase: task.phase, gate: task.gate, status: task.status, risk: task.risk, areas: task.areas, updatedAt: task.updatedAt, source: "active" }));
|
|
6
7
|
const archived = options.includeArchive ? Object.values(state.archive ?? {}).map((task) => ({ id: task.id, title: task.title, phase: task.phase, gate: task.gate, status: task.status, risk: task.risk, areas: task.areas, updatedAt: task.updatedAt, source: "archive" })) : [];
|
|
7
8
|
const queryTokens = tokens(options.query ?? "");
|
|
8
9
|
const statuses = new Set(options.statuses ?? []);
|
|
9
|
-
const filtered = [...active, ...archived]
|
|
10
|
+
const filtered = [...active, ...archived]
|
|
11
|
+
.filter((item) => (!statuses.size || statuses.has(item.status)) &&
|
|
12
|
+
(!queryTokens.length || queryTokens.every((token) => tokens(`${item.id} ${item.title} ${item.areas.join(" ")}`).includes(token))))
|
|
13
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt) || a.id.localeCompare(b.id));
|
|
10
14
|
const cursor = Math.max(0, options.cursor ?? 0);
|
|
11
15
|
const limit = Math.max(1, Math.min(options.limit ?? 20, 100));
|
|
12
16
|
const items = filtered.slice(cursor, cursor + limit);
|
|
@@ -15,7 +19,7 @@ export const listTaskSummaries = (state, options = {}) => {
|
|
|
15
19
|
/** Renders the markdown workplan for a task. */
|
|
16
20
|
export const renderWorkplan = (task) => {
|
|
17
21
|
const decisions = task.decisions.length ? task.decisions.map((item) => `- [${item.status === "approved" ? "x" : " "}] ${item.id} — ${item.label}${item.resolution ? ` — ${item.resolution}` : ""}`).join("\n") : "- None";
|
|
18
|
-
const tasks = task.tasks.length ? task.tasks.map((item) => `- [${item.status === "done" ? "x" : item.status === "in_progress" ? "~" : " "}] ${item.id} — ${item.label}`).join("\n") : "- None";
|
|
22
|
+
const tasks = task.tasks.length ? task.tasks.map((item) => `- [${item.status === "done" ? "x" : item.status === "in_progress" ? "~" : " "}] ${item.id} — ${item.label}${workplanScope(item)}`).join("\n") : "- None";
|
|
19
23
|
const lifecycle = [`- Status: \`${task.status}\``, task.handoff ? `- Handoff: \`${task.handoff.kind}\` — ${task.handoff.reason}` : "", task.closure ? `- Closure: ${task.closure.reason}` : "", task.predecessorTaskId ? `- Predecessor: \`${task.predecessorTaskId}\`` : "", task.successorTaskId ? `- Successor: \`${task.successorTaskId}\`` : "", task.lessonDisposition ? `- Lessons: \`${task.lessonDisposition.status}\`` : ""].filter(Boolean).join("\n");
|
|
20
24
|
return `# Workplan — ${task.title} (\`${task.id}\`)\n\n> Generated from canonical JSON state.\n\n## Lifecycle\n${lifecycle}\n\n## 🧩 Decisions (Approve plan — before implementation)\n${decisions}\n\n## 🧩 Tasks (Review implementation — work items)\n${tasks}\n`;
|
|
21
25
|
};
|
|
@@ -23,7 +27,6 @@ export const renderViews = (root, state = loadState(root), selected = Object.val
|
|
|
23
27
|
for (const task of selected) {
|
|
24
28
|
if (!task.artifacts.workplan)
|
|
25
29
|
continue;
|
|
26
|
-
|
|
27
|
-
writeAtomic(join(resolve(root), task.artifacts.workplan), renderWorkplan(task));
|
|
30
|
+
writeAtomic(safeResolvedPath(root, task.artifacts.workplan), renderWorkplan(task));
|
|
28
31
|
}
|
|
29
32
|
};
|
package/dist/src/state/store.js
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { buildArchiveRecord } from "./archive-record.js";
|
|
3
|
+
import { digest, emptyState, terminal } from "./domain.js";
|
|
4
|
+
import { loadJsonOr, parseJsonText, safeResolvedPath, saveJson, writeAtomic } from "./io.js";
|
|
5
|
+
import { statePath, statePathRelative } from "./paths.js";
|
|
3
6
|
import { validateState, validateTask } from "./validate.js";
|
|
4
|
-
|
|
5
|
-
export const loadState = (root) => {
|
|
6
|
-
const path = statePath(root);
|
|
7
|
-
assertNoSymlinkPath(root, ".agents/data/state/aidlc-state.json");
|
|
8
|
-
return existsSync(path) ? validateState(parseJsonFile(path, "AI-DLC state file", "restore it from git history or the archive records")) : emptyState();
|
|
9
|
-
};
|
|
7
|
+
export const loadState = (root) => loadJsonOr(root, statePathRelative, validateState, emptyState, "AI-DLC state file", "restore it from git history or the archive records");
|
|
10
8
|
export const loadTask = (root, id, state = loadState(root)) => {
|
|
11
9
|
if (state.tasks[id])
|
|
12
10
|
return state.tasks[id];
|
|
13
11
|
const summary = state.archive?.[id];
|
|
14
12
|
if (!summary)
|
|
15
13
|
return undefined;
|
|
16
|
-
const path =
|
|
17
|
-
assertNoSymlinkPath(root, summary.record);
|
|
14
|
+
const path = safeResolvedPath(root, summary.record);
|
|
18
15
|
if (!existsSync(path))
|
|
19
16
|
throw new Error(`Archived task record is missing: ${summary.record}`);
|
|
20
17
|
const content = readFileSync(path, "utf8");
|
|
@@ -32,10 +29,8 @@ const archiveTerminalTasks = (normalized, persisted, root) => {
|
|
|
32
29
|
for (const task of Object.values(normalized.tasks)) {
|
|
33
30
|
if (!terminal(task))
|
|
34
31
|
continue;
|
|
35
|
-
const content =
|
|
36
|
-
const
|
|
37
|
-
const path = join(resolve(root), summary.record);
|
|
38
|
-
assertNoSymlinkPath(root, summary.record);
|
|
32
|
+
const { content, summary } = buildArchiveRecord(task);
|
|
33
|
+
const path = safeResolvedPath(root, summary.record);
|
|
39
34
|
if (existsSync(path)) {
|
|
40
35
|
const current = readFileSync(path, "utf8");
|
|
41
36
|
if (digest(current) !== summary.digest)
|
|
@@ -72,8 +67,7 @@ export const saveState = (root, state) => {
|
|
|
72
67
|
const archived = normalized.schemaVersion >= 3 ? archiveTerminalTasks(normalized, persisted, root) : [];
|
|
73
68
|
if (normalized.schemaVersion >= 3)
|
|
74
69
|
validateState(normalized);
|
|
75
|
-
|
|
76
|
-
writeAtomic(statePath(root), `${JSON.stringify(normalized, null, 2)}\n`);
|
|
70
|
+
saveJson(root, statePathRelative, normalized);
|
|
77
71
|
state.tasks = normalized.tasks;
|
|
78
72
|
state.archive = normalized.archive;
|
|
79
73
|
state.schemaVersion = normalized.schemaVersion;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { phaseGate, phases, terminal } from "./domain.js";
|
|
2
2
|
export const latestApproval = (task, gate) => {
|
|
3
3
|
for (let index = task.evidence.length - 1; index >= 0; index -= 1) {
|
|
4
4
|
const item = task.evidence[index];
|
|
5
|
-
if (item.kind === "approval" && item.gate === gate)
|
|
5
|
+
if (item && item.kind === "approval" && item.gate === gate)
|
|
6
6
|
return item;
|
|
7
7
|
}
|
|
8
8
|
return undefined;
|
|
@@ -12,7 +12,7 @@ export const buildBoundary = (task) => {
|
|
|
12
12
|
let boundary;
|
|
13
13
|
for (let index = 0; index < task.evidence.length; index += 1) {
|
|
14
14
|
const item = task.evidence[index];
|
|
15
|
-
if (item.kind === "approval" && item.gate === "G1_review" && item.result === "pass")
|
|
15
|
+
if (item && item.kind === "approval" && item.gate === "G1_review" && item.result === "pass")
|
|
16
16
|
boundary = index;
|
|
17
17
|
}
|
|
18
18
|
return boundary;
|
|
@@ -23,9 +23,11 @@ const currentBuildEvidence = (task) => {
|
|
|
23
23
|
return boundary === undefined ? [] : task.evidence.slice(boundary + 1);
|
|
24
24
|
};
|
|
25
25
|
const latestInOrder = (items, predicate) => {
|
|
26
|
-
for (let index = items.length - 1; index >= 0; index -= 1)
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
27
|
+
const item = items[index];
|
|
28
|
+
if (item && predicate(item))
|
|
29
|
+
return item;
|
|
30
|
+
}
|
|
29
31
|
return undefined;
|
|
30
32
|
};
|
|
31
33
|
export const hasCurrentBuildApproval = (task, gate) => latestInOrder(currentBuildEvidence(task), (item) => item.kind === "approval" && item.gate === gate)?.result === "pass";
|
|
@@ -45,7 +47,7 @@ export const repairBounds = (task) => {
|
|
|
45
47
|
verifyFailures[area] = (verifyFailures[area] ?? 0) + 1;
|
|
46
48
|
}
|
|
47
49
|
const reviewFailures = evidence.filter((item) => item.kind === "review" && item.result === "fail").length;
|
|
48
|
-
const exhaustedAreas = Object.keys(verifyFailures).filter((area) => verifyFailures[area] >= 3).sort();
|
|
50
|
+
const exhaustedAreas = Object.keys(verifyFailures).filter((area) => (verifyFailures[area] ?? 0) >= 3).sort();
|
|
49
51
|
return { verifyFailures, reviewFailures, exhaustedAreas, verifyExhausted: exhaustedAreas.length > 0, reviewExhausted: reviewFailures >= 2 };
|
|
50
52
|
};
|
|
51
53
|
/** True only for a migration-stamped legacy G2 wait that is still fully ready; the stamp is written exclusively by schema migration. */
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
+
import { gates, handoffKinds, phaseGate, phases } from "./domain.js";
|
|
2
|
+
import { assertTaskArtifactPath, idPattern, iso, text } from "./guards.js";
|
|
1
3
|
import { readyTaskItems } from "./items.js";
|
|
2
|
-
import {
|
|
4
|
+
import { archiveRecordPath } from "./paths.js";
|
|
3
5
|
const taskStatuses = ["active", "blocked_on_user", "paused", "done", "closed", "superseded"];
|
|
6
|
+
const commitPattern = /^[0-9a-f]{7,64}$/;
|
|
7
|
+
const commitFields = ["baseCommit", "finalCommit"];
|
|
4
8
|
export const validateLesson = (taskId, lesson) => {
|
|
5
|
-
if (!lesson
|
|
9
|
+
if (!lesson)
|
|
10
|
+
throw new Error(`Invalid lesson: ${taskId}`);
|
|
11
|
+
if (!idPattern.test(lesson.id) || lesson.taskId !== taskId)
|
|
12
|
+
throw new Error(`Invalid lesson: ${taskId}`);
|
|
13
|
+
if (!Array.isArray(lesson.areas) || !lesson.areas.length || lesson.areas.some((area) => !text(area)))
|
|
14
|
+
throw new Error(`Invalid lesson: ${taskId}`);
|
|
15
|
+
if (!text(lesson.summary) || !text(lesson.prevention) || !text(lesson.example))
|
|
16
|
+
throw new Error(`Invalid lesson: ${taskId}`);
|
|
17
|
+
if (!text(lesson.promotion) || !text(lesson.source) || !iso(lesson.recordedAt))
|
|
6
18
|
throw new Error(`Invalid lesson: ${taskId}`);
|
|
7
19
|
};
|
|
8
20
|
export const validateTask = (id, task) => {
|
|
@@ -23,7 +35,13 @@ export const validateTask = (id, task) => {
|
|
|
23
35
|
if (task.tasks.some((item) => !item.id || !item.label || !["todo", "in_progress", "done", "deferred"].includes(item.status)))
|
|
24
36
|
throw new Error(`Invalid execution tasks: ${id}`);
|
|
25
37
|
readyTaskItems(task);
|
|
26
|
-
if (task.evidence.some((item) => !["approval", "spec", "test", "lint", "review", "diagnostic"].includes(item.kind) ||
|
|
38
|
+
if (task.evidence.some((item) => !["approval", "spec", "test", "lint", "review", "diagnostic"].includes(item.kind) ||
|
|
39
|
+
!["pass", "fail", "skip"].includes(item.result) ||
|
|
40
|
+
!item.source ||
|
|
41
|
+
!iso(item.recordedAt) ||
|
|
42
|
+
(item.area !== undefined && !text(item.area)) ||
|
|
43
|
+
(item.gate !== undefined && !gates.includes(item.gate)) ||
|
|
44
|
+
(item.kind === "approval" && (!item.gate || item.gate === "none"))))
|
|
27
45
|
throw new Error(`Invalid evidence: ${id}`);
|
|
28
46
|
const lessonIds = new Set();
|
|
29
47
|
for (const lesson of task.lessons ?? []) {
|
|
@@ -32,11 +50,21 @@ export const validateTask = (id, task) => {
|
|
|
32
50
|
throw new Error(`Duplicate lesson id: ${id}/${lesson.id}`);
|
|
33
51
|
lessonIds.add(lesson.id);
|
|
34
52
|
}
|
|
35
|
-
if (task.lessonDisposition && (!["captured", "none"].includes(task.lessonDisposition.status) ||
|
|
53
|
+
if (task.lessonDisposition && (!["captured", "none"].includes(task.lessonDisposition.status) ||
|
|
54
|
+
!text(task.lessonDisposition.source) ||
|
|
55
|
+
!iso(task.lessonDisposition.recordedAt) ||
|
|
56
|
+
(task.lessonDisposition.status === "captured" && !(task.lessons?.length)) ||
|
|
57
|
+
(task.lessonDisposition.status === "none" && (Boolean(task.lessons?.length) || !text(task.lessonDisposition.reason)))))
|
|
36
58
|
throw new Error(`Invalid lesson disposition: ${id}`);
|
|
37
59
|
if (task.handoff && (!handoffKinds.includes(task.handoff.kind) || !text(task.handoff.reason) || !text(task.handoff.source) || !iso(task.handoff.recordedAt) || task.status !== "paused"))
|
|
38
60
|
throw new Error(`Invalid task handoff: ${id}`);
|
|
39
|
-
if (task.legacyG2Wait !== undefined && (typeof task.legacyG2Wait !== "object" ||
|
|
61
|
+
if (task.legacyG2Wait !== undefined && (typeof task.legacyG2Wait !== "object" ||
|
|
62
|
+
Array.isArray(task.legacyG2Wait) ||
|
|
63
|
+
!iso(task.legacyG2Wait.migratedAt) ||
|
|
64
|
+
!text(task.legacyG2Wait.source) ||
|
|
65
|
+
task.phase !== "build" ||
|
|
66
|
+
task.gate !== "G2_codereview" ||
|
|
67
|
+
task.status !== "blocked_on_user"))
|
|
40
68
|
throw new Error(`Invalid legacy G2 wait marker: ${id}`);
|
|
41
69
|
if ((task.status === "closed" || task.status === "superseded") !== Boolean(task.closure))
|
|
42
70
|
throw new Error(`Invalid task closure: ${id}`);
|
|
@@ -46,12 +74,29 @@ export const validateTask = (id, task) => {
|
|
|
46
74
|
throw new Error(`Invalid task successor: ${id}`);
|
|
47
75
|
if (task.predecessorTaskId !== undefined && !text(task.predecessorTaskId))
|
|
48
76
|
throw new Error(`Invalid task predecessor: ${id}`);
|
|
77
|
+
for (const field of commitFields)
|
|
78
|
+
if (task[field] !== undefined && !(typeof task[field] === "string" && commitPattern.test(task[field])))
|
|
79
|
+
throw new Error(`Invalid task ${field}: ${id}`);
|
|
49
80
|
for (const path of Object.values(task.artifacts))
|
|
50
81
|
if (path)
|
|
51
82
|
assertTaskArtifactPath(id, path);
|
|
52
83
|
};
|
|
53
84
|
const validateArchiveSummary = (id, item) => {
|
|
54
|
-
if (!idPattern.test(id)
|
|
85
|
+
if (!idPattern.test(id))
|
|
86
|
+
throw new Error(`Invalid archived task summary: ${id}`);
|
|
87
|
+
if (!item || item.id !== id || !text(item.title))
|
|
88
|
+
throw new Error(`Invalid archived task summary: ${id}`);
|
|
89
|
+
if (!["feature", "bug", "refactor", "infra"].includes(item.type))
|
|
90
|
+
throw new Error(`Invalid archived task summary: ${id}`);
|
|
91
|
+
if (!phases.includes(item.phase) || !gates.includes(item.gate) || item.gate !== phaseGate(item.phase))
|
|
92
|
+
throw new Error(`Invalid archived task summary: ${id}`);
|
|
93
|
+
if (!["done", "closed", "superseded"].includes(item.status) || !["low", "normal", "high", "regulated"].includes(item.risk))
|
|
94
|
+
throw new Error(`Invalid archived task summary: ${id}`);
|
|
95
|
+
if (!Array.isArray(item.areas) || !item.areas.length || item.areas.some((area) => !text(area)))
|
|
96
|
+
throw new Error(`Invalid archived task summary: ${id}`);
|
|
97
|
+
if (item.record !== archiveRecordPath(id) || !/^[a-f0-9]{64}$/.test(item.digest))
|
|
98
|
+
throw new Error(`Invalid archived task summary: ${id}`);
|
|
99
|
+
if (!Number.isInteger(item.lessonCount) || item.lessonCount < 0 || !iso(item.createdAt) || !iso(item.updatedAt))
|
|
55
100
|
throw new Error(`Invalid archived task summary: ${id}`);
|
|
56
101
|
if ((item.status === "superseded") !== Boolean(item.successorTaskId) || (item.status === "closed" && item.successorTaskId) || (item.predecessorTaskId !== undefined && !text(item.predecessorTaskId)))
|
|
57
102
|
throw new Error(`Invalid archived task links: ${id}`);
|
|
@@ -93,7 +138,10 @@ export const validateState = (value) => {
|
|
|
93
138
|
if (seen.has(cursor.successorTaskId))
|
|
94
139
|
throw new Error(`Cyclic successor link: ${id}`);
|
|
95
140
|
seen.add(cursor.successorTaskId);
|
|
96
|
-
|
|
141
|
+
const next = references[cursor.successorTaskId];
|
|
142
|
+
if (!next)
|
|
143
|
+
throw new Error(`Invalid successor link: ${id}`);
|
|
144
|
+
cursor = next;
|
|
97
145
|
}
|
|
98
146
|
}
|
|
99
147
|
return state;
|
package/dist/src/state.d.ts
CHANGED
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
* Facade for the lifecycle-state domain. Behavior-preserving re-export of the
|
|
3
3
|
* split modules under `src/state/`; import paths from outside remain unchanged.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
5
|
+
export { stampLegacyG2Waits, prepareV4Migration, migrateState } from "./state/migrate.js";
|
|
6
|
+
export { taskRecordContent, archivedSummary } from "./state/archive-record.js";
|
|
7
|
+
export { emptyState, legacyG2WaitSource, phases, gates, handoffKinds, terminal, phaseGate, digest } from "./state/domain.js";
|
|
8
|
+
export { iso, text, idPattern, audited, tokens, assertTaskArtifactPath } from "./state/guards.js";
|
|
9
|
+
export { assertNoSymlinkPath, writeAtomic, parseJsonText, parseJsonFile } from "./state/io.js";
|
|
10
|
+
export { type NextAction, readyTaskItems, nextAction } from "./state/items.js";
|
|
11
|
+
export { recordLesson, recordNoLessons, lessonStateDigest, createLessonIndex, rebuildLessonIndex, searchLessons } from "./state/lessons.js";
|
|
12
|
+
export { handoffTask, closeTask, supersedeTask, reopenTask } from "./state/lifecycle.js";
|
|
13
|
+
export { type StateLockOptions, acquireStateLock } from "./state/lock.js";
|
|
14
|
+
export { emptyMemoryRegistry, validateMemoryRegistry, loadMemoryRegistry, saveMemoryRegistry, selectAgenticMemory, promoteAgenticMemory, retireAgenticMemory } from "./state/memory.js";
|
|
15
|
+
export { statePath, lessonIndexPath, memoryRegistryPath, stateLockPath, stateLockGuardPath, archiveRecordPath } from "./state/paths.js";
|
|
16
|
+
export { type TaskSummary, listTaskSummaries, renderWorkplan, renderViews } from "./state/render.js";
|
|
17
|
+
export { pathOverlaps, pathsOverlap, concurrentItemPairs, partitionDiagnostics, loopPartitionStrict, workplanScope } from "./state/partition.js";
|
|
18
|
+
export { loadState, loadTask, saveState } from "./state/store.js";
|
|
19
|
+
export { latestApproval, buildBoundary, hasApproval, hasCurrentBuildApproval, hasAreaVerification, hasVerification, hasReview, type RepairBounds, repairBounds, isLegacyG2Wait, transitionDiagnostics, transitionTask } from "./state/transitions.js";
|
|
20
|
+
export { validateLesson, validateTask, validateState } from "./state/validate.js";
|
package/dist/src/state.js
CHANGED
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
* Facade for the lifecycle-state domain. Behavior-preserving re-export of the
|
|
3
3
|
* split modules under `src/state/`; import paths from outside remain unchanged.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
5
|
+
export { stampLegacyG2Waits, prepareV4Migration, migrateState } from "./state/migrate.js";
|
|
6
|
+
export { taskRecordContent, archivedSummary } from "./state/archive-record.js";
|
|
7
|
+
export { emptyState, legacyG2WaitSource, phases, gates, handoffKinds, terminal, phaseGate, digest } from "./state/domain.js";
|
|
8
|
+
export { iso, text, idPattern, audited, tokens, assertTaskArtifactPath } from "./state/guards.js";
|
|
9
|
+
export { assertNoSymlinkPath, writeAtomic, parseJsonText, parseJsonFile } from "./state/io.js";
|
|
10
|
+
export { readyTaskItems, nextAction } from "./state/items.js";
|
|
11
|
+
export { recordLesson, recordNoLessons, lessonStateDigest, createLessonIndex, rebuildLessonIndex, searchLessons } from "./state/lessons.js";
|
|
12
|
+
export { handoffTask, closeTask, supersedeTask, reopenTask } from "./state/lifecycle.js";
|
|
13
|
+
export { acquireStateLock } from "./state/lock.js";
|
|
14
|
+
export { emptyMemoryRegistry, validateMemoryRegistry, loadMemoryRegistry, saveMemoryRegistry, selectAgenticMemory, promoteAgenticMemory, retireAgenticMemory } from "./state/memory.js";
|
|
15
|
+
export { statePath, lessonIndexPath, memoryRegistryPath, stateLockPath, stateLockGuardPath, archiveRecordPath } from "./state/paths.js";
|
|
16
|
+
export { listTaskSummaries, renderWorkplan, renderViews } from "./state/render.js";
|
|
17
|
+
export { pathOverlaps, pathsOverlap, concurrentItemPairs, partitionDiagnostics, loopPartitionStrict, workplanScope } from "./state/partition.js";
|
|
18
|
+
export { loadState, loadTask, saveState } from "./state/store.js";
|
|
19
|
+
export { latestApproval, buildBoundary, hasApproval, hasCurrentBuildApproval, hasAreaVerification, hasVerification, hasReview, repairBounds, isLegacyG2Wait, transitionDiagnostics, transitionTask } from "./state/transitions.js";
|
|
20
|
+
export { validateLesson, validateTask, validateState } from "./state/validate.js";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local web UI for the AI-DLC loop and task status.
|
|
3
|
+
*
|
|
4
|
+
* Read-only over canonical local state (`.agents/data/state/aidlc-state.json`).
|
|
5
|
+
* Loop start/resume are thin facades that spawn the installed `loop-entry.mjs`
|
|
6
|
+
* exactly as `aidlc-kit loop` does; gate approval is a thin facade that spawns
|
|
7
|
+
* the same atomic `state.mjs gate approve` command, and gate feedback spawns the
|
|
8
|
+
* audited `state.mjs task status … --status active --mode audited` cancel (the
|
|
9
|
+
* task stays at its gate; the loop lead revises and re-presents it). This module
|
|
10
|
+
* never mutates lifecycle state itself and never bypasses the fail-closed gate
|
|
11
|
+
* channel.
|
|
12
|
+
*
|
|
13
|
+
* Built on `node:http` only; Bulma CSS, the dashboard theme layer, and the
|
|
14
|
+
* client scripts are vendored static assets (no CDN, no runtime npm dependency).
|
|
15
|
+
* The dashboard has no session credential (it runs on trusted local/LAN
|
|
16
|
+
* networks); mutating actions (loop start/resume, gate feedback, gate approve)
|
|
17
|
+
* are protected by request-shape guards instead: a pinned Host header, a
|
|
18
|
+
* matching Origin, Sec-Fetch-Site, and a custom request header that forces a
|
|
19
|
+
* CORS preflight (see `./ui/security.ts`). Fleet gate approval additionally
|
|
20
|
+
* needs the reviewer credential (`AIDLC_FLEET_REVIEWER_TOKEN` or
|
|
21
|
+
* `--reviewer-token-file`), which reaches only the approve child process.
|
|
22
|
+
*
|
|
23
|
+
* The implementation lives in `./ui/` (see `./ui/server.ts` for the route
|
|
24
|
+
* table); this module is the stable public entry point.
|
|
25
|
+
*/
|
|
26
|
+
export { openBrowser, readCredentialFile, resolveUiMode, scrubbedEnv, type UiAuthorityMode, type UiOptions } from "./ui/options.js";
|
|
27
|
+
export { requestAllowed } from "./ui/security.js";
|
|
28
|
+
export { daemonHealth, daemonStatus, stopDaemon, type DaemonError, type DaemonStatus, type StopResult } from "./ui/daemon.js";
|
|
29
|
+
export { markdownToHtml } from "./ui/markdown.js";
|
|
30
|
+
export { NOTIFY_PAGE } from "./ui/views/layout.js";
|
|
31
|
+
export { DONE_COLUMN_LIMIT } from "./ui/views/board.js";
|
|
32
|
+
export { SSE_REPLAY_LINES } from "./ui/sse.js";
|
|
33
|
+
export { createUiHandler, startUiServer } from "./ui/server.js";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local web UI for the AI-DLC loop and task status.
|
|
3
|
+
*
|
|
4
|
+
* Read-only over canonical local state (`.agents/data/state/aidlc-state.json`).
|
|
5
|
+
* Loop start/resume are thin facades that spawn the installed `loop-entry.mjs`
|
|
6
|
+
* exactly as `aidlc-kit loop` does; gate approval is a thin facade that spawns
|
|
7
|
+
* the same atomic `state.mjs gate approve` command, and gate feedback spawns the
|
|
8
|
+
* audited `state.mjs task status … --status active --mode audited` cancel (the
|
|
9
|
+
* task stays at its gate; the loop lead revises and re-presents it). This module
|
|
10
|
+
* never mutates lifecycle state itself and never bypasses the fail-closed gate
|
|
11
|
+
* channel.
|
|
12
|
+
*
|
|
13
|
+
* Built on `node:http` only; Bulma CSS, the dashboard theme layer, and the
|
|
14
|
+
* client scripts are vendored static assets (no CDN, no runtime npm dependency).
|
|
15
|
+
* The dashboard has no session credential (it runs on trusted local/LAN
|
|
16
|
+
* networks); mutating actions (loop start/resume, gate feedback, gate approve)
|
|
17
|
+
* are protected by request-shape guards instead: a pinned Host header, a
|
|
18
|
+
* matching Origin, Sec-Fetch-Site, and a custom request header that forces a
|
|
19
|
+
* CORS preflight (see `./ui/security.ts`). Fleet gate approval additionally
|
|
20
|
+
* needs the reviewer credential (`AIDLC_FLEET_REVIEWER_TOKEN` or
|
|
21
|
+
* `--reviewer-token-file`), which reaches only the approve child process.
|
|
22
|
+
*
|
|
23
|
+
* The implementation lives in `./ui/` (see `./ui/server.ts` for the route
|
|
24
|
+
* table); this module is the stable public entry point.
|
|
25
|
+
*/
|
|
26
|
+
export { openBrowser, readCredentialFile, resolveUiMode, scrubbedEnv } from "./ui/options.js";
|
|
27
|
+
export { requestAllowed } from "./ui/security.js";
|
|
28
|
+
export { daemonHealth, daemonStatus, stopDaemon } from "./ui/daemon.js";
|
|
29
|
+
export { markdownToHtml } from "./ui/markdown.js";
|
|
30
|
+
export { NOTIFY_PAGE } from "./ui/views/layout.js";
|
|
31
|
+
export { DONE_COLUMN_LIMIT } from "./ui/views/board.js";
|
|
32
|
+
export { SSE_REPLAY_LINES } from "./ui/sse.js";
|
|
33
|
+
export { createUiHandler, startUiServer } from "./ui/server.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Client scripts, loaded as classic scripts in this order: `common` first, page scripts after. */
|
|
2
|
+
export type ClientScript = "common" | "dashboard" | "detail" | "live-log" | "changes";
|
|
3
|
+
/** The whitelisted asset for a request path, or undefined (the caller answers 404). */
|
|
4
|
+
export declare const staticAsset: (route: string) => {
|
|
5
|
+
contentType: string;
|
|
6
|
+
body: string;
|
|
7
|
+
} | undefined;
|
|
8
|
+
/** Stylesheets served from `/assets`, loaded in this order. */
|
|
9
|
+
export type Stylesheet = "bulma" | "dashboard";
|
|
10
|
+
/** `<script src>` for a client script; `?v=<content hash>` makes an upgrade bust the browser cache. */
|
|
11
|
+
export declare const scriptTag: (name: ClientScript) => string;
|
|
12
|
+
/** `<link rel="stylesheet">` for a stylesheet, versioned like `scriptTag` so an upgrade is not
|
|
13
|
+
* stuck behind the cached theme while the hashed scripts already changed. */
|
|
14
|
+
export declare const stylesheetTag: (name: Stylesheet) => string;
|