@piagent/platform 1.2.9 → 1.2.10
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/CHANGELOG.md +42 -0
- package/README.md +4 -4
- package/catalog/capabilities.json +2 -2
- package/docs/command-reference-vietnamese.md +7 -4
- package/docs/context-engine.md +23 -4
- package/docs/distribution-standard.md +7 -7
- package/docs/mcp-and-tools.md +7 -0
- package/docs/operator-manual-vietnamese.md +27 -11
- package/docs/publishing-for-teams.md +1 -0
- package/docs/release-install-policy.md +6 -6
- package/docs/security-threat-model.md +7 -2
- package/docs/subagents-and-multiagent.md +1 -1
- package/docs/team-onboarding.md +2 -2
- package/docs-site/architecture.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/content/context-engine.html +8 -1
- package/docs-site/content/security.html +1 -0
- package/docs-site/context-engine.html +9 -2
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +2 -1
- package/docs-site/team.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/package.json +1 -1
- package/packages/piagent-core/README.md +6 -1
- package/packages/piagent-core/extensions/context-engine.js +134 -69
- package/packages/piagent-core/extensions/context-index-policy.js +1 -1
- package/packages/piagent-core/extensions/piagent-guard.ts +191 -34
- package/packages/piagent-core/mcp/mcp-config-layers.js +52 -6
- package/packages/piagent-core/mcp/mcp-tool-naming.js +12 -1
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/subagents/piagent-reviewer.md +3 -3
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.10 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/profiles.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.10 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.10 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/runtime.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.10 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/scope.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.10 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/security.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.10 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
<ul>
|
|
129
129
|
<li>Có thể bị ảnh hưởng bởi symlink, encoding, nested payload, shell expansion hoặc tool mới chưa được policy hoá.</li>
|
|
130
130
|
<li>Redaction là backstop giảm rò rỉ, không phải giấy phép để đưa secret thật vào prompt/output.</li>
|
|
131
|
+
<li>Context index là local cache có source body: mode owner-only giảm cross-account access, nhưng process chạy cùng OS account vẫn cần được tin cậy.</li>
|
|
131
132
|
<li>Workload không tin cậy cần container/VM với giới hạn filesystem, process, network và credential.</li>
|
|
132
133
|
</ul>
|
|
133
134
|
</div>
|
package/docs-site/team.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.10 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/docs-site/workflows.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<span>Pi Agent Platform</span>
|
|
28
28
|
</a>
|
|
29
29
|
<div class="top-actions">
|
|
30
|
-
<span class="pill"><span class="pill-dot"></span>v1.2.
|
|
30
|
+
<span class="pill"><span class="pill-dot"></span>v1.2.10 docs</span>
|
|
31
31
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
32
32
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
33
33
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
package/package.json
CHANGED
|
@@ -147,10 +147,15 @@ Runtime task tools write local state to:
|
|
|
147
147
|
|
|
148
148
|
Project-local state belongs in `.pi/.gitignore`.
|
|
149
149
|
|
|
150
|
+
The Context Engine SQLite index contains allowed source bodies. Its directory is
|
|
151
|
+
kept at mode `0700`, with database/WAL/SHM at `0600`; core and FTS5 secure-delete
|
|
152
|
+
cover normal refreshes, while exclusion-policy changes trigger a retryable FTS
|
|
153
|
+
rebuild and database vacuum before the new policy is considered clean.
|
|
154
|
+
|
|
150
155
|
Passing final gates require an observed exit `0` command that exactly matches one entry in `task.verifyCommands`. Other observed commands are traceable but advisory.
|
|
151
156
|
|
|
152
157
|
Raw path-like tool access to protected paths is blocked before execution. This includes Pi built-ins (`read`, `write`, `edit`, `grep`, `find`, `ls`) and custom/MCP tools with nested path-like strings, arrays, or `file://` URIs. Path-like strings are percent-decoded once, and input nesting above `MAX_TOOL_INPUT_INSPECTION_DEPTH=32` fails closed. Known content fields such as `content`, `query`, `pattern`, `text`, and `command` are excluded from generic extraction. `grep.glob` and `find.pattern` are checked when they explicitly target protected paths, while broad `grep`, `find`, and `ls` results are filtered so protected content lines or path metadata are redacted before reaching the model.
|
|
153
158
|
|
|
154
159
|
Raw `bash` access to protected paths is blocked through shell path extraction. `.pi/piagent-state/**` and `.pi/piagent-profile.json` are self-protected; use `piagent_context` and piagent task tools for governed access.
|
|
155
160
|
|
|
156
|
-
The input hook also detects local image paths in chat prompts. Supported formats are `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, and `.bmp
|
|
161
|
+
The input hook also detects local image paths in chat prompts. Supported formats are `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, and `.bmp`. The canonical target must be inside the project or a directory granted through `additionalReadRoots`/`PIAGENT_ADDITIONAL_READ_ROOTS`; protected paths and project files outside the resolved filesystem read scope remain blocked. File bytes are sniffed before attachment, and the checked file identity must still match when opened. Up to 4 images are attached per input, with an 8 MB per-image cap. Oversized images should be read through Pi's `read` tool so Pi can resize them.
|
|
@@ -92,6 +92,21 @@ export function contextEnginePaths(cwd) {
|
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
function hardenContextDatabaseArtifacts(cwd) {
|
|
96
|
+
const database = contextEnginePaths(cwd).database;
|
|
97
|
+
for (const artifact of [database, `${database}-wal`, `${database}-shm`]) {
|
|
98
|
+
try {
|
|
99
|
+
const stat = fs.lstatSync(artifact);
|
|
100
|
+
if (!stat.isFile()) {
|
|
101
|
+
throw new Error(`Context index storage path must be a regular file: ${artifact}`);
|
|
102
|
+
}
|
|
103
|
+
fs.chmodSync(artifact, 0o600);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error?.code !== "ENOENT") throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
95
110
|
async function loadSqlite() {
|
|
96
111
|
if (!sqliteModulePromise) {
|
|
97
112
|
sqliteModulePromise = (async () => {
|
|
@@ -117,53 +132,85 @@ async function loadSqlite() {
|
|
|
117
132
|
async function openDatabase(cwd, { create = true } = {}) {
|
|
118
133
|
const paths = contextEnginePaths(cwd);
|
|
119
134
|
if (!create && !fs.existsSync(paths.database)) return undefined;
|
|
120
|
-
fs.mkdirSync(paths.root, { recursive: true });
|
|
135
|
+
fs.mkdirSync(paths.root, { recursive: true, mode: 0o700 });
|
|
136
|
+
if (!fs.lstatSync(paths.root).isDirectory()) {
|
|
137
|
+
throw new Error(`Context index storage root must be a directory: ${paths.root}`);
|
|
138
|
+
}
|
|
139
|
+
fs.chmodSync(paths.root, 0o700);
|
|
140
|
+
hardenContextDatabaseArtifacts(cwd);
|
|
141
|
+
const descriptor = fs.openSync(
|
|
142
|
+
paths.database,
|
|
143
|
+
fs.constants.O_RDWR
|
|
144
|
+
| (create ? fs.constants.O_CREAT : 0)
|
|
145
|
+
| (fs.constants.O_NOFOLLOW ?? 0),
|
|
146
|
+
0o600
|
|
147
|
+
);
|
|
148
|
+
try {
|
|
149
|
+
fs.fchmodSync(descriptor, 0o600);
|
|
150
|
+
} finally {
|
|
151
|
+
fs.closeSync(descriptor);
|
|
152
|
+
}
|
|
121
153
|
const { DatabaseSync } = await loadSqlite();
|
|
122
154
|
const db = new DatabaseSync(paths.database);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
155
|
+
try {
|
|
156
|
+
db.exec(`
|
|
157
|
+
PRAGMA journal_mode = WAL;
|
|
158
|
+
PRAGMA synchronous = NORMAL;
|
|
159
|
+
PRAGMA foreign_keys = ON;
|
|
160
|
+
PRAGMA secure_delete = ON;
|
|
161
|
+
CREATE TABLE IF NOT EXISTS metadata (
|
|
162
|
+
key TEXT PRIMARY KEY,
|
|
163
|
+
value TEXT NOT NULL
|
|
164
|
+
);
|
|
165
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
166
|
+
path TEXT PRIMARY KEY,
|
|
167
|
+
hash TEXT NOT NULL,
|
|
168
|
+
bytes INTEGER NOT NULL,
|
|
169
|
+
mtime_ms REAL NOT NULL,
|
|
170
|
+
language TEXT NOT NULL,
|
|
171
|
+
indexed_at TEXT NOT NULL
|
|
172
|
+
);
|
|
173
|
+
CREATE TABLE IF NOT EXISTS symbols (
|
|
174
|
+
id INTEGER PRIMARY KEY,
|
|
175
|
+
file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
|
|
176
|
+
name TEXT NOT NULL,
|
|
177
|
+
kind TEXT NOT NULL,
|
|
178
|
+
line INTEGER NOT NULL,
|
|
179
|
+
end_line INTEGER NOT NULL,
|
|
180
|
+
signature TEXT NOT NULL
|
|
181
|
+
);
|
|
182
|
+
CREATE INDEX IF NOT EXISTS symbols_name_idx ON symbols(name);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS symbols_file_idx ON symbols(file_path);
|
|
184
|
+
CREATE TABLE IF NOT EXISTS imports (
|
|
185
|
+
id INTEGER PRIMARY KEY,
|
|
186
|
+
file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
|
|
187
|
+
specifier TEXT NOT NULL,
|
|
188
|
+
target_path TEXT,
|
|
189
|
+
line INTEGER NOT NULL
|
|
190
|
+
);
|
|
191
|
+
CREATE INDEX IF NOT EXISTS imports_file_idx ON imports(file_path);
|
|
192
|
+
CREATE INDEX IF NOT EXISTS imports_target_idx ON imports(target_path);
|
|
193
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS file_fts USING fts5(
|
|
194
|
+
path UNINDEXED,
|
|
195
|
+
body,
|
|
196
|
+
symbol_names,
|
|
197
|
+
tokenize = 'unicode61 remove_diacritics 2'
|
|
198
|
+
);
|
|
199
|
+
`);
|
|
200
|
+
const ftsSecureDelete = db.prepare(`
|
|
201
|
+
SELECT v
|
|
202
|
+
FROM file_fts_config
|
|
203
|
+
WHERE k = 'secure-delete'
|
|
204
|
+
`).get();
|
|
205
|
+
if (Number(ftsSecureDelete?.v) !== 1) {
|
|
206
|
+
db.exec("INSERT INTO file_fts(file_fts, rank) VALUES('secure-delete', 1)");
|
|
207
|
+
}
|
|
208
|
+
hardenContextDatabaseArtifacts(cwd);
|
|
209
|
+
return db;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
db.close();
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
167
214
|
}
|
|
168
215
|
|
|
169
216
|
function metadataMap(db) {
|
|
@@ -465,6 +512,7 @@ export function classifyContextTask(prompt) {
|
|
|
465
512
|
|
|
466
513
|
export async function buildContextIndexV2(cwd, options = {}) {
|
|
467
514
|
const excludePatterns = requireExplicitExcludePatterns(options, "buildContextIndexV2");
|
|
515
|
+
const excludeDigest = contextIndexExcludeDigest(excludePatterns);
|
|
468
516
|
const startedAt = Date.now();
|
|
469
517
|
const projectRoot = fs.realpathSync.native(cwd);
|
|
470
518
|
const directoryCache = new Map();
|
|
@@ -473,7 +521,10 @@ export async function buildContextIndexV2(cwd, options = {}) {
|
|
|
473
521
|
const listed = gitFileList(cwd) ?? walkedFileList(cwd, maxFiles);
|
|
474
522
|
const candidates = listed.filter((file) => shouldIndexPath(file, { ...options, excludePatterns })).slice(0, maxFiles).sort();
|
|
475
523
|
const db = await openDatabase(cwd);
|
|
524
|
+
const previousMetadata = metadataMap(db);
|
|
476
525
|
const existing = new Map(db.prepare("SELECT path, hash, bytes, mtime_ms FROM files").all().map((row) => [row.path, row]));
|
|
526
|
+
const purgeStaleContent = Boolean(previousMetadata.builtAt || existing.size > 0)
|
|
527
|
+
&& (previousMetadata.purgePending === "1" || previousMetadata.excludeDigest !== excludeDigest);
|
|
477
528
|
const current = new Set();
|
|
478
529
|
const currentHashes = new Map();
|
|
479
530
|
const changed = [];
|
|
@@ -525,8 +576,10 @@ export async function buildContextIndexV2(cwd, options = {}) {
|
|
|
525
576
|
}
|
|
526
577
|
|
|
527
578
|
const removed = [...existing.keys()].filter((file) => !current.has(file));
|
|
528
|
-
|
|
579
|
+
let transactionOpen = false;
|
|
529
580
|
try {
|
|
581
|
+
db.exec("BEGIN IMMEDIATE");
|
|
582
|
+
transactionOpen = true;
|
|
530
583
|
const deleteFile = db.prepare("DELETE FROM files WHERE path = ?");
|
|
531
584
|
const deleteFts = db.prepare("DELETE FROM file_fts WHERE path = ?");
|
|
532
585
|
for (const relativePath of [...removed, ...changed.map((item) => item.relativePath)]) {
|
|
@@ -574,12 +627,28 @@ export async function buildContextIndexV2(cwd, options = {}) {
|
|
|
574
627
|
importCount: counts.imports,
|
|
575
628
|
maxFileBytes,
|
|
576
629
|
excludePatterns: JSON.stringify(excludePatterns),
|
|
577
|
-
excludeDigest
|
|
630
|
+
excludeDigest,
|
|
578
631
|
excludePolicyVersion: contextIndexExcludePolicyVersion(),
|
|
632
|
+
purgePending: purgeStaleContent ? 1 : 0,
|
|
579
633
|
skippedPaths: JSON.stringify(skippedPaths)
|
|
580
634
|
});
|
|
581
635
|
db.exec("COMMIT");
|
|
582
|
-
|
|
636
|
+
transactionOpen = false;
|
|
637
|
+
if (purgeStaleContent) {
|
|
638
|
+
if (typeof options.onPurgeStart === "function") await options.onPurgeStart();
|
|
639
|
+
db.exec("INSERT INTO file_fts(file_fts) VALUES('rebuild')");
|
|
640
|
+
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
641
|
+
db.exec("VACUUM");
|
|
642
|
+
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
643
|
+
db.exec("BEGIN IMMEDIATE");
|
|
644
|
+
transactionOpen = true;
|
|
645
|
+
setMetadata(db, {
|
|
646
|
+
purgePending: 0,
|
|
647
|
+
purgedAt: nowIso()
|
|
648
|
+
});
|
|
649
|
+
db.exec("COMMIT");
|
|
650
|
+
transactionOpen = false;
|
|
651
|
+
}
|
|
583
652
|
return {
|
|
584
653
|
schemaVersion: INDEX_SCHEMA_VERSION,
|
|
585
654
|
builtAt: indexedAt,
|
|
@@ -590,25 +659,24 @@ export async function buildContextIndexV2(cwd, options = {}) {
|
|
|
590
659
|
removed: removed.length,
|
|
591
660
|
skippedLarge,
|
|
592
661
|
skippedBinary,
|
|
662
|
+
purgedStaleContent: purgeStaleContent,
|
|
593
663
|
...counts
|
|
594
664
|
};
|
|
595
665
|
} catch (error) {
|
|
596
|
-
db.exec("ROLLBACK");
|
|
597
|
-
db.close();
|
|
666
|
+
if (transactionOpen) db.exec("ROLLBACK");
|
|
598
667
|
throw error;
|
|
668
|
+
} finally {
|
|
669
|
+
db.close();
|
|
670
|
+
hardenContextDatabaseArtifacts(cwd);
|
|
599
671
|
}
|
|
600
672
|
}
|
|
601
673
|
|
|
602
674
|
export async function contextIndexV2Status(cwd, options = {}) {
|
|
675
|
+
const expectedExcludePatterns = requireExplicitExcludePatterns(options, "contextIndexV2Status");
|
|
676
|
+
const expectedExcludeDigest = contextIndexExcludeDigest(expectedExcludePatterns);
|
|
603
677
|
const paths = contextEnginePaths(cwd);
|
|
604
678
|
const projectRoot = fs.realpathSync.native(cwd);
|
|
605
679
|
const directoryCache = new Map();
|
|
606
|
-
const expectedExcludePatterns = options.excludePatterns === undefined
|
|
607
|
-
? undefined
|
|
608
|
-
: normalizeContextIndexExcludePatterns(options.excludePatterns);
|
|
609
|
-
const expectedExcludeDigest = expectedExcludePatterns
|
|
610
|
-
? contextIndexExcludeDigest(expectedExcludePatterns)
|
|
611
|
-
: undefined;
|
|
612
680
|
if (!fs.existsSync(paths.database)) {
|
|
613
681
|
return {
|
|
614
682
|
schemaVersion: INDEX_SCHEMA_VERSION,
|
|
@@ -617,6 +685,7 @@ export async function contextIndexV2Status(cwd, options = {}) {
|
|
|
617
685
|
stale: false,
|
|
618
686
|
policyStale: false,
|
|
619
687
|
expectedExcludeDigest,
|
|
688
|
+
purgePending: false,
|
|
620
689
|
warnings: ["index-v2 missing"]
|
|
621
690
|
};
|
|
622
691
|
}
|
|
@@ -628,20 +697,14 @@ export async function contextIndexV2Status(cwd, options = {}) {
|
|
|
628
697
|
const symbols = Number(db.prepare("SELECT COUNT(*) count FROM symbols").get().count);
|
|
629
698
|
const imports = Number(db.prepare("SELECT COUNT(*) count FROM imports").get().count);
|
|
630
699
|
const indexedPaths = new Set(fileRows.map((row) => row.path));
|
|
631
|
-
let storedExcludePatterns = [];
|
|
632
|
-
try {
|
|
633
|
-
const parsed = JSON.parse(metadata.excludePatterns ?? "[]");
|
|
634
|
-
if (Array.isArray(parsed)) storedExcludePatterns = normalizeContextIndexExcludePatterns(parsed);
|
|
635
|
-
} catch {
|
|
636
|
-
storedExcludePatterns = [];
|
|
637
|
-
}
|
|
638
|
-
const activeExcludePatterns = expectedExcludePatterns ?? storedExcludePatterns;
|
|
639
700
|
const storedExcludeDigest = metadata.excludeDigest;
|
|
640
|
-
const
|
|
641
|
-
|
|
701
|
+
const purgePending = metadata.purgePending === "1";
|
|
702
|
+
const excludeDigestStale = typeof storedExcludeDigest !== "string"
|
|
703
|
+
|| storedExcludeDigest !== expectedExcludeDigest;
|
|
704
|
+
const policyStale = purgePending || excludeDigestStale;
|
|
642
705
|
const statChanged = [];
|
|
643
706
|
for (const row of fileRows) {
|
|
644
|
-
if (!shouldIndexPath(row.path, { excludePatterns:
|
|
707
|
+
if (!shouldIndexPath(row.path, { excludePatterns: expectedExcludePatterns })) continue;
|
|
645
708
|
const file = projectFileInfo(cwd, row.path, projectRoot, directoryCache);
|
|
646
709
|
if (
|
|
647
710
|
!file
|
|
@@ -660,11 +723,12 @@ export async function contextIndexV2Status(cwd, options = {}) {
|
|
|
660
723
|
}
|
|
661
724
|
const listed = gitFileList(cwd);
|
|
662
725
|
const newlyVisible = listed
|
|
663
|
-
? listed.filter((file) => !indexedPaths.has(file) && !skippedPaths.has(file) && shouldIndexPath(file, { excludePatterns:
|
|
726
|
+
? listed.filter((file) => !indexedPaths.has(file) && !skippedPaths.has(file) && shouldIndexPath(file, { excludePatterns: expectedExcludePatterns }))
|
|
664
727
|
: [];
|
|
665
728
|
const stalePaths = [...new Set([...statChanged, ...newlyVisible])];
|
|
666
729
|
const warnings = [];
|
|
667
|
-
if (
|
|
730
|
+
if (purgePending) warnings.push("context index storage purge pending");
|
|
731
|
+
if (excludeDigestStale) warnings.push("context index exclusion policy changed");
|
|
668
732
|
if (stalePaths.length > 0) warnings.push(`${stalePaths.length} changed file(s) since the last index build`);
|
|
669
733
|
return {
|
|
670
734
|
schemaVersion: Number(metadata.schemaVersion ?? INDEX_SCHEMA_VERSION),
|
|
@@ -676,6 +740,7 @@ export async function contextIndexV2Status(cwd, options = {}) {
|
|
|
676
740
|
imports,
|
|
677
741
|
stale: policyStale || stalePaths.length > 0,
|
|
678
742
|
policyStale,
|
|
743
|
+
purgePending,
|
|
679
744
|
excludePolicyVersion: metadata.excludePolicyVersion,
|
|
680
745
|
excludeDigest: storedExcludeDigest,
|
|
681
746
|
expectedExcludeDigest,
|
|
@@ -6,7 +6,7 @@ import { resolveProjectProfileDocument } from "../capabilities/project-profile.j
|
|
|
6
6
|
|
|
7
7
|
const DEFAULT_CONTEXT_INDEX_PATH = ".pi/context-index.json";
|
|
8
8
|
const CONTEXT_ENGINE_STATE_PATTERN = ".pi/piagent-state/**";
|
|
9
|
-
const EXCLUDE_POLICY_VERSION =
|
|
9
|
+
const EXCLUDE_POLICY_VERSION = 2;
|
|
10
10
|
|
|
11
11
|
function stringArray(value) {
|
|
12
12
|
return Array.isArray(value)
|