gsd-pi 2.81.0-dev.72a81bdf3 → 2.82.0-dev.725028083
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/README.md +46 -28
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/tools/screenshot.js +1 -0
- package/dist/resources/extensions/browser-tools/tools/zoom.js +1 -0
- package/dist/resources/extensions/gsd/auto/phases.js +9 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +11 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +5 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +55 -12
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +15 -2
- package/dist/resources/extensions/gsd/context-store.js +112 -0
- package/dist/resources/extensions/gsd/db-writer.js +150 -84
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/doctor-git-checks.js +41 -6
- package/dist/resources/extensions/gsd/knowledge-backfill.js +144 -0
- package/dist/resources/extensions/gsd/knowledge-capture.js +136 -0
- package/dist/resources/extensions/gsd/knowledge-parser.js +154 -0
- package/dist/resources/extensions/gsd/knowledge-projection.js +210 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +6 -1
- package/dist/resources/extensions/gsd/memory-backfill.js +73 -17
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +222 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -2
- package/dist/resources/extensions/gsd/provider-switch-observer.js +146 -0
- package/dist/resources/extensions/gsd/templates/knowledge.md +2 -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 +11 -11
- 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 +11 -11
- 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 +3 -2
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/README.md +2 -0
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/workflow-tools-parity.test.ts +244 -0
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/index.d.ts +2 -2
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -1
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts +11 -0
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +20 -0
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-ai/src/index.ts +7 -2
- package/packages/pi-ai/src/providers/transform-messages.ts +24 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +4 -4
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js +17 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/system-prompt.ts +4 -4
- package/packages/pi-coding-agent/src/tests/system-prompt-file-safety.test.ts +22 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/tools/screenshot.ts +1 -0
- package/src/resources/extensions/browser-tools/tools/zoom.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +14 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +11 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +58 -15
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +19 -2
- package/src/resources/extensions/gsd/context-store.ts +120 -1
- package/src/resources/extensions/gsd/db-writer.ts +167 -84
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/doctor-git-checks.ts +44 -6
- package/src/resources/extensions/gsd/doctor-types.ts +2 -0
- package/src/resources/extensions/gsd/knowledge-backfill.ts +164 -0
- package/src/resources/extensions/gsd/knowledge-capture.ts +160 -0
- package/src/resources/extensions/gsd/knowledge-parser.ts +174 -0
- package/src/resources/extensions/gsd/knowledge-projection.ts +241 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +6 -1
- package/src/resources/extensions/gsd/memory-backfill.ts +89 -17
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +277 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -2
- package/src/resources/extensions/gsd/provider-switch-observer.ts +185 -0
- package/src/resources/extensions/gsd/templates/knowledge.md +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/browser-tools-compatibility-declarations.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/context-store-decisions-from-memories.test.ts +312 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +13 -8
- package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/decisions-stop-table-writes.test.ts +348 -0
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +11 -7
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts +13 -5
- package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +323 -0
- package/src/resources/extensions/gsd/tests/knowledge-capture.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -2
- package/src/resources/extensions/gsd/tests/load-knowledge-block-rules-only.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-sketch-render.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +252 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +16 -4
- package/src/resources/extensions/gsd/workflow-logger.ts +4 -0
- /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → KDRTXR-22LPCsa80X9dey}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → KDRTXR-22LPCsa80X9dey}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// GSD-2 — ADR-013 Phase 6 preflight scanner.
|
|
2
|
+
//
|
|
3
|
+
// Read-only detection of rows in the legacy knowledge surfaces (decisions
|
|
4
|
+
// table, `.gsd/KNOWLEDGE.md`) that lack a corresponding `memories` row.
|
|
5
|
+
// Runs on session start (and on demand via doctor); never mutates state.
|
|
6
|
+
//
|
|
7
|
+
// The scanner exists so the destructive Phase 6 cutover (#5755) can prove
|
|
8
|
+
// the migration is complete before any tables are dropped. Today's
|
|
9
|
+
// `backfillDecisionsToMemories` writes a `structured_fields.sourceDecisionId`
|
|
10
|
+
// marker on each migrated row; the scanner uses that marker for decisions
|
|
11
|
+
// detection. A `sourceKnowledgeId` marker is reserved for the parallel
|
|
12
|
+
// KNOWLEDGE.md backfill that Phase 6 will introduce — until that ships,
|
|
13
|
+
// every KNOWLEDGE.md row is reported as unmigrated, which is the honest
|
|
14
|
+
// state of the consolidation.
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { _getAdapter, isDbAvailable } from "./gsd-db.js";
|
|
17
|
+
import { appendNotification } from "./notification-store.js";
|
|
18
|
+
import { resolveGsdRootFile } from "./paths.js";
|
|
19
|
+
import { logWarning } from "./workflow-logger.js";
|
|
20
|
+
// ─── KNOWLEDGE.md parsing ────────────────────────────────────────────────────
|
|
21
|
+
const KNOWLEDGE_SECTIONS = [
|
|
22
|
+
{ table: "rules", heading: "## Rules", idPrefix: "K" },
|
|
23
|
+
{ table: "patterns", heading: "## Patterns", idPrefix: "P" },
|
|
24
|
+
{ table: "lessons", heading: "## Lessons Learned", idPrefix: "L" },
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Parse `.gsd/KNOWLEDGE.md` into rows, one per table entry. Skips the table
|
|
28
|
+
* header and separator lines; ignores rows from unrecognized sections.
|
|
29
|
+
*
|
|
30
|
+
* The format is locked in `files.ts:appendKnowledge` — three `## ` sections
|
|
31
|
+
* (Rules, Patterns, Lessons Learned), each a Markdown table. Row IDs are
|
|
32
|
+
* `K###` / `P###` / `L###`.
|
|
33
|
+
*/
|
|
34
|
+
export function parseKnowledgeRows(content) {
|
|
35
|
+
const rows = [];
|
|
36
|
+
if (!content.trim())
|
|
37
|
+
return rows;
|
|
38
|
+
// Slice the content into sections by heading. The leading text before the
|
|
39
|
+
// first ## heading is intro prose — ignored.
|
|
40
|
+
const lines = content.split("\n");
|
|
41
|
+
let activeSection;
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
const trimmed = line.trim();
|
|
44
|
+
if (trimmed.startsWith("## ")) {
|
|
45
|
+
activeSection = KNOWLEDGE_SECTIONS.find((s) => s.heading === trimmed);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!activeSection)
|
|
49
|
+
continue;
|
|
50
|
+
if (!trimmed.startsWith("|"))
|
|
51
|
+
continue;
|
|
52
|
+
// Skip the table header rows: the column-titles line and the |---|---| separator.
|
|
53
|
+
// Real data rows start with `| <prefix>### |`.
|
|
54
|
+
const idMatch = new RegExp(`^\\|\\s*(${activeSection.idPrefix}\\d+)\\s*\\|`).exec(trimmed);
|
|
55
|
+
if (!idMatch)
|
|
56
|
+
continue;
|
|
57
|
+
rows.push({
|
|
58
|
+
table: activeSection.table,
|
|
59
|
+
id: idMatch[1] ?? "",
|
|
60
|
+
row: trimmed,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return rows;
|
|
64
|
+
}
|
|
65
|
+
function knowledgeMdContent(basePath) {
|
|
66
|
+
const path = resolveGsdRootFile(basePath, "KNOWLEDGE");
|
|
67
|
+
if (!existsSync(path))
|
|
68
|
+
return "";
|
|
69
|
+
try {
|
|
70
|
+
return readFileSync(path, "utf-8");
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function getActiveDecisions() {
|
|
77
|
+
if (!isDbAvailable())
|
|
78
|
+
return [];
|
|
79
|
+
const adapter = _getAdapter();
|
|
80
|
+
if (!adapter)
|
|
81
|
+
return [];
|
|
82
|
+
try {
|
|
83
|
+
const rows = adapter
|
|
84
|
+
.prepare("SELECT id, decision FROM decisions WHERE superseded_by IS NULL")
|
|
85
|
+
.all();
|
|
86
|
+
return rows
|
|
87
|
+
.map((row) => ({
|
|
88
|
+
id: String(row["id"] ?? ""),
|
|
89
|
+
decision: String(row["decision"] ?? ""),
|
|
90
|
+
}))
|
|
91
|
+
.filter((row) => row.id.length > 0);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* True when a memory row has a `structured_fields` JSON payload containing
|
|
99
|
+
* the given `markerKey: "value"` pair. Matches the LIKE pattern used by
|
|
100
|
+
* `backfillDecisionsToMemories` so the scanner is consistent with the
|
|
101
|
+
* backfill's idempotency check.
|
|
102
|
+
*/
|
|
103
|
+
function memoryHasSourceMarker(markerKey, value) {
|
|
104
|
+
if (!isDbAvailable())
|
|
105
|
+
return false;
|
|
106
|
+
const adapter = _getAdapter();
|
|
107
|
+
if (!adapter)
|
|
108
|
+
return false;
|
|
109
|
+
try {
|
|
110
|
+
const pattern = `%"${markerKey}":"${value}"%`;
|
|
111
|
+
const row = adapter
|
|
112
|
+
.prepare("SELECT 1 FROM memories WHERE structured_fields LIKE :pattern LIMIT 1")
|
|
113
|
+
.get({ ":pattern": pattern });
|
|
114
|
+
return row !== undefined;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
121
|
+
const SAMPLE_LIMIT = 5;
|
|
122
|
+
/**
|
|
123
|
+
* Scan the legacy knowledge surfaces and return a structured report of what's
|
|
124
|
+
* already in the `memories` table vs what's still unmigrated. Pure detection
|
|
125
|
+
* — no DB writes, no file writes, no notifications.
|
|
126
|
+
*/
|
|
127
|
+
export function scanConsolidationGaps(basePath) {
|
|
128
|
+
// ── Decisions ────────────────────────────────────────────────────────
|
|
129
|
+
const decisions = getActiveDecisions();
|
|
130
|
+
const decisionSamples = [];
|
|
131
|
+
let decisionMigrated = 0;
|
|
132
|
+
for (const decision of decisions) {
|
|
133
|
+
if (memoryHasSourceMarker("sourceDecisionId", decision.id)) {
|
|
134
|
+
decisionMigrated += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (decisionSamples.length < SAMPLE_LIMIT) {
|
|
138
|
+
decisionSamples.push({
|
|
139
|
+
id: decision.id,
|
|
140
|
+
decision: decision.decision.length > 80 ? decision.decision.slice(0, 79) + "…" : decision.decision,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// ── KNOWLEDGE.md ─────────────────────────────────────────────────────
|
|
145
|
+
const knowledgeRows = parseKnowledgeRows(knowledgeMdContent(basePath));
|
|
146
|
+
const knowledgeByTable = { rules: 0, patterns: 0, lessons: 0 };
|
|
147
|
+
const knowledgeSamples = [];
|
|
148
|
+
let knowledgeMigrated = 0;
|
|
149
|
+
for (const row of knowledgeRows) {
|
|
150
|
+
knowledgeByTable[row.table] += 1;
|
|
151
|
+
// Phase 6 will introduce a `sourceKnowledgeId` marker as part of the
|
|
152
|
+
// KNOWLEDGE.md backfill. Until that path ships, this check returns
|
|
153
|
+
// false for every row, which is the honest state of the consolidation.
|
|
154
|
+
if (memoryHasSourceMarker("sourceKnowledgeId", row.id)) {
|
|
155
|
+
knowledgeMigrated += 1;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (knowledgeSamples.length < SAMPLE_LIMIT) {
|
|
159
|
+
knowledgeSamples.push({
|
|
160
|
+
table: row.table,
|
|
161
|
+
id: row.id,
|
|
162
|
+
row: row.row.length > 100 ? row.row.slice(0, 99) + "…" : row.row,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const decisionsReport = {
|
|
167
|
+
total: decisions.length,
|
|
168
|
+
migrated: decisionMigrated,
|
|
169
|
+
unmigrated: decisions.length - decisionMigrated,
|
|
170
|
+
samples: decisionSamples,
|
|
171
|
+
};
|
|
172
|
+
const knowledgeReport = {
|
|
173
|
+
total: knowledgeRows.length,
|
|
174
|
+
migrated: knowledgeMigrated,
|
|
175
|
+
unmigrated: knowledgeRows.length - knowledgeMigrated,
|
|
176
|
+
byTable: knowledgeByTable,
|
|
177
|
+
samples: knowledgeSamples,
|
|
178
|
+
};
|
|
179
|
+
const totalGaps = decisionsReport.unmigrated + knowledgeReport.unmigrated;
|
|
180
|
+
// Summary line is intentionally short so it fits in a single notification
|
|
181
|
+
// (notification-store truncates messages over 500 chars). Detail is
|
|
182
|
+
// accessible via getProviderSwitchStats-style callers, not embedded here.
|
|
183
|
+
const parts = [];
|
|
184
|
+
if (decisionsReport.unmigrated > 0) {
|
|
185
|
+
parts.push(`${decisionsReport.unmigrated} of ${decisionsReport.total} active decisions`);
|
|
186
|
+
}
|
|
187
|
+
if (knowledgeReport.unmigrated > 0) {
|
|
188
|
+
parts.push(`${knowledgeReport.unmigrated} of ${knowledgeReport.total} KNOWLEDGE.md rows`);
|
|
189
|
+
}
|
|
190
|
+
const summary = parts.length === 0
|
|
191
|
+
? "Memory consolidation: all decisions and KNOWLEDGE.md rows are in memories."
|
|
192
|
+
: `Memory consolidation: ${parts.join(" and ")} not yet in memories table. Run /doctor for details.`;
|
|
193
|
+
return { decisions: decisionsReport, knowledge: knowledgeReport, totalGaps, summary };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Run the scanner and emit a persistent notification + workflow-logger
|
|
197
|
+
* warning when gaps exist. Best-effort: never throws; a broken scanner
|
|
198
|
+
* cannot block agent startup.
|
|
199
|
+
*
|
|
200
|
+
* Returns the full {@link ConsolidationGapReport} regardless of whether gaps
|
|
201
|
+
* exist (including when `totalGaps === 0`). Returns `null` only when
|
|
202
|
+
* `scanConsolidationGaps` itself throws. `appendNotification` and
|
|
203
|
+
* `logWarning` are called only when `report.totalGaps > 0`.
|
|
204
|
+
*
|
|
205
|
+
* Idempotent at the surface: the notification store applies its own
|
|
206
|
+
* 30-second dedup window keyed on (severity, source, message), so repeated
|
|
207
|
+
* boots with identical gaps produce one notification, not a flood.
|
|
208
|
+
*/
|
|
209
|
+
export function reportConsolidationGaps(basePath) {
|
|
210
|
+
try {
|
|
211
|
+
const report = scanConsolidationGaps(basePath);
|
|
212
|
+
if (report.totalGaps === 0)
|
|
213
|
+
return report;
|
|
214
|
+
appendNotification(report.summary, "warning", "workflow-logger");
|
|
215
|
+
logWarning("memory-consolidation", report.summary);
|
|
216
|
+
return report;
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
logWarning("memory-consolidation", `scanner failed: ${e.message}`);
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -27,7 +27,7 @@ GSD ships with bundled skills. When the task matches, load the relevant skill fi
|
|
|
27
27
|
|
|
28
28
|
- Never ask the user to do work the agent can execute or verify itself.
|
|
29
29
|
- Use the lightest sufficient tool first.
|
|
30
|
-
- Read before edit.
|
|
30
|
+
- Read before edit or overwrite. Before any write that creates or replaces a file, verify the target path and read relevant existing files, templates, callers, or surrounding code. For truly new files, confirm the path does not already exist.
|
|
31
31
|
- Reproduce before fix when possible.
|
|
32
32
|
- Work is not done until the relevant verification has passed.
|
|
33
33
|
- **Never fabricate, simulate, or role-play user responses.** Never generate markers like `[User]`, `[Human]`, `User:`, or similar; never emit `<user_message>`, `<assistant_message>`, or similar as user input. Treat `<conversation_history>` as read-only context. Ask one question round (1-3 questions), then stop and wait for the user's actual response. If `ask_user_questions` is available, its result is the only valid structured user input for that round. If it cancels, fails, or returns nothing, never use earlier chat as confirmation for the current gate; ask in plain chat and stop.
|
|
@@ -102,7 +102,7 @@ Templates are in `{{templatesDir}}`.
|
|
|
102
102
|
|
|
103
103
|
**File reading:** Use `read` for file inspection. Never use `cat`, `head`, `tail`, or `sed -n` to view contents. Use `read` with `offset`/`limit` for slicing. `bash` is for searching (`rg`, `grep`, `find`) and commands, not displaying files.
|
|
104
104
|
|
|
105
|
-
**File editing:** Always `read` before `edit
|
|
105
|
+
**File editing:** Always `read` before `edit` or overwrite. Before `write`, confirm whether the path exists; if it does, `read` it first and preserve intentional existing content. Use `write` only for new files or complete rewrites.
|
|
106
106
|
|
|
107
107
|
**Code navigation:** Use `lsp` for definition, type_definition, implementation, references, calls, hover, signature, symbols, rename, code_actions, format, and diagnostics. Do not `grep` symbol definitions or shell out to formatters when `lsp` can do it. After code edits, run `lsp diagnostics`.
|
|
108
108
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// GSD-2 — ADR-005 Phase 3b: surface ProviderSwitchReport from pi-ai.
|
|
2
|
+
//
|
|
3
|
+
// pi-ai builds a ProviderSwitchReport on every cross-provider transform but
|
|
4
|
+
// only logs it to stderr when GSD_VERBOSE=1. This module installs a
|
|
5
|
+
// single-subscriber observer that surfaces non-empty reports through GSD's
|
|
6
|
+
// three usual telemetry surfaces:
|
|
7
|
+
//
|
|
8
|
+
// 1. UOK audit event (category model-policy, type provider-switch) — only
|
|
9
|
+
// when an auto trace is active.
|
|
10
|
+
// 2. Persistent notification (.gsd/notifications.jsonl, severity warning) —
|
|
11
|
+
// whenever the GSD basePath is known, so users see the loss in the
|
|
12
|
+
// dashboard / status surface without GSD_VERBOSE.
|
|
13
|
+
// 3. In-memory counter, exposed via getProviderSwitchStats() so any
|
|
14
|
+
// caller (dashboard, doctor, tests) can read the rollup.
|
|
15
|
+
import { setProviderSwitchObserver } from "@gsd/pi-ai";
|
|
16
|
+
import { autoSession } from "./auto-runtime-state.js";
|
|
17
|
+
import { appendNotification } from "./notification-store.js";
|
|
18
|
+
import { buildAuditEnvelope, emitUokAuditEvent } from "./uok/audit.js";
|
|
19
|
+
const INTERACTIVE_TRACE_KEY = "interactive";
|
|
20
|
+
let installed = false;
|
|
21
|
+
let totalSwitches = 0;
|
|
22
|
+
const totals = {
|
|
23
|
+
thinkingBlocksDropped: 0,
|
|
24
|
+
thinkingBlocksDowngraded: 0,
|
|
25
|
+
toolCallIdsRemapped: 0,
|
|
26
|
+
syntheticToolResultsInserted: 0,
|
|
27
|
+
thoughtSignaturesDropped: 0,
|
|
28
|
+
};
|
|
29
|
+
const byTrace = new Map();
|
|
30
|
+
let lastReport = null;
|
|
31
|
+
let lastAt = null;
|
|
32
|
+
/** Format a one-line summary suitable for a notification message. */
|
|
33
|
+
function summarize(report) {
|
|
34
|
+
const parts = [];
|
|
35
|
+
if (report.thinkingBlocksDropped > 0)
|
|
36
|
+
parts.push(`${report.thinkingBlocksDropped} thinking dropped`);
|
|
37
|
+
if (report.thinkingBlocksDowngraded > 0)
|
|
38
|
+
parts.push(`${report.thinkingBlocksDowngraded} thinking downgraded`);
|
|
39
|
+
if (report.toolCallIdsRemapped > 0)
|
|
40
|
+
parts.push(`${report.toolCallIdsRemapped} tool ids remapped`);
|
|
41
|
+
if (report.syntheticToolResultsInserted > 0)
|
|
42
|
+
parts.push(`${report.syntheticToolResultsInserted} synthetic tool results`);
|
|
43
|
+
if (report.thoughtSignaturesDropped > 0)
|
|
44
|
+
parts.push(`${report.thoughtSignaturesDropped} thought signatures dropped`);
|
|
45
|
+
return `Provider switch ${report.fromApi} → ${report.toApi}: ${parts.join(", ")}`;
|
|
46
|
+
}
|
|
47
|
+
function recordReport(report) {
|
|
48
|
+
const now = new Date().toISOString();
|
|
49
|
+
totalSwitches += 1;
|
|
50
|
+
totals.thinkingBlocksDropped += report.thinkingBlocksDropped;
|
|
51
|
+
totals.thinkingBlocksDowngraded += report.thinkingBlocksDowngraded;
|
|
52
|
+
totals.toolCallIdsRemapped += report.toolCallIdsRemapped;
|
|
53
|
+
totals.syntheticToolResultsInserted += report.syntheticToolResultsInserted;
|
|
54
|
+
totals.thoughtSignaturesDropped += report.thoughtSignaturesDropped;
|
|
55
|
+
lastReport = report;
|
|
56
|
+
lastAt = now;
|
|
57
|
+
const traceKey = autoSession.currentTraceId ?? INTERACTIVE_TRACE_KEY;
|
|
58
|
+
const existing = byTrace.get(traceKey);
|
|
59
|
+
if (existing) {
|
|
60
|
+
existing.switches += 1;
|
|
61
|
+
existing.lastReport = report;
|
|
62
|
+
existing.lastAt = now;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
byTrace.set(traceKey, { switches: 1, lastReport: report, lastAt: now });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function emitAudit(report) {
|
|
69
|
+
const traceId = autoSession.currentTraceId;
|
|
70
|
+
const basePath = autoSession.basePath;
|
|
71
|
+
if (!traceId || !basePath)
|
|
72
|
+
return;
|
|
73
|
+
try {
|
|
74
|
+
emitUokAuditEvent(basePath, buildAuditEnvelope({
|
|
75
|
+
traceId,
|
|
76
|
+
category: "model-policy",
|
|
77
|
+
type: "provider-switch",
|
|
78
|
+
payload: {
|
|
79
|
+
fromApi: report.fromApi,
|
|
80
|
+
toApi: report.toApi,
|
|
81
|
+
thinkingBlocksDropped: report.thinkingBlocksDropped,
|
|
82
|
+
thinkingBlocksDowngraded: report.thinkingBlocksDowngraded,
|
|
83
|
+
toolCallIdsRemapped: report.toolCallIdsRemapped,
|
|
84
|
+
syntheticToolResultsInserted: report.syntheticToolResultsInserted,
|
|
85
|
+
thoughtSignaturesDropped: report.thoughtSignaturesDropped,
|
|
86
|
+
},
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Audit emission is best-effort. Counter + notification still fire.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function emitNotification(report) {
|
|
94
|
+
try {
|
|
95
|
+
appendNotification(summarize(report), "warning", "workflow-logger");
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Notification persistence is best-effort.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function handleReport(report) {
|
|
102
|
+
recordReport(report);
|
|
103
|
+
emitAudit(report);
|
|
104
|
+
emitNotification(report);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Install the pi-ai observer. Idempotent — calling more than once is a no-op
|
|
108
|
+
* after the first install.
|
|
109
|
+
*/
|
|
110
|
+
export function installProviderSwitchObserver() {
|
|
111
|
+
if (installed)
|
|
112
|
+
return;
|
|
113
|
+
setProviderSwitchObserver(handleReport);
|
|
114
|
+
installed = true;
|
|
115
|
+
}
|
|
116
|
+
/** Uninstall the observer. Intended for tests. */
|
|
117
|
+
export function uninstallProviderSwitchObserver() {
|
|
118
|
+
setProviderSwitchObserver(undefined);
|
|
119
|
+
installed = false;
|
|
120
|
+
}
|
|
121
|
+
/** Read-only snapshot of the in-memory rollup. */
|
|
122
|
+
export function getProviderSwitchStats() {
|
|
123
|
+
const trace = {};
|
|
124
|
+
for (const [key, value] of byTrace) {
|
|
125
|
+
trace[key] = { switches: value.switches, lastReport: { ...value.lastReport }, lastAt: value.lastAt };
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
totalSwitches,
|
|
129
|
+
totals: { ...totals },
|
|
130
|
+
byTrace: trace,
|
|
131
|
+
lastReport: lastReport ? { ...lastReport } : null,
|
|
132
|
+
lastAt,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** Reset the in-memory rollup. Intended for tests. */
|
|
136
|
+
export function _resetProviderSwitchStats() {
|
|
137
|
+
totalSwitches = 0;
|
|
138
|
+
totals.thinkingBlocksDropped = 0;
|
|
139
|
+
totals.thinkingBlocksDowngraded = 0;
|
|
140
|
+
totals.toolCallIdsRemapped = 0;
|
|
141
|
+
totals.syntheticToolResultsInserted = 0;
|
|
142
|
+
totals.thoughtSignaturesDropped = 0;
|
|
143
|
+
byTrace.clear();
|
|
144
|
+
lastReport = null;
|
|
145
|
+
lastAt = null;
|
|
146
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Project Knowledge
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Project-specific rules plus projected patterns and lessons learned.
|
|
4
|
+
Rules are maintained in this file. Patterns and lessons are persisted to the memories table and rendered here on the next session-start projection.
|
|
5
5
|
|
|
6
6
|
## Rules
|
|
7
7
|
|