valent-pipeline 0.2.5 → 0.2.7
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/bin/cli.js +146 -0
- package/package.json +1 -1
- package/pipeline/prompts/embed.md +1 -2
- package/pipeline/prompts/knowledge.md +7 -7
- package/pipeline/prompts/lead.md +2 -4
- package/pipeline/steps/orchestration/sprint-groom.md +2 -4
- package/pipeline/steps/orchestration/sprint-init.md +1 -2
- package/pipeline/steps/orchestration/sprint-review.md +1 -2
- package/pipeline/steps/readiness/sprint-review.md +1 -2
- package/skills/valent-setup-backlog/SKILL.md +2 -2
- package/src/commands/db-index.js +254 -0
- package/src/commands/db-query.js +140 -0
package/bin/cli.js
CHANGED
|
@@ -75,4 +75,150 @@ dbCmd
|
|
|
75
75
|
await dbRebuild();
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
dbCmd
|
|
79
|
+
.command('index-handoff')
|
|
80
|
+
.description('Index a single handoff artifact into the database')
|
|
81
|
+
.requiredOption('--file <path>', 'Path to the artifact file')
|
|
82
|
+
.requiredOption('--story-id <id>', 'Story identifier')
|
|
83
|
+
.requiredOption('--agent <name>', 'Agent that produced the artifact')
|
|
84
|
+
.requiredOption('--artifact-type <type>', 'Artifact type (e.g., reqs-brief)')
|
|
85
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
86
|
+
.action(async (options) => {
|
|
87
|
+
const { dbIndexHandoff } = await import('../src/commands/db-index.js');
|
|
88
|
+
await dbIndexHandoff(options);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
dbCmd
|
|
92
|
+
.command('index-working')
|
|
93
|
+
.description('Index an artifact into the grooming working table')
|
|
94
|
+
.requiredOption('--file <path>', 'Path to the artifact file')
|
|
95
|
+
.requiredOption('--story-id <id>', 'Story identifier')
|
|
96
|
+
.requiredOption('--agent <name>', 'Agent that produced the artifact')
|
|
97
|
+
.requiredOption('--artifact-type <type>', 'Artifact type')
|
|
98
|
+
.option('--sprint-id <id>', 'Sprint identifier')
|
|
99
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
100
|
+
.action(async (options) => {
|
|
101
|
+
const { dbIndexWorking } = await import('../src/commands/db-index.js');
|
|
102
|
+
await dbIndexWorking(options);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
dbCmd
|
|
106
|
+
.command('flush-working')
|
|
107
|
+
.description('Flush working table to main artifacts table')
|
|
108
|
+
.option('--sprint-id <id>', 'Sprint identifier')
|
|
109
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
110
|
+
.action(async (options) => {
|
|
111
|
+
const { dbFlushWorking } = await import('../src/commands/db-index.js');
|
|
112
|
+
await dbFlushWorking(options);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
dbCmd
|
|
116
|
+
.command('query-working')
|
|
117
|
+
.description('Query artifacts from the grooming working table')
|
|
118
|
+
.option('--sprint-id <id>', 'Sprint identifier')
|
|
119
|
+
.option('--exclude-story <id>', 'Exclude this story from results')
|
|
120
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
121
|
+
.action(async (options) => {
|
|
122
|
+
const { dbQueryWorking } = await import('../src/commands/db-index.js');
|
|
123
|
+
await dbQueryWorking(options);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
dbCmd
|
|
127
|
+
.command('record-calibration')
|
|
128
|
+
.description('Record story calibration data for estimation accuracy')
|
|
129
|
+
.requiredOption('--story-id <id>', 'Story identifier')
|
|
130
|
+
.option('--story-points <n>', 'Fibonacci story points', parseInt)
|
|
131
|
+
.option('--ac-count <n>', 'Number of acceptance criteria', parseInt)
|
|
132
|
+
.option('--surface <type>', 'Surface area (backend, full-stack, etc.)')
|
|
133
|
+
.option('--estimated-points <n>', 'Estimated points', parseInt)
|
|
134
|
+
.option('--actual-mins <n>', 'Actual execution minutes', parseFloat)
|
|
135
|
+
.option('--rework-cycles <n>', 'Number of rework cycles', parseInt)
|
|
136
|
+
.option('--sprint-id <id>', 'Sprint identifier')
|
|
137
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
138
|
+
.action(async (options) => {
|
|
139
|
+
const { dbRecordCalibration } = await import('../src/commands/db-index.js');
|
|
140
|
+
await dbRecordCalibration(options);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
dbCmd
|
|
144
|
+
.command('query-velocity')
|
|
145
|
+
.description('Query sprint velocity history for estimation')
|
|
146
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
147
|
+
.action(async (options) => {
|
|
148
|
+
const { dbQueryVelocity } = await import('../src/commands/db-index.js');
|
|
149
|
+
await dbQueryVelocity(options);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
dbCmd
|
|
153
|
+
.command('embed')
|
|
154
|
+
.description('Process embed-instructions.md from retrospective')
|
|
155
|
+
.requiredOption('--file <path>', 'Path to embed-instructions.md')
|
|
156
|
+
.option('--curated-path <path>', 'Path to curated knowledge files')
|
|
157
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
158
|
+
.action(async (options) => {
|
|
159
|
+
const { dbEmbed } = await import('../src/commands/db-index.js');
|
|
160
|
+
await dbEmbed(options);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// db query commands
|
|
164
|
+
dbCmd
|
|
165
|
+
.command('query-artifact')
|
|
166
|
+
.description('Get a specific artifact from the database')
|
|
167
|
+
.requiredOption('--story <id>', 'Story identifier')
|
|
168
|
+
.requiredOption('--type <type>', 'Artifact type')
|
|
169
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
170
|
+
.action(async (options) => {
|
|
171
|
+
const { dbQueryArtifact } = await import('../src/commands/db-query.js');
|
|
172
|
+
await dbQueryArtifact(options);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
dbCmd
|
|
176
|
+
.command('query-directives')
|
|
177
|
+
.description('Get active correction directives')
|
|
178
|
+
.option('--agent <name>', 'Filter by target agent')
|
|
179
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
180
|
+
.action(async (options) => {
|
|
181
|
+
const { dbQueryDirectives } = await import('../src/commands/db-query.js');
|
|
182
|
+
await dbQueryDirectives(options);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
dbCmd
|
|
186
|
+
.command('search')
|
|
187
|
+
.description('Full-text search across all artifacts')
|
|
188
|
+
.requiredOption('--query <text>', 'Search query')
|
|
189
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
190
|
+
.action(async (options) => {
|
|
191
|
+
const { dbQuerySearch } = await import('../src/commands/db-query.js');
|
|
192
|
+
await dbQuerySearch(options);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
dbCmd
|
|
196
|
+
.command('query-list')
|
|
197
|
+
.description('List all artifacts for a story')
|
|
198
|
+
.requiredOption('--story <id>', 'Story identifier')
|
|
199
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
200
|
+
.action(async (options) => {
|
|
201
|
+
const { dbQueryList } = await import('../src/commands/db-query.js');
|
|
202
|
+
await dbQueryList(options);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
dbCmd
|
|
206
|
+
.command('query-stories')
|
|
207
|
+
.description('List all stories in the database')
|
|
208
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
209
|
+
.action(async (options) => {
|
|
210
|
+
const { dbQueryStories } = await import('../src/commands/db-query.js');
|
|
211
|
+
await dbQueryStories(options);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
dbCmd
|
|
215
|
+
.command('query-bugs-since')
|
|
216
|
+
.description('Get bugs filed since a date')
|
|
217
|
+
.requiredOption('--since <date>', 'Date in YYYY-MM-DD format')
|
|
218
|
+
.option('--db-path <path>', 'Database path (defaults to config)')
|
|
219
|
+
.action(async (options) => {
|
|
220
|
+
const { dbQueryBugsSince } = await import('../src/commands/db-query.js');
|
|
221
|
+
await dbQueryBugsSince(options);
|
|
222
|
+
});
|
|
223
|
+
|
|
78
224
|
program.parse();
|
package/package.json
CHANGED
|
@@ -30,8 +30,7 @@ Verify `{story_output_dir}/embed-instructions.md` exists. If missing, send `[BLO
|
|
|
30
30
|
**If `{knowledge_mode}` is `sqlite` (recommended):**
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
|
|
34
|
-
--db-path {sqlite_db_path} \
|
|
33
|
+
node .valent-pipeline/bin/cli.js db embed {story_output_dir}/embed-instructions.md \
|
|
35
34
|
--curated-path {curated_files_path}
|
|
36
35
|
```
|
|
37
36
|
|
|
@@ -38,7 +38,7 @@ Read all files in `{curated_files_path}`. Build in-memory index of file names, s
|
|
|
38
38
|
**If `{knowledge_mode}` is `sqlite`:**
|
|
39
39
|
Verify the database is accessible by running:
|
|
40
40
|
```bash
|
|
41
|
-
|
|
41
|
+
node .valent-pipeline/bin/cli.js db query-stories
|
|
42
42
|
```
|
|
43
43
|
If it returns results or "No stories in database", the DB is accessible. If the command fails, operate in curated-only mode.
|
|
44
44
|
|
|
@@ -53,12 +53,12 @@ For each incoming query:
|
|
|
53
53
|
1. Search correction directives for relevant entries
|
|
54
54
|
2. Search curated knowledge files for matching sections
|
|
55
55
|
3. If database connected (SQLite mode): query using the CLI tool and read stdout for results:
|
|
56
|
-
- Fetch a specific artifact: `
|
|
57
|
-
- Fetch directives for an agent: `
|
|
58
|
-
- Full-text search: `
|
|
59
|
-
- List artifacts for a story: `
|
|
60
|
-
- List all stories: `
|
|
61
|
-
- Cross-story bug search: `
|
|
56
|
+
- Fetch a specific artifact: `node .valent-pipeline/bin/cli.js db query-artifact --story KANBAN-001 --type reqs-brief`
|
|
57
|
+
- Fetch directives for an agent: `node .valent-pipeline/bin/cli.js db query-directives --agent BEND`
|
|
58
|
+
- Full-text search: `node .valent-pipeline/bin/cli.js db search --query "acceptance criteria"`
|
|
59
|
+
- List artifacts for a story: `node .valent-pipeline/bin/cli.js db query-list --story KANBAN-001`
|
|
60
|
+
- List all stories: `node .valent-pipeline/bin/cli.js db query-stories`
|
|
61
|
+
- Cross-story bug search: `node .valent-pipeline/bin/cli.js db query-bugs-since --since 2026-03-01`
|
|
62
62
|
If ChromaDB mode: use collection query (ChromaDB)
|
|
63
63
|
4. Compose response: targeted, SHORT (aim ~200 tokens, max 500)
|
|
64
64
|
5. Include source reference: `Source: curated/{file}#section` or `Source: sqlite:artifacts/{story_id}/{type}` or `Source: correction-directives#{directive-id}`
|
package/pipeline/prompts/lead.md
CHANGED
|
@@ -493,8 +493,7 @@ This runs PMCP in parallel with QA-B's test execution, removing it from the crit
|
|
|
493
493
|
When `{knowledge_mode}` is `sqlite` and you receive a `[HANDOFF]` from an agent that produces an output file, index the artifact into the SQLite database so downstream agents can query it via Knowledge:
|
|
494
494
|
|
|
495
495
|
```bash
|
|
496
|
-
|
|
497
|
-
--db-path {sqlite_db_path} \
|
|
496
|
+
node .valent-pipeline/bin/cli.js db index-handoff --file {story_output_dir}/{artifact_file} \
|
|
498
497
|
--story-id {story_id} \
|
|
499
498
|
--agent {agent_name} \
|
|
500
499
|
--artifact-type {type}
|
|
@@ -589,8 +588,7 @@ Tear down all per-story teammates. Send `shutdown_request` to each individually.
|
|
|
589
588
|
If `{knowledge_mode}` is `sqlite`, record story actuals to the calibration table for future estimation accuracy:
|
|
590
589
|
|
|
591
590
|
```bash
|
|
592
|
-
|
|
593
|
-
--db-path {sqlite_db_path} \
|
|
591
|
+
node .valent-pipeline/bin/cli.js db record-calibration \
|
|
594
592
|
--story-id {story_id} \
|
|
595
593
|
--ac-count {ac_count_from_reqs_brief} \
|
|
596
594
|
--surface {project_type} \
|
|
@@ -20,8 +20,7 @@ For each story in grooming candidates (up to `{groom_target}` from sprint-init):
|
|
|
20
20
|
4. On UXA handoff → update status to `test-case-development` → QA-A writes `qa-test-spec.md`
|
|
21
21
|
5. On QA-A handoff → index all artifacts to SQLite **working table**:
|
|
22
22
|
```bash
|
|
23
|
-
|
|
24
|
-
--db-path {sqlite_db_path} \
|
|
23
|
+
node .valent-pipeline/bin/cli.js db index-working \
|
|
25
24
|
--story-id {story_id} \
|
|
26
25
|
--sprint-id {current_sprint_id}
|
|
27
26
|
```
|
|
@@ -47,8 +46,7 @@ After every `{sprint_max_groom_batch}` stories (default: 10), kill and respawn P
|
|
|
47
46
|
After all stories groomed:
|
|
48
47
|
|
|
49
48
|
```bash
|
|
50
|
-
|
|
51
|
-
--db-path {sqlite_db_path} \
|
|
49
|
+
node .valent-pipeline/bin/cli.js db flush-working \
|
|
52
50
|
--sprint-id {current_sprint_id}
|
|
53
51
|
```
|
|
54
52
|
|
|
@@ -15,8 +15,7 @@ Sprint ID format:
|
|
|
15
15
|
Query the calibration table for historical velocity data:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
|
|
19
|
-
--db-path {sqlite_db_path}
|
|
18
|
+
node .valent-pipeline/bin/cli.js db query-velocity
|
|
20
19
|
```
|
|
21
20
|
|
|
22
21
|
**Velocity rules:**
|
|
@@ -34,8 +34,7 @@ Update `sprint-{n}-status.yaml`:
|
|
|
34
34
|
For each shipped story, ensure the calibration table has complete data:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
|
|
38
|
-
--db-path {sqlite_db_path} \
|
|
37
|
+
node .valent-pipeline/bin/cli.js db record-calibration \
|
|
39
38
|
--story-id {story_id} \
|
|
40
39
|
--story-points {fibonacci_estimate} \
|
|
41
40
|
--estimated-points {fibonacci_estimate} \
|
|
@@ -9,8 +9,7 @@ These steps run AFTER the standalone review (Steps 1-8) passes. If the standalon
|
|
|
9
9
|
Query the SQLite working table for specs from other stories groomed in this sprint batch:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
|
|
13
|
-
--db-path {sqlite_db_path} \
|
|
12
|
+
node .valent-pipeline/bin/cli.js db query-working \
|
|
14
13
|
--sprint-id {current_sprint_id} \
|
|
15
14
|
--exclude-story {story_id}
|
|
16
15
|
```
|
|
@@ -173,8 +173,8 @@ If the repo is empty or brand new (no source code yet), skip the subagents and c
|
|
|
173
173
|
### Step 7b: Initialize and Populate Knowledge Database
|
|
174
174
|
|
|
175
175
|
After writing the curated knowledge files:
|
|
176
|
-
1. Run `valent-pipeline db init` to create the SQLite database if it doesn't exist
|
|
177
|
-
2. Run `
|
|
176
|
+
1. Run `node .valent-pipeline/bin/cli.js db init` to create the SQLite database if it doesn't exist
|
|
177
|
+
2. Run `node .valent-pipeline/bin/cli.js db rebuild` to index any existing story artifacts
|
|
178
178
|
3. The database is now ready for the Knowledge Agent to query during story execution
|
|
179
179
|
|
|
180
180
|
## Step 8: Report
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { join, dirname } from 'path';
|
|
2
|
+
import { readFileSync, existsSync, appendFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
3
|
+
|
|
4
|
+
const ARTIFACT_MAP = {
|
|
5
|
+
'reqs-brief.md': { type: 'reqs-brief', agent: 'REQS' },
|
|
6
|
+
'uxa-spec.md': { type: 'uxa-spec', agent: 'UXA' },
|
|
7
|
+
'qa-test-spec.md': { type: 'qa-test-spec', agent: 'QA-A' },
|
|
8
|
+
'bend-handoff.md': { type: 'bend-handoff', agent: 'BEND' },
|
|
9
|
+
'fend-handoff.md': { type: 'fend-handoff', agent: 'FEND' },
|
|
10
|
+
'critic-review.md': { type: 'critic-review', agent: 'CRITIC' },
|
|
11
|
+
'execution-report.md': { type: 'execution-report', agent: 'QA-B' },
|
|
12
|
+
'bugs.md': { type: 'bugs', agent: 'QA-B' },
|
|
13
|
+
'traceability-matrix.md': { type: 'traceability-matrix', agent: 'QA-B' },
|
|
14
|
+
'readiness-review.md': { type: 'readiness-review', agent: 'READINESS' },
|
|
15
|
+
'judge-review.md': { type: 'judge-review', agent: 'JUDGE' },
|
|
16
|
+
'judge-decision.md': { type: 'judge-decision', agent: 'JUDGE' },
|
|
17
|
+
'story-report.md': { type: 'story-report', agent: 'JUDGE' },
|
|
18
|
+
'pmcp-evidence.md': { type: 'pmcp-evidence', agent: 'PMCP' },
|
|
19
|
+
'visual-validation-checklist.md': { type: 'visual-validation-checklist', agent: 'QA-A' },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function getDb(dbPath) {
|
|
23
|
+
const Database = require(join(process.cwd(), '.valent-pipeline', 'node_modules', 'better-sqlite3'));
|
|
24
|
+
return new Database(dbPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveDbPath(options) {
|
|
28
|
+
if (options.dbPath) return options.dbPath;
|
|
29
|
+
const configPath = join(process.cwd(), '.valent-pipeline', 'pipeline-config.yaml');
|
|
30
|
+
if (existsSync(configPath)) {
|
|
31
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
32
|
+
const match = content.match(/sqlite_db_path:\s*"?([^"\n]+)"?/);
|
|
33
|
+
if (match) return join(process.cwd(), match[1].trim());
|
|
34
|
+
}
|
|
35
|
+
return join(process.cwd(), '.valent-pipeline', 'pipeline.db');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function dbIndexHandoff(options) {
|
|
39
|
+
const dbPath = resolveDbPath(options);
|
|
40
|
+
const { file, storyId, agent, artifactType } = options;
|
|
41
|
+
|
|
42
|
+
if (!existsSync(file)) {
|
|
43
|
+
console.error(`File not found: ${file}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const content = readFileSync(file, 'utf-8');
|
|
48
|
+
if (content.includes('status: skipped')) {
|
|
49
|
+
console.log(`Skipping pass-through artifact: ${file}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const db = getDb(dbPath);
|
|
54
|
+
const id = `${storyId}:${artifactType}`;
|
|
55
|
+
db.prepare(`
|
|
56
|
+
INSERT OR REPLACE INTO artifacts (id, story_id, agent, artifact_type, content, metadata)
|
|
57
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
58
|
+
`).run(id, storyId, agent, artifactType, content, JSON.stringify({ source: 'handoff', file }));
|
|
59
|
+
db.close();
|
|
60
|
+
console.log(`Indexed ${id} (${content.length} chars)`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function dbIndexWorking(options) {
|
|
64
|
+
const dbPath = resolveDbPath(options);
|
|
65
|
+
const { file, storyId, agent, artifactType, sprintId } = options;
|
|
66
|
+
|
|
67
|
+
if (!existsSync(file)) {
|
|
68
|
+
console.error(`File not found: ${file}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const content = readFileSync(file, 'utf-8');
|
|
73
|
+
const db = getDb(dbPath);
|
|
74
|
+
db.prepare(`
|
|
75
|
+
INSERT OR REPLACE INTO artifacts_working (story_id, agent, artifact_type, content, metadata)
|
|
76
|
+
VALUES (?, ?, ?, ?, ?)
|
|
77
|
+
`).run(storyId, agent, artifactType, content, JSON.stringify({ sprint_id: sprintId, file }));
|
|
78
|
+
db.close();
|
|
79
|
+
console.log(`Indexed to working table: ${storyId}:${artifactType}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function dbFlushWorking(options) {
|
|
83
|
+
const dbPath = resolveDbPath(options);
|
|
84
|
+
const { sprintId } = options;
|
|
85
|
+
|
|
86
|
+
const db = getDb(dbPath);
|
|
87
|
+
const rows = db.prepare('SELECT * FROM artifacts_working').all();
|
|
88
|
+
|
|
89
|
+
const insert = db.prepare(`
|
|
90
|
+
INSERT OR REPLACE INTO artifacts (id, story_id, agent, artifact_type, content, metadata)
|
|
91
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
92
|
+
`);
|
|
93
|
+
|
|
94
|
+
const flush = db.transaction(() => {
|
|
95
|
+
for (const row of rows) {
|
|
96
|
+
const id = `${row.story_id}:${row.artifact_type}`;
|
|
97
|
+
insert.run(id, row.story_id, row.agent, row.artifact_type, row.content, row.metadata);
|
|
98
|
+
}
|
|
99
|
+
db.prepare('DELETE FROM artifacts_working').run();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
flush();
|
|
103
|
+
db.close();
|
|
104
|
+
console.log(`Flushed ${rows.length} artifacts from working table to main table`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function dbQueryWorking(options) {
|
|
108
|
+
const dbPath = resolveDbPath(options);
|
|
109
|
+
const { sprintId, excludeStory } = options;
|
|
110
|
+
|
|
111
|
+
const db = getDb(dbPath);
|
|
112
|
+
let rows;
|
|
113
|
+
if (excludeStory) {
|
|
114
|
+
rows = db.prepare(
|
|
115
|
+
'SELECT story_id, agent, artifact_type, content FROM artifacts_working WHERE story_id != ?'
|
|
116
|
+
).all(excludeStory);
|
|
117
|
+
} else {
|
|
118
|
+
rows = db.prepare('SELECT story_id, agent, artifact_type, content FROM artifacts_working').all();
|
|
119
|
+
}
|
|
120
|
+
db.close();
|
|
121
|
+
|
|
122
|
+
if (rows.length === 0) {
|
|
123
|
+
console.log('No artifacts in working table');
|
|
124
|
+
} else {
|
|
125
|
+
for (const r of rows) {
|
|
126
|
+
console.log(`--- ${r.story_id}:${r.artifact_type} (${r.agent}) ---`);
|
|
127
|
+
console.log(r.content);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function dbRecordCalibration(options) {
|
|
133
|
+
const dbPath = resolveDbPath(options);
|
|
134
|
+
const { storyId, storyPoints, acCount, surface, estimatedPoints, actualMins, reworkCycles, sprintId } = options;
|
|
135
|
+
|
|
136
|
+
const db = getDb(dbPath);
|
|
137
|
+
db.prepare(`
|
|
138
|
+
INSERT OR REPLACE INTO calibration (story_id, story_points, ac_count, surface, estimated_points, actual_mins, rework_cycles, sprint_id)
|
|
139
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
140
|
+
`).run(storyId, storyPoints ?? null, acCount ?? null, surface ?? null, estimatedPoints ?? null, actualMins ?? null, reworkCycles ?? 0, sprintId ?? null);
|
|
141
|
+
db.close();
|
|
142
|
+
console.log(`Recorded calibration data for ${storyId}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function dbQueryVelocity(options) {
|
|
146
|
+
const dbPath = resolveDbPath(options);
|
|
147
|
+
const db = getDb(dbPath);
|
|
148
|
+
|
|
149
|
+
const rows = db.prepare(`
|
|
150
|
+
SELECT sprint_id, SUM(story_points) as points_shipped
|
|
151
|
+
FROM calibration
|
|
152
|
+
WHERE sprint_id IS NOT NULL AND story_points IS NOT NULL
|
|
153
|
+
GROUP BY sprint_id
|
|
154
|
+
ORDER BY created_at DESC
|
|
155
|
+
LIMIT 5
|
|
156
|
+
`).all();
|
|
157
|
+
|
|
158
|
+
db.close();
|
|
159
|
+
|
|
160
|
+
if (rows.length === 0) {
|
|
161
|
+
console.log('No sprint velocity data available');
|
|
162
|
+
} else {
|
|
163
|
+
console.log('Sprint velocity history (most recent first):');
|
|
164
|
+
for (const r of rows) {
|
|
165
|
+
console.log(` ${r.sprint_id}: ${r.points_shipped} points`);
|
|
166
|
+
}
|
|
167
|
+
const avg = rows.reduce((sum, r) => sum + r.points_shipped, 0) / rows.length;
|
|
168
|
+
console.log(` SMA-${rows.length}: ${Math.round(avg)} points`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function dbEmbed(options) {
|
|
173
|
+
const dbPath = resolveDbPath(options);
|
|
174
|
+
const { file, curatedPath } = options;
|
|
175
|
+
|
|
176
|
+
if (!existsSync(file)) {
|
|
177
|
+
console.error(`File not found: ${file}`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const content = readFileSync(file, 'utf-8');
|
|
182
|
+
const items = parseEmbedInstructions(content);
|
|
183
|
+
console.log(`Parsed ${items.length} embed instructions`);
|
|
184
|
+
|
|
185
|
+
const db = getDb(dbPath);
|
|
186
|
+
let indexed = 0;
|
|
187
|
+
let curatedCount = 0;
|
|
188
|
+
|
|
189
|
+
for (const item of items) {
|
|
190
|
+
if (item.target.startsWith('curated/')) {
|
|
191
|
+
const targetFile = join(curatedPath || './knowledge/curated', item.target.replace('curated/', ''));
|
|
192
|
+
mkdirSync(dirname(targetFile), { recursive: true });
|
|
193
|
+
|
|
194
|
+
if (item.metadata?.append_section && existsSync(targetFile)) {
|
|
195
|
+
const existing = readFileSync(targetFile, 'utf-8');
|
|
196
|
+
if (existing.includes(`## ${item.metadata.append_section}`)) {
|
|
197
|
+
console.log(` Skipped duplicate section: ${item.metadata.append_section}`);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const section = item.metadata?.append_section
|
|
203
|
+
? `\n## ${item.metadata.append_section}\n\n${item.content}\n`
|
|
204
|
+
: `\n${item.content}\n`;
|
|
205
|
+
appendFileSync(targetFile, section, 'utf-8');
|
|
206
|
+
curatedCount++;
|
|
207
|
+
} else {
|
|
208
|
+
const id = `embed:${Date.now()}:${indexed}`;
|
|
209
|
+
db.prepare(`
|
|
210
|
+
INSERT OR REPLACE INTO artifacts (id, story_id, agent, artifact_type, content, metadata)
|
|
211
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
212
|
+
`).run(id, item.metadata?.stories?.[0] || 'batch', 'Retrospective', item.metadata?.category || 'pattern', item.content, JSON.stringify(item.metadata || {}));
|
|
213
|
+
indexed++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
db.close();
|
|
218
|
+
console.log(`Complete: ${indexed} items indexed, ${curatedCount} curated files updated`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function parseEmbedInstructions(content) {
|
|
222
|
+
const items = [];
|
|
223
|
+
const sections = content.split(/^### \d+\./m).filter(s => s.trim());
|
|
224
|
+
|
|
225
|
+
for (const section of sections) {
|
|
226
|
+
const yamlMatch = section.match(/```yaml\n([\s\S]*?)```/);
|
|
227
|
+
if (!yamlMatch) continue;
|
|
228
|
+
|
|
229
|
+
const yamlContent = yamlMatch[1];
|
|
230
|
+
const contentMatch = yamlContent.match(/content:\s*>\s*\n([\s\S]*?)(?=\n\s+target:|$)/);
|
|
231
|
+
const targetMatch = yamlContent.match(/target:\s*"?([^"\n]+)"?/);
|
|
232
|
+
|
|
233
|
+
if (contentMatch && targetMatch) {
|
|
234
|
+
const itemContent = contentMatch[1].split('\n').map(l => l.trim()).join('\n').trim();
|
|
235
|
+
const target = targetMatch[1].trim();
|
|
236
|
+
const metadata = {};
|
|
237
|
+
const metadataMatch = yamlContent.match(/metadata:\n([\s\S]*?)(?=\n\S|$)/);
|
|
238
|
+
if (metadataMatch) {
|
|
239
|
+
for (const line of metadataMatch[1].split('\n')) {
|
|
240
|
+
const kv = line.match(/^\s+(\w+):\s+(.+)$/);
|
|
241
|
+
if (kv) {
|
|
242
|
+
let val = kv[2].trim();
|
|
243
|
+
if (val.startsWith('[') && val.endsWith(']')) {
|
|
244
|
+
val = val.slice(1, -1).split(',').map(s => s.trim());
|
|
245
|
+
}
|
|
246
|
+
metadata[kv[1]] = val;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
items.push({ content: itemContent, target, metadata });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return items;
|
|
254
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
|
|
4
|
+
function resolveDbPath(options) {
|
|
5
|
+
if (options.dbPath) return options.dbPath;
|
|
6
|
+
const configPath = join(process.cwd(), '.valent-pipeline', 'pipeline-config.yaml');
|
|
7
|
+
if (existsSync(configPath)) {
|
|
8
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
9
|
+
const match = content.match(/sqlite_db_path:\s*"?([^"\n]+)"?/);
|
|
10
|
+
if (match) return join(process.cwd(), match[1].trim());
|
|
11
|
+
}
|
|
12
|
+
return join(process.cwd(), '.valent-pipeline', 'pipeline.db');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getDb(dbPath) {
|
|
16
|
+
const Database = require(join(process.cwd(), '.valent-pipeline', 'node_modules', 'better-sqlite3'));
|
|
17
|
+
return new Database(dbPath, { readonly: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function dbQueryArtifact(options) {
|
|
21
|
+
const dbPath = resolveDbPath(options);
|
|
22
|
+
const { story, type } = options;
|
|
23
|
+
|
|
24
|
+
const db = getDb(dbPath);
|
|
25
|
+
const row = db.prepare(
|
|
26
|
+
'SELECT content, agent, created_at FROM artifacts WHERE story_id = ? AND artifact_type = ?'
|
|
27
|
+
).get(story, type);
|
|
28
|
+
db.close();
|
|
29
|
+
|
|
30
|
+
if (row) {
|
|
31
|
+
console.log(`--- ${type} for ${story} (by ${row.agent}, ${row.created_at}) ---`);
|
|
32
|
+
console.log(row.content);
|
|
33
|
+
} else {
|
|
34
|
+
console.log(`No artifact found: story=${story}, type=${type}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function dbQueryDirectives(options) {
|
|
39
|
+
const dbPath = resolveDbPath(options);
|
|
40
|
+
const { agent } = options;
|
|
41
|
+
|
|
42
|
+
const db = getDb(dbPath);
|
|
43
|
+
let rows;
|
|
44
|
+
if (agent) {
|
|
45
|
+
rows = db.prepare(
|
|
46
|
+
"SELECT id, directive, reason FROM correction_directives WHERE target_agent = ? AND status = 'active'"
|
|
47
|
+
).all(agent);
|
|
48
|
+
} else {
|
|
49
|
+
rows = db.prepare(
|
|
50
|
+
"SELECT id, target_agent, directive, reason FROM correction_directives WHERE status = 'active'"
|
|
51
|
+
).all();
|
|
52
|
+
}
|
|
53
|
+
db.close();
|
|
54
|
+
|
|
55
|
+
if (rows.length === 0) {
|
|
56
|
+
console.log(agent ? `No active directives for ${agent}` : 'No active correction directives');
|
|
57
|
+
} else {
|
|
58
|
+
for (const r of rows) {
|
|
59
|
+
const target = r.target_agent ? ` [${r.target_agent}]` : '';
|
|
60
|
+
console.log(`${r.id}${target}: ${r.directive}`);
|
|
61
|
+
if (r.reason) console.log(` Reason: ${r.reason}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function dbQuerySearch(options) {
|
|
67
|
+
const dbPath = resolveDbPath(options);
|
|
68
|
+
const { query } = options;
|
|
69
|
+
|
|
70
|
+
const db = getDb(dbPath);
|
|
71
|
+
const rows = db.prepare(
|
|
72
|
+
"SELECT story_id, agent, artifact_type, snippet(artifacts_fts, 0, '>>>', '<<<', '...', 40) as snippet FROM artifacts_fts WHERE artifacts_fts MATCH ? ORDER BY rank LIMIT 10"
|
|
73
|
+
).all(query);
|
|
74
|
+
db.close();
|
|
75
|
+
|
|
76
|
+
if (rows.length === 0) {
|
|
77
|
+
console.log(`No results for: ${query}`);
|
|
78
|
+
} else {
|
|
79
|
+
for (const r of rows) {
|
|
80
|
+
console.log(`[${r.story_id}] ${r.artifact_type} (${r.agent}): ${r.snippet}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function dbQueryList(options) {
|
|
86
|
+
const dbPath = resolveDbPath(options);
|
|
87
|
+
const { story } = options;
|
|
88
|
+
|
|
89
|
+
const db = getDb(dbPath);
|
|
90
|
+
const rows = db.prepare(
|
|
91
|
+
'SELECT artifact_type, agent, length(content) as size, created_at FROM artifacts WHERE story_id = ? ORDER BY created_at'
|
|
92
|
+
).all(story);
|
|
93
|
+
db.close();
|
|
94
|
+
|
|
95
|
+
if (rows.length === 0) {
|
|
96
|
+
console.log(`No artifacts for story ${story}`);
|
|
97
|
+
} else {
|
|
98
|
+
console.log(`Artifacts for ${story}:`);
|
|
99
|
+
for (const r of rows) {
|
|
100
|
+
console.log(` ${r.artifact_type} (${r.agent}, ${r.size} chars, ${r.created_at})`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function dbQueryStories(options) {
|
|
106
|
+
const dbPath = resolveDbPath(options);
|
|
107
|
+
const db = getDb(dbPath);
|
|
108
|
+
const rows = db.prepare(
|
|
109
|
+
'SELECT DISTINCT story_id, COUNT(*) as artifact_count FROM artifacts GROUP BY story_id ORDER BY story_id'
|
|
110
|
+
).all();
|
|
111
|
+
db.close();
|
|
112
|
+
|
|
113
|
+
if (rows.length === 0) {
|
|
114
|
+
console.log('No stories in database');
|
|
115
|
+
} else {
|
|
116
|
+
for (const r of rows) {
|
|
117
|
+
console.log(`${r.story_id}: ${r.artifact_count} artifacts`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function dbQueryBugsSince(options) {
|
|
123
|
+
const dbPath = resolveDbPath(options);
|
|
124
|
+
const { since } = options;
|
|
125
|
+
|
|
126
|
+
const db = getDb(dbPath);
|
|
127
|
+
const rows = db.prepare(
|
|
128
|
+
"SELECT story_id, content FROM artifacts WHERE artifact_type = 'bugs' AND created_at > ? ORDER BY created_at"
|
|
129
|
+
).all(since);
|
|
130
|
+
db.close();
|
|
131
|
+
|
|
132
|
+
if (rows.length === 0) {
|
|
133
|
+
console.log(`No bugs filed since ${since}`);
|
|
134
|
+
} else {
|
|
135
|
+
for (const r of rows) {
|
|
136
|
+
console.log(`--- Bugs from ${r.story_id} ---`);
|
|
137
|
+
console.log(r.content);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|