gsd-pi 2.80.0-dev.710c06e97 → 2.80.0-dev.8b3a59f5c
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/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +131 -9
- package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
- package/dist/resources/extensions/gsd/db-base-schema.js +14 -0
- package/dist/resources/extensions/gsd/db-migration-steps.js +16 -0
- package/dist/resources/extensions/gsd/gsd-db.js +102 -2
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +44 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +27 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +51 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +57 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +51 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +45 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +55 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +72 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +131 -7
- package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
- package/src/resources/extensions/gsd/db-base-schema.ts +15 -0
- package/src/resources/extensions/gsd/db-migration-steps.ts +17 -0
- package/src/resources/extensions/gsd/gsd-db.ts +119 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +125 -1
- package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +44 -0
- /package/dist/web/standalone/.next/static/{CaMwumvGbBPZrJicfsCzV → k-MDjzPMF62Rg1-FR437h}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{CaMwumvGbBPZrJicfsCzV → k-MDjzPMF62Rg1-FR437h}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2b428bb01a7f0322
|
|
@@ -12,7 +12,7 @@ import { appendEvent } from "./workflow-events.js";
|
|
|
12
12
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
13
13
|
import { clearParseCache } from "./files.js";
|
|
14
14
|
import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
|
|
15
|
-
import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk } from "./gsd-db.js";
|
|
15
|
+
import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk, getCompletedMilestoneTaskFileHints, getMilestoneCommitAttributionShas, recordMilestoneCommitAttribution } from "./gsd-db.js";
|
|
16
16
|
import { isValidationTerminal } from "./state.js";
|
|
17
17
|
import { getErrorMessage } from "./error-utils.js";
|
|
18
18
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
@@ -110,11 +110,11 @@ export function hasImplementationArtifacts(basePath, milestoneId) {
|
|
|
110
110
|
// milestone commits instead of treating the self-diff as proof of no work.
|
|
111
111
|
if (changedFiles.length === 0) {
|
|
112
112
|
if (milestoneId && currentBranch === integrationBranch) {
|
|
113
|
-
const
|
|
114
|
-
if (!
|
|
113
|
+
const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
|
|
114
|
+
if (!milestoneEvidence.ok)
|
|
115
115
|
return "unknown";
|
|
116
|
-
if (
|
|
117
|
-
return classifyImplementationFiles(
|
|
116
|
+
if (milestoneEvidence.matched)
|
|
117
|
+
return classifyImplementationFiles(milestoneEvidence.files);
|
|
118
118
|
}
|
|
119
119
|
if (currentBranch && currentBranch !== "HEAD")
|
|
120
120
|
return "absent";
|
|
@@ -129,11 +129,11 @@ export function hasImplementationArtifacts(basePath, milestoneId) {
|
|
|
129
129
|
// insufficient; use the same milestone-tagged evidence fallback as the
|
|
130
130
|
// self-diff retry path before declaring the milestone implementation-free.
|
|
131
131
|
if (milestoneId) {
|
|
132
|
-
const
|
|
133
|
-
if (!
|
|
132
|
+
const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
|
|
133
|
+
if (!milestoneEvidence.ok)
|
|
134
134
|
return "unknown";
|
|
135
|
-
if (
|
|
136
|
-
return classifyImplementationFiles(
|
|
135
|
+
if (milestoneEvidence.matched)
|
|
136
|
+
return classifyImplementationFiles(milestoneEvidence.files);
|
|
137
137
|
}
|
|
138
138
|
return "absent";
|
|
139
139
|
}
|
|
@@ -163,6 +163,9 @@ function classifyImplementationFiles(files) {
|
|
|
163
163
|
function isImplementationPath(file) {
|
|
164
164
|
return !file.startsWith(".gsd/") && !file.startsWith(".gsd\\");
|
|
165
165
|
}
|
|
166
|
+
function normalizeRepoPath(file) {
|
|
167
|
+
return file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
168
|
+
}
|
|
166
169
|
/**
|
|
167
170
|
* Detect the main/master branch name.
|
|
168
171
|
*/
|
|
@@ -256,6 +259,125 @@ function getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId) {
|
|
|
256
259
|
files: [...new Set([...scoped.files, ...unscoped.files])],
|
|
257
260
|
};
|
|
258
261
|
}
|
|
262
|
+
function getChangedFilesFromMilestoneEvidence(basePath, milestoneId) {
|
|
263
|
+
const tagged = getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId);
|
|
264
|
+
if (!tagged.ok)
|
|
265
|
+
return tagged;
|
|
266
|
+
if (tagged.matched && classifyImplementationFiles(tagged.files) === "present")
|
|
267
|
+
return tagged;
|
|
268
|
+
const attributed = getChangedFilesFromAttributedMilestoneCommits(basePath, milestoneId);
|
|
269
|
+
if (!attributed.ok)
|
|
270
|
+
return tagged.matched ? tagged : attributed;
|
|
271
|
+
if (attributed.matched && classifyImplementationFiles(attributed.files) === "present")
|
|
272
|
+
return attributed;
|
|
273
|
+
const backfilled = backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId);
|
|
274
|
+
if (!backfilled.ok)
|
|
275
|
+
return tagged.matched ? tagged : attributed.matched ? attributed : backfilled;
|
|
276
|
+
if (!backfilled.matched) {
|
|
277
|
+
if (tagged.matched)
|
|
278
|
+
return tagged;
|
|
279
|
+
return attributed.matched ? attributed : backfilled;
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
ok: true,
|
|
283
|
+
matched: true,
|
|
284
|
+
files: [...new Set([...tagged.files, ...attributed.files, ...backfilled.files])],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function getChangedFilesFromAttributedMilestoneCommits(basePath, milestoneId) {
|
|
288
|
+
try {
|
|
289
|
+
const shas = getMilestoneCommitAttributionShas(milestoneId);
|
|
290
|
+
if (shas.length === 0)
|
|
291
|
+
return { ok: true, matched: false, files: [] };
|
|
292
|
+
const files = new Set();
|
|
293
|
+
let matched = false;
|
|
294
|
+
for (const sha of shas) {
|
|
295
|
+
if (!isFullCommitSha(sha))
|
|
296
|
+
continue;
|
|
297
|
+
const commitFiles = getChangedFilesForCommit(basePath, sha);
|
|
298
|
+
if (commitFiles.length === 0)
|
|
299
|
+
continue;
|
|
300
|
+
matched = true;
|
|
301
|
+
for (const file of commitFiles)
|
|
302
|
+
files.add(file);
|
|
303
|
+
}
|
|
304
|
+
return { ok: true, matched, files: [...files] };
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
logWarning("recovery", `milestone attribution scan failed: ${e.message}`);
|
|
308
|
+
return { ok: false, matched: false, files: [] };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId) {
|
|
312
|
+
try {
|
|
313
|
+
const milestone = getMilestone(milestoneId);
|
|
314
|
+
const milestoneStartedAt = milestone?.created_at ? Math.floor(Date.parse(milestone.created_at) / 1000) * 1000 : NaN;
|
|
315
|
+
if (!Number.isFinite(milestoneStartedAt))
|
|
316
|
+
return { ok: true, matched: false, files: [] };
|
|
317
|
+
const taskFileHints = getCompletedMilestoneTaskFileHints(milestoneId);
|
|
318
|
+
if (taskFileHints.length === 0)
|
|
319
|
+
return { ok: true, matched: false, files: [] };
|
|
320
|
+
const hintSet = new Set(taskFileHints.map(normalizeRepoPath).filter(Boolean));
|
|
321
|
+
if (hintSet.size === 0)
|
|
322
|
+
return { ok: true, matched: false, files: [] };
|
|
323
|
+
const records = getCommitRecords(basePath);
|
|
324
|
+
const files = new Set();
|
|
325
|
+
let matched = false;
|
|
326
|
+
for (const record of records) {
|
|
327
|
+
if (!isFullCommitSha(record.hash))
|
|
328
|
+
continue;
|
|
329
|
+
if (Date.parse(record.committedAt) < milestoneStartedAt)
|
|
330
|
+
continue;
|
|
331
|
+
if (record.parents.trim().split(/\s+/).filter(Boolean).length > 1)
|
|
332
|
+
continue;
|
|
333
|
+
if (commitMessageHasGsdTrailer(record.message))
|
|
334
|
+
continue;
|
|
335
|
+
const commitFiles = getChangedFilesForCommit(basePath, record.hash);
|
|
336
|
+
const implementationFiles = commitFiles.map(normalizeRepoPath).filter(isImplementationPath);
|
|
337
|
+
if (implementationFiles.length === 0)
|
|
338
|
+
continue;
|
|
339
|
+
if (!implementationFiles.some((file) => hintSet.has(file)))
|
|
340
|
+
continue;
|
|
341
|
+
matched = true;
|
|
342
|
+
for (const file of implementationFiles)
|
|
343
|
+
files.add(file);
|
|
344
|
+
recordMilestoneCommitAttribution({
|
|
345
|
+
commitSha: record.hash,
|
|
346
|
+
milestoneId,
|
|
347
|
+
source: "backfill",
|
|
348
|
+
confidence: 0.8,
|
|
349
|
+
files: implementationFiles,
|
|
350
|
+
createdAt: new Date().toISOString(),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
return { ok: true, matched, files: [...files] };
|
|
354
|
+
}
|
|
355
|
+
catch (e) {
|
|
356
|
+
logWarning("recovery", `milestone attribution backfill failed: ${e.message}`);
|
|
357
|
+
return { ok: false, matched: false, files: [] };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function getCommitRecords(basePath) {
|
|
361
|
+
const logOutput = execFileSync("git", ["log", "--format=%H%x1f%P%x1f%cI%x1f%B%x1e", "HEAD"], {
|
|
362
|
+
cwd: basePath,
|
|
363
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
364
|
+
encoding: "utf-8",
|
|
365
|
+
});
|
|
366
|
+
return logOutput
|
|
367
|
+
.split("\x1e")
|
|
368
|
+
.map((record) => record.trim())
|
|
369
|
+
.filter(Boolean)
|
|
370
|
+
.flatMap((record) => {
|
|
371
|
+
const parts = record.split("\x1f");
|
|
372
|
+
if (parts.length < 4)
|
|
373
|
+
return [];
|
|
374
|
+
const [hash, parents, committedAt, ...messageParts] = parts;
|
|
375
|
+
return [{ hash: hash.trim(), parents: parents.trim(), committedAt: committedAt.trim(), message: messageParts.join("\x1f") }];
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function isFullCommitSha(value) {
|
|
379
|
+
return /^[0-9a-f]{40}$/i.test(value);
|
|
380
|
+
}
|
|
259
381
|
function scanGsdTaggedCommits(basePath, milestoneId, gitArgs) {
|
|
260
382
|
try {
|
|
261
383
|
const logOutput = execFileSync("git", [...gitArgs], {
|
|
@@ -114,14 +114,19 @@ Using the \`write\` tool, persist the full structured report to
|
|
|
114
114
|
LEARNINGS.md is the full, cited audit trail. Write it first — subsequent steps
|
|
115
115
|
feed from its content.
|
|
116
116
|
|
|
117
|
-
### Step 3 —
|
|
117
|
+
### Step 3 — Run one bounded duplicate check
|
|
118
118
|
|
|
119
|
-
Before persisting
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
Before persisting extracted items in Steps 4–6, do at most one duplicate-check
|
|
120
|
+
pass for the durable Decisions, Lessons, and Patterns from this milestone. Use
|
|
121
|
+
the already-written LEARNINGS.md content and call \`memory_query\` once with a
|
|
122
|
+
compact keyword summary for the whole batch. If that result clearly shows a
|
|
123
|
+
semantically equivalent high-confidence memory for an item, mark only that item
|
|
124
|
+
as already captured and skip it in its respective persistence step.
|
|
125
|
+
|
|
126
|
+
Do not re-read milestone artefacts or repeat memory queries category-by-category
|
|
127
|
+
after this point. The memory store is the single source of truth for
|
|
128
|
+
cross-session durable knowledge — no other persistence call is part of this
|
|
129
|
+
flow.
|
|
125
130
|
|
|
126
131
|
### Step 4 — Persist Patterns via \`capture_thought\`
|
|
127
132
|
|
|
@@ -160,11 +165,11 @@ later projection back to a human-visible decisions register stays lossless
|
|
|
160
165
|
|
|
161
166
|
### Step 7 — Deduplication rule (applies to Steps 4, 5, 6)
|
|
162
167
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
over creating a second slightly-different row
|
|
167
|
-
signal.
|
|
168
|
+
Use only the duplicate-check result from Step 3. If that bounded check returned
|
|
169
|
+
a semantically equivalent memory at high confidence for an extracted item, skip
|
|
170
|
+
the capture entirely. Otherwise, persist the item once via \`capture_thought\`.
|
|
171
|
+
Prefer skipping a near-duplicate over creating a second slightly-different row
|
|
172
|
+
— redundancy degrades the signal.
|
|
168
173
|
|
|
169
174
|
### Step 8 — Surprises stay only in LEARNINGS.md
|
|
170
175
|
|
|
@@ -294,6 +294,19 @@ export function createBaseSchemaObjects(db, hooks) {
|
|
|
294
294
|
updated_at TEXT NOT NULL DEFAULT '',
|
|
295
295
|
PRIMARY KEY (trace_id, turn_id, stage)
|
|
296
296
|
)
|
|
297
|
+
`);
|
|
298
|
+
db.exec(`
|
|
299
|
+
CREATE TABLE IF NOT EXISTS milestone_commit_attributions (
|
|
300
|
+
commit_sha TEXT NOT NULL,
|
|
301
|
+
milestone_id TEXT NOT NULL,
|
|
302
|
+
slice_id TEXT DEFAULT NULL,
|
|
303
|
+
task_id TEXT DEFAULT NULL,
|
|
304
|
+
source TEXT NOT NULL DEFAULT 'recorded',
|
|
305
|
+
confidence REAL NOT NULL DEFAULT 1.0,
|
|
306
|
+
files_json TEXT NOT NULL DEFAULT '[]',
|
|
307
|
+
created_at TEXT NOT NULL DEFAULT '',
|
|
308
|
+
PRIMARY KEY (commit_sha, milestone_id)
|
|
309
|
+
)
|
|
297
310
|
`);
|
|
298
311
|
db.exec(`
|
|
299
312
|
CREATE TABLE IF NOT EXISTS audit_events (
|
|
@@ -329,6 +342,7 @@ export function createBaseSchemaObjects(db, hooks) {
|
|
|
329
342
|
db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_turn ON gate_runs(trace_id, turn_id)");
|
|
330
343
|
db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_lookup ON gate_runs(milestone_id, slice_id, task_id, gate_id)");
|
|
331
344
|
db.exec("CREATE INDEX IF NOT EXISTS idx_turn_git_tx_turn ON turn_git_transactions(trace_id, turn_id)");
|
|
345
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_milestone_commit_attr_milestone ON milestone_commit_attributions(milestone_id)");
|
|
332
346
|
db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_trace ON audit_events(trace_id, ts)");
|
|
333
347
|
db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_turn ON audit_events(trace_id, turn_id, ts)");
|
|
334
348
|
db.exec("CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL");
|
|
@@ -363,6 +363,22 @@ export function applyMigrationV21StructuredMemories(db) {
|
|
|
363
363
|
export function applyMigrationV23MilestoneQueue(db) {
|
|
364
364
|
ensureColumn(db, "milestones", "sequence", "ALTER TABLE milestones ADD COLUMN sequence INTEGER DEFAULT 0");
|
|
365
365
|
}
|
|
366
|
+
export function applyMigrationV26MilestoneCommitAttributions(db) {
|
|
367
|
+
db.exec(`
|
|
368
|
+
CREATE TABLE IF NOT EXISTS milestone_commit_attributions (
|
|
369
|
+
commit_sha TEXT NOT NULL,
|
|
370
|
+
milestone_id TEXT NOT NULL,
|
|
371
|
+
slice_id TEXT DEFAULT NULL,
|
|
372
|
+
task_id TEXT DEFAULT NULL,
|
|
373
|
+
source TEXT NOT NULL DEFAULT 'recorded',
|
|
374
|
+
confidence REAL NOT NULL DEFAULT 1.0,
|
|
375
|
+
files_json TEXT NOT NULL DEFAULT '[]',
|
|
376
|
+
created_at TEXT NOT NULL DEFAULT '',
|
|
377
|
+
PRIMARY KEY (commit_sha, milestone_id)
|
|
378
|
+
)
|
|
379
|
+
`);
|
|
380
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_milestone_commit_attr_milestone ON milestone_commit_attributions(milestone_id)");
|
|
381
|
+
}
|
|
366
382
|
export function applyMigrationV22QualityGateRepair(db, hooks) {
|
|
367
383
|
const qgInfo = db.prepare("PRAGMA table_info(quality_gates)").all();
|
|
368
384
|
const taskIdCol = qgInfo.find((r) => r["name"] === "task_id");
|
|
@@ -36,7 +36,7 @@ import { rowToActiveDecision, rowToActiveRequirement, rowToDecision, rowToRequir
|
|
|
36
36
|
import { rowToGate } from "./db-gate-rows.js";
|
|
37
37
|
import { rowToArtifact, rowToMilestone } from "./db-milestone-artifact-rows.js";
|
|
38
38
|
import { backupDatabaseBeforeMigration } from "./db-migration-backup.js";
|
|
39
|
-
import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, } from "./db-migration-steps.js";
|
|
39
|
+
import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, applyMigrationV26MilestoneCommitAttributions, } from "./db-migration-steps.js";
|
|
40
40
|
import { isMemoriesFtsAvailableSchema, tryCreateMemoriesFtsSchema } from "./db-memory-fts-schema.js";
|
|
41
41
|
import { createDbOpenState } from "./db-open-state.js";
|
|
42
42
|
import { createRuntimeKvTableV25 } from "./db-runtime-kv-schema.js";
|
|
@@ -52,7 +52,7 @@ const providerLoader = createSqliteProviderLoader({
|
|
|
52
52
|
nodeVersion: process.versions.node,
|
|
53
53
|
writeStderr: (message) => process.stderr.write(message),
|
|
54
54
|
});
|
|
55
|
-
export const SCHEMA_VERSION =
|
|
55
|
+
export const SCHEMA_VERSION = 26;
|
|
56
56
|
function initSchema(db, fileBacked) {
|
|
57
57
|
if (fileBacked)
|
|
58
58
|
db.exec("PRAGMA journal_mode=WAL");
|
|
@@ -246,6 +246,10 @@ function migrateSchema(db) {
|
|
|
246
246
|
createRuntimeKvTableV25(db);
|
|
247
247
|
recordSchemaVersion(db, 25);
|
|
248
248
|
}
|
|
249
|
+
if (currentVersion < 26) {
|
|
250
|
+
applyMigrationV26MilestoneCommitAttributions(db);
|
|
251
|
+
recordSchemaVersion(db, 26);
|
|
252
|
+
}
|
|
249
253
|
db.exec("COMMIT");
|
|
250
254
|
}
|
|
251
255
|
catch (err) {
|
|
@@ -1158,6 +1162,47 @@ export function getSliceTasks(milestoneId, sliceId) {
|
|
|
1158
1162
|
const rows = currentDb.prepare("SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid ORDER BY sequence, id").all({ ":mid": milestoneId, ":sid": sliceId });
|
|
1159
1163
|
return rows.map(rowToTask);
|
|
1160
1164
|
}
|
|
1165
|
+
export function getCompletedMilestoneTaskFileHints(milestoneId) {
|
|
1166
|
+
if (!currentDb)
|
|
1167
|
+
return [];
|
|
1168
|
+
const rows = currentDb.prepare(`SELECT files, key_files
|
|
1169
|
+
FROM tasks
|
|
1170
|
+
WHERE milestone_id = :mid AND status IN ('complete', 'done')`).all({ ":mid": milestoneId });
|
|
1171
|
+
const hints = new Set();
|
|
1172
|
+
for (const row of rows) {
|
|
1173
|
+
for (const raw of [row["files"], row["key_files"]]) {
|
|
1174
|
+
for (const file of parseStringArrayColumn(raw)) {
|
|
1175
|
+
const normalized = normalizeRepoPath(file);
|
|
1176
|
+
if (normalized)
|
|
1177
|
+
hints.add(normalized);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return [...hints];
|
|
1182
|
+
}
|
|
1183
|
+
function parseStringArrayColumn(raw) {
|
|
1184
|
+
if (Array.isArray(raw))
|
|
1185
|
+
return raw.filter((entry) => typeof entry === "string");
|
|
1186
|
+
if (typeof raw !== "string")
|
|
1187
|
+
return [];
|
|
1188
|
+
const trimmed = raw.trim();
|
|
1189
|
+
if (!trimmed)
|
|
1190
|
+
return [];
|
|
1191
|
+
try {
|
|
1192
|
+
const parsed = JSON.parse(trimmed);
|
|
1193
|
+
if (Array.isArray(parsed))
|
|
1194
|
+
return parsed.filter((entry) => typeof entry === "string");
|
|
1195
|
+
if (typeof parsed === "string")
|
|
1196
|
+
return [parsed];
|
|
1197
|
+
}
|
|
1198
|
+
catch {
|
|
1199
|
+
return trimmed.split(",");
|
|
1200
|
+
}
|
|
1201
|
+
return [];
|
|
1202
|
+
}
|
|
1203
|
+
function normalizeRepoPath(file) {
|
|
1204
|
+
return file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
1205
|
+
}
|
|
1161
1206
|
// ─── ADR-011 Phase 2 escalation helpers ──────────────────────────────────
|
|
1162
1207
|
/** Set pause-on-escalation state on a completed task. Mutually exclusive with awaiting_review. */
|
|
1163
1208
|
export function setTaskEscalationPending(milestoneId, sliceId, taskId, artifactPath) {
|
|
@@ -1735,6 +1780,7 @@ export function deleteMilestone(milestoneId) {
|
|
|
1735
1780
|
currentDb.prepare(`DELETE FROM replan_history WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
1736
1781
|
currentDb.prepare(`DELETE FROM assessments WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
1737
1782
|
currentDb.prepare(`DELETE FROM artifacts WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
1783
|
+
currentDb.prepare(`DELETE FROM milestone_commit_attributions WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
1738
1784
|
currentDb.prepare(`DELETE FROM milestone_leases WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
1739
1785
|
currentDb.prepare(`DELETE FROM milestones WHERE id = :mid`).run({ ":mid": milestoneId });
|
|
1740
1786
|
});
|
|
@@ -1962,6 +2008,59 @@ export function upsertTurnGitTransaction(entry) {
|
|
|
1962
2008
|
":updated_at": entry.updatedAt,
|
|
1963
2009
|
});
|
|
1964
2010
|
}
|
|
2011
|
+
export function getMilestoneCommitAttributionShas(milestoneId) {
|
|
2012
|
+
if (!currentDb)
|
|
2013
|
+
return [];
|
|
2014
|
+
const rows = currentDb.prepare(`SELECT commit_sha
|
|
2015
|
+
FROM milestone_commit_attributions
|
|
2016
|
+
WHERE milestone_id = :mid
|
|
2017
|
+
ORDER BY created_at, commit_sha`).all({ ":mid": milestoneId });
|
|
2018
|
+
return rows
|
|
2019
|
+
.map((row) => typeof row["commit_sha"] === "string" ? row["commit_sha"] : "")
|
|
2020
|
+
.filter(Boolean);
|
|
2021
|
+
}
|
|
2022
|
+
export function recordMilestoneCommitAttribution(entry) {
|
|
2023
|
+
if (!currentDb)
|
|
2024
|
+
return;
|
|
2025
|
+
transaction(() => {
|
|
2026
|
+
currentDb.prepare(`INSERT OR REPLACE INTO milestone_commit_attributions (
|
|
2027
|
+
commit_sha, milestone_id, slice_id, task_id, source, confidence, files_json, created_at
|
|
2028
|
+
) VALUES (
|
|
2029
|
+
:commit_sha, :milestone_id, :slice_id, :task_id, :source, :confidence, :files_json, :created_at
|
|
2030
|
+
)`).run({
|
|
2031
|
+
":commit_sha": entry.commitSha,
|
|
2032
|
+
":milestone_id": entry.milestoneId,
|
|
2033
|
+
":slice_id": entry.sliceId ?? null,
|
|
2034
|
+
":task_id": entry.taskId ?? null,
|
|
2035
|
+
":source": entry.source,
|
|
2036
|
+
":confidence": entry.confidence,
|
|
2037
|
+
":files_json": JSON.stringify(entry.files),
|
|
2038
|
+
":created_at": entry.createdAt,
|
|
2039
|
+
});
|
|
2040
|
+
currentDb.prepare(`INSERT OR IGNORE INTO audit_events (
|
|
2041
|
+
event_id, trace_id, turn_id, caused_by, category, type, ts, payload_json
|
|
2042
|
+
) VALUES (
|
|
2043
|
+
:event_id, :trace_id, :turn_id, :caused_by, :category, :type, :ts, :payload_json
|
|
2044
|
+
)`).run({
|
|
2045
|
+
":event_id": `milestone-commit-attribution:${entry.milestoneId}:${entry.commitSha}`,
|
|
2046
|
+
":trace_id": "milestone-commit-attribution",
|
|
2047
|
+
":turn_id": null,
|
|
2048
|
+
":caused_by": null,
|
|
2049
|
+
":category": "git",
|
|
2050
|
+
":type": "milestone-commit-attribution-recorded",
|
|
2051
|
+
":ts": entry.createdAt,
|
|
2052
|
+
":payload_json": JSON.stringify({
|
|
2053
|
+
commitSha: entry.commitSha,
|
|
2054
|
+
milestoneId: entry.milestoneId,
|
|
2055
|
+
sliceId: entry.sliceId ?? null,
|
|
2056
|
+
taskId: entry.taskId ?? null,
|
|
2057
|
+
source: entry.source,
|
|
2058
|
+
confidence: entry.confidence,
|
|
2059
|
+
files: entry.files,
|
|
2060
|
+
}),
|
|
2061
|
+
});
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
1965
2064
|
export function insertAuditEvent(entry) {
|
|
1966
2065
|
if (!currentDb)
|
|
1967
2066
|
return;
|
|
@@ -2048,6 +2147,7 @@ export function clearEngineHierarchy() {
|
|
|
2048
2147
|
currentDb.exec("DELETE FROM slice_dependencies");
|
|
2049
2148
|
currentDb.exec("DELETE FROM assessments");
|
|
2050
2149
|
currentDb.exec("DELETE FROM replan_history");
|
|
2150
|
+
currentDb.exec("DELETE FROM milestone_commit_attributions");
|
|
2051
2151
|
currentDb.exec("DELETE FROM tasks");
|
|
2052
2152
|
currentDb.exec("DELETE FROM slices");
|
|
2053
2153
|
currentDb.exec("DELETE FROM milestone_leases");
|