forge-cc 1.0.2 → 2.0.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/.forge.json +6 -5
- package/README.md +7 -7
- package/dist/cli.js +193 -131
- package/dist/cli.js.map +1 -1
- package/dist/codex-poll.d.ts +38 -0
- package/dist/codex-poll.js +70 -0
- package/dist/codex-poll.js.map +1 -0
- package/dist/config/schema.d.ts +0 -28
- package/dist/config/schema.js +0 -7
- package/dist/config/schema.js.map +1 -1
- package/dist/graph/index.d.ts +6 -0
- package/dist/graph/index.js +11 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/query.d.ts +52 -0
- package/dist/graph/query.js +319 -0
- package/dist/graph/query.js.map +1 -0
- package/dist/graph/reader.d.ts +13 -0
- package/dist/graph/reader.js +140 -0
- package/dist/graph/reader.js.map +1 -0
- package/dist/graph/schemas.d.ts +215 -0
- package/dist/graph/schemas.js +54 -0
- package/dist/graph/schemas.js.map +1 -0
- package/dist/graph/types.d.ts +109 -0
- package/dist/graph/types.js +2 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/graph/validator.d.ts +32 -0
- package/dist/graph/validator.js +253 -0
- package/dist/graph/validator.js.map +1 -0
- package/dist/graph/writer.d.ts +18 -0
- package/dist/graph/writer.js +159 -0
- package/dist/graph/writer.js.map +1 -0
- package/dist/linear/client.d.ts +94 -4
- package/dist/linear/client.js +203 -20
- package/dist/linear/client.js.map +1 -1
- package/dist/linear/sync.d.ts +11 -14
- package/dist/linear/sync.js +63 -83
- package/dist/linear/sync.js.map +1 -1
- package/dist/runner/loop.d.ts +1 -1
- package/dist/runner/loop.js +93 -100
- package/dist/runner/loop.js.map +1 -1
- package/dist/runner/prompt.d.ts +7 -9
- package/dist/runner/prompt.js +27 -30
- package/dist/runner/prompt.js.map +1 -1
- package/dist/setup.js +26 -3
- package/dist/setup.js.map +1 -1
- package/dist/types.d.ts +0 -23
- package/package.json +3 -5
- package/skills/README.md +35 -33
- package/skills/forge-build.md +344 -0
- package/skills/forge-capture.md +204 -0
- package/skills/forge-fix.md +207 -0
- package/skills/forge-plan.md +335 -0
- package/skills/forge-quick.md +154 -0
- package/skills/forge-setup.md +2 -2
- package/skills/ref/adversarial-review.md +117 -0
- package/skills/ref/graph-correction.md +88 -0
- package/skills/ref/requirement-sizing.md +146 -0
- package/dist/server.d.ts +0 -6
- package/dist/server.js +0 -51
- package/dist/server.js.map +0 -1
- package/dist/state/status.d.ts +0 -66
- package/dist/state/status.js +0 -96
- package/dist/state/status.js.map +0 -1
- package/skills/forge-go.md +0 -583
- package/skills/forge-spec.md +0 -367
- package/skills/forge-triage.md +0 -179
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const requirementStatusEnum: z.ZodEnum<["pending", "in_progress", "complete", "discovered", "rejected"]>;
|
|
3
|
+
export declare const requirementFilesSchema: z.ZodObject<{
|
|
4
|
+
creates: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
5
|
+
modifies: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
creates: string[];
|
|
8
|
+
modifies: string[];
|
|
9
|
+
}, {
|
|
10
|
+
creates?: string[] | undefined;
|
|
11
|
+
modifies?: string[] | undefined;
|
|
12
|
+
}>;
|
|
13
|
+
/** Validates YAML frontmatter of a requirement .md file. */
|
|
14
|
+
export declare const requirementFrontmatterSchema: z.ZodObject<{
|
|
15
|
+
id: z.ZodString;
|
|
16
|
+
title: z.ZodString;
|
|
17
|
+
dependsOn: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
18
|
+
files: z.ZodDefault<z.ZodObject<{
|
|
19
|
+
creates: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
20
|
+
modifies: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
creates: string[];
|
|
23
|
+
modifies: string[];
|
|
24
|
+
}, {
|
|
25
|
+
creates?: string[] | undefined;
|
|
26
|
+
modifies?: string[] | undefined;
|
|
27
|
+
}>>;
|
|
28
|
+
acceptance: z.ZodArray<z.ZodString, "many">;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
id: string;
|
|
31
|
+
title: string;
|
|
32
|
+
files: {
|
|
33
|
+
creates: string[];
|
|
34
|
+
modifies: string[];
|
|
35
|
+
};
|
|
36
|
+
acceptance: string[];
|
|
37
|
+
dependsOn?: string[] | undefined;
|
|
38
|
+
}, {
|
|
39
|
+
id: string;
|
|
40
|
+
title: string;
|
|
41
|
+
acceptance: string[];
|
|
42
|
+
dependsOn?: string[] | undefined;
|
|
43
|
+
files?: {
|
|
44
|
+
creates?: string[] | undefined;
|
|
45
|
+
modifies?: string[] | undefined;
|
|
46
|
+
} | undefined;
|
|
47
|
+
}>;
|
|
48
|
+
/** Validates a single requirement entry in _index.yaml. */
|
|
49
|
+
export declare const requirementMetaSchema: z.ZodObject<{
|
|
50
|
+
group: z.ZodString;
|
|
51
|
+
status: z.ZodEnum<["pending", "in_progress", "complete", "discovered", "rejected"]>;
|
|
52
|
+
dependsOn: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
53
|
+
priority: z.ZodDefault<z.ZodNumber>;
|
|
54
|
+
linearIssueId: z.ZodOptional<z.ZodString>;
|
|
55
|
+
completedAt: z.ZodOptional<z.ZodString>;
|
|
56
|
+
discoveredBy: z.ZodOptional<z.ZodString>;
|
|
57
|
+
rejectedReason: z.ZodOptional<z.ZodString>;
|
|
58
|
+
}, "strip", z.ZodTypeAny, {
|
|
59
|
+
status: "pending" | "in_progress" | "complete" | "discovered" | "rejected";
|
|
60
|
+
dependsOn: string[];
|
|
61
|
+
group: string;
|
|
62
|
+
priority: number;
|
|
63
|
+
linearIssueId?: string | undefined;
|
|
64
|
+
completedAt?: string | undefined;
|
|
65
|
+
discoveredBy?: string | undefined;
|
|
66
|
+
rejectedReason?: string | undefined;
|
|
67
|
+
}, {
|
|
68
|
+
status: "pending" | "in_progress" | "complete" | "discovered" | "rejected";
|
|
69
|
+
group: string;
|
|
70
|
+
dependsOn?: string[] | undefined;
|
|
71
|
+
priority?: number | undefined;
|
|
72
|
+
linearIssueId?: string | undefined;
|
|
73
|
+
completedAt?: string | undefined;
|
|
74
|
+
discoveredBy?: string | undefined;
|
|
75
|
+
rejectedReason?: string | undefined;
|
|
76
|
+
}>;
|
|
77
|
+
/** Validates a group definition in _index.yaml. */
|
|
78
|
+
export declare const groupDefSchema: z.ZodObject<{
|
|
79
|
+
name: z.ZodString;
|
|
80
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
81
|
+
dependsOn: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
82
|
+
linearMilestoneId: z.ZodOptional<z.ZodString>;
|
|
83
|
+
}, "strip", z.ZodTypeAny, {
|
|
84
|
+
dependsOn: string[];
|
|
85
|
+
name: string;
|
|
86
|
+
order?: number | undefined;
|
|
87
|
+
linearMilestoneId?: string | undefined;
|
|
88
|
+
}, {
|
|
89
|
+
name: string;
|
|
90
|
+
dependsOn?: string[] | undefined;
|
|
91
|
+
order?: number | undefined;
|
|
92
|
+
linearMilestoneId?: string | undefined;
|
|
93
|
+
}>;
|
|
94
|
+
/** Validates the linear config block in _index.yaml. */
|
|
95
|
+
export declare const linearConfigSchema: z.ZodObject<{
|
|
96
|
+
projectId: z.ZodString;
|
|
97
|
+
teamId: z.ZodString;
|
|
98
|
+
}, "strip", z.ZodTypeAny, {
|
|
99
|
+
projectId: string;
|
|
100
|
+
teamId: string;
|
|
101
|
+
}, {
|
|
102
|
+
projectId: string;
|
|
103
|
+
teamId: string;
|
|
104
|
+
}>;
|
|
105
|
+
/** Validates the full _index.yaml structure. */
|
|
106
|
+
export declare const graphIndexSchema: z.ZodObject<{
|
|
107
|
+
project: z.ZodString;
|
|
108
|
+
slug: z.ZodString;
|
|
109
|
+
branch: z.ZodString;
|
|
110
|
+
createdAt: z.ZodString;
|
|
111
|
+
linear: z.ZodOptional<z.ZodObject<{
|
|
112
|
+
projectId: z.ZodString;
|
|
113
|
+
teamId: z.ZodString;
|
|
114
|
+
}, "strip", z.ZodTypeAny, {
|
|
115
|
+
projectId: string;
|
|
116
|
+
teamId: string;
|
|
117
|
+
}, {
|
|
118
|
+
projectId: string;
|
|
119
|
+
teamId: string;
|
|
120
|
+
}>>;
|
|
121
|
+
groups: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
122
|
+
name: z.ZodString;
|
|
123
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
124
|
+
dependsOn: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
125
|
+
linearMilestoneId: z.ZodOptional<z.ZodString>;
|
|
126
|
+
}, "strip", z.ZodTypeAny, {
|
|
127
|
+
dependsOn: string[];
|
|
128
|
+
name: string;
|
|
129
|
+
order?: number | undefined;
|
|
130
|
+
linearMilestoneId?: string | undefined;
|
|
131
|
+
}, {
|
|
132
|
+
name: string;
|
|
133
|
+
dependsOn?: string[] | undefined;
|
|
134
|
+
order?: number | undefined;
|
|
135
|
+
linearMilestoneId?: string | undefined;
|
|
136
|
+
}>>;
|
|
137
|
+
requirements: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
138
|
+
group: z.ZodString;
|
|
139
|
+
status: z.ZodEnum<["pending", "in_progress", "complete", "discovered", "rejected"]>;
|
|
140
|
+
dependsOn: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
141
|
+
priority: z.ZodDefault<z.ZodNumber>;
|
|
142
|
+
linearIssueId: z.ZodOptional<z.ZodString>;
|
|
143
|
+
completedAt: z.ZodOptional<z.ZodString>;
|
|
144
|
+
discoveredBy: z.ZodOptional<z.ZodString>;
|
|
145
|
+
rejectedReason: z.ZodOptional<z.ZodString>;
|
|
146
|
+
}, "strip", z.ZodTypeAny, {
|
|
147
|
+
status: "pending" | "in_progress" | "complete" | "discovered" | "rejected";
|
|
148
|
+
dependsOn: string[];
|
|
149
|
+
group: string;
|
|
150
|
+
priority: number;
|
|
151
|
+
linearIssueId?: string | undefined;
|
|
152
|
+
completedAt?: string | undefined;
|
|
153
|
+
discoveredBy?: string | undefined;
|
|
154
|
+
rejectedReason?: string | undefined;
|
|
155
|
+
}, {
|
|
156
|
+
status: "pending" | "in_progress" | "complete" | "discovered" | "rejected";
|
|
157
|
+
group: string;
|
|
158
|
+
dependsOn?: string[] | undefined;
|
|
159
|
+
priority?: number | undefined;
|
|
160
|
+
linearIssueId?: string | undefined;
|
|
161
|
+
completedAt?: string | undefined;
|
|
162
|
+
discoveredBy?: string | undefined;
|
|
163
|
+
rejectedReason?: string | undefined;
|
|
164
|
+
}>>;
|
|
165
|
+
}, "strip", z.ZodTypeAny, {
|
|
166
|
+
project: string;
|
|
167
|
+
slug: string;
|
|
168
|
+
branch: string;
|
|
169
|
+
createdAt: string;
|
|
170
|
+
groups: Record<string, {
|
|
171
|
+
dependsOn: string[];
|
|
172
|
+
name: string;
|
|
173
|
+
order?: number | undefined;
|
|
174
|
+
linearMilestoneId?: string | undefined;
|
|
175
|
+
}>;
|
|
176
|
+
requirements: Record<string, {
|
|
177
|
+
status: "pending" | "in_progress" | "complete" | "discovered" | "rejected";
|
|
178
|
+
dependsOn: string[];
|
|
179
|
+
group: string;
|
|
180
|
+
priority: number;
|
|
181
|
+
linearIssueId?: string | undefined;
|
|
182
|
+
completedAt?: string | undefined;
|
|
183
|
+
discoveredBy?: string | undefined;
|
|
184
|
+
rejectedReason?: string | undefined;
|
|
185
|
+
}>;
|
|
186
|
+
linear?: {
|
|
187
|
+
projectId: string;
|
|
188
|
+
teamId: string;
|
|
189
|
+
} | undefined;
|
|
190
|
+
}, {
|
|
191
|
+
project: string;
|
|
192
|
+
slug: string;
|
|
193
|
+
branch: string;
|
|
194
|
+
createdAt: string;
|
|
195
|
+
groups: Record<string, {
|
|
196
|
+
name: string;
|
|
197
|
+
dependsOn?: string[] | undefined;
|
|
198
|
+
order?: number | undefined;
|
|
199
|
+
linearMilestoneId?: string | undefined;
|
|
200
|
+
}>;
|
|
201
|
+
requirements: Record<string, {
|
|
202
|
+
status: "pending" | "in_progress" | "complete" | "discovered" | "rejected";
|
|
203
|
+
group: string;
|
|
204
|
+
dependsOn?: string[] | undefined;
|
|
205
|
+
priority?: number | undefined;
|
|
206
|
+
linearIssueId?: string | undefined;
|
|
207
|
+
completedAt?: string | undefined;
|
|
208
|
+
discoveredBy?: string | undefined;
|
|
209
|
+
rejectedReason?: string | undefined;
|
|
210
|
+
}>;
|
|
211
|
+
linear?: {
|
|
212
|
+
projectId: string;
|
|
213
|
+
teamId: string;
|
|
214
|
+
} | undefined;
|
|
215
|
+
}>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const requirementStatusEnum = z.enum([
|
|
3
|
+
"pending",
|
|
4
|
+
"in_progress",
|
|
5
|
+
"complete",
|
|
6
|
+
"discovered",
|
|
7
|
+
"rejected",
|
|
8
|
+
]);
|
|
9
|
+
export const requirementFilesSchema = z.object({
|
|
10
|
+
creates: z.array(z.string()).default([]),
|
|
11
|
+
modifies: z.array(z.string()).default([]),
|
|
12
|
+
});
|
|
13
|
+
/** Validates YAML frontmatter of a requirement .md file. */
|
|
14
|
+
export const requirementFrontmatterSchema = z.object({
|
|
15
|
+
id: z.string().min(1),
|
|
16
|
+
title: z.string().min(1),
|
|
17
|
+
dependsOn: z.array(z.string()).optional(),
|
|
18
|
+
files: requirementFilesSchema.default({ creates: [], modifies: [] }),
|
|
19
|
+
acceptance: z.array(z.string()).min(1),
|
|
20
|
+
});
|
|
21
|
+
/** Validates a single requirement entry in _index.yaml. */
|
|
22
|
+
export const requirementMetaSchema = z.object({
|
|
23
|
+
group: z.string().min(1),
|
|
24
|
+
status: requirementStatusEnum,
|
|
25
|
+
dependsOn: z.array(z.string()).default([]),
|
|
26
|
+
priority: z.number().int().default(0),
|
|
27
|
+
linearIssueId: z.string().optional(),
|
|
28
|
+
completedAt: z.string().optional(),
|
|
29
|
+
discoveredBy: z.string().optional(),
|
|
30
|
+
rejectedReason: z.string().optional(),
|
|
31
|
+
});
|
|
32
|
+
/** Validates a group definition in _index.yaml. */
|
|
33
|
+
export const groupDefSchema = z.object({
|
|
34
|
+
name: z.string().min(1),
|
|
35
|
+
order: z.number().int().positive().optional(),
|
|
36
|
+
dependsOn: z.array(z.string()).default([]),
|
|
37
|
+
linearMilestoneId: z.string().optional(),
|
|
38
|
+
});
|
|
39
|
+
/** Validates the linear config block in _index.yaml. */
|
|
40
|
+
export const linearConfigSchema = z.object({
|
|
41
|
+
projectId: z.string().min(1),
|
|
42
|
+
teamId: z.string().min(1),
|
|
43
|
+
});
|
|
44
|
+
/** Validates the full _index.yaml structure. */
|
|
45
|
+
export const graphIndexSchema = z.object({
|
|
46
|
+
project: z.string().min(1),
|
|
47
|
+
slug: z.string().min(1),
|
|
48
|
+
branch: z.string().min(1),
|
|
49
|
+
createdAt: z.string().min(1),
|
|
50
|
+
linear: linearConfigSchema.optional(),
|
|
51
|
+
groups: z.record(z.string(), groupDefSchema),
|
|
52
|
+
requirements: z.record(z.string(), requirementMetaSchema),
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/graph/schemas.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** Requirement status lifecycle. */
|
|
2
|
+
export type RequirementStatus = "pending" | "in_progress" | "complete" | "discovered" | "rejected";
|
|
3
|
+
/** Scope declaration: which files a requirement creates or modifies. */
|
|
4
|
+
export interface RequirementFiles {
|
|
5
|
+
creates: string[];
|
|
6
|
+
modifies: string[];
|
|
7
|
+
}
|
|
8
|
+
/** Requirement content parsed from a .md file in requirements/. */
|
|
9
|
+
export interface Requirement {
|
|
10
|
+
/** Unique identifier. Must match key in GraphIndex.requirements. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** Human-readable title. */
|
|
13
|
+
title: string;
|
|
14
|
+
/** Informational mirror of dependsOn from the index. For human readability only — the graph reader ignores this; the index is authoritative. */
|
|
15
|
+
dependsOn?: string[];
|
|
16
|
+
/** File scope declaration. Best-effort estimates from spec time. */
|
|
17
|
+
files: RequirementFiles;
|
|
18
|
+
/** Acceptance criteria — each item is a testable statement. */
|
|
19
|
+
acceptance: string[];
|
|
20
|
+
/** Full markdown body below the YAML frontmatter. */
|
|
21
|
+
body: string;
|
|
22
|
+
}
|
|
23
|
+
/** Requirement metadata stored in _index.yaml. */
|
|
24
|
+
export interface RequirementMeta {
|
|
25
|
+
/** Key into GraphIndex.groups. */
|
|
26
|
+
group: string;
|
|
27
|
+
/** Current lifecycle status. */
|
|
28
|
+
status: RequirementStatus;
|
|
29
|
+
/** Requirement IDs that must be complete before this one starts. */
|
|
30
|
+
dependsOn: string[];
|
|
31
|
+
/** Scheduling priority. Higher = scheduled first. Default 0. */
|
|
32
|
+
priority?: number;
|
|
33
|
+
/** Linear issue UUID. */
|
|
34
|
+
linearIssueId?: string;
|
|
35
|
+
/** ISO 8601 timestamp — set when status transitions to complete. */
|
|
36
|
+
completedAt?: string;
|
|
37
|
+
/** Agent name — set when an agent creates a discovered requirement. */
|
|
38
|
+
discoveredBy?: string;
|
|
39
|
+
/** Reason — set when a discovered requirement is rejected. */
|
|
40
|
+
rejectedReason?: string;
|
|
41
|
+
}
|
|
42
|
+
/** Group definition — organizational grouping that replaces milestones. */
|
|
43
|
+
export interface GroupDef {
|
|
44
|
+
/** Human-readable group name. */
|
|
45
|
+
name: string;
|
|
46
|
+
/** Tie-breaker for independent groups. Primary order is derived from the dependency DAG. */
|
|
47
|
+
order?: number;
|
|
48
|
+
/** Group keys that must fully complete before this group starts. */
|
|
49
|
+
dependsOn?: string[];
|
|
50
|
+
/** Linear ProjectMilestone UUID. */
|
|
51
|
+
linearMilestoneId?: string;
|
|
52
|
+
}
|
|
53
|
+
/** Linear integration configuration. */
|
|
54
|
+
export interface LinearConfig {
|
|
55
|
+
/** Linear project UUID. */
|
|
56
|
+
projectId: string;
|
|
57
|
+
/** Linear team UUID. */
|
|
58
|
+
teamId: string;
|
|
59
|
+
}
|
|
60
|
+
/** Full _index.yaml structure — single source of truth for graph metadata. */
|
|
61
|
+
export interface GraphIndex {
|
|
62
|
+
/** Human-readable project name. */
|
|
63
|
+
project: string;
|
|
64
|
+
/** URL-safe identifier, used for directory naming. */
|
|
65
|
+
slug: string;
|
|
66
|
+
/** Git feature branch name. */
|
|
67
|
+
branch: string;
|
|
68
|
+
/** ISO 8601 creation date. */
|
|
69
|
+
createdAt: string;
|
|
70
|
+
/** Linear integration config. Absent if Linear is not configured. */
|
|
71
|
+
linear?: LinearConfig;
|
|
72
|
+
/** Execution groups (organizational, replaces milestones). */
|
|
73
|
+
groups: Record<string, GroupDef>;
|
|
74
|
+
/** All requirement metadata. Keyed by requirement ID. */
|
|
75
|
+
requirements: Record<string, RequirementMeta>;
|
|
76
|
+
}
|
|
77
|
+
/** Resolved requirement = content + metadata joined from file + index. */
|
|
78
|
+
export interface ResolvedRequirement {
|
|
79
|
+
/** Requirement content from the .md file. */
|
|
80
|
+
content: Requirement;
|
|
81
|
+
/** Requirement metadata from _index.yaml. */
|
|
82
|
+
meta: RequirementMeta;
|
|
83
|
+
}
|
|
84
|
+
/** The full project graph — index + overview + all requirement content. */
|
|
85
|
+
export interface ProjectGraph {
|
|
86
|
+
/** Parsed _index.yaml. */
|
|
87
|
+
index: GraphIndex;
|
|
88
|
+
/** Raw markdown content of overview.md. */
|
|
89
|
+
overview: string;
|
|
90
|
+
/** Requirement content keyed by ID. */
|
|
91
|
+
requirements: Map<string, Requirement>;
|
|
92
|
+
}
|
|
93
|
+
/** Structural validation error. */
|
|
94
|
+
export interface ValidationError {
|
|
95
|
+
type: "cycle" | "dangling_dep" | "missing_file" | "orphan_requirement" | "unknown_group" | "duplicate_id" | "schema_error" | "file_conflict";
|
|
96
|
+
message: string;
|
|
97
|
+
/** Contextual data — varies by error type. */
|
|
98
|
+
context: Record<string, unknown>;
|
|
99
|
+
}
|
|
100
|
+
/** Group completion summary. */
|
|
101
|
+
export interface GroupStatus {
|
|
102
|
+
total: number;
|
|
103
|
+
complete: number;
|
|
104
|
+
inProgress: number;
|
|
105
|
+
pending: number;
|
|
106
|
+
discovered: number;
|
|
107
|
+
rejected: number;
|
|
108
|
+
isComplete: boolean;
|
|
109
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ProjectGraph, GraphIndex, Requirement, ValidationError } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Run all structural validation checks on a project graph.
|
|
4
|
+
* Returns all errors found (does not short-circuit on first error).
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateGraph(graph: ProjectGraph): ValidationError[];
|
|
7
|
+
/**
|
|
8
|
+
* Detect cycles in the requirement and group dependency graphs.
|
|
9
|
+
* Returns the cycle path if found, null if acyclic.
|
|
10
|
+
*/
|
|
11
|
+
export declare function detectCycles(index: GraphIndex): string[] | null;
|
|
12
|
+
/**
|
|
13
|
+
* Find all dangling dependency edges — references to IDs that don't exist.
|
|
14
|
+
*/
|
|
15
|
+
export declare function findDanglingEdges(index: GraphIndex): Array<{
|
|
16
|
+
from: string;
|
|
17
|
+
to: string;
|
|
18
|
+
level: "requirement" | "group";
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Find orphan requirements — files in the Map that aren't tracked in the index.
|
|
22
|
+
*/
|
|
23
|
+
export declare function findOrphans(graph: ProjectGraph): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Find file conflicts — files touched by multiple requirements that could run in parallel.
|
|
26
|
+
* Two requirements can run in parallel if they are in the same group and neither
|
|
27
|
+
* directly depends on the other.
|
|
28
|
+
*/
|
|
29
|
+
export declare function findFileConflicts(requirements: Map<string, Requirement>, index: GraphIndex): Array<{
|
|
30
|
+
file: string;
|
|
31
|
+
requirements: string[];
|
|
32
|
+
}>;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run all structural validation checks on a project graph.
|
|
3
|
+
* Returns all errors found (does not short-circuit on first error).
|
|
4
|
+
*/
|
|
5
|
+
export function validateGraph(graph) {
|
|
6
|
+
const errors = [];
|
|
7
|
+
// Dependency cycles
|
|
8
|
+
const reqCycle = detectCyclesInRequirements(graph.index);
|
|
9
|
+
if (reqCycle) {
|
|
10
|
+
errors.push({
|
|
11
|
+
type: "cycle",
|
|
12
|
+
message: `Requirement dependency cycle: ${reqCycle.join(" → ")}`,
|
|
13
|
+
context: { cycle: reqCycle, level: "requirement" },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const groupCycle = detectCyclesInGroups(graph.index);
|
|
17
|
+
if (groupCycle) {
|
|
18
|
+
errors.push({
|
|
19
|
+
type: "cycle",
|
|
20
|
+
message: `Group dependency cycle: ${groupCycle.join(" → ")}`,
|
|
21
|
+
context: { cycle: groupCycle, level: "group" },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// Dangling edges
|
|
25
|
+
for (const edge of findDanglingEdges(graph.index)) {
|
|
26
|
+
errors.push({
|
|
27
|
+
type: "dangling_dep",
|
|
28
|
+
message: `${edge.level} "${edge.from}" depends on non-existent ${edge.level} "${edge.to}"`,
|
|
29
|
+
context: edge,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// Missing files: in index but not in requirements Map
|
|
33
|
+
for (const id of Object.keys(graph.index.requirements)) {
|
|
34
|
+
if (!graph.requirements.has(id)) {
|
|
35
|
+
errors.push({
|
|
36
|
+
type: "missing_file",
|
|
37
|
+
message: `Requirement "${id}" is in the index but has no matching .md file`,
|
|
38
|
+
context: { id },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Orphan files: in requirements Map but not in index
|
|
43
|
+
for (const id of findOrphans(graph)) {
|
|
44
|
+
errors.push({
|
|
45
|
+
type: "orphan_requirement",
|
|
46
|
+
message: `Requirement file "${id}" exists but is not tracked in the index`,
|
|
47
|
+
context: { id },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// Unknown groups
|
|
51
|
+
for (const [id, meta] of Object.entries(graph.index.requirements)) {
|
|
52
|
+
if (!(meta.group in graph.index.groups)) {
|
|
53
|
+
errors.push({
|
|
54
|
+
type: "unknown_group",
|
|
55
|
+
message: `Requirement "${id}" references unknown group "${meta.group}"`,
|
|
56
|
+
context: { id, group: meta.group },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// File conflicts
|
|
61
|
+
for (const conflict of findFileConflicts(graph.requirements, graph.index)) {
|
|
62
|
+
errors.push({
|
|
63
|
+
type: "file_conflict",
|
|
64
|
+
message: `File "${conflict.file}" is touched by multiple parallelizable requirements: ${conflict.requirements.join(", ")}`,
|
|
65
|
+
context: conflict,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return errors;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Detect cycles in the requirement and group dependency graphs.
|
|
72
|
+
* Returns the cycle path if found, null if acyclic.
|
|
73
|
+
*/
|
|
74
|
+
export function detectCycles(index) {
|
|
75
|
+
return detectCyclesInRequirements(index) ?? detectCyclesInGroups(index);
|
|
76
|
+
}
|
|
77
|
+
/** DFS cycle detection on the requirement dependency graph. */
|
|
78
|
+
function detectCyclesInRequirements(index) {
|
|
79
|
+
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
80
|
+
const color = new Map();
|
|
81
|
+
for (const id of Object.keys(index.requirements)) {
|
|
82
|
+
color.set(id, WHITE);
|
|
83
|
+
}
|
|
84
|
+
for (const id of Object.keys(index.requirements)) {
|
|
85
|
+
if (color.get(id) === WHITE) {
|
|
86
|
+
const cycle = dfsVisit(id, color, (node) => index.requirements[node]?.dependsOn ?? []);
|
|
87
|
+
if (cycle)
|
|
88
|
+
return cycle;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
/** DFS cycle detection on the group dependency graph. */
|
|
94
|
+
function detectCyclesInGroups(index) {
|
|
95
|
+
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
96
|
+
const color = new Map();
|
|
97
|
+
for (const key of Object.keys(index.groups)) {
|
|
98
|
+
color.set(key, WHITE);
|
|
99
|
+
}
|
|
100
|
+
for (const key of Object.keys(index.groups)) {
|
|
101
|
+
if (color.get(key) === WHITE) {
|
|
102
|
+
const cycle = dfsVisit(key, color, (node) => index.groups[node]?.dependsOn ?? []);
|
|
103
|
+
if (cycle)
|
|
104
|
+
return cycle;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Generic DFS visit with gray-node cycle detection.
|
|
111
|
+
* Returns the cycle path when a back-edge is found, null otherwise.
|
|
112
|
+
*/
|
|
113
|
+
function dfsVisit(node, color, getNeighbors, path = []) {
|
|
114
|
+
const GRAY = 1, BLACK = 2;
|
|
115
|
+
color.set(node, GRAY);
|
|
116
|
+
path.push(node);
|
|
117
|
+
for (const neighbor of getNeighbors(node)) {
|
|
118
|
+
if (color.get(neighbor) === GRAY) {
|
|
119
|
+
// Found cycle — extract the cycle portion of the path
|
|
120
|
+
const cycleStart = path.indexOf(neighbor);
|
|
121
|
+
return [...path.slice(cycleStart), neighbor];
|
|
122
|
+
}
|
|
123
|
+
if (color.get(neighbor) !== BLACK && color.has(neighbor)) {
|
|
124
|
+
const cycle = dfsVisit(neighbor, color, getNeighbors, path);
|
|
125
|
+
if (cycle)
|
|
126
|
+
return cycle;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
path.pop();
|
|
130
|
+
color.set(node, BLACK);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Find all dangling dependency edges — references to IDs that don't exist.
|
|
135
|
+
*/
|
|
136
|
+
export function findDanglingEdges(index) {
|
|
137
|
+
const dangling = [];
|
|
138
|
+
// Requirement-level dangling edges
|
|
139
|
+
for (const [id, meta] of Object.entries(index.requirements)) {
|
|
140
|
+
for (const dep of meta.dependsOn) {
|
|
141
|
+
if (!(dep in index.requirements)) {
|
|
142
|
+
dangling.push({ from: id, to: dep, level: "requirement" });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Group-level dangling edges
|
|
147
|
+
for (const [key, group] of Object.entries(index.groups)) {
|
|
148
|
+
for (const dep of group.dependsOn ?? []) {
|
|
149
|
+
if (!(dep in index.groups)) {
|
|
150
|
+
dangling.push({ from: key, to: dep, level: "group" });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return dangling;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Find orphan requirements — files in the Map that aren't tracked in the index.
|
|
158
|
+
*/
|
|
159
|
+
export function findOrphans(graph) {
|
|
160
|
+
const orphans = [];
|
|
161
|
+
for (const id of graph.requirements.keys()) {
|
|
162
|
+
if (!(id in graph.index.requirements)) {
|
|
163
|
+
orphans.push(id);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return orphans;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Find file conflicts — files touched by multiple requirements that could run in parallel.
|
|
170
|
+
* Two requirements can run in parallel if they are in the same group and neither
|
|
171
|
+
* directly depends on the other.
|
|
172
|
+
*/
|
|
173
|
+
export function findFileConflicts(requirements, index) {
|
|
174
|
+
// Build a map of file → requirement IDs that touch it
|
|
175
|
+
const fileToReqs = new Map();
|
|
176
|
+
for (const [id, req] of requirements) {
|
|
177
|
+
// Only consider requirements that are in the index
|
|
178
|
+
if (!(id in index.requirements))
|
|
179
|
+
continue;
|
|
180
|
+
const allFiles = [...req.files.creates, ...req.files.modifies];
|
|
181
|
+
for (const file of allFiles) {
|
|
182
|
+
const existing = fileToReqs.get(file);
|
|
183
|
+
if (existing) {
|
|
184
|
+
existing.push(id);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
fileToReqs.set(file, [id]);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const conflicts = [];
|
|
192
|
+
for (const [file, reqIds] of fileToReqs) {
|
|
193
|
+
if (reqIds.length < 2)
|
|
194
|
+
continue;
|
|
195
|
+
// Filter to only those that could run in parallel:
|
|
196
|
+
// same group, no direct dependency between them
|
|
197
|
+
const parallelizable = findParallelizable(reqIds, index);
|
|
198
|
+
if (parallelizable.length >= 2) {
|
|
199
|
+
conflicts.push({ file, requirements: parallelizable });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return conflicts;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Given a set of requirement IDs, return those that could theoretically run in parallel.
|
|
206
|
+
* Requirements can run in parallel if they share the same group and neither
|
|
207
|
+
* directly depends on the other.
|
|
208
|
+
*/
|
|
209
|
+
function findParallelizable(reqIds, index) {
|
|
210
|
+
// Group requirements by their group
|
|
211
|
+
const byGroup = new Map();
|
|
212
|
+
for (const id of reqIds) {
|
|
213
|
+
const meta = index.requirements[id];
|
|
214
|
+
if (!meta)
|
|
215
|
+
continue;
|
|
216
|
+
const existing = byGroup.get(meta.group);
|
|
217
|
+
if (existing) {
|
|
218
|
+
existing.push(id);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
byGroup.set(meta.group, [id]);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const result = [];
|
|
225
|
+
for (const groupReqs of byGroup.values()) {
|
|
226
|
+
if (groupReqs.length < 2)
|
|
227
|
+
continue;
|
|
228
|
+
// Build direct dependency set for fast lookup
|
|
229
|
+
const depSets = new Map();
|
|
230
|
+
for (const id of groupReqs) {
|
|
231
|
+
depSets.set(id, new Set(index.requirements[id]?.dependsOn ?? []));
|
|
232
|
+
}
|
|
233
|
+
// Find pairs that have no direct dependency
|
|
234
|
+
for (const id of groupReqs) {
|
|
235
|
+
let isParallel = false;
|
|
236
|
+
for (const otherId of groupReqs) {
|
|
237
|
+
if (id === otherId)
|
|
238
|
+
continue;
|
|
239
|
+
const idDeps = depSets.get(id);
|
|
240
|
+
const otherDeps = depSets.get(otherId);
|
|
241
|
+
if (!idDeps.has(otherId) && !otherDeps.has(id)) {
|
|
242
|
+
isParallel = true;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (isParallel) {
|
|
247
|
+
result.push(id);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=validator.js.map
|