@opengsd/gsd-pi 1.7.0-dev.30c4ec2f → 1.7.0-dev.e0039c3c
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/doctor.js +653 -14
- package/dist/resources/extensions/gsd/md-importer.js +2 -7
- package/dist/resources/extensions/gsd/migrate/planning-writer.js +2 -8
- package/dist/resources/extensions/gsd/migration-auto-check.js +0 -4
- package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +14 -54
- package/dist/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.js +5 -48
- 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 +8 -8
- 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 +8 -8
- 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/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/package.json +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/doctor.ts +673 -14
- package/src/resources/extensions/gsd/md-importer.ts +2 -7
- package/src/resources/extensions/gsd/migrate/planning-writer.ts +3 -9
- package/src/resources/extensions/gsd/migration-auto-check.ts +0 -3
- package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +20 -67
- package/src/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.ts +5 -47
- package/src/resources/extensions/gsd/tests/db-authority-regression.test.ts +1 -76
- package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +0 -53
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +0 -53
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -4
- package/src/resources/extensions/gsd/tests/planning-writer.test.ts +1 -18
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +3 -18
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +25 -177
- package/dist/resources/extensions/gsd/doctor-history.js +0 -65
- package/dist/resources/extensions/gsd/doctor-state-checks.js +0 -509
- package/dist/resources/extensions/gsd/doctor-workspace-checks.js +0 -91
- package/src/resources/extensions/gsd/doctor-history.ts +0 -81
- package/src/resources/extensions/gsd/doctor-state-checks.ts +0 -527
- package/src/resources/extensions/gsd/doctor-workspace-checks.ts +0 -97
- package/src/resources/extensions/gsd/tests/doctor-history-public-api.test.ts +0 -25
- /package/dist/web/standalone/.next/static/{ue2JDvA1wboxPBPeblD5N → QoErQJVEEGAIpfz1TMIHj}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{ue2JDvA1wboxPBPeblD5N → QoErQJVEEGAIpfz1TMIHj}/_ssgManifest.js +0 -0
|
@@ -656,13 +656,8 @@ export function migrateHierarchyToDb(basePath) {
|
|
|
656
656
|
updateSliceStatus(milestoneId, sliceEntry.id, sliceStatus, importCompletionTimestamp(sliceSummary));
|
|
657
657
|
}
|
|
658
658
|
counts.slices++;
|
|
659
|
-
// Insert tasks from parsed plan
|
|
660
|
-
|
|
661
|
-
// tasks from a placeholder <tasks> block (would falsely flip
|
|
662
|
-
// planning -> executing before the slice is decomposed). Once the
|
|
663
|
-
// `[sketch]` badge is removed by a real plan-slice, tasks import
|
|
664
|
-
// normally on the next pass. (#1286)
|
|
665
|
-
if (!plan || sliceEntry.isSketch)
|
|
659
|
+
// Insert tasks from parsed plan
|
|
660
|
+
if (!plan)
|
|
666
661
|
continue;
|
|
667
662
|
for (const taskEntry of plan.tasks) {
|
|
668
663
|
// Per K002: use 'complete' not 'done'
|
|
@@ -9,7 +9,6 @@ import { mkdirSync } from "node:fs";
|
|
|
9
9
|
import { join, relative } from "node:path";
|
|
10
10
|
import { getAllMilestones, getMilestoneSlices, getSliceTasks } from "../gsd-db.js";
|
|
11
11
|
import { saveFile } from "../files.js";
|
|
12
|
-
import { isClosedStatus } from "../status-guards.js";
|
|
13
12
|
import { applyPlanningProjectionWrites, } from "../compat/planning-compat.js";
|
|
14
13
|
function planningRoot(basePath) {
|
|
15
14
|
return join(basePath, ".planning");
|
|
@@ -87,11 +86,7 @@ function formatPlanningPlan(phaseNum, planNum, title, tasks) {
|
|
|
87
86
|
lines.push("<tasks>");
|
|
88
87
|
for (const t of tasks) {
|
|
89
88
|
const est = t.estimate ? ` _(${t.estimate})_` : "";
|
|
90
|
-
|
|
91
|
-
// projection preserves completion. Hardcoding `[ ]` here silently reset
|
|
92
|
-
// every completed historical task to unchecked on each reconcile (#1276).
|
|
93
|
-
const box = t.done ? "[x]" : "[ ]";
|
|
94
|
-
lines.push(`- ${box} **${t.id}**: ${t.title}${est}`);
|
|
89
|
+
lines.push(`- [ ] **${t.id}**: ${t.title}${est}`);
|
|
95
90
|
}
|
|
96
91
|
lines.push("</tasks>");
|
|
97
92
|
lines.push("");
|
|
@@ -136,7 +131,7 @@ export async function writePlanningDirectory(basePath, layout) {
|
|
|
136
131
|
const phaseDir = join(root, "phases", phaseDirName);
|
|
137
132
|
mkdirSync(phaseDir, { recursive: true });
|
|
138
133
|
const tasks = getSliceTasks(milestone.id, slice.id);
|
|
139
|
-
const isDone = tasks.length > 0 && tasks.every((t) =>
|
|
134
|
+
const isDone = tasks.length > 0 && tasks.every((t) => t.status === "complete");
|
|
140
135
|
roadmapEntries.push({
|
|
141
136
|
number: phaseNum,
|
|
142
137
|
title: slice.title || slice.id,
|
|
@@ -162,7 +157,6 @@ export async function writePlanningDirectory(basePath, layout) {
|
|
|
162
157
|
id: task.id,
|
|
163
158
|
title: task.title || task.id,
|
|
164
159
|
estimate: task.estimate || undefined,
|
|
165
|
-
done: isClosedStatus(task.status),
|
|
166
160
|
},
|
|
167
161
|
]));
|
|
168
162
|
paths.push(planPath);
|
|
@@ -117,10 +117,6 @@ export function scanMarkdownHierarchy(basePath) {
|
|
|
117
117
|
scan.counts.slices += roadmap.slices.length;
|
|
118
118
|
for (const slice of roadmap.slices) {
|
|
119
119
|
scan.slices.add(`${milestoneId}/${slice.id}`);
|
|
120
|
-
// Sketch slices carry only a stub PLAN until refined; match migrateHierarchyToDb
|
|
121
|
-
// and do not count placeholder tasks from the <tasks> block (#1286).
|
|
122
|
-
if (slice.isSketch)
|
|
123
|
-
continue;
|
|
124
120
|
const planPath = resolveSliceFile(basePath, milestoneId, slice.id, "PLAN");
|
|
125
121
|
if (!planPath || !existsSync(planPath))
|
|
126
122
|
continue;
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// instruction. Markdown hierarchy import is reserved for operator-controlled
|
|
6
6
|
// migration/recovery commands, not automatic runtime reconciliation.
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
|
-
import {
|
|
9
|
-
import { findMilestoneIds
|
|
8
|
+
import { getMilestone, isDbAvailable } from "../../gsd-db.js";
|
|
9
|
+
import { findMilestoneIds } from "../../milestone-ids.js";
|
|
10
10
|
import { resolveMilestoneFile, resolveMilestonePath } from "../../paths.js";
|
|
11
11
|
function milestoneHasContent(basePath, milestoneId) {
|
|
12
12
|
const roadmap = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
@@ -16,26 +16,6 @@ function milestoneHasContent(basePath, milestoneId) {
|
|
|
16
16
|
(context !== null && existsSync(context)) ||
|
|
17
17
|
(summary !== null && existsSync(summary)));
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
* A directory extracted to a bare `M{NNN}` id is not unregistered when the
|
|
21
|
-
* milestone is actually registered under a `unique_milestone_ids` suffixed id
|
|
22
|
-
* (`M{NNN}-<suffix>`) for the same sequence number. The flat-phase extractor in
|
|
23
|
-
* `milestone-ids` cannot recover the suffix from a descriptive slug (e.g.
|
|
24
|
-
* `07-v40fmq-m007-...-footer-system`), so it falls through to the bare `M007`.
|
|
25
|
-
* Probing the DB for a suffixed row on that sequence resolves the directory to
|
|
26
|
-
* its registered identity instead of firing a false-positive drift (issue
|
|
27
|
-
* #1281).
|
|
28
|
-
*/
|
|
29
|
-
function hasRegisteredSuffixedVariant(milestoneId) {
|
|
30
|
-
const { suffix, num } = parseMilestoneId(milestoneId);
|
|
31
|
-
// Only bare, well-formed `M{NNN}` ids can be ambiguous with a suffixed row.
|
|
32
|
-
if (suffix || num === 0)
|
|
33
|
-
return false;
|
|
34
|
-
return getAllMilestones().some((m) => {
|
|
35
|
-
const parsed = parseMilestoneId(m.id);
|
|
36
|
-
return parsed.num === num && parsed.suffix !== undefined;
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
19
|
export function detectUnregisteredMilestoneDrift(_state, ctx) {
|
|
40
20
|
if (!isDbAvailable())
|
|
41
21
|
return [];
|
|
@@ -43,8 +23,6 @@ export function detectUnregisteredMilestoneDrift(_state, ctx) {
|
|
|
43
23
|
for (const milestoneId of findMilestoneIds(ctx.basePath)) {
|
|
44
24
|
if (getMilestone(milestoneId))
|
|
45
25
|
continue;
|
|
46
|
-
if (hasRegisteredSuffixedVariant(milestoneId))
|
|
47
|
-
continue;
|
|
48
26
|
if (!milestoneHasContent(ctx.basePath, milestoneId))
|
|
49
27
|
continue;
|
|
50
28
|
drifts.push({ kind: "unregistered-milestone", milestoneId });
|
|
@@ -52,47 +30,29 @@ export function detectUnregisteredMilestoneDrift(_state, ctx) {
|
|
|
52
30
|
return drifts;
|
|
53
31
|
}
|
|
54
32
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
33
|
+
* Repair intentionally fails closed. The project-root DB is authoritative at
|
|
34
|
+
* runtime; markdown-only milestones must be reconciled through an explicit,
|
|
35
|
+
* operator-controlled action so operators opt into changing canonical state.
|
|
36
|
+
*
|
|
37
|
+
* The hint deliberately leads with the *targeted*, non-destructive options. The
|
|
38
|
+
* common cause of this drift is a directory left under an old ID after a
|
|
39
|
+
* `unique_milestone_ids` rename, where the right fix is to rename (move) the
|
|
40
|
+
* directory — not a full DB reimport. `/gsd recover --confirm` is a destructive
|
|
41
|
+
* clear-and-reimport of the entire DB and is offered only as a last resort, so
|
|
42
|
+
* users do not reach for it expecting a targeted repair (see issue #826).
|
|
62
43
|
*/
|
|
63
|
-
function
|
|
44
|
+
export function repairUnregisteredMilestone(record, ctx) {
|
|
64
45
|
const dir = resolveMilestonePath(ctx.basePath, record.milestoneId);
|
|
65
46
|
const dirHint = dir ?? `the .gsd directory for ${record.milestoneId}`;
|
|
66
|
-
|
|
47
|
+
throw new Error(`Milestone ${record.milestoneId} exists only as markdown projection ` +
|
|
67
48
|
"(on-disk ROADMAP/CONTEXT/SUMMARY with no authoritative DB row). " +
|
|
68
49
|
"Runtime reconciliation will not import markdown into the DB. Choose one:\n" +
|
|
69
50
|
` • Rename: if this directory is the same milestone under an old ID (e.g. a unique_milestone_ids rename), move \`${dirHint}\` to the current ID's directory and re-run.\n` +
|
|
70
51
|
` • Discard: if this milestone is no longer relevant, delete \`${dirHint}\` and re-run.\n` +
|
|
71
52
|
" • Last resort: `/gsd recover --confirm` performs a destructive full DB clear-and-reimport — it does NOT do a targeted import and can replace or duplicate existing DB milestones.");
|
|
72
53
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Terminal blocker for an unregistered milestone. The project-root DB is
|
|
75
|
-
* authoritative at runtime; markdown-only milestones must be reconciled through
|
|
76
|
-
* an explicit, operator-controlled action so operators opt into changing
|
|
77
|
-
* canonical state. Exposing this as a `blocker` (matching the `artifact-db`
|
|
78
|
-
* convention) surfaces the drift as a pause-with-hint that gates dispatch,
|
|
79
|
-
* instead of a guaranteed repair throw that escalates auto-mode health to red
|
|
80
|
-
* (see issue #1281).
|
|
81
|
-
*/
|
|
82
|
-
export function describeUnregisteredMilestoneBlocker(record, ctx) {
|
|
83
|
-
return unregisteredMilestoneGuidance(record, ctx);
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Repair intentionally fails closed. Retained as a defensive fallback for the
|
|
87
|
-
* reconciliation engine; in practice `blocker` short-circuits this drift into a
|
|
88
|
-
* non-fatal pause before repair is ever attempted.
|
|
89
|
-
*/
|
|
90
|
-
export function repairUnregisteredMilestone(record, ctx) {
|
|
91
|
-
throw new Error(unregisteredMilestoneGuidance(record, ctx));
|
|
92
|
-
}
|
|
93
54
|
export const unregisteredMilestoneHandler = {
|
|
94
55
|
kind: "unregistered-milestone",
|
|
95
56
|
detect: detectUnregisteredMilestoneDrift,
|
|
96
|
-
blocker: describeUnregisteredMilestoneBlocker,
|
|
97
57
|
repair: repairUnregisteredMilestone,
|
|
98
58
|
};
|
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
// 2. Flag-OFF downgrade: when progressive_planning is off, dispatch routes
|
|
8
8
|
// sketch slices to plan-slice, which writes PLAN.md but leaves
|
|
9
9
|
// is_sketch=1 — the next reconciliation pass clears it.
|
|
10
|
-
import { existsSync
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
11
|
import { getSketchedSliceIds, isDbAvailable, setSliceSketchFlag, } from "../../gsd-db.js";
|
|
12
|
-
import { parsePlan } from "../../parsers-legacy.js";
|
|
13
12
|
import { resolveSliceFile } from "../../paths.js";
|
|
14
13
|
export function detectStaleSketchFlags(state, ctx) {
|
|
15
14
|
if (!isDbAvailable())
|
|
@@ -19,54 +18,12 @@ export function detectStaleSketchFlags(state, ctx) {
|
|
|
19
18
|
return [];
|
|
20
19
|
const sliceIds = getSketchedSliceIds(mid);
|
|
21
20
|
return sliceIds
|
|
22
|
-
.filter((sid) =>
|
|
21
|
+
.filter((sid) => {
|
|
22
|
+
const planPath = resolveSliceFile(ctx.basePath, mid, sid, "PLAN");
|
|
23
|
+
return planPath !== null && existsSync(planPath);
|
|
24
|
+
})
|
|
23
25
|
.map((sid) => ({ kind: "stale-sketch-flag", mid, sid }));
|
|
24
26
|
}
|
|
25
|
-
/**
|
|
26
|
-
* True when a task title is a synthetic placeholder emitted by a projection
|
|
27
|
-
* round-trip rather than a real, refined task. `migrate/transformer.buildTaskTitle`
|
|
28
|
-
* produces two shapes when a plan was never decomposed:
|
|
29
|
-
* - `Plan NN` (fallback when phase/plan frontmatter is absent)
|
|
30
|
-
* - `${phase} ${plan}` (e.g. `00 01`, `00 03b`, `29-auth-system 01`)
|
|
31
|
-
* Neither carries genuine planning intent, so both must be treated as stubs.
|
|
32
|
-
*/
|
|
33
|
-
function isPlaceholderTaskTitle(title) {
|
|
34
|
-
const t = title.trim();
|
|
35
|
-
if (/^Plan\s+\d+[a-z]*$/i.test(t))
|
|
36
|
-
return true;
|
|
37
|
-
// buildTaskTitle returns `${phase} ${plan}` when frontmatter has both fields.
|
|
38
|
-
// Phase slugs are digit-led (e.g. `00`, `29-auth-system`); require that so
|
|
39
|
-
// legitimate titles like `Step 1` are not misclassified as stubs.
|
|
40
|
-
return /^\d[\w-]*\s+\d+[a-z]*$/i.test(t);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* A sketch slice counts as "planned" (so its is_sketch flag may be cleared)
|
|
44
|
-
* only when it has a *real* plan on disk — not merely any PLAN file.
|
|
45
|
-
*
|
|
46
|
-
* File existence alone (#1287) is too weak: a stub/placeholder PLAN, a
|
|
47
|
-
* crash-leftover, or a projection round-trip that emits synthetic placeholder
|
|
48
|
-
* task titles (migrate/transformer.buildTaskTitle) all satisfy existsSync yet
|
|
49
|
-
* were never actually refined. Clearing the flag for one strips the `refining`
|
|
50
|
-
* guard in phase derivation and lets a phantom task drive dispatch.
|
|
51
|
-
*
|
|
52
|
-
* A legitimate plan-slice always decomposes into >= 1 genuine task, so require
|
|
53
|
-
* at least one non-placeholder task. This is strictly stricter than the old
|
|
54
|
-
* existence check — the documented crash-recovery scenarios (a real plan-slice
|
|
55
|
-
* that wrote its tasks) still clear, a bare stub no longer does.
|
|
56
|
-
*/
|
|
57
|
-
function sketchIsPlanned(basePath, mid, sid) {
|
|
58
|
-
const planPath = resolveSliceFile(basePath, mid, sid, "PLAN");
|
|
59
|
-
if (planPath === null || !existsSync(planPath))
|
|
60
|
-
return false;
|
|
61
|
-
try {
|
|
62
|
-
const plan = parsePlan(readFileSync(planPath, "utf-8"));
|
|
63
|
-
return plan.tasks.some((t) => !isPlaceholderTaskTitle(t.title));
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// A PLAN we cannot parse is not a trustworthy "planning done" signal.
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
27
|
export function repairStaleSketchFlag(record) {
|
|
71
28
|
setSliceSketchFlag(record.mid, record.sid, false);
|
|
72
29
|
}
|