requ-mcp 0.2.0 → 0.5.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/dist/schema.js CHANGED
@@ -3,15 +3,13 @@ import { z } from "zod";
3
3
  * The requ-mcp data model.
4
4
  *
5
5
  * Traceability spine:
6
- * Requirement → User Story → Acceptance Criterion → TestLink ──┐
7
- * │ (resolved per phase)
8
- * Phase → Execution (test result for a given run) ─────────────┘
6
+ * Component ← Requirement → User Story → (acceptance criteria)
7
+ *
8
+ * Phase → Execution (a scenario result for a run) ─── @US-xxx tag in feature files
9
9
  *
10
- * A TestLink is pure intent ("this Conductor test verifies this criterion").
11
- * Results are Executions owned by a Phase, so coverage is computed *per phase*
12
- * and its evolution can be tracked across releases.
13
- *
14
- * Everything is persisted as flat YAML in `.requ/`.
10
+ * Component: a sub-system/module that maps to broker domain_tags.
11
+ * Phase.id is free-form use the same value as the broker phase_id (e.g. "P1")
12
+ * so both systems share a single identifier.
15
13
  */
16
14
  // ---------------------------------------------------------------------------
17
15
  // Shared
@@ -20,6 +18,21 @@ export const Priority = z.enum(["low", "medium", "high", "critical"]);
20
18
  /** ISO-8601 timestamp string. */
21
19
  export const Timestamp = z.string();
22
20
  // ---------------------------------------------------------------------------
21
+ // Component — sub-system/module; maps to broker domain_tags
22
+ // ---------------------------------------------------------------------------
23
+ export const ComponentStatus = z.enum(["active", "deprecated"]);
24
+ export const Component = z.object({
25
+ /** Unique identifier. Use the same value as broker domain_tag (e.g. 'C-auth'). */
26
+ id: z.string().min(1),
27
+ name: z.string().min(1),
28
+ description: z.string().default(""),
29
+ /** Broker routing tags this component maps to. E.g. ["auth","security"]. */
30
+ domainTags: z.array(z.string()).default([]),
31
+ status: ComponentStatus.default("active"),
32
+ createdAt: Timestamp,
33
+ updatedAt: Timestamp,
34
+ });
35
+ // ---------------------------------------------------------------------------
23
36
  // Requirement — imported source of truth ("what must be built")
24
37
  // ---------------------------------------------------------------------------
25
38
  export const RequirementStatus = z.enum(["active", "deprecated"]);
@@ -30,25 +43,23 @@ export const Requirement = z.object({
30
43
  /** Provenance: where this requirement came from (doc, spec section, ticket). */
31
44
  source: z.string().default(""),
32
45
  priority: Priority.default("medium"),
33
- /** Sub-systems / modules this requirement belongs to (used to slice coverage). */
46
+ /** Component IDs this requirement belongs to (matches Component.id). */
34
47
  components: z.array(z.string()).default([]),
35
48
  tags: z.array(z.string()).default([]),
36
49
  status: RequirementStatus.default("active"),
50
+ /** Target phase this requirement is planned for (matches Phase.id). Optional;
51
+ * unassigned requirements are always in scope for every phase report. */
52
+ phase: z.string().optional(),
37
53
  createdAt: Timestamp,
38
54
  updatedAt: Timestamp,
39
55
  });
40
56
  // ---------------------------------------------------------------------------
41
- // Conductor test identity (shared by TestLink and Execution)
57
+ // Conductor test identity (shared by Execution)
42
58
  // ---------------------------------------------------------------------------
43
59
  export const TestStatus = z.enum(["pass", "fail", "pending"]);
44
60
  /**
45
61
  * Identity of a Conductor test = a cucumber scenario, addressed by its feature
46
- * name + scenario name. Maestro flows run through cucumber step definitions and
47
- * appear in the report as scenarios too, so this is the single unit of linkage.
48
- *
49
- * Scenario → story links are NOT stored here. They live in the feature files as
50
- * `@US-xxx` tags and are derived by scanning the Conductor project (see
51
- * conductor.ts). Executions reference scenarios by this identity.
62
+ * name + scenario name.
52
63
  */
53
64
  const testIdentity = {
54
65
  feature: z.string().min(1),
@@ -61,7 +72,7 @@ export function testKey(t) {
61
72
  /** Tag convention: a scenario tag like `@US-007` links it to story US-007. */
62
73
  export const STORY_TAG_RE = /^@?(US-\d+)$/;
63
74
  // ---------------------------------------------------------------------------
64
- // Acceptance Criterion — descriptive PO content (not individually tested)
75
+ // Acceptance Criterion — descriptive PO content
65
76
  // ---------------------------------------------------------------------------
66
77
  export const AcceptanceCriterion = z.object({
67
78
  id: z.string().regex(/^AC-\d+$/, "id must look like AC-1"),
@@ -79,15 +90,24 @@ export const UserStory = z.object({
79
90
  requirements: z.array(z.string().regex(/^REQ-\d+$/)).min(1),
80
91
  acceptanceCriteria: z.array(AcceptanceCriterion).default([]),
81
92
  status: StoryStatus.default("draft"),
93
+ /** Target phase this story is planned for (matches Phase.id). Optional and
94
+ * independent of the story's requirements; unassigned stories are always in
95
+ * scope for every phase report. */
96
+ phase: z.string().optional(),
82
97
  createdAt: Timestamp,
83
98
  updatedAt: Timestamp,
84
99
  });
85
100
  // ---------------------------------------------------------------------------
86
- // Phase / Release — the dimension coverage evolves along
101
+ // Phase / Release — id is free-form to align with broker phase_id (e.g. "P1")
87
102
  // ---------------------------------------------------------------------------
88
103
  export const PhaseStatus = z.enum(["planned", "active", "completed"]);
89
104
  export const Phase = z.object({
90
- id: z.string().regex(/^PHASE-\d+$/, "id must look like PHASE-001"),
105
+ /**
106
+ * Free-form identifier. Use the same value as the broker phase_id
107
+ * (e.g. "P1", "Sprint-3") so both systems share one identifier.
108
+ * Previously required PHASE-\d+ format; that format is still valid.
109
+ */
110
+ id: z.string().min(1),
91
111
  name: z.string().min(1),
92
112
  /** Sort key for evolution; lower = earlier. */
93
113
  order: z.number().int(),
@@ -109,9 +129,9 @@ export const Execution = z.object({
109
129
  source: ExecutionSource.default("manual"),
110
130
  note: z.string().optional(),
111
131
  });
112
- /** Per-phase execution log file shape. */
132
+ /** Per-phase execution log file shape (YAML mode). */
113
133
  export const ExecutionLog = z.object({
114
- phase: z.string().regex(/^PHASE-\d+$/),
134
+ phase: z.string(),
115
135
  runs: z.array(Execution).default([]),
116
136
  });
117
137
  // ---------------------------------------------------------------------------
@@ -119,18 +139,59 @@ export const ExecutionLog = z.object({
119
139
  // ---------------------------------------------------------------------------
120
140
  export const Config = z.object({
121
141
  name: z.string().default("requ project"),
122
- /**
123
- * Path to the Conductor project root (dir containing features/ and flows/).
124
- * Absolute, or relative to the .requ/ parent (the repo root).
125
- */
142
+ key: z.string().optional(),
143
+ brief: z.string().optional(),
126
144
  conductorPath: z.string().default("."),
127
- /** Detected name of the Conductor project (package.json name or folder name). */
128
145
  conductorName: z.string().optional(),
129
- /** Default path to Conductor's cucumber-json result file (for import). */
130
146
  conductorReportPath: z.string().optional(),
131
- /** The phase new executions are recorded against by default. */
132
- activePhase: z.string().regex(/^PHASE-\d+$/).optional(),
147
+ /** Free-form phase identifier (e.g. "P1"). */
148
+ activePhase: z.string().optional(),
149
+ /** VCS repository reference (requ-mcp never calls VCS; it only records references). */
150
+ repoUrl: z.string().optional(),
151
+ /** Default branch name; treated as "main" when unset. */
152
+ defaultBranch: z.string().optional(),
153
+ vcsType: z.enum(["gitlab"]).optional(),
154
+ });
155
+ // ---------------------------------------------------------------------------
156
+ // VcsRef — a recorded reference to a VCS branch or merge request.
157
+ // requ-mcp holds NO token and never calls the VCS provider; it only stores
158
+ // references that nodes report, for traceability.
159
+ // ---------------------------------------------------------------------------
160
+ export const VcsRefKind = z.enum(["branch", "mr"]);
161
+ export const VcsRefState = z.enum(["opened", "merged", "closed"]);
162
+ export const VcsRef = z.object({
163
+ /** Auto-id, e.g. "MR-5" / "BR-1". */
164
+ id: z.string().min(1),
165
+ kind: VcsRefKind,
166
+ /** MR iid as string, or branch name. */
167
+ ref: z.string().min(1),
168
+ url: z.string().default(""),
169
+ branch: z.string().default(""),
170
+ targetBranch: z.string().optional(),
171
+ component: z.string().optional(),
172
+ storyIds: z.array(z.string()).default([]),
173
+ requirementIds: z.array(z.string()).default([]),
174
+ state: VcsRefState.default("opened"),
175
+ mergeCommit: z.string().optional(),
176
+ createdAt: Timestamp,
177
+ updatedAt: Timestamp,
133
178
  });
134
179
  /** Coverage resolution mode across phases. */
135
180
  export const CoverageMode = z.enum(["cumulative", "strict"]);
181
+ // ---------------------------------------------------------------------------
182
+ // Export / Import
183
+ // ---------------------------------------------------------------------------
184
+ export const ExportPayload = z.object({
185
+ version: z.literal("1"),
186
+ exportedAt: z.string(),
187
+ source: z.object({ name: z.string() }).optional(),
188
+ data: z.object({
189
+ components: z.array(Component).default([]),
190
+ requirements: z.array(Requirement).default([]),
191
+ stories: z.array(UserStory).default([]),
192
+ phases: z.array(Phase).default([]),
193
+ executions: z.record(z.array(Execution)).default({}),
194
+ vcsRefs: z.array(VcsRef).default([]),
195
+ }),
196
+ });
136
197
  //# sourceMappingURL=schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { type Component as TComponent, type Config as TConfig, type Execution as TExecution, type Phase as TPhase, type Requirement as TRequirement, type UserStory as TUserStory, type VcsRef as TVcsRef } from "./schema.js";
2
+ import { Store } from "./storage.js";
3
+ /**
4
+ * SQLite-backed store for HTTP mode.
5
+ * Same async interface as Store; synchronous better-sqlite3 calls wrapped in Promises.
6
+ * A single instance is reused for the lifetime of the HTTP server (WAL mode).
7
+ */
8
+ export declare class SqliteStore {
9
+ readonly root: string;
10
+ readonly baseDir: string;
11
+ private db;
12
+ constructor(root: string, dbPathOverride?: string);
13
+ private get;
14
+ private all;
15
+ private put;
16
+ isInitialized(): Promise<boolean>;
17
+ init(config: TConfig): Promise<void>;
18
+ readConfig(): Promise<TConfig>;
19
+ writeConfig(config: TConfig): Promise<void>;
20
+ conductorRoot(): Promise<string>;
21
+ resolvePath(p: string): string;
22
+ listComponents(): Promise<TComponent[]>;
23
+ getComponent(id: string): Promise<TComponent | null>;
24
+ writeComponent(comp: TComponent): Promise<void>;
25
+ listRequirements(): Promise<TRequirement[]>;
26
+ getRequirement(id: string): Promise<TRequirement | null>;
27
+ writeRequirement(req: TRequirement): Promise<void>;
28
+ listStories(): Promise<TUserStory[]>;
29
+ getStory(id: string): Promise<TUserStory | null>;
30
+ writeStory(story: TUserStory): Promise<void>;
31
+ listPhases(): Promise<TPhase[]>;
32
+ getPhase(id: string): Promise<TPhase | null>;
33
+ writePhase(phase: TPhase): Promise<void>;
34
+ resolvePhaseId(explicit?: string): Promise<string | null>;
35
+ readExecutionLog(phaseId: string): Promise<TExecution[]>;
36
+ appendExecutions(phaseId: string, runs: TExecution[]): Promise<void>;
37
+ readAllExecutions(): Promise<Map<string, TExecution[]>>;
38
+ listVcsRefs(): Promise<TVcsRef[]>;
39
+ getVcsRef(id: string): Promise<TVcsRef | null>;
40
+ writeVcsRef(ref: TVcsRef): Promise<void>;
41
+ updateVcsRef(id: string, patch: Partial<TVcsRef>): Promise<TVcsRef | null>;
42
+ static nextId: typeof Store.nextId;
43
+ }
@@ -0,0 +1,210 @@
1
+ import Database from "better-sqlite3";
2
+ import { mkdirSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { Component, Config, Execution, Phase, Requirement, UserStory, VcsRef, } from "./schema.js";
5
+ import { Store } from "./storage.js";
6
+ const SCHEMA_SQL = `
7
+ CREATE TABLE IF NOT EXISTS config (
8
+ key TEXT PRIMARY KEY,
9
+ value TEXT NOT NULL
10
+ );
11
+ CREATE TABLE IF NOT EXISTS components (
12
+ id TEXT PRIMARY KEY,
13
+ data TEXT NOT NULL
14
+ );
15
+ CREATE TABLE IF NOT EXISTS requirements (
16
+ id TEXT PRIMARY KEY,
17
+ data TEXT NOT NULL
18
+ );
19
+ CREATE TABLE IF NOT EXISTS stories (
20
+ id TEXT PRIMARY KEY,
21
+ data TEXT NOT NULL
22
+ );
23
+ CREATE TABLE IF NOT EXISTS phases (
24
+ id TEXT PRIMARY KEY,
25
+ sort_order INTEGER NOT NULL,
26
+ data TEXT NOT NULL
27
+ );
28
+ CREATE TABLE IF NOT EXISTS executions (
29
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
30
+ phase_id TEXT NOT NULL,
31
+ feature TEXT NOT NULL,
32
+ name TEXT NOT NULL,
33
+ status TEXT NOT NULL,
34
+ ran_at TEXT NOT NULL,
35
+ run_id TEXT,
36
+ source TEXT NOT NULL DEFAULT 'manual',
37
+ note TEXT
38
+ );
39
+ CREATE INDEX IF NOT EXISTS idx_exec_phase ON executions(phase_id);
40
+ CREATE TABLE IF NOT EXISTS vcs_refs (
41
+ id TEXT PRIMARY KEY,
42
+ data TEXT NOT NULL
43
+ );
44
+ `;
45
+ /**
46
+ * SQLite-backed store for HTTP mode.
47
+ * Same async interface as Store; synchronous better-sqlite3 calls wrapped in Promises.
48
+ * A single instance is reused for the lifetime of the HTTP server (WAL mode).
49
+ */
50
+ export class SqliteStore {
51
+ root;
52
+ baseDir;
53
+ db;
54
+ constructor(root, dbPathOverride) {
55
+ this.root = path.resolve(root);
56
+ this.baseDir = path.join(this.root, ".requ");
57
+ const dbPath = dbPathOverride ?? path.join(this.baseDir, "requ.db");
58
+ mkdirSync(path.dirname(dbPath), { recursive: true });
59
+ this.db = new Database(dbPath);
60
+ this.db.pragma("journal_mode = WAL");
61
+ this.db.exec(SCHEMA_SQL);
62
+ }
63
+ // --- helpers ---
64
+ get(stmt, params, schema) {
65
+ const row = (this.db.prepare(stmt).get(...params));
66
+ return row ? schema.parse(JSON.parse(row.data)) : null;
67
+ }
68
+ all(stmt, schema) {
69
+ const rows = (this.db.prepare(stmt).all());
70
+ return rows.map(r => schema.parse(JSON.parse(r.data)));
71
+ }
72
+ put(table, id, data) {
73
+ this.db.prepare(`INSERT OR REPLACE INTO ${table}(id, data) VALUES (?, ?)`)
74
+ .run(id, JSON.stringify(data));
75
+ }
76
+ // --- init / config ---
77
+ async isInitialized() {
78
+ const row = this.db.prepare("SELECT value FROM config WHERE key = ?").get("config");
79
+ return !!row;
80
+ }
81
+ async init(config) {
82
+ await this.writeConfig(config);
83
+ }
84
+ async readConfig() {
85
+ const row = this.db.prepare("SELECT value FROM config WHERE key = ?").get("config");
86
+ if (!row)
87
+ throw new Error("requ project not initialized. Run init_project first.");
88
+ return Config.parse(JSON.parse(row.value));
89
+ }
90
+ async writeConfig(config) {
91
+ const v = Config.parse(config);
92
+ this.db.prepare("INSERT OR REPLACE INTO config(key, value) VALUES (?, ?)").run("config", JSON.stringify(v));
93
+ }
94
+ async conductorRoot() {
95
+ const cfg = await this.readConfig();
96
+ return path.isAbsolute(cfg.conductorPath)
97
+ ? cfg.conductorPath
98
+ : path.resolve(this.root, cfg.conductorPath);
99
+ }
100
+ resolvePath(p) {
101
+ return path.isAbsolute(p) ? p : path.resolve(this.root, p);
102
+ }
103
+ // --- components ---
104
+ async listComponents() {
105
+ return this.all("SELECT data FROM components ORDER BY id", Component);
106
+ }
107
+ async getComponent(id) {
108
+ return this.get("SELECT data FROM components WHERE id = ?", [id], Component);
109
+ }
110
+ async writeComponent(comp) {
111
+ const v = Component.parse(comp);
112
+ this.put("components", v.id, v);
113
+ }
114
+ // --- requirements ---
115
+ async listRequirements() {
116
+ return this.all("SELECT data FROM requirements ORDER BY id", Requirement);
117
+ }
118
+ async getRequirement(id) {
119
+ return this.get("SELECT data FROM requirements WHERE id = ?", [id], Requirement);
120
+ }
121
+ async writeRequirement(req) {
122
+ const v = Requirement.parse(req);
123
+ this.put("requirements", v.id, v);
124
+ }
125
+ // --- stories ---
126
+ async listStories() {
127
+ return this.all("SELECT data FROM stories ORDER BY id", UserStory);
128
+ }
129
+ async getStory(id) {
130
+ return this.get("SELECT data FROM stories WHERE id = ?", [id], UserStory);
131
+ }
132
+ async writeStory(story) {
133
+ const v = UserStory.parse(story);
134
+ this.put("stories", v.id, v);
135
+ }
136
+ // --- phases ---
137
+ async listPhases() {
138
+ const rows = (this.db.prepare("SELECT data FROM phases ORDER BY sort_order").all());
139
+ return rows.map(r => Phase.parse(JSON.parse(r.data)));
140
+ }
141
+ async getPhase(id) {
142
+ const row = this.db.prepare("SELECT data FROM phases WHERE id = ?").get(id);
143
+ return row ? Phase.parse(JSON.parse(row.data)) : null;
144
+ }
145
+ async writePhase(phase) {
146
+ const v = Phase.parse(phase);
147
+ this.db.prepare("INSERT OR REPLACE INTO phases(id, sort_order, data) VALUES (?, ?, ?)").run(v.id, v.order, JSON.stringify(v));
148
+ }
149
+ async resolvePhaseId(explicit) {
150
+ if (explicit)
151
+ return explicit;
152
+ const cfg = await this.readConfig();
153
+ if (cfg.activePhase)
154
+ return cfg.activePhase;
155
+ const phases = await this.listPhases();
156
+ return phases.length ? phases[phases.length - 1].id : null;
157
+ }
158
+ // --- executions ---
159
+ async readExecutionLog(phaseId) {
160
+ const rows = (this.db.prepare("SELECT feature, name, status, ran_at, run_id, source, note FROM executions WHERE phase_id = ? ORDER BY ran_at").all(phaseId));
161
+ return rows.map(r => Execution.parse({
162
+ feature: r.feature,
163
+ name: r.name,
164
+ status: r.status,
165
+ ranAt: r.ran_at,
166
+ runId: r.run_id ?? undefined,
167
+ source: r.source,
168
+ note: r.note ?? undefined,
169
+ }));
170
+ }
171
+ async appendExecutions(phaseId, runs) {
172
+ const stmt = this.db.prepare("INSERT INTO executions(phase_id, feature, name, status, ran_at, run_id, source, note) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
173
+ const tx = this.db.transaction(() => {
174
+ for (const run of runs) {
175
+ const v = Execution.parse(run);
176
+ stmt.run(phaseId, v.feature, v.name, v.status, v.ranAt, v.runId ?? null, v.source, v.note ?? null);
177
+ }
178
+ });
179
+ tx();
180
+ }
181
+ async readAllExecutions() {
182
+ const phases = await this.listPhases();
183
+ const out = new Map();
184
+ for (const p of phases)
185
+ out.set(p.id, await this.readExecutionLog(p.id));
186
+ return out;
187
+ }
188
+ // --- vcs refs ---
189
+ async listVcsRefs() {
190
+ return this.all("SELECT data FROM vcs_refs ORDER BY id", VcsRef);
191
+ }
192
+ async getVcsRef(id) {
193
+ return this.get("SELECT data FROM vcs_refs WHERE id = ?", [id], VcsRef);
194
+ }
195
+ async writeVcsRef(ref) {
196
+ const v = VcsRef.parse(ref);
197
+ this.put("vcs_refs", v.id, v);
198
+ }
199
+ async updateVcsRef(id, patch) {
200
+ const existing = await this.getVcsRef(id);
201
+ if (!existing)
202
+ return null;
203
+ const merged = VcsRef.parse({ ...existing, ...patch, id: existing.id });
204
+ await this.writeVcsRef(merged);
205
+ return merged;
206
+ }
207
+ // Reuse static helper from Store
208
+ static nextId = Store.nextId;
209
+ }
210
+ //# sourceMappingURL=sqlite-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-store.js","sourceRoot":"","sources":["../src/sqlite-store.ts"],"names":[],"mappings":"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"}
package/dist/storage.d.ts CHANGED
@@ -1,21 +1,24 @@
1
- import { type Config as TConfig, type Execution as TExecution, type Phase as TPhase, type Requirement as TRequirement, type UserStory as TUserStory } from "./schema.js";
1
+ import { type Component as TComponent, type Config as TConfig, type Execution as TExecution, type Phase as TPhase, type Requirement as TRequirement, type UserStory as TUserStory, type VcsRef as TVcsRef } from "./schema.js";
2
2
  /**
3
- * File-backed store for the `.requ/` directory.
3
+ * File-backed store for the `.requ/` directory (stdio / YAML mode).
4
4
  *
5
5
  * <root>/.requ/config.yaml
6
+ * <root>/.requ/components/C-auth.yaml
6
7
  * <root>/.requ/requirements/REQ-001.yaml
7
8
  * <root>/.requ/stories/US-001.yaml
8
- * <root>/.requ/phases/PHASE-001.yaml
9
- * <root>/.requ/executions/PHASE-001.yaml (append-style run log)
9
+ * <root>/.requ/phases/P1.yaml
10
+ * <root>/.requ/executions/P1.yaml (append-style run log)
10
11
  */
11
12
  export declare class Store {
12
13
  readonly root: string;
13
14
  readonly baseDir: string;
14
15
  constructor(root: string);
16
+ private get componentDir();
15
17
  private get reqDir();
16
18
  private get storyDir();
17
19
  private get phaseDir();
18
20
  private get execDir();
21
+ private get vcsDir();
19
22
  private get configPath();
20
23
  isInitialized(): Promise<boolean>;
21
24
  init(config: TConfig): Promise<void>;
@@ -24,6 +27,9 @@ export declare class Store {
24
27
  conductorRoot(): Promise<string>;
25
28
  /** Resolve a possibly-relative path against the repo root. */
26
29
  resolvePath(p: string): string;
30
+ listComponents(): Promise<TComponent[]>;
31
+ getComponent(id: string): Promise<TComponent | null>;
32
+ writeComponent(comp: TComponent): Promise<void>;
27
33
  listRequirements(): Promise<TRequirement[]>;
28
34
  getRequirement(id: string): Promise<TRequirement | null>;
29
35
  writeRequirement(req: TRequirement): Promise<void>;
@@ -41,6 +47,12 @@ export declare class Store {
41
47
  appendExecutions(phaseId: string, runs: TExecution[]): Promise<void>;
42
48
  /** All execution logs keyed by phase id. */
43
49
  readAllExecutions(): Promise<Map<string, TExecution[]>>;
50
+ /** Build the on-disk path for a vcs ref id, asserting it stays within vcsDir. */
51
+ private vcsRefPath;
52
+ listVcsRefs(): Promise<TVcsRef[]>;
53
+ getVcsRef(id: string): Promise<TVcsRef | null>;
54
+ writeVcsRef(ref: TVcsRef): Promise<void>;
55
+ updateVcsRef(id: string, patch: Partial<TVcsRef>): Promise<TVcsRef | null>;
44
56
  private readOne;
45
57
  private readAll;
46
58
  /** Next numeric id for a prefix, e.g. nextId("REQ", existing) -> "REQ-003". */
package/dist/storage.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
3
  import YAML from "yaml";
4
- import { Config, Execution, ExecutionLog, Phase, Requirement, UserStory, } from "./schema.js";
4
+ import { Component, Config, Execution, ExecutionLog, Phase, Requirement, UserStory, VcsRef, } from "./schema.js";
5
5
  /**
6
- * File-backed store for the `.requ/` directory.
6
+ * File-backed store for the `.requ/` directory (stdio / YAML mode).
7
7
  *
8
8
  * <root>/.requ/config.yaml
9
+ * <root>/.requ/components/C-auth.yaml
9
10
  * <root>/.requ/requirements/REQ-001.yaml
10
11
  * <root>/.requ/stories/US-001.yaml
11
- * <root>/.requ/phases/PHASE-001.yaml
12
- * <root>/.requ/executions/PHASE-001.yaml (append-style run log)
12
+ * <root>/.requ/phases/P1.yaml
13
+ * <root>/.requ/executions/P1.yaml (append-style run log)
13
14
  */
14
15
  export class Store {
15
16
  root;
@@ -18,21 +19,13 @@ export class Store {
18
19
  this.root = path.resolve(root);
19
20
  this.baseDir = path.join(this.root, ".requ");
20
21
  }
21
- get reqDir() {
22
- return path.join(this.baseDir, "requirements");
23
- }
24
- get storyDir() {
25
- return path.join(this.baseDir, "stories");
26
- }
27
- get phaseDir() {
28
- return path.join(this.baseDir, "phases");
29
- }
30
- get execDir() {
31
- return path.join(this.baseDir, "executions");
32
- }
33
- get configPath() {
34
- return path.join(this.baseDir, "config.yaml");
35
- }
22
+ get componentDir() { return path.join(this.baseDir, "components"); }
23
+ get reqDir() { return path.join(this.baseDir, "requirements"); }
24
+ get storyDir() { return path.join(this.baseDir, "stories"); }
25
+ get phaseDir() { return path.join(this.baseDir, "phases"); }
26
+ get execDir() { return path.join(this.baseDir, "executions"); }
27
+ get vcsDir() { return path.join(this.baseDir, "vcs"); }
28
+ get configPath() { return path.join(this.baseDir, "config.yaml"); }
36
29
  async isInitialized() {
37
30
  try {
38
31
  await fs.access(this.configPath);
@@ -43,10 +36,12 @@ export class Store {
43
36
  }
44
37
  }
45
38
  async init(config) {
39
+ await fs.mkdir(this.componentDir, { recursive: true });
46
40
  await fs.mkdir(this.reqDir, { recursive: true });
47
41
  await fs.mkdir(this.storyDir, { recursive: true });
48
42
  await fs.mkdir(this.phaseDir, { recursive: true });
49
43
  await fs.mkdir(this.execDir, { recursive: true });
44
+ await fs.mkdir(this.vcsDir, { recursive: true });
50
45
  await this.writeConfig(config);
51
46
  }
52
47
  // --- config ---
@@ -68,6 +63,18 @@ export class Store {
68
63
  resolvePath(p) {
69
64
  return path.isAbsolute(p) ? p : path.resolve(this.root, p);
70
65
  }
66
+ // --- components ---
67
+ async listComponents() {
68
+ return this.readAll(this.componentDir, Component);
69
+ }
70
+ async getComponent(id) {
71
+ return this.readOne(path.join(this.componentDir, `${id}.yaml`), Component);
72
+ }
73
+ async writeComponent(comp) {
74
+ await fs.mkdir(this.componentDir, { recursive: true });
75
+ const v = Component.parse(comp);
76
+ await fs.writeFile(path.join(this.componentDir, `${v.id}.yaml`), YAML.stringify(v), "utf8");
77
+ }
71
78
  // --- requirements ---
72
79
  async listRequirements() {
73
80
  return this.readAll(this.reqDir, Requirement);
@@ -139,6 +146,33 @@ export class Store {
139
146
  out.set(p.id, await this.readExecutionLog(p.id));
140
147
  return out;
141
148
  }
149
+ // --- vcs refs ---
150
+ /** Build the on-disk path for a vcs ref id, asserting it stays within vcsDir. */
151
+ vcsRefPath(id) {
152
+ const p = path.join(this.vcsDir, `${id}.yaml`);
153
+ if (path.relative(this.vcsDir, p).startsWith(".."))
154
+ throw new Error("invalid vcs ref id");
155
+ return p;
156
+ }
157
+ async listVcsRefs() {
158
+ return this.readAll(this.vcsDir, VcsRef);
159
+ }
160
+ async getVcsRef(id) {
161
+ return this.readOne(this.vcsRefPath(id), VcsRef);
162
+ }
163
+ async writeVcsRef(ref) {
164
+ await fs.mkdir(this.vcsDir, { recursive: true });
165
+ const v = VcsRef.parse(ref);
166
+ await fs.writeFile(this.vcsRefPath(v.id), YAML.stringify(v), "utf8");
167
+ }
168
+ async updateVcsRef(id, patch) {
169
+ const existing = await this.getVcsRef(id);
170
+ if (!existing)
171
+ return null;
172
+ const merged = VcsRef.parse({ ...existing, ...patch, id: existing.id });
173
+ await this.writeVcsRef(merged);
174
+ return merged;
175
+ }
142
176
  // --- helpers ---
143
177
  async readOne(file, schema) {
144
178
  try {