@felixdotgo/aidlc-kit 0.1.0 → 0.2.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 -0
- package/README.md +14 -6
- 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 +1 -1
- 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/manifest.schema.json +1 -1
- package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +5 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-authority.mjs +245 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-commit-message.mjs +45 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +232 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +21 -4
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +86 -28
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +7 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +22 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +46 -15
- package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +22 -9
- package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +23 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +43 -15
- package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +82 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +60 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +142 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor.mjs +9 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +31 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-image.mjs +64 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-snapshot.mjs +3 -2
- package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +59 -4
- package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +85 -73
- package/dist/assets/.agents/aidlc/scripts/loop-gate.mjs +19 -7
- package/dist/assets/.agents/aidlc/scripts/loop.mjs +2 -1
- package/dist/assets/.agents/aidlc/scripts/remote-state.mjs +2 -0
- package/dist/assets/.agents/aidlc/scripts/state.mjs +4 -0
- 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/dashboard.css +414 -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 +59 -4
- 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 +11 -6
- package/dist/src/cli/argv.d.ts +12 -0
- package/dist/src/cli/argv.js +62 -0
- package/dist/src/cli/commands.d.ts +8 -0
- package/dist/src/cli/commands.js +129 -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 +67 -217
- package/dist/src/context.js +2 -0
- package/dist/src/gate.d.ts +1 -0
- package/dist/src/gate.js +51 -37
- package/dist/src/installer.d.ts +19 -7
- package/dist/src/installer.js +111 -178
- 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 +138 -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 +8 -0
- package/dist/src/profiles.js +63 -8
- package/dist/src/project-migration.d.ts +1 -0
- package/dist/src/project-migration.js +7 -0
- 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 +24 -0
- package/dist/src/state/items.js +147 -0
- package/dist/src/state/lessons.d.ts +7 -0
- package/dist/src/state/lessons.js +74 -0
- package/dist/src/state/lifecycle.d.ts +5 -0
- package/dist/src/state/lifecycle.js +78 -0
- package/dist/src/state/lock.d.ts +7 -0
- package/dist/src/state/lock.js +125 -0
- package/dist/src/state/memory.d.ts +8 -0
- package/dist/src/state/memory.js +100 -0
- package/dist/src/state/migrate.d.ts +17 -0
- package/dist/src/state/migrate.js +60 -0
- 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.d.ts +26 -0
- package/dist/src/state/render.js +32 -0
- package/dist/src/state/store.d.ts +4 -0
- package/dist/src/state/store.js +75 -0
- package/dist/src/state/transitions.d.ts +21 -0
- package/dist/src/state/transitions.js +95 -0
- package/dist/src/state/validate.d.ts +4 -0
- package/dist/src/state/validate.js +143 -0
- package/dist/src/state.d.ts +20 -117
- package/dist/src/state.js +20 -815
- package/dist/src/task-ui.d.ts +54 -0
- package/dist/src/task-ui.js +1570 -0
- package/dist/src/upgrade-board.d.ts +5 -0
- package/dist/src/upgrade-board.js +89 -0
- package/dist/src/upgrade.d.ts +3 -5
- package/dist/src/upgrade.js +174 -255
- package/dist/src/workflow.js +4 -4
- package/docs/README.md +6 -2
- package/docs/architecture.md +12 -8
- package/docs/command-reference.md +83 -19
- package/docs/configuration.md +30 -7
- package/docs/coordination-service.md +20 -5
- package/docs/design-decisions.md +83 -0
- package/docs/development.md +41 -2
- package/docs/loop-local-run.md +495 -0
- package/docs/loop-runbook.md +235 -0
- package/docs/operating-workflow.md +5 -2
- package/docs/testing-and-release.md +11 -7
- package/docs/troubleshooting.md +125 -0
- package/package.json +5 -2
- package/dist/src/ledger.d.ts +0 -22
- package/dist/src/ledger.js +0 -119
- package/dist/src/tracker.d.ts +0 -29
- package/dist/src/tracker.js +0 -41
package/dist/src/model.d.ts
CHANGED
|
@@ -1,264 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type
|
|
6
|
-
export type
|
|
7
|
-
export type
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
owner: string;
|
|
13
|
-
strategy?: "replace" | "managed-block";
|
|
14
|
-
ownershipClass?: "managed" | "project" | "state";
|
|
15
|
-
}
|
|
16
|
-
export interface Adapter {
|
|
17
|
-
id: AgentId;
|
|
18
|
-
displayName: string;
|
|
19
|
-
detect(root: string): boolean;
|
|
20
|
-
files(): FileSpec[];
|
|
21
|
-
}
|
|
22
|
-
export interface PlannedWrite extends FileSpec {
|
|
23
|
-
action: "create" | "update" | "delete" | "skip" | "conflict" | "migrate" | "preserve";
|
|
24
|
-
reason: string;
|
|
25
|
-
contentEncoding?: "base64";
|
|
26
|
-
}
|
|
27
|
-
export interface InitOptions {
|
|
28
|
-
root: string;
|
|
29
|
-
agents: AgentId[];
|
|
30
|
-
all: boolean;
|
|
31
|
-
dryRun: boolean;
|
|
32
|
-
yes: boolean;
|
|
33
|
-
force: boolean;
|
|
34
|
-
loop?: LoopConfig;
|
|
35
|
-
}
|
|
36
|
-
export type LoopMode = "off" | "local" | "fleet";
|
|
37
|
-
export declare const LOOP_PROVIDERS: readonly ["codex", "claude", "opencode"];
|
|
38
|
-
export type LoopAgent = (typeof LOOP_PROVIDERS)[number];
|
|
39
|
-
export interface LoopConfig {
|
|
40
|
-
mode: LoopMode;
|
|
41
|
-
agents?: {
|
|
42
|
-
lead: LoopAgent;
|
|
43
|
-
workers: Record<string, LoopAgent>;
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
export interface CommandSpec {
|
|
47
|
-
command: string;
|
|
48
|
-
args: string[];
|
|
49
|
-
}
|
|
50
|
-
export interface Profile {
|
|
51
|
-
schemaVersion: 1;
|
|
52
|
-
id: string;
|
|
53
|
-
extends?: string[];
|
|
54
|
-
topology: string;
|
|
55
|
-
discovery?: {
|
|
56
|
-
roots?: string[];
|
|
57
|
-
workspaceMarkers?: string[];
|
|
58
|
-
};
|
|
59
|
-
specs?: {
|
|
60
|
-
roots?: string[];
|
|
61
|
-
};
|
|
62
|
-
commands?: Record<string, CommandSpec>;
|
|
63
|
-
rules?: {
|
|
64
|
-
include?: string[];
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
export interface ProjectConfig {
|
|
68
|
-
schemaVersion: 1 | 2 | 3;
|
|
69
|
-
extends: string[];
|
|
70
|
-
specs: {
|
|
71
|
-
roots: string[];
|
|
72
|
-
};
|
|
73
|
-
commands: Record<string, CommandSpec>;
|
|
74
|
-
rules: {
|
|
75
|
-
include: string[];
|
|
76
|
-
};
|
|
77
|
-
risk: {
|
|
78
|
-
default: RiskLevel;
|
|
79
|
-
};
|
|
80
|
-
context: {
|
|
81
|
-
maxChars: number;
|
|
82
|
-
};
|
|
83
|
-
agentState: Partial<Record<AgentId, StateMutationMode>>;
|
|
84
|
-
tracker?: TrackerConfig;
|
|
85
|
-
mcp: McpConfig;
|
|
86
|
-
loop?: LoopConfig;
|
|
87
|
-
}
|
|
88
|
-
export interface McpConfig {
|
|
89
|
-
enabled: boolean;
|
|
90
|
-
endpoint?: string;
|
|
91
|
-
workspace?: string;
|
|
92
|
-
tokenEnv?: string;
|
|
93
|
-
pollMs?: number;
|
|
94
|
-
providers?: Array<"jira" | "trello" | "github-issues">;
|
|
95
|
-
}
|
|
96
|
-
export interface TrackerConfig {
|
|
97
|
-
enabled: boolean;
|
|
98
|
-
provider: "backlog";
|
|
99
|
-
spaceUrl: string;
|
|
100
|
-
project: string;
|
|
101
|
-
tokenEnv: string;
|
|
102
|
-
workflow: "waterfall" | "scrum";
|
|
103
|
-
mapping: {
|
|
104
|
-
statuses?: Partial<Record<"clarify" | "plan" | "build" | "wrap" | "done", number>>;
|
|
105
|
-
gateFieldId?: number;
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
export interface TaskDecision {
|
|
109
|
-
id: string;
|
|
110
|
-
label: string;
|
|
111
|
-
status: DecisionStatus;
|
|
112
|
-
resolution?: string;
|
|
113
|
-
}
|
|
114
|
-
export interface Evidence {
|
|
115
|
-
kind: "approval" | "spec" | "test" | "lint" | "review" | "diagnostic";
|
|
116
|
-
gate?: Gate;
|
|
117
|
-
area?: string;
|
|
118
|
-
source: string;
|
|
119
|
-
result: "pass" | "fail" | "skip";
|
|
120
|
-
detail?: string;
|
|
121
|
-
recordedAt: string;
|
|
122
|
-
}
|
|
123
|
-
export interface LessonRecord {
|
|
124
|
-
id: string;
|
|
125
|
-
taskId: string;
|
|
126
|
-
areas: string[];
|
|
127
|
-
summary: string;
|
|
128
|
-
prevention: string;
|
|
129
|
-
example: string;
|
|
130
|
-
promotion: string;
|
|
131
|
-
source: string;
|
|
132
|
-
recordedAt: string;
|
|
133
|
-
}
|
|
134
|
-
export interface LessonDisposition {
|
|
135
|
-
status: "captured" | "none";
|
|
136
|
-
reason?: string;
|
|
137
|
-
source: string;
|
|
138
|
-
recordedAt: string;
|
|
139
|
-
}
|
|
140
|
-
export interface TaskState {
|
|
141
|
-
id: string;
|
|
142
|
-
featureId?: string;
|
|
143
|
-
title: string;
|
|
144
|
-
type: "feature" | "bug" | "refactor" | "infra";
|
|
145
|
-
phase: Phase;
|
|
146
|
-
gate: Gate;
|
|
147
|
-
status: TaskStatus;
|
|
148
|
-
language: "vi" | "en";
|
|
149
|
-
risk: RiskLevel;
|
|
150
|
-
areas: string[];
|
|
151
|
-
branch: string;
|
|
152
|
-
artifacts: {
|
|
153
|
-
intent?: string;
|
|
154
|
-
design?: string;
|
|
155
|
-
workplan?: string;
|
|
156
|
-
};
|
|
157
|
-
decisions: TaskDecision[];
|
|
158
|
-
tasks: Array<{
|
|
159
|
-
id: string;
|
|
160
|
-
label: string;
|
|
161
|
-
status: "todo" | "in_progress" | "done" | "deferred";
|
|
162
|
-
dependsOn?: string[];
|
|
163
|
-
ownedPaths?: string[];
|
|
164
|
-
}>;
|
|
165
|
-
evidence: Evidence[];
|
|
166
|
-
lessons?: LessonRecord[];
|
|
167
|
-
lessonDisposition?: LessonDisposition;
|
|
168
|
-
handoff?: {
|
|
169
|
-
kind: HandoffKind;
|
|
170
|
-
reason: string;
|
|
171
|
-
source: string;
|
|
172
|
-
recordedAt: string;
|
|
173
|
-
};
|
|
174
|
-
closure?: {
|
|
175
|
-
reason: string;
|
|
176
|
-
source: string;
|
|
177
|
-
recordedAt: string;
|
|
178
|
-
};
|
|
179
|
-
legacyG2Wait?: {
|
|
180
|
-
migratedAt: string;
|
|
181
|
-
source: string;
|
|
182
|
-
};
|
|
183
|
-
predecessorTaskId?: string;
|
|
184
|
-
successorTaskId?: string;
|
|
185
|
-
createdAt: string;
|
|
186
|
-
updatedAt: string;
|
|
187
|
-
}
|
|
188
|
-
export interface WorkflowState {
|
|
189
|
-
schemaVersion: 1 | 2 | 3 | 4;
|
|
190
|
-
tasks: Record<string, TaskState>;
|
|
191
|
-
archive?: Record<string, ArchivedTaskSummary>;
|
|
192
|
-
}
|
|
193
|
-
export interface ArchivedTaskSummary {
|
|
194
|
-
id: string;
|
|
195
|
-
title: string;
|
|
196
|
-
type: TaskState["type"];
|
|
197
|
-
phase: Phase;
|
|
198
|
-
gate: Gate;
|
|
199
|
-
status: "done" | "closed" | "superseded";
|
|
200
|
-
risk: RiskLevel;
|
|
201
|
-
areas: string[];
|
|
202
|
-
record: string;
|
|
203
|
-
digest: string;
|
|
204
|
-
lessonCount: number;
|
|
205
|
-
predecessorTaskId?: string;
|
|
206
|
-
successorTaskId?: string;
|
|
207
|
-
createdAt: string;
|
|
208
|
-
updatedAt: string;
|
|
209
|
-
}
|
|
210
|
-
export interface LessonIndexEntry {
|
|
211
|
-
id: string;
|
|
212
|
-
taskId: string;
|
|
213
|
-
areas: string[];
|
|
214
|
-
summary: string;
|
|
215
|
-
prevention: string;
|
|
216
|
-
source: string;
|
|
217
|
-
recordedAt: string;
|
|
218
|
-
}
|
|
219
|
-
export interface LessonIndex {
|
|
220
|
-
schemaVersion: 1;
|
|
221
|
-
sourceDigest: string;
|
|
222
|
-
lessons: LessonIndexEntry[];
|
|
223
|
-
}
|
|
224
|
-
export interface AgenticMemoryEntry {
|
|
225
|
-
id: string;
|
|
226
|
-
summary: string;
|
|
227
|
-
guidance: string;
|
|
228
|
-
areas: string[];
|
|
229
|
-
phases: Array<Exclude<Phase, "done"> | "*">;
|
|
230
|
-
priority: number;
|
|
231
|
-
sourceTaskId: string;
|
|
232
|
-
sourceLessonId: string;
|
|
233
|
-
approvedBy: string;
|
|
234
|
-
approvedAt: string;
|
|
235
|
-
}
|
|
236
|
-
export interface AgenticMemoryRegistry {
|
|
237
|
-
schemaVersion: 1;
|
|
238
|
-
entries: AgenticMemoryEntry[];
|
|
239
|
-
retired: Array<AgenticMemoryEntry & {
|
|
240
|
-
retiredBy: string;
|
|
241
|
-
retiredAt: string;
|
|
242
|
-
reason: string;
|
|
243
|
-
}>;
|
|
244
|
-
}
|
|
245
|
-
export interface ManifestInventoryItem {
|
|
246
|
-
hash: string;
|
|
247
|
-
owner: string;
|
|
248
|
-
strategy: "replace" | "managed-block";
|
|
249
|
-
}
|
|
250
|
-
export interface WorkflowManifest {
|
|
251
|
-
schemaVersion: 2;
|
|
252
|
-
packageVersion: string;
|
|
253
|
-
workflow: "AI-DLC";
|
|
254
|
-
source: "local-package-assets";
|
|
255
|
-
remoteUpdates: false;
|
|
256
|
-
managedBy: "aidlc-workflow";
|
|
257
|
-
adapters: AgentId[];
|
|
258
|
-
files: Record<string, ManifestInventoryItem>;
|
|
259
|
-
}
|
|
260
|
-
export interface Diagnostic {
|
|
261
|
-
level: "ERROR" | "WARN" | "INFO";
|
|
262
|
-
code: string;
|
|
263
|
-
message: string;
|
|
264
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Facade for the shared type hub. Behavior-preserving re-export of the split
|
|
3
|
+
* modules under `src/model/`; import paths from outside remain unchanged.
|
|
4
|
+
*/
|
|
5
|
+
export type { AgentId, StateMutationMode, Phase, Gate, TaskStatus, RiskLevel, DecisionStatus, HandoffKind, Diagnostic } from "./model/lifecycle-enums.js";
|
|
6
|
+
export type { FileSpec, Adapter, PlannedWrite, InitOptions } from "./model/install-contracts.js";
|
|
7
|
+
export type { LoopMode, LoopAgent, LoopConfig, LoopLimits } from "./model/loop.js";
|
|
8
|
+
export { LOOP_PROVIDERS } from "./model/loop.js";
|
|
9
|
+
export type { CommandSpec, Profile, ProjectConfig, McpConfig, TrackerConfig } from "./model/config-contracts.js";
|
|
10
|
+
export type { TaskDecision, Evidence, LessonRecord, LessonDisposition, TaskState, WorkflowState, ArchivedTaskSummary, LessonIndexEntry, LessonIndex, AgenticMemoryEntry, AgenticMemoryRegistry } from "./model/records.js";
|
|
11
|
+
export type { ManifestInventoryItem, WorkflowManifest } from "./model/manifest.js";
|
package/dist/src/model.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { LOOP_PROVIDERS } from "./model/loop.js";
|
package/dist/src/profiles.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { LoopConfig, McpConfig, Profile, ProjectConfig, RiskLevel } from "./model.js";
|
|
2
2
|
export declare const builtInProfiles: Readonly<Record<string, Profile>>;
|
|
3
|
+
/** Validates and normalizes a loop-execution configuration block. */
|
|
3
4
|
export declare const validateLoopConfig: (value: unknown) => LoopConfig;
|
|
5
|
+
/** Validates a single profile file (built-in or local). */
|
|
4
6
|
export declare const validateProfile: (value: unknown, label?: string) => Profile;
|
|
5
7
|
export declare const defaultConfig: () => ProjectConfig;
|
|
8
|
+
/** Validates and normalizes a parsed `.agents/config.json` value. */
|
|
9
|
+
export declare const validateProjectConfig: (value: unknown) => ProjectConfig;
|
|
10
|
+
/** Loads and validates `.agents/config.json`, falling back to the default config when absent. */
|
|
6
11
|
export declare const loadProjectConfig: (root: string) => ProjectConfig;
|
|
12
|
+
/** Resolves a list of profile ids into an ordered, cycle-free list of profiles. */
|
|
7
13
|
export declare const resolveProfiles: (root: string, ids: string[]) => Profile[];
|
|
14
|
+
/** Merges built-in and local profiles with the project config into the effective configuration. */
|
|
8
15
|
export declare const resolveEffectiveConfig: (root: string, config: ProjectConfig) => {
|
|
9
16
|
profiles: Profile[];
|
|
10
17
|
discovery: {
|
|
@@ -27,4 +34,5 @@ export declare const resolveEffectiveConfig: (root: string, config: ProjectConfi
|
|
|
27
34
|
mcp: McpConfig;
|
|
28
35
|
loop: LoopConfig;
|
|
29
36
|
};
|
|
37
|
+
/** Expands rule include patterns into concrete markdown file paths inside the project. */
|
|
30
38
|
export declare const includedRuleFiles: (root: string, patterns: string[]) => string[];
|
package/dist/src/profiles.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project profiles and configuration. Loads `.agents/config.json`, validates
|
|
3
|
+
* every nested section (loop, tracker, MCP, risk, context), and resolves the
|
|
4
|
+
* effective configuration by merging built-in and local profiles.
|
|
5
|
+
*/
|
|
1
6
|
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
2
7
|
import { dirname, isAbsolute, join, normalize, resolve } from "node:path";
|
|
3
8
|
import { LOOP_PROVIDERS } from "./model.js";
|
|
@@ -23,14 +28,20 @@ const object = (value, label) => {
|
|
|
23
28
|
throw new Error(`${label} must be an object`);
|
|
24
29
|
return value;
|
|
25
30
|
};
|
|
31
|
+
/** Validates and normalizes a loop-execution configuration block. */
|
|
26
32
|
export const validateLoopConfig = (value) => {
|
|
27
33
|
if (value === undefined)
|
|
28
34
|
return { mode: "off" };
|
|
29
35
|
const item = object(value, "config.loop");
|
|
30
|
-
if (Object.keys(item).some((key) => !["mode", "agents"].includes(key)) || typeof item.mode !== "string" || !["off", "local", "fleet"].includes(item.mode))
|
|
36
|
+
if (Object.keys(item).some((key) => !["mode", "agents", "workerImage", "pollMs", "limits"].includes(key)) || typeof item.mode !== "string" || !["off", "local", "fleet"].includes(item.mode))
|
|
31
37
|
throw new Error("config.loop requires a valid mode and only supported fields");
|
|
38
|
+
const workerImage = validateWorkerImageMap(item.workerImage);
|
|
39
|
+
if (item.pollMs !== undefined && (!Number.isSafeInteger(item.pollMs) || Number(item.pollMs) < 1_000 || Number(item.pollMs) > 300_000))
|
|
40
|
+
throw new Error("config.loop.pollMs must be an integer from 1000 to 300000");
|
|
41
|
+
const limits = validateLoopLimits(item.limits);
|
|
42
|
+
const optional = { ...(workerImage ? { workerImage } : {}), ...(item.pollMs !== undefined ? { pollMs: Number(item.pollMs) } : {}), ...(limits ? { limits } : {}) };
|
|
32
43
|
if (item.agents === undefined)
|
|
33
|
-
return { mode: item.mode };
|
|
44
|
+
return { mode: item.mode, ...optional };
|
|
34
45
|
const agents = object(item.agents, "config.loop.agents");
|
|
35
46
|
if (Object.keys(agents).some((key) => !["lead", "workers"].includes(key)) || !isLoopProvider(agents.lead))
|
|
36
47
|
throw new Error(`config.loop.agents.lead must be ${loopProviderNames}`);
|
|
@@ -38,7 +49,34 @@ export const validateLoopConfig = (value) => {
|
|
|
38
49
|
for (const [id, provider] of Object.entries(workers))
|
|
39
50
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id) || !isLoopProvider(provider))
|
|
40
51
|
throw new Error(`config.loop.agents.workers must map worker IDs to ${loopProviderNames}`);
|
|
41
|
-
return { mode: item.mode, agents: { lead: agents.lead, workers: workers } };
|
|
52
|
+
return { mode: item.mode, agents: { lead: agents.lead, workers: workers }, ...optional };
|
|
53
|
+
};
|
|
54
|
+
/** Bounds mirror `validateLimits` in assets/aidlc/scripts/lib/loop-runtime.mjs. */
|
|
55
|
+
const LOOP_LIMIT_BOUNDS = { maxTurns: 1_000, turnTimeoutMs: 3_600_000, maxStallTurns: 20 };
|
|
56
|
+
const validateLoopLimits = (value) => {
|
|
57
|
+
if (value === undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
const map = object(value, "config.loop.limits");
|
|
60
|
+
const result = {};
|
|
61
|
+
for (const [key, entry] of Object.entries(map)) {
|
|
62
|
+
if (!Object.hasOwn(LOOP_LIMIT_BOUNDS, key))
|
|
63
|
+
throw new Error(`config.loop.limits supports only ${Object.keys(LOOP_LIMIT_BOUNDS).join(", ")}`);
|
|
64
|
+
const max = LOOP_LIMIT_BOUNDS[key];
|
|
65
|
+
if (!Number.isSafeInteger(entry) || Number(entry) < 1 || Number(entry) > max)
|
|
66
|
+
throw new Error(`config.loop.limits.${key} must be an integer from 1 to ${max}`);
|
|
67
|
+
result[key] = Number(entry);
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
};
|
|
71
|
+
const validateWorkerImageMap = (value) => {
|
|
72
|
+
if (value === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
const map = object(value, "config.loop.workerImage");
|
|
75
|
+
for (const [provider, tag] of Object.entries(map)) {
|
|
76
|
+
if (!isLoopProvider(provider) || typeof tag !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._/:@-]{0,255}$/.test(tag))
|
|
77
|
+
throw new Error(`config.loop.workerImage must map ${loopProviderNames} to image tags`);
|
|
78
|
+
}
|
|
79
|
+
return map;
|
|
42
80
|
};
|
|
43
81
|
const strings = (value, label) => {
|
|
44
82
|
if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
|
|
@@ -98,6 +136,7 @@ const mcp = (value) => {
|
|
|
98
136
|
throw new Error("config.mcp.providers is invalid");
|
|
99
137
|
return { enabled: true, endpoint: item.endpoint, workspace: item.workspace, ...(item.tokenEnv === undefined ? {} : { tokenEnv: item.tokenEnv }), ...(item.pollMs === undefined ? {} : { pollMs: Number(item.pollMs) }), ...(providers === undefined ? {} : { providers: providers }) };
|
|
100
138
|
};
|
|
139
|
+
/** Validates a single profile file (built-in or local). */
|
|
101
140
|
export const validateProfile = (value, label = "profile") => {
|
|
102
141
|
const item = object(value, label);
|
|
103
142
|
if (item.schemaVersion !== 1 || typeof item.id !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/.test(item.id) || typeof item.topology !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/.test(item.topology))
|
|
@@ -120,6 +159,7 @@ export const validateProfile = (value, label = "profile") => {
|
|
|
120
159
|
};
|
|
121
160
|
};
|
|
122
161
|
export const defaultConfig = () => ({
|
|
162
|
+
$schema: "./aidlc/schemas/config.schema.json",
|
|
123
163
|
schemaVersion: 2,
|
|
124
164
|
extends: ["topology/single"],
|
|
125
165
|
specs: { roots: [] },
|
|
@@ -131,11 +171,9 @@ export const defaultConfig = () => ({
|
|
|
131
171
|
mcp: { enabled: false },
|
|
132
172
|
loop: { mode: "off" }
|
|
133
173
|
});
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return defaultConfig();
|
|
138
|
-
const item = object(JSON.parse(readFileSync(path, "utf8")), ".agents/config.json");
|
|
174
|
+
/** Validates and normalizes a parsed `.agents/config.json` value. */
|
|
175
|
+
export const validateProjectConfig = (value) => {
|
|
176
|
+
const item = object(value, ".agents/config.json");
|
|
139
177
|
const risk = object(item.risk ?? {}, "config.risk");
|
|
140
178
|
const context = object(item.context ?? {}, "config.context");
|
|
141
179
|
const levels = ["low", "normal", "high", "regulated"];
|
|
@@ -151,7 +189,14 @@ export const loadProjectConfig = (root) => {
|
|
|
151
189
|
const maxChars = Number(context.maxChars ?? 16_000);
|
|
152
190
|
if (!Number.isInteger(maxChars) || maxChars < 4_000 || maxChars > 128_000)
|
|
153
191
|
throw new Error("context.maxChars must be an integer from 4000 to 128000");
|
|
192
|
+
let schema;
|
|
193
|
+
if (item.$schema !== undefined) {
|
|
194
|
+
if (typeof item.$schema !== "string" || item.$schema.length > 512)
|
|
195
|
+
throw new Error("config.$schema must be a string of at most 512 characters");
|
|
196
|
+
schema = item.$schema;
|
|
197
|
+
}
|
|
154
198
|
return {
|
|
199
|
+
$schema: schema,
|
|
155
200
|
schemaVersion: Number(item.schemaVersion),
|
|
156
201
|
extends: strings(item.extends ?? ["topology/single"], "config.extends"),
|
|
157
202
|
specs: { roots: strings(object(item.specs ?? {}, "config.specs").roots ?? [], "config.specs.roots").map((entry) => safeRelative(entry, "config.specs.roots")) },
|
|
@@ -165,6 +210,13 @@ export const loadProjectConfig = (root) => {
|
|
|
165
210
|
loop: validateLoopConfig(item.loop)
|
|
166
211
|
};
|
|
167
212
|
};
|
|
213
|
+
/** Loads and validates `.agents/config.json`, falling back to the default config when absent. */
|
|
214
|
+
export const loadProjectConfig = (root) => {
|
|
215
|
+
const path = join(resolve(root), ".agents/config.json");
|
|
216
|
+
if (!existsSync(path))
|
|
217
|
+
return defaultConfig();
|
|
218
|
+
return validateProjectConfig(JSON.parse(readFileSync(path, "utf8")));
|
|
219
|
+
};
|
|
168
220
|
const readLocalProfile = (root, id) => {
|
|
169
221
|
const path = join(resolve(root), ".agents/project/profiles", safeRelative(id, "profile id"), "profile.json");
|
|
170
222
|
if (!existsSync(path))
|
|
@@ -173,6 +225,7 @@ const readLocalProfile = (root, id) => {
|
|
|
173
225
|
throw new Error(`Profile escapes the project: ${id}`);
|
|
174
226
|
return validateProfile(JSON.parse(readFileSync(path, "utf8")), id);
|
|
175
227
|
};
|
|
228
|
+
/** Resolves a list of profile ids into an ordered, cycle-free list of profiles. */
|
|
176
229
|
export const resolveProfiles = (root, ids) => {
|
|
177
230
|
const resolved = [];
|
|
178
231
|
const visiting = new Set();
|
|
@@ -195,6 +248,7 @@ export const resolveProfiles = (root, ids) => {
|
|
|
195
248
|
return resolved;
|
|
196
249
|
};
|
|
197
250
|
const stable = (values) => [...new Set(values)];
|
|
251
|
+
/** Merges built-in and local profiles with the project config into the effective configuration. */
|
|
198
252
|
export const resolveEffectiveConfig = (root, config) => {
|
|
199
253
|
const profiles = resolveProfiles(root, config.extends);
|
|
200
254
|
return {
|
|
@@ -209,6 +263,7 @@ export const resolveEffectiveConfig = (root, config) => {
|
|
|
209
263
|
loop: config.loop ?? { mode: "off" }
|
|
210
264
|
};
|
|
211
265
|
};
|
|
266
|
+
/** Expands rule include patterns into concrete markdown file paths inside the project. */
|
|
212
267
|
export const includedRuleFiles = (root, patterns) => {
|
|
213
268
|
const project = resolve(root);
|
|
214
269
|
const realProject = realpathSync(project);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
|
|
2
2
|
import { resolveProjectPath } from "./project-path.js";
|
|
3
3
|
const safePath = (root, relative) => resolveProjectPath(root, relative, "Unsafe migration path");
|
|
4
|
+
/* Legacy `.aidlc/...` path translation into the canonical `.agents/` layout. */
|
|
4
5
|
const legacyReference = (value) => {
|
|
5
6
|
if (value === ".aidlc/config.json")
|
|
6
7
|
return ".agents/config.json";
|
|
@@ -15,6 +16,7 @@ const legacyReference = (value) => {
|
|
|
15
16
|
return value;
|
|
16
17
|
};
|
|
17
18
|
const rewriteStrings = (value) => Array.isArray(value) ? value.map((entry) => typeof entry === "string" ? legacyReference(entry) : entry) : value;
|
|
19
|
+
/** Rewrites legacy `.aidlc/` path references inside the JSON file formats that are known to hold them. */
|
|
18
20
|
const rewriteKnownJson = (relative, content) => {
|
|
19
21
|
const isConfig = relative === "config.json";
|
|
20
22
|
const isProfile = /^profiles\/.+\/profile\.json$/.test(relative);
|
|
@@ -63,6 +65,7 @@ const legacyDestination = (relative) => {
|
|
|
63
65
|
return `.agents/data/index/${relative.slice("index/".length)}`;
|
|
64
66
|
return `.agents/project/${relative}`;
|
|
65
67
|
};
|
|
68
|
+
/** Plans the copy of one legacy file into its destination, including a tracked delete of the source. */
|
|
66
69
|
const planMigrationFile = (project, plan, sourcePath, destinationPath, legacyRelative) => {
|
|
67
70
|
const source = safePath(project, sourcePath);
|
|
68
71
|
const payload = migrationPayload(legacyRelative, readFileSync(source));
|
|
@@ -95,6 +98,7 @@ const planMigrationFile = (project, plan, sourcePath, destinationPath, legacyRel
|
|
|
95
98
|
plan.push({ path: sourcePath, owner: "aidlc-project", ownershipClass: "project", content: "", action: "delete", reason: `remove migrated legacy project file after creating ${destinationPath}` });
|
|
96
99
|
}
|
|
97
100
|
};
|
|
101
|
+
/** Walks the legacy `.aidlc/` tree, refusing symlinks and special entries with a conflicted plan entry. */
|
|
98
102
|
const planLegacyTreeMigration = (project, plan) => {
|
|
99
103
|
const legacyRoot = safePath(project, ".aidlc");
|
|
100
104
|
if (!existsSync(legacyRoot))
|
|
@@ -118,6 +122,7 @@ const planLegacyTreeMigration = (project, plan) => {
|
|
|
118
122
|
};
|
|
119
123
|
visit(legacyRoot);
|
|
120
124
|
};
|
|
125
|
+
/** Rewrites legacy `.aidlc/` references inside an already-installed canonical project config. */
|
|
121
126
|
const planExistingConfigRewrite = (project, plan) => {
|
|
122
127
|
const path = ".agents/config.json";
|
|
123
128
|
const target = safePath(project, path);
|
|
@@ -130,6 +135,7 @@ const planExistingConfigRewrite = (project, plan) => {
|
|
|
130
135
|
if (item)
|
|
131
136
|
Object.assign(item, { content: rewritten.content, action: "migrate", reason: "rewrite legacy .aidlc references in canonical project config" });
|
|
132
137
|
};
|
|
138
|
+
/** Moves the legacy `.agents/data/state/{repo-map,specs-index}` files into the data/index directory. */
|
|
133
139
|
const planLegacyIndexMigration = (project, plan) => {
|
|
134
140
|
for (const name of ["repo-map.md", "specs-index.md"]) {
|
|
135
141
|
const source = `.agents/data/state/${name}`;
|
|
@@ -137,6 +143,7 @@ const planLegacyIndexMigration = (project, plan) => {
|
|
|
137
143
|
planMigrationFile(project, plan, source, `.agents/data/index/${name}`, `index/${name}`);
|
|
138
144
|
}
|
|
139
145
|
};
|
|
146
|
+
/** Plans the full legacy-layout migration: config rewrite, `.aidlc/` tree, and legacy index files. */
|
|
140
147
|
export const planProjectLayoutMigration = (project, plan) => {
|
|
141
148
|
planExistingConfigRewrite(project, plan);
|
|
142
149
|
planLegacyTreeMigration(project, plan);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ArchivedTaskSummary, TaskState } from "../model.js";
|
|
2
|
+
export declare const taskRecordContent: (task: TaskState) => string;
|
|
3
|
+
/** Builds the immutable archive-catalog summary for a terminal task from its record content. */
|
|
4
|
+
export declare const archivedSummary: (task: TaskState, content?: string) => ArchivedTaskSummary;
|
|
5
|
+
/** Single terminal-to-archive policy shared by auto-archive on save and schema migration. */
|
|
6
|
+
export declare const buildArchiveRecord: (task: TaskState) => {
|
|
7
|
+
content: string;
|
|
8
|
+
summary: ArchivedTaskSummary;
|
|
9
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { digest } from "./domain.js";
|
|
2
|
+
import { archiveRecordPath } from "./paths.js";
|
|
3
|
+
export const taskRecordContent = (task) => `${JSON.stringify(task, null, 2)}\n`;
|
|
4
|
+
/** Builds the immutable archive-catalog summary for a terminal task from its record content. */
|
|
5
|
+
export const archivedSummary = (task, content = taskRecordContent(task)) => ({
|
|
6
|
+
id: task.id,
|
|
7
|
+
title: task.title,
|
|
8
|
+
type: task.type,
|
|
9
|
+
phase: task.phase,
|
|
10
|
+
gate: task.gate,
|
|
11
|
+
status: task.status,
|
|
12
|
+
risk: task.risk,
|
|
13
|
+
areas: task.areas,
|
|
14
|
+
record: archiveRecordPath(task.id),
|
|
15
|
+
digest: digest(content),
|
|
16
|
+
lessonCount: task.lessons?.length ?? 0,
|
|
17
|
+
predecessorTaskId: task.predecessorTaskId,
|
|
18
|
+
successorTaskId: task.successorTaskId,
|
|
19
|
+
createdAt: task.createdAt,
|
|
20
|
+
updatedAt: task.updatedAt
|
|
21
|
+
});
|
|
22
|
+
/** Single terminal-to-archive policy shared by auto-archive on save and schema migration. */
|
|
23
|
+
export const buildArchiveRecord = (task) => {
|
|
24
|
+
const content = taskRecordContent(task);
|
|
25
|
+
return { content, summary: archivedSummary(task, content) };
|
|
26
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TaskState } from "../model.js";
|
|
2
|
+
export type NextActionActions = Array<{
|
|
3
|
+
id: "reopen_g1" | "create_successor" | "supersede" | "close" | "record_handoff" | "resume";
|
|
4
|
+
command: string;
|
|
5
|
+
}>;
|
|
6
|
+
export interface CommandKit {
|
|
7
|
+
continueItem: (taskId: string, itemId: string) => string;
|
|
8
|
+
continueVerification: (taskId: string) => string;
|
|
9
|
+
continuePhase: (taskId: string, phase: string) => string;
|
|
10
|
+
recoverG1Wait: (taskId: string) => string;
|
|
11
|
+
recoverGatelessWrap: (taskId: string) => string;
|
|
12
|
+
approveGate: (taskId: string, gate: string) => string;
|
|
13
|
+
handoffActions: (task: TaskState) => NextActionActions;
|
|
14
|
+
pausedActions: (taskId: string) => NextActionActions;
|
|
15
|
+
repairHandoffAction: (taskId: string, kind: "review_exhausted" | "repair_exhausted") => NextActionActions;
|
|
16
|
+
}
|
|
17
|
+
/** Command-string synthesis for `nextAction`: script paths, `--root` suffixes, and CLI placeholders exist only here. */
|
|
18
|
+
export declare const commandKit: (root?: string) => CommandKit;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
/** Command-string synthesis for `nextAction`: script paths, `--root` suffixes, and CLI placeholders exist only here. */
|
|
3
|
+
export const commandKit = (root) => {
|
|
4
|
+
const suffix = root === undefined ? "" : ` --root ${JSON.stringify(resolve(root))}`;
|
|
5
|
+
const script = (name) => root === undefined ? `node .agents/aidlc/scripts/${name}` : `node ${JSON.stringify(join(resolve(root), ".agents/aidlc/scripts", name))}`;
|
|
6
|
+
const stateScript = script("state.mjs");
|
|
7
|
+
const contextScript = script("context.mjs");
|
|
8
|
+
return {
|
|
9
|
+
continueItem: (taskId, itemId) => `${contextScript} ${taskId} --phase build --item ${itemId}${suffix}`,
|
|
10
|
+
continueVerification: (taskId) => `${contextScript} ${taskId} --phase build${suffix}`,
|
|
11
|
+
continuePhase: (taskId, phase) => `${contextScript} ${taskId} --phase ${phase}${suffix}`,
|
|
12
|
+
recoverG1Wait: (taskId) => `${contextScript} ${taskId} --phase plan${suffix}`,
|
|
13
|
+
recoverGatelessWrap: (taskId) => `${stateScript} task status ${taskId} --status active${suffix}`,
|
|
14
|
+
approveGate: (taskId, gate) => `${stateScript} gate approve ${taskId} --gate ${gate} --source <explicit-user-approval>${suffix}`,
|
|
15
|
+
handoffActions: (task) => [
|
|
16
|
+
...(task.phase === "build" ? [{ id: "reopen_g1", command: `${stateScript} task reopen ${task.id} --to plan --reason <reason> --source <explicit-user-request>${suffix}` }] : []),
|
|
17
|
+
{ id: "create_successor", command: `${stateScript} task create <new-task-id> --title <title>${suffix}` },
|
|
18
|
+
{ id: "supersede", command: `${stateScript} task supersede ${task.id} --successor <new-task-id> --reason <reason> --source <explicit-user-request>${suffix}` },
|
|
19
|
+
{ id: "close", command: `${stateScript} task close ${task.id} --reason <reason> --source <explicit-user-request>${suffix}` }
|
|
20
|
+
],
|
|
21
|
+
pausedActions: (taskId) => [
|
|
22
|
+
{ id: "resume", command: `${stateScript} task status ${taskId} --status active${suffix}` },
|
|
23
|
+
{ id: "record_handoff", command: `${stateScript} task handoff ${taskId} --kind <kind> --reason <reason> --source <source>${suffix}` }
|
|
24
|
+
],
|
|
25
|
+
repairHandoffAction: (taskId, kind) => [{ id: "record_handoff", command: `${stateScript} task handoff ${taskId} --kind ${kind} --reason <summary-of-exhausted-repairs> --source <agent>${suffix}` }]
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Gate, HandoffKind, Phase, TaskState, WorkflowState } from "../model.js";
|
|
2
|
+
export declare const emptyState: () => WorkflowState;
|
|
3
|
+
export declare const legacyG2WaitSource = "schema-v4-migration";
|
|
4
|
+
export declare const phases: Phase[];
|
|
5
|
+
export declare const gates: Gate[];
|
|
6
|
+
export declare const handoffKinds: HandoffKind[];
|
|
7
|
+
export declare const terminal: (task: TaskState) => boolean;
|
|
8
|
+
export declare const phaseGate: (phase: Phase) => Gate;
|
|
9
|
+
export declare const digest: (content: string) => string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export const emptyState = () => ({ schemaVersion: 4, tasks: {}, archive: {} });
|
|
3
|
+
export const legacyG2WaitSource = "schema-v4-migration";
|
|
4
|
+
export const phases = ["clarify", "plan", "build", "wrap", "done"];
|
|
5
|
+
export const gates = ["none", "G0_confirm", "G1_review", "G2_codereview"];
|
|
6
|
+
export const handoffKinds = ["repair_exhausted", "review_exhausted", "g2_failed", "release_failed", "structural_change", "other"];
|
|
7
|
+
export const terminal = (task) => task.status === "done" || task.status === "closed" || task.status === "superseded";
|
|
8
|
+
export const phaseGate = (phase) => phase === "clarify" ? "G0_confirm" : phase === "plan" ? "G1_review" : phase === "build" ? "G2_codereview" : "none";
|
|
9
|
+
export const digest = (content) => createHash("sha256").update(content).digest("hex");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const iso: (value: unknown) => value is string;
|
|
2
|
+
export declare const text: (value: unknown) => value is string;
|
|
3
|
+
export declare const idPattern: RegExp;
|
|
4
|
+
export declare const audited: (reason: string, source: string) => void;
|
|
5
|
+
export declare const tokens: (value: string) => string[];
|
|
6
|
+
/** Rejects artifact paths that escape the project-owned tasks directory. */
|
|
7
|
+
export declare const assertTaskArtifactPath: (id: string, path: string) => void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { isAbsolute, normalize } from "node:path";
|
|
2
|
+
export const iso = (value) => typeof value === "string" && Number.isFinite(Date.parse(value)) && new Date(Date.parse(value)).toISOString() === value;
|
|
3
|
+
export const text = (value) => typeof value === "string" && Boolean(value.trim());
|
|
4
|
+
export const idPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
5
|
+
export const audited = (reason, source) => {
|
|
6
|
+
if (!text(reason) || !text(source))
|
|
7
|
+
throw new Error("Lifecycle mutation requires non-empty --reason and --source");
|
|
8
|
+
};
|
|
9
|
+
export const tokens = (value) => [...new Set(value.toLocaleLowerCase().split(/[^\p{L}\p{N}]+/u).filter((item) => item.length > 1))];
|
|
10
|
+
/** Rejects artifact paths that escape the project-owned tasks directory. */
|
|
11
|
+
export const assertTaskArtifactPath = (id, path) => {
|
|
12
|
+
const separator = process.platform === "win32" ? "\\" : "/";
|
|
13
|
+
const cleaned = normalize(path);
|
|
14
|
+
if (isAbsolute(cleaned) || cleaned === ".." || cleaned.startsWith(`..${separator}`) || !cleaned.startsWith(`.agents${separator}data${separator}tasks${separator}`)) {
|
|
15
|
+
throw new Error(`Invalid artifact path for ${id}: ${path}`);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const assertNoSymlinkPath: (root: string, relative: string) => void;
|
|
2
|
+
export declare const writeAtomic: (path: string, content: string) => void;
|
|
3
|
+
export declare const parseJsonText: (content: string, label: string, recovery?: string) => unknown;
|
|
4
|
+
export declare const parseJsonFile: (path: string, label: string, recovery?: string) => unknown;
|
|
5
|
+
/** Resolves a project-relative state path to its absolute form, rejecting symlink-crossing variants first. */
|
|
6
|
+
export declare const safeResolvedPath: (root: string, relative: string) => string;
|
|
7
|
+
/** Loads and validates a canonical JSON state file, or produces the fallback when the file does not exist. */
|
|
8
|
+
export declare const loadJsonOr: <T>(root: string, relative: string, validate: (value: unknown) => T, fallback: () => T, label: string, recovery?: string) => T;
|
|
9
|
+
/** Persists a JSON state file atomically in the canonical 2-space, trailing-newline formatting. */
|
|
10
|
+
export declare const saveJson: (root: string, relative: string, value: unknown) => void;
|