@arcbridge/core 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +69 -60
- package/dist/index.js +193 -157
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,103 +1,92 @@
|
|
|
1
1
|
// src/schemas/config.ts
|
|
2
|
+
import { z as z2 } from "zod";
|
|
3
|
+
|
|
4
|
+
// src/schemas/quality-scenarios.ts
|
|
2
5
|
import { z } from "zod";
|
|
3
|
-
var
|
|
6
|
+
var QualityCategorySchema = z.string().regex(/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/, "Must be lowercase kebab-case (e.g., 'security', 'data-integrity')");
|
|
7
|
+
var QUALITY_PRIORITIES_DESCRIPTION = "Quality priorities in order. Common: security, performance, accessibility, reliability, maintainability, usability, portability, compatibility. Custom categories like data-integrity or compliance are also supported.";
|
|
8
|
+
var QualityPrioritySchema = z.enum(["must", "should", "could"]);
|
|
9
|
+
var QualityScenarioStatusSchema = z.enum([
|
|
10
|
+
"passing",
|
|
11
|
+
"failing",
|
|
12
|
+
"untested",
|
|
13
|
+
"partial"
|
|
14
|
+
]);
|
|
15
|
+
var QualityScenarioSchema = z.object({
|
|
16
|
+
id: z.string().regex(/^[A-Z0-9]+-\d+$/, "Must match pattern like SEC-01"),
|
|
4
17
|
name: z.string().min(1),
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
18
|
+
category: QualityCategorySchema,
|
|
19
|
+
priority: QualityPrioritySchema,
|
|
20
|
+
scenario: z.string().min(1),
|
|
21
|
+
expected: z.string().min(1),
|
|
22
|
+
linked_code: z.array(z.string()).default([]),
|
|
23
|
+
linked_tests: z.array(z.string()).default([]),
|
|
24
|
+
linked_blocks: z.array(z.string()).default([]),
|
|
25
|
+
verification: z.enum(["automatic", "manual", "semi-automatic"]),
|
|
26
|
+
status: QualityScenarioStatusSchema.default("untested")
|
|
9
27
|
});
|
|
10
|
-
var
|
|
28
|
+
var QualityGoalSchema = z.object({
|
|
29
|
+
id: QualityCategorySchema,
|
|
30
|
+
priority: z.number().int().min(1),
|
|
31
|
+
description: z.string().min(1)
|
|
32
|
+
});
|
|
33
|
+
var QualityScenariosFileSchema = z.object({
|
|
11
34
|
schema_version: z.literal(1).default(1),
|
|
12
|
-
|
|
13
|
-
|
|
35
|
+
last_updated: z.string(),
|
|
36
|
+
quality_goals: z.array(QualityGoalSchema),
|
|
37
|
+
scenarios: z.array(QualityScenarioSchema)
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// src/schemas/config.ts
|
|
41
|
+
var ServiceSchema = z2.object({
|
|
42
|
+
name: z2.string().min(1),
|
|
43
|
+
path: z2.string().default("."),
|
|
44
|
+
type: z2.enum(["nextjs", "react", "fastify", "express", "hono", "dotnet", "unity"]),
|
|
45
|
+
tsconfig: z2.string().optional(),
|
|
46
|
+
csproj: z2.string().optional()
|
|
47
|
+
});
|
|
48
|
+
var ArcBridgeConfigSchema = z2.object({
|
|
49
|
+
schema_version: z2.literal(1).default(1),
|
|
50
|
+
project_name: z2.string().min(1),
|
|
51
|
+
project_type: z2.enum([
|
|
14
52
|
"nextjs-app-router",
|
|
15
53
|
"react-vite",
|
|
16
54
|
"api-service",
|
|
17
55
|
"dotnet-webapi",
|
|
18
56
|
"unity-game"
|
|
19
57
|
]).default("nextjs-app-router"),
|
|
20
|
-
services:
|
|
21
|
-
platforms:
|
|
22
|
-
quality_priorities:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"maintainability"
|
|
29
|
-
])
|
|
30
|
-
).default(["security", "performance", "accessibility"]),
|
|
31
|
-
indexing: z.object({
|
|
32
|
-
include: z.array(z.string()).default(["src/**/*", "app/**/*"]),
|
|
33
|
-
exclude: z.array(z.string()).default(["node_modules", "dist", ".next", "coverage"]),
|
|
34
|
-
default_mode: z.enum(["fast", "deep"]).default("fast"),
|
|
35
|
-
csharp_indexer: z.enum(["auto", "roslyn", "tree-sitter"]).default("auto").describe(
|
|
58
|
+
services: z2.array(ServiceSchema).default([]),
|
|
59
|
+
platforms: z2.array(z2.enum(["claude", "copilot", "gemini", "codex"])).default(["claude"]),
|
|
60
|
+
quality_priorities: z2.array(QualityCategorySchema).default(["security", "performance", "accessibility"]).describe(QUALITY_PRIORITIES_DESCRIPTION),
|
|
61
|
+
indexing: z2.object({
|
|
62
|
+
include: z2.array(z2.string()).default(["src/**/*", "app/**/*"]),
|
|
63
|
+
exclude: z2.array(z2.string()).default(["node_modules", "dist", ".next", "coverage"]),
|
|
64
|
+
default_mode: z2.enum(["fast", "deep"]).default("fast"),
|
|
65
|
+
csharp_indexer: z2.enum(["auto", "roslyn", "tree-sitter"]).default("auto").describe(
|
|
36
66
|
"C# indexer backend: 'auto' prefers the arcbridge-dotnet-indexer global tool, falls back to monorepo source if dotnet CLI is available, else tree-sitter. 'tree-sitter' works without .NET SDK. 'roslyn' requires global tool or monorepo source + .NET SDK."
|
|
37
67
|
)
|
|
38
68
|
}).default({}),
|
|
39
|
-
testing:
|
|
40
|
-
test_command:
|
|
41
|
-
timeout_ms:
|
|
69
|
+
testing: z2.object({
|
|
70
|
+
test_command: z2.string().min(1).default("npx vitest run").describe("Command to run tests (space-separated, no shell syntax). File paths are appended as arguments."),
|
|
71
|
+
timeout_ms: z2.number().int().min(1e3).default(6e4).describe("Timeout per test run in milliseconds")
|
|
42
72
|
}).default({}),
|
|
43
|
-
drift:
|
|
44
|
-
ignore_paths:
|
|
73
|
+
drift: z2.object({
|
|
74
|
+
ignore_paths: z2.array(z2.string()).default([]).describe(
|
|
45
75
|
"File paths or prefixes to ignore in undocumented_module drift checks. Framework files (e.g. next.config.ts, root layout/page) are auto-ignored for known project types."
|
|
46
76
|
)
|
|
47
77
|
}).default({}),
|
|
48
|
-
metrics:
|
|
49
|
-
auto_record:
|
|
78
|
+
metrics: z2.object({
|
|
79
|
+
auto_record: z2.boolean().default(false).describe(
|
|
50
80
|
"Automatically record agent activity (tool name, duration, quality snapshot) when key MCP tools are invoked. Token/model info is optional and caller-provided."
|
|
51
81
|
)
|
|
52
82
|
}).default({}),
|
|
53
|
-
sync:
|
|
54
|
-
auto_detect_drift:
|
|
55
|
-
drift_severity_threshold:
|
|
56
|
-
propose_updates_on:
|
|
83
|
+
sync: z2.object({
|
|
84
|
+
auto_detect_drift: z2.boolean().default(true),
|
|
85
|
+
drift_severity_threshold: z2.enum(["info", "warning", "error"]).default("warning"),
|
|
86
|
+
propose_updates_on: z2.enum(["session-end", "phase-complete", "manual"]).default("phase-complete")
|
|
57
87
|
}).default({})
|
|
58
88
|
});
|
|
59
89
|
|
|
60
|
-
// src/schemas/quality-scenarios.ts
|
|
61
|
-
import { z as z2 } from "zod";
|
|
62
|
-
var QualityCategorySchema = z2.enum([
|
|
63
|
-
"security",
|
|
64
|
-
"performance",
|
|
65
|
-
"accessibility",
|
|
66
|
-
"reliability",
|
|
67
|
-
"maintainability"
|
|
68
|
-
]);
|
|
69
|
-
var QualityPrioritySchema = z2.enum(["must", "should", "could"]);
|
|
70
|
-
var QualityScenarioStatusSchema = z2.enum([
|
|
71
|
-
"passing",
|
|
72
|
-
"failing",
|
|
73
|
-
"untested",
|
|
74
|
-
"partial"
|
|
75
|
-
]);
|
|
76
|
-
var QualityScenarioSchema = z2.object({
|
|
77
|
-
id: z2.string().regex(/^[A-Z0-9]+-\d+$/, "Must match pattern like SEC-01"),
|
|
78
|
-
name: z2.string().min(1),
|
|
79
|
-
category: QualityCategorySchema,
|
|
80
|
-
priority: QualityPrioritySchema,
|
|
81
|
-
scenario: z2.string().min(1),
|
|
82
|
-
expected: z2.string().min(1),
|
|
83
|
-
linked_code: z2.array(z2.string()).default([]),
|
|
84
|
-
linked_tests: z2.array(z2.string()).default([]),
|
|
85
|
-
linked_blocks: z2.array(z2.string()).default([]),
|
|
86
|
-
verification: z2.enum(["automatic", "manual", "semi-automatic"]),
|
|
87
|
-
status: QualityScenarioStatusSchema.default("untested")
|
|
88
|
-
});
|
|
89
|
-
var QualityGoalSchema = z2.object({
|
|
90
|
-
id: QualityCategorySchema,
|
|
91
|
-
priority: z2.number().int().min(1),
|
|
92
|
-
description: z2.string().min(1)
|
|
93
|
-
});
|
|
94
|
-
var QualityScenariosFileSchema = z2.object({
|
|
95
|
-
schema_version: z2.literal(1).default(1),
|
|
96
|
-
last_updated: z2.string(),
|
|
97
|
-
quality_goals: z2.array(QualityGoalSchema),
|
|
98
|
-
scenarios: z2.array(QualityScenarioSchema)
|
|
99
|
-
});
|
|
100
|
-
|
|
101
90
|
// src/schemas/building-blocks.ts
|
|
102
91
|
import { z as z3 } from "zod";
|
|
103
92
|
var BuildingBlockSchema = z3.object({
|
|
@@ -832,6 +821,41 @@ ${input.quality_priorities.map((q, i) => `| ${i + 1} | ${q} | *Describe what ${q
|
|
|
832
821
|
};
|
|
833
822
|
}
|
|
834
823
|
|
|
824
|
+
// src/templates/arc42/02-constraints.ts
|
|
825
|
+
function constraintsTemplate(input) {
|
|
826
|
+
return {
|
|
827
|
+
frontmatter: {
|
|
828
|
+
section: "constraints",
|
|
829
|
+
schema_version: 1
|
|
830
|
+
},
|
|
831
|
+
body: `# Architecture Constraints
|
|
832
|
+
|
|
833
|
+
Document the constraints that shape architectural decisions for ${input.name}. Constraints are non-negotiable requirements from the environment \u2014 they are not chosen, they are given.
|
|
834
|
+
|
|
835
|
+
## Technical Constraints
|
|
836
|
+
|
|
837
|
+
| Constraint | Description |
|
|
838
|
+
|-----------|-------------|
|
|
839
|
+
| *e.g., Must run on Node.js 22+* | *Explain why this constraint exists* |
|
|
840
|
+
| *e.g., No native dependencies* | *Required for CI/CD compatibility* |
|
|
841
|
+
|
|
842
|
+
## Organizational Constraints
|
|
843
|
+
|
|
844
|
+
| Constraint | Description |
|
|
845
|
+
|-----------|-------------|
|
|
846
|
+
| *e.g., Team of 1-2 developers* | *Impacts architecture complexity budget* |
|
|
847
|
+
| *e.g., Must ship MVP in 4 weeks* | *Impacts scope and technology choices* |
|
|
848
|
+
|
|
849
|
+
## Legal / Compliance Constraints
|
|
850
|
+
|
|
851
|
+
| Constraint | Description |
|
|
852
|
+
|-----------|-------------|
|
|
853
|
+
| *e.g., GDPR compliance required* | *Impacts data storage and processing* |
|
|
854
|
+
| *e.g., MIT-compatible dependencies only* | *Impacts library selection* |
|
|
855
|
+
`
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
835
859
|
// src/templates/arc42/03-context.ts
|
|
836
860
|
function techStack(template) {
|
|
837
861
|
switch (template) {
|
|
@@ -887,6 +911,47 @@ ${techStack(input.template)}
|
|
|
887
911
|
};
|
|
888
912
|
}
|
|
889
913
|
|
|
914
|
+
// src/templates/arc42/04-solution-strategy.ts
|
|
915
|
+
function solutionStrategyTemplate(input) {
|
|
916
|
+
return {
|
|
917
|
+
frontmatter: {
|
|
918
|
+
section: "solution-strategy",
|
|
919
|
+
schema_version: 1
|
|
920
|
+
},
|
|
921
|
+
body: `# Solution Strategy
|
|
922
|
+
|
|
923
|
+
Document the fundamental decisions and solution approaches that drive the architecture of ${input.name}. This section explains *why* the architecture looks the way it does \u2014 connecting quality goals to technical decisions.
|
|
924
|
+
|
|
925
|
+
## Technology Decisions
|
|
926
|
+
|
|
927
|
+
| Decision | Rationale | Quality Goal |
|
|
928
|
+
|----------|-----------|-------------|
|
|
929
|
+
| *e.g., Use TypeScript* | *Type safety reduces runtime errors* | *Reliability, Maintainability* |
|
|
930
|
+
| *e.g., Use SQLite for local storage* | *Zero-config, no external service dependency* | *Maintainability* |
|
|
931
|
+
|
|
932
|
+
## Architecture Approach
|
|
933
|
+
|
|
934
|
+
*Describe the high-level architecture pattern and why it was chosen.*
|
|
935
|
+
|
|
936
|
+
- *e.g., Layered architecture with clear module boundaries (enforced by ArcBridge building blocks)*
|
|
937
|
+
- *e.g., YAML as source of truth, DB as queryable cache \u2014 enables version control and human readability*
|
|
938
|
+
|
|
939
|
+
## Quality Goal Strategies
|
|
940
|
+
|
|
941
|
+
| Quality Goal | Strategy |
|
|
942
|
+
|-------------|----------|
|
|
943
|
+
${input.quality_priorities.map((q) => `| ${q} | *How will you achieve ${q}?* |`).join("\n")}
|
|
944
|
+
|
|
945
|
+
## Decomposition Approach
|
|
946
|
+
|
|
947
|
+
*How is the system broken down into building blocks? What principles guide the decomposition?*
|
|
948
|
+
|
|
949
|
+
- *e.g., Feature-based: each major feature is a building block with clear interfaces*
|
|
950
|
+
- *e.g., Layer-based: presentation, business logic, data access*
|
|
951
|
+
`
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
|
|
890
955
|
// src/templates/arc42/05-building-blocks.ts
|
|
891
956
|
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
892
957
|
import { join as join3 } from "path";
|
|
@@ -2297,7 +2362,9 @@ function generateArc42(targetDir, input) {
|
|
|
2297
2362
|
mkdirSync2(decisionsDir, { recursive: true });
|
|
2298
2363
|
const sections = [
|
|
2299
2364
|
{ file: "01-introduction.md", template: introductionTemplate },
|
|
2365
|
+
{ file: "02-constraints.md", template: constraintsTemplate },
|
|
2300
2366
|
{ file: "03-context.md", template: contextTemplate },
|
|
2367
|
+
{ file: "04-solution-strategy.md", template: solutionStrategyTemplate },
|
|
2301
2368
|
{ file: "05-building-blocks.md", template: buildingBlocksTemplate },
|
|
2302
2369
|
{ file: "06-runtime-views.md", template: runtimeViewsTemplate },
|
|
2303
2370
|
{ file: "07-deployment.md", template: deploymentTemplate },
|
|
@@ -2335,7 +2402,7 @@ function phasePlanTemplate(_input) {
|
|
|
2335
2402
|
id: "phase-0-setup",
|
|
2336
2403
|
name: "Project Setup",
|
|
2337
2404
|
phase_number: 0,
|
|
2338
|
-
status: "
|
|
2405
|
+
status: "planned",
|
|
2339
2406
|
description: "Initialize project structure, install dependencies, configure tooling",
|
|
2340
2407
|
gate_requirements: [
|
|
2341
2408
|
"Project builds successfully",
|
|
@@ -2600,7 +2667,7 @@ function phasePlanTemplate2(_input) {
|
|
|
2600
2667
|
id: "phase-0-setup",
|
|
2601
2668
|
name: "Project Setup",
|
|
2602
2669
|
phase_number: 0,
|
|
2603
|
-
status: "
|
|
2670
|
+
status: "planned",
|
|
2604
2671
|
description: "Initialize Vite + React project, configure TypeScript, install dependencies",
|
|
2605
2672
|
gate_requirements: [
|
|
2606
2673
|
"Project builds successfully with Vite",
|
|
@@ -2866,7 +2933,7 @@ function phasePlanTemplate3(_input) {
|
|
|
2866
2933
|
id: "phase-0-setup",
|
|
2867
2934
|
name: "Project Setup",
|
|
2868
2935
|
phase_number: 0,
|
|
2869
|
-
status: "
|
|
2936
|
+
status: "planned",
|
|
2870
2937
|
description: "Initialize API service, configure TypeScript, set up database and middleware",
|
|
2871
2938
|
gate_requirements: [
|
|
2872
2939
|
"Project builds successfully",
|
|
@@ -3049,7 +3116,7 @@ function phasePlanTemplate4(_input) {
|
|
|
3049
3116
|
id: "phase-0-setup",
|
|
3050
3117
|
name: "Project Setup",
|
|
3051
3118
|
phase_number: 0,
|
|
3052
|
-
status: "
|
|
3119
|
+
status: "planned",
|
|
3053
3120
|
description: "Initialize ASP.NET Core project, configure DI, set up database and middleware pipeline",
|
|
3054
3121
|
gate_requirements: [
|
|
3055
3122
|
"Project builds and runs successfully",
|
|
@@ -3248,7 +3315,7 @@ function phasePlanTemplate5(_input) {
|
|
|
3248
3315
|
id: "phase-0-setup",
|
|
3249
3316
|
name: "Project Setup",
|
|
3250
3317
|
phase_number: 0,
|
|
3251
|
-
status: "
|
|
3318
|
+
status: "planned",
|
|
3252
3319
|
description: "Initialize Unity project structure, configure input system, core game loop, and assembly definitions",
|
|
3253
3320
|
gate_requirements: [
|
|
3254
3321
|
"Unity project opens and runs without errors",
|
|
@@ -3533,7 +3600,9 @@ You are responsible for maintaining these sections in \`.arcbridge/arc42/\`. Upd
|
|
|
3533
3600
|
| Section | File | When to update |
|
|
3534
3601
|
|---------|------|----------------|
|
|
3535
3602
|
| **01 Introduction & Goals** | \`01-introduction.md\` | When project scope, stakeholders, or key goals change |
|
|
3603
|
+
| **02 Architecture Constraints** | \`02-constraints.md\` | When new constraints are discovered (technical, organizational, legal) |
|
|
3536
3604
|
| **03 Context & Scope** | \`03-context.md\` | When adding/removing external systems, APIs, or integrations |
|
|
3605
|
+
| **04 Solution Strategy** | \`04-solution-strategy.md\` | When fundamental technology or architecture decisions change |
|
|
3537
3606
|
| **05 Building Blocks** | \`05-building-blocks.md\` | When adding new modules, changing responsibilities, or restructuring layers |
|
|
3538
3607
|
| **06 Runtime Views** | \`06-runtime-views.md\` | When adding key workflows (e.g., auth flow, order processing, data sync) |
|
|
3539
3608
|
| **07 Deployment** | \`07-deployment.md\` | When changing infrastructure, environments, or deployment strategy |
|
|
@@ -7200,20 +7269,26 @@ function safeParseJson(value, fallback) {
|
|
|
7200
7269
|
import { join as join15 } from "path";
|
|
7201
7270
|
import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync } from "fs";
|
|
7202
7271
|
import { parse as parse2, stringify as stringify4 } from "yaml";
|
|
7272
|
+
function readTaskFile(projectRoot, phaseId) {
|
|
7273
|
+
const path = join15(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
|
|
7274
|
+
if (!existsSync8(path)) return { error: "not-found" };
|
|
7275
|
+
const raw = readFileSync8(path, "utf-8");
|
|
7276
|
+
const result = TaskFileSchema.safeParse(parse2(raw));
|
|
7277
|
+
if (!result.success) return { error: "invalid" };
|
|
7278
|
+
return { data: result.data, path };
|
|
7279
|
+
}
|
|
7280
|
+
function readPhasesFile(projectRoot) {
|
|
7281
|
+
const path = join15(projectRoot, ".arcbridge", "plan", "phases.yaml");
|
|
7282
|
+
if (!existsSync8(path)) return { error: "not-found" };
|
|
7283
|
+
const raw = readFileSync8(path, "utf-8");
|
|
7284
|
+
const result = PhasesFileSchema.safeParse(parse2(raw));
|
|
7285
|
+
if (!result.success) return { error: "invalid" };
|
|
7286
|
+
return { data: result.data, path };
|
|
7287
|
+
}
|
|
7203
7288
|
function syncTaskToYaml(projectRoot, phaseId, taskId, status, completedAt) {
|
|
7204
|
-
const
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
"plan",
|
|
7208
|
-
"tasks",
|
|
7209
|
-
`${phaseId}.yaml`
|
|
7210
|
-
);
|
|
7211
|
-
if (!existsSync8(taskPath)) return;
|
|
7212
|
-
const raw = readFileSync8(taskPath, "utf-8");
|
|
7213
|
-
const parsed = parse2(raw);
|
|
7214
|
-
const result = TaskFileSchema.safeParse(parsed);
|
|
7215
|
-
if (!result.success) return;
|
|
7216
|
-
const taskFile = result.data;
|
|
7289
|
+
const readResult = readTaskFile(projectRoot, phaseId);
|
|
7290
|
+
if ("error" in readResult) return;
|
|
7291
|
+
const { data: taskFile, path: taskPath } = readResult;
|
|
7217
7292
|
const task = taskFile.tasks.find((t) => t.id === taskId);
|
|
7218
7293
|
if (!task) return;
|
|
7219
7294
|
task.status = status;
|
|
@@ -7226,39 +7301,19 @@ function syncTaskToYaml(projectRoot, phaseId, taskId, status, completedAt) {
|
|
|
7226
7301
|
}
|
|
7227
7302
|
function addTaskToYaml(projectRoot, phaseId, task) {
|
|
7228
7303
|
const tasksDir = join15(projectRoot, ".arcbridge", "plan", "tasks");
|
|
7229
|
-
const taskPath = join15(tasksDir, `${phaseId}.yaml`);
|
|
7230
7304
|
mkdirSync5(tasksDir, { recursive: true });
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
const raw = readFileSync8(taskPath, "utf-8");
|
|
7234
|
-
const parsed = parse2(raw);
|
|
7235
|
-
const result = TaskFileSchema.safeParse(parsed);
|
|
7236
|
-
if (result.success) {
|
|
7237
|
-
taskFile = result.data;
|
|
7238
|
-
} else {
|
|
7239
|
-
taskFile = { schema_version: 1, phase_id: phaseId, tasks: [] };
|
|
7240
|
-
}
|
|
7241
|
-
} else {
|
|
7242
|
-
taskFile = { schema_version: 1, phase_id: phaseId, tasks: [] };
|
|
7243
|
-
}
|
|
7305
|
+
const readResult = readTaskFile(projectRoot, phaseId);
|
|
7306
|
+
const taskFile = "error" in readResult ? { schema_version: 1, phase_id: phaseId, tasks: [] } : readResult.data;
|
|
7244
7307
|
if (!taskFile.tasks.some((t) => t.id === task.id)) {
|
|
7245
7308
|
taskFile.tasks.push(task);
|
|
7246
7309
|
}
|
|
7310
|
+
const taskPath = join15(tasksDir, `${phaseId}.yaml`);
|
|
7247
7311
|
writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
|
|
7248
7312
|
}
|
|
7249
7313
|
function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
|
|
7250
|
-
const
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
"plan",
|
|
7254
|
-
"phases.yaml"
|
|
7255
|
-
);
|
|
7256
|
-
if (!existsSync8(phasesPath)) return;
|
|
7257
|
-
const raw = readFileSync8(phasesPath, "utf-8");
|
|
7258
|
-
const parsed = parse2(raw);
|
|
7259
|
-
const result = PhasesFileSchema.safeParse(parsed);
|
|
7260
|
-
if (!result.success) return;
|
|
7261
|
-
const phasesFile = result.data;
|
|
7314
|
+
const readResult = readPhasesFile(projectRoot);
|
|
7315
|
+
if ("error" in readResult) return;
|
|
7316
|
+
const { data: phasesFile, path: phasesPath } = readResult;
|
|
7262
7317
|
const phase = phasesFile.phases.find((p) => p.id === phaseId);
|
|
7263
7318
|
if (!phase) return;
|
|
7264
7319
|
phase.status = status;
|
|
@@ -7268,21 +7323,14 @@ function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
|
|
|
7268
7323
|
}
|
|
7269
7324
|
function addPhaseToYaml(projectRoot, phase) {
|
|
7270
7325
|
try {
|
|
7271
|
-
const
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
if (!existsSync8(phasesPath)) {
|
|
7278
|
-
return { success: false, warning: "phases.yaml not found" };
|
|
7279
|
-
}
|
|
7280
|
-
const raw = readFileSync8(phasesPath, "utf-8");
|
|
7281
|
-
const result = PhasesFileSchema.safeParse(parse2(raw));
|
|
7282
|
-
if (!result.success) {
|
|
7283
|
-
return { success: false, warning: "phases.yaml failed validation" };
|
|
7326
|
+
const readResult = readPhasesFile(projectRoot);
|
|
7327
|
+
if ("error" in readResult) {
|
|
7328
|
+
return {
|
|
7329
|
+
success: false,
|
|
7330
|
+
warning: readResult.error === "not-found" ? "phases.yaml not found" : "phases.yaml failed validation"
|
|
7331
|
+
};
|
|
7284
7332
|
}
|
|
7285
|
-
const phasesFile =
|
|
7333
|
+
const { data: phasesFile, path: phasesPath } = readResult;
|
|
7286
7334
|
const existingById = phasesFile.phases.some((p) => p.id === phase.id);
|
|
7287
7335
|
const existingByNumber = phasesFile.phases.some((p) => p.phase_number === phase.phase_number);
|
|
7288
7336
|
if (existingByNumber && !existingById) {
|
|
@@ -7349,25 +7397,17 @@ function syncScenarioToYaml(projectRoot, scenarioId, status, linkedTests, verifi
|
|
|
7349
7397
|
}
|
|
7350
7398
|
function deleteTaskFromYaml(projectRoot, phaseId, taskId) {
|
|
7351
7399
|
try {
|
|
7352
|
-
const
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
`${phaseId}.yaml`
|
|
7358
|
-
);
|
|
7359
|
-
if (!existsSync8(taskPath)) {
|
|
7360
|
-
return { success: true };
|
|
7361
|
-
}
|
|
7362
|
-
const raw = readFileSync8(taskPath, "utf-8");
|
|
7363
|
-
const result = TaskFileSchema.safeParse(parse2(raw));
|
|
7364
|
-
if (!result.success) {
|
|
7400
|
+
const readResult = readTaskFile(projectRoot, phaseId);
|
|
7401
|
+
if ("error" in readResult) {
|
|
7402
|
+
if (readResult.error === "not-found") {
|
|
7403
|
+
return { success: true };
|
|
7404
|
+
}
|
|
7365
7405
|
return {
|
|
7366
7406
|
success: false,
|
|
7367
7407
|
warning: `Could not parse ${phaseId}.yaml \u2014 task may reappear after reindex`
|
|
7368
7408
|
};
|
|
7369
7409
|
}
|
|
7370
|
-
const taskFile =
|
|
7410
|
+
const { data: taskFile, path: taskPath } = readResult;
|
|
7371
7411
|
const before = taskFile.tasks.length;
|
|
7372
7412
|
taskFile.tasks = taskFile.tasks.filter((t) => t.id !== taskId);
|
|
7373
7413
|
if (taskFile.tasks.length === before) {
|
|
@@ -7384,19 +7424,14 @@ function deleteTaskFromYaml(projectRoot, phaseId, taskId) {
|
|
|
7384
7424
|
}
|
|
7385
7425
|
function deletePhaseFromYaml(projectRoot, phaseId) {
|
|
7386
7426
|
try {
|
|
7387
|
-
const
|
|
7388
|
-
if (
|
|
7389
|
-
return { success: false, warning: "phases.yaml not found" };
|
|
7390
|
-
}
|
|
7391
|
-
const raw = readFileSync8(phasesPath, "utf-8");
|
|
7392
|
-
const result = PhasesFileSchema.safeParse(parse2(raw));
|
|
7393
|
-
if (!result.success) {
|
|
7427
|
+
const readResult = readPhasesFile(projectRoot);
|
|
7428
|
+
if ("error" in readResult) {
|
|
7394
7429
|
return {
|
|
7395
7430
|
success: false,
|
|
7396
|
-
warning: "Could not parse phases.yaml \u2014 phase may reappear after reindex"
|
|
7431
|
+
warning: readResult.error === "not-found" ? "phases.yaml not found" : "Could not parse phases.yaml \u2014 phase may reappear after reindex"
|
|
7397
7432
|
};
|
|
7398
7433
|
}
|
|
7399
|
-
const phasesFile =
|
|
7434
|
+
const { data: phasesFile, path: phasesPath } = readResult;
|
|
7400
7435
|
const before = phasesFile.phases.length;
|
|
7401
7436
|
phasesFile.phases = phasesFile.phases.filter((p) => p.id !== phaseId);
|
|
7402
7437
|
if (phasesFile.phases.length === before) {
|
|
@@ -8384,6 +8419,7 @@ export {
|
|
|
8384
8419
|
CURRENT_SCHEMA_VERSION,
|
|
8385
8420
|
PhaseSchema,
|
|
8386
8421
|
PhasesFileSchema,
|
|
8422
|
+
QUALITY_PRIORITIES_DESCRIPTION,
|
|
8387
8423
|
QualityCategorySchema,
|
|
8388
8424
|
QualityPrioritySchema,
|
|
8389
8425
|
QualityScenarioSchema,
|