pantheon-opencode 1.3.5 → 1.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/package.json +3 -3
- package/scripts/_pantheon_paths.py +0 -1
- package/scripts/build-agents-md.mjs +4 -2
- package/scripts/code_mode_server.py +307 -21
- package/scripts/doctor.mjs +26 -13
- package/scripts/hooks/format-multi-language.sh +6 -3
- package/scripts/hooks/log-session-start.sh +6 -0
- package/scripts/hooks/on-subagent-delegation-start.sh +22 -6
- package/scripts/hooks/validate-post-conditions.sh +1 -1
- package/scripts/hooks/validate-tool-safety.sh +5 -2
- package/scripts/install/agents-md.mjs +3 -1
- package/scripts/install/cli-ui.mjs +24 -4
- package/scripts/install/interactive.mjs +18 -7
- package/scripts/install/migrate-config.mjs +13 -19
- package/scripts/install/migrate.mjs +8 -5
- package/scripts/install/model-picker.mjs +4 -1
- package/scripts/install/opencode.mjs +2 -0
- package/scripts/install/shared.mjs +6 -4
- package/scripts/install/state.mjs +9 -6
- package/scripts/mcp_persistence_server.py +23 -22
- package/scripts/mcp_resources_server.py +60 -1
- package/scripts/memory_mcp_server.py +80 -4
- package/scripts/postinstall.mjs +27 -25
- package/scripts/prune.mjs +1 -1
- package/scripts/redaction-gate.mjs +3 -1
- package/scripts/sync-tui.mjs +148 -0
- package/scripts/uninstall.mjs +0 -2
- package/scripts/validation-policy.mjs +6 -1
- package/scripts/version-check.mjs +9 -4
- package/src/agents/aphrodite.md +1 -1
- package/src/agents/demeter.md +1 -1
- package/src/agents/hephaestus.md +1 -1
- package/src/agents/hermes.md +1 -1
- package/src/agents/themis.md +4 -0
- package/src/mcp/code_mode_server.py +304 -18
- package/src/mcp/eval_store.py +233 -0
- package/src/mcp/mcp_persistence_server.py +23 -22
- package/src/mcp/mcp_resources_server.py +58 -0
- package/src/mcp/memory_mcp_server.py +80 -3
- package/src/pantheon/auto-wake.ts +2 -4
- package/src/pantheon/background-job-board.ts +17 -4
- package/src/pantheon/comment-checker.ts +48 -0
- package/src/pantheon/delegation-classifier.ts +307 -0
- package/src/pantheon/delegation-enforce.ts +21 -21
- package/src/pantheon/delegation-finalize.ts +44 -4
- package/src/pantheon/delegation-notify.ts +97 -0
- package/src/pantheon/delegation.ts +413 -23
- package/src/pantheon/dispatch-guard.ts +2 -0
- package/src/pantheon/file-persistence.ts +7 -3
- package/src/pantheon/permission-eval.ts +284 -0
- package/src/pantheon/permission-globs.ts +86 -0
- package/src/pantheon/presets.d.mts +41 -0
- package/src/pantheon/presets.mjs +134 -0
- package/src/pantheon/retry-policy.ts +212 -0
- package/src/pantheon/session-guard.ts +1 -3
- package/src/pantheon/step-cap.ts +110 -0
- package/src/pantheon/tui-version-check.ts +72 -0
- package/src/pantheon/vision.ts +7 -5
- package/src/pantheon/zeus-delegate-with-retry.ts +81 -12
- package/src/plugin.ts +57 -3
- package/src/plugins/hook-runner.ts +1 -1
- package/src/plugins/pantheon-hooks.ts +1 -1
- package/src/plugins/tui/dist/tui.d.ts +1 -1
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +19 -14
- package/src/plugins/tui/dist/tui.tsx +52 -18
- package/src/plugins/tui/package.json +1 -1
- package/src/plugins/tui/src/index.tsx +106 -24
- package/src/plugins/tui/tsdown.config.ts +1 -1
- package/src/routing.yml +42 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# Auto-generated: resolved symlink from ../src/mcp/memory_mcp_server.py
|
|
2
1
|
#!/usr/bin/env python3
|
|
3
2
|
"""Pantheon Memory MCP Server — lightweight, zero heavy deps.
|
|
4
3
|
|
|
@@ -26,6 +25,7 @@ import json
|
|
|
26
25
|
import os
|
|
27
26
|
import sqlite3
|
|
28
27
|
import time
|
|
28
|
+
from datetime import datetime, timezone
|
|
29
29
|
from pathlib import Path
|
|
30
30
|
from typing import Any
|
|
31
31
|
|
|
@@ -187,10 +187,55 @@ def _parse_metadata(row: dict[str, Any]) -> dict[str, Any]:
|
|
|
187
187
|
_RRF_CONST = 60
|
|
188
188
|
|
|
189
189
|
|
|
190
|
+
def _parse_iso_ts(value: str) -> float:
|
|
191
|
+
"""Parse an ISO 8601 UTC timestamp to epoch seconds.
|
|
192
|
+
|
|
193
|
+
Handles both aware ISO strings (``2026-08-21T10:00:00Z`` /
|
|
194
|
+
``...+00:00``) and legacy naive rows from ``DEFAULT (datetime('now'))``
|
|
195
|
+
(``YYYY-MM-DD HH:MM:SS``, space separator, no TZ). Naive timestamps are
|
|
196
|
+
assumed UTC — SQLite's ``datetime('now')`` is UTC, so interpreting them
|
|
197
|
+
in the local timezone would skew freshness decay.
|
|
198
|
+
"""
|
|
199
|
+
dt = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
200
|
+
if dt.tzinfo is None:
|
|
201
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
202
|
+
return dt.timestamp()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _fetch_created_at_map(
|
|
206
|
+
db: sqlite3.Connection, ids: list[int]
|
|
207
|
+
) -> dict[int, str]:
|
|
208
|
+
"""Fetch created_at timestamps for the given memory IDs.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
db: Active SQLite connection.
|
|
212
|
+
ids: Memory row IDs to look up.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Mapping of id -> created_at ISO string. Empty on failure.
|
|
216
|
+
"""
|
|
217
|
+
created_at_map: dict[int, str] = {}
|
|
218
|
+
if not ids:
|
|
219
|
+
return created_at_map
|
|
220
|
+
try:
|
|
221
|
+
placeholders = ",".join("?" * len(ids))
|
|
222
|
+
rows = db.execute(
|
|
223
|
+
f"SELECT id, created_at FROM memories "
|
|
224
|
+
f"WHERE id IN ({placeholders})",
|
|
225
|
+
ids,
|
|
226
|
+
).fetchall()
|
|
227
|
+
created_at_map = {r["id"]: r["created_at"] for r in rows}
|
|
228
|
+
except Exception:
|
|
229
|
+
created_at_map = {}
|
|
230
|
+
return created_at_map
|
|
231
|
+
|
|
232
|
+
|
|
190
233
|
def _rrf_fuse(
|
|
191
234
|
vec_ids: list[int],
|
|
192
235
|
fts_ids: list[int],
|
|
193
236
|
top_k: int,
|
|
237
|
+
created_at_map: dict[int, str] | None = None,
|
|
238
|
+
decay_days: float | None = None,
|
|
194
239
|
) -> list[tuple[int, float]]:
|
|
195
240
|
"""Reciprocal Rank Fusion of vector and FTS5 result ID lists.
|
|
196
241
|
|
|
@@ -198,6 +243,12 @@ def _rrf_fuse(
|
|
|
198
243
|
vec_ids: Ordered list of IDs from vector search (most relevant first).
|
|
199
244
|
fts_ids: Ordered list of IDs from FTS5 search (most relevant first).
|
|
200
245
|
top_k: Maximum number of fused results to return.
|
|
246
|
+
created_at_map: Optional mapping of doc_id -> created_at ISO string,
|
|
247
|
+
used to compute freshness when ``decay_days`` is set.
|
|
248
|
+
decay_days: Optional freshness half-life in days. When set, each fused
|
|
249
|
+
score is multiplied by ``freshness = 2^(-days/decay_days)`` so
|
|
250
|
+
older entries rank lower. Default None = no decay (backward
|
|
251
|
+
compatible).
|
|
201
252
|
|
|
202
253
|
Returns:
|
|
203
254
|
List of (id, rrf_score) tuples sorted by descending score.
|
|
@@ -207,6 +258,19 @@ def _rrf_fuse(
|
|
|
207
258
|
scores[doc_id] = 1.0 / (_RRF_CONST + rank)
|
|
208
259
|
for rank, doc_id in enumerate(fts_ids, start=1):
|
|
209
260
|
scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (_RRF_CONST + rank)
|
|
261
|
+
|
|
262
|
+
if decay_days is not None and decay_days > 0 and created_at_map:
|
|
263
|
+
now = time.time()
|
|
264
|
+
for doc_id in list(scores):
|
|
265
|
+
created = created_at_map.get(doc_id)
|
|
266
|
+
if not created:
|
|
267
|
+
continue
|
|
268
|
+
try:
|
|
269
|
+
days = max(0.0, (now - _parse_iso_ts(created)) / 86400.0)
|
|
270
|
+
except (ValueError, TypeError):
|
|
271
|
+
continue
|
|
272
|
+
scores[doc_id] *= 2.0 ** (-days / decay_days)
|
|
273
|
+
|
|
210
274
|
ranked = sorted(scores.items(), key=lambda x: (-x[1], x[0]))
|
|
211
275
|
return ranked[:top_k]
|
|
212
276
|
|
|
@@ -283,12 +347,15 @@ def memory_store(
|
|
|
283
347
|
@mcp.tool(
|
|
284
348
|
description="Hybrid semantic search across memories. "
|
|
285
349
|
"Combines vector cosine similarity and FTS5 BM25 keyword search "
|
|
286
|
-
"via Reciprocal Rank Fusion (RRF)."
|
|
350
|
+
"via Reciprocal Rank Fusion (RRF). Optional decay_days applies a "
|
|
351
|
+
"freshness half-life (2^(-days/decay_days)) so recent entries rank "
|
|
352
|
+
"higher; default None keeps the original rank-only behavior.",
|
|
287
353
|
)
|
|
288
354
|
def memory_search( # noqa: PLR0912
|
|
289
355
|
query: str,
|
|
290
356
|
namespace: str | None = None,
|
|
291
357
|
top_k: int = 5,
|
|
358
|
+
decay_days: float | None = None,
|
|
292
359
|
) -> list[dict[str, Any]]:
|
|
293
360
|
"""Hybrid search: vector + keyword fused via RRF.
|
|
294
361
|
|
|
@@ -300,6 +367,9 @@ def memory_search( # noqa: PLR0912
|
|
|
300
367
|
query: Search query text.
|
|
301
368
|
namespace: Optional namespace filter.
|
|
302
369
|
top_k: Maximum results (default 5, max 50).
|
|
370
|
+
decay_days: Optional freshness half-life in days. When set, fused
|
|
371
|
+
scores are multiplied by ``2^(-days_since_created/decay_days)``
|
|
372
|
+
so older entries rank lower. Default None = no decay.
|
|
303
373
|
|
|
304
374
|
Returns:
|
|
305
375
|
List of memory entries with score and metadata.
|
|
@@ -365,8 +435,14 @@ def memory_search( # noqa: PLR0912
|
|
|
365
435
|
except Exception:
|
|
366
436
|
fts_ids = []
|
|
367
437
|
|
|
368
|
-
# 4. RRF fusion
|
|
369
|
-
|
|
438
|
+
# 4. RRF fusion (optionally freshness-decayed)
|
|
439
|
+
candidate_ids = list(dict.fromkeys([*vec_ids, *fts_ids]))
|
|
440
|
+
created_at_map = (
|
|
441
|
+
_fetch_created_at_map(db, candidate_ids)
|
|
442
|
+
if decay_days is not None and decay_days > 0
|
|
443
|
+
else {}
|
|
444
|
+
)
|
|
445
|
+
fused = _rrf_fuse(vec_ids, fts_ids, top_k, created_at_map, decay_days)
|
|
370
446
|
|
|
371
447
|
if not fused:
|
|
372
448
|
return []
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -5,43 +5,45 @@
|
|
|
5
5
|
* Runs after `npm install` completes. Validates environment and prints
|
|
6
6
|
* setup instructions. Does NOT install Pantheon platform components.
|
|
7
7
|
*/
|
|
8
|
-
import { existsSync } from 'node:fs'
|
|
9
|
-
import { join } from 'node:path'
|
|
10
|
-
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import { existsSync } from 'node:fs'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
import { fileURLToPath } from 'node:url'
|
|
11
11
|
|
|
12
|
-
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
13
|
-
const
|
|
12
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
13
|
+
const _ROOT = join(__dirname, '..')
|
|
14
14
|
|
|
15
|
-
const REQUIRED_NODE_MAJOR = 18
|
|
16
|
-
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10)
|
|
15
|
+
const REQUIRED_NODE_MAJOR = 18
|
|
16
|
+
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10)
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
19
|
// Validate Node.js version
|
|
20
20
|
if (nodeMajor < REQUIRED_NODE_MAJOR) {
|
|
21
|
-
console.error(
|
|
22
|
-
|
|
21
|
+
console.error(
|
|
22
|
+
`❌ Node.js >= ${REQUIRED_NODE_MAJOR} required (current: ${process.versions.node})`,
|
|
23
|
+
)
|
|
24
|
+
process.exit(1)
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
// Validate js-yaml is available
|
|
26
28
|
try {
|
|
27
|
-
await import('js-yaml')
|
|
29
|
+
await import('js-yaml')
|
|
28
30
|
} catch {
|
|
29
|
-
console.warn('⚠️ js-yaml not found — run `npm install` to install dependencies')
|
|
31
|
+
console.warn('⚠️ js-yaml not found — run `npm install` to install dependencies')
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
console.log('')
|
|
33
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
34
|
-
console.log(' Pantheon OpenCode — npm dependencies installed ✅')
|
|
35
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
36
|
-
console.log('')
|
|
37
|
-
console.log(' Next steps:')
|
|
38
|
-
console.log(' 1. Configure Pantheon:')
|
|
39
|
-
console.log(' npm run setup')
|
|
40
|
-
console.log(' # or: npx pantheon-opencode init')
|
|
41
|
-
console.log(' 2. Verify installation:')
|
|
42
|
-
console.log(' npm run doctor')
|
|
43
|
-
console.log('')
|
|
34
|
+
console.log('')
|
|
35
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
36
|
+
console.log(' Pantheon OpenCode — npm dependencies installed ✅')
|
|
37
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
38
|
+
console.log('')
|
|
39
|
+
console.log(' Next steps:')
|
|
40
|
+
console.log(' 1. Configure Pantheon:')
|
|
41
|
+
console.log(' npm run setup')
|
|
42
|
+
console.log(' # or: npx pantheon-opencode init')
|
|
43
|
+
console.log(' 2. Verify installation:')
|
|
44
|
+
console.log(' npm run doctor')
|
|
45
|
+
console.log('')
|
|
44
46
|
} catch (err) {
|
|
45
|
-
console.error(`❌ Post-install check failed: ${err.message}`)
|
|
46
|
-
process.exit(1)
|
|
47
|
+
console.error(`❌ Post-install check failed: ${err.message}`)
|
|
48
|
+
process.exit(1)
|
|
47
49
|
}
|
package/scripts/prune.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import { dirname, join, resolve } from 'node:path'
|
|
|
34
34
|
import { fileURLToPath } from 'node:url'
|
|
35
35
|
|
|
36
36
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
37
|
-
const
|
|
37
|
+
const _ROOT = join(__dirname, '..')
|
|
38
38
|
const DEFAULT_BACKUP_AGE_DAYS = 30
|
|
39
39
|
|
|
40
40
|
// ---------------------------------------------------------------------------
|
|
@@ -30,7 +30,9 @@ function scanFile(path) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function scanTarball(path) {
|
|
33
|
-
const entries = execFileSync('tar', ['-tzf', path], { encoding: 'utf8' })
|
|
33
|
+
const entries = execFileSync('tar', ['-tzf', path], { encoding: 'utf8' })
|
|
34
|
+
.split('\n')
|
|
35
|
+
.filter(Boolean)
|
|
34
36
|
const result = []
|
|
35
37
|
for (const entry of entries) {
|
|
36
38
|
const content = execFileSync('tar', ['-xOzf', path, entry])
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* sync-tui.mjs — Postinstall hook: sync TUI plugin to user's OpenCode config
|
|
4
|
+
*
|
|
5
|
+
* Runs automatically after `npm install pantheon-opencode@latest`. Detects the
|
|
6
|
+
* user's OpenCode config directory and, if the TUI plugin was previously
|
|
7
|
+
* installed (via `npx pantheon-opencode init`), copies fresh files from the
|
|
8
|
+
* installed package and refreshes dependencies.
|
|
9
|
+
*
|
|
10
|
+
* If the user hasn't initialized yet → do nothing (silent exit).
|
|
11
|
+
*/
|
|
12
|
+
import { execSync } from 'node:child_process'
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
14
|
+
import { homedir } from 'node:os'
|
|
15
|
+
import { join } from 'node:path'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
|
|
18
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
19
|
+
const ROOT = join(__dirname, '..')
|
|
20
|
+
|
|
21
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Write content to filePath only if it differs from what's on disk.
|
|
25
|
+
* @returns {boolean} true if file was written (changed), false if skipped
|
|
26
|
+
*/
|
|
27
|
+
function writeIfChanged(filePath, content) {
|
|
28
|
+
const existing = existsSync(filePath) ? readFileSync(filePath, 'utf8') : null
|
|
29
|
+
if (existing === content) return false
|
|
30
|
+
writeFileSync(filePath, content, 'utf8')
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Read the `version` field from a package.json, or null on failure.
|
|
36
|
+
*/
|
|
37
|
+
function readVersion(pkgPath) {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8')).version || null
|
|
40
|
+
} catch {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the user's OpenCode config directory.
|
|
47
|
+
* Priority: $XDG_CONFIG_HOME/opencode → ~/.opencode → null (not initialized)
|
|
48
|
+
*/
|
|
49
|
+
function resolveConfigDir() {
|
|
50
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), '.config')
|
|
51
|
+
const xdgDir = join(xdgConfig, 'opencode')
|
|
52
|
+
if (existsSync(xdgDir)) return xdgDir
|
|
53
|
+
|
|
54
|
+
const homeDir = join(homedir(), '.opencode')
|
|
55
|
+
if (existsSync(homeDir)) return homeDir
|
|
56
|
+
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Copy plugin runtime files (dist/*, package.json, src/index.tsx) from srcDir
|
|
62
|
+
* to dstDir. Skips files that are byte-identical.
|
|
63
|
+
* @returns {{ created: number, skipped: number }}
|
|
64
|
+
*/
|
|
65
|
+
function copyPluginFiles(srcDir, dstDir) {
|
|
66
|
+
const result = { created: 0, skipped: 0 }
|
|
67
|
+
|
|
68
|
+
mkdirSync(dstDir, { recursive: true })
|
|
69
|
+
mkdirSync(join(dstDir, 'dist'), { recursive: true })
|
|
70
|
+
|
|
71
|
+
// src/index.tsx → index.tsx
|
|
72
|
+
const srcIdx = join(srcDir, 'src', 'index.tsx')
|
|
73
|
+
if (existsSync(srcIdx)) {
|
|
74
|
+
const content = readFileSync(srcIdx, 'utf8')
|
|
75
|
+
if (writeIfChanged(join(dstDir, 'index.tsx'), content)) result.created++
|
|
76
|
+
else result.skipped++
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// dist/*
|
|
80
|
+
const distSrc = join(srcDir, 'dist')
|
|
81
|
+
if (existsSync(distSrc)) {
|
|
82
|
+
for (const f of readdirSync(distSrc)) {
|
|
83
|
+
const content = readFileSync(join(distSrc, f), 'utf8')
|
|
84
|
+
if (writeIfChanged(join(dstDir, 'dist', f), content)) result.created++
|
|
85
|
+
else result.skipped++
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// package.json
|
|
90
|
+
const pkgSrc = join(srcDir, 'package.json')
|
|
91
|
+
if (existsSync(pkgSrc)) {
|
|
92
|
+
const content = readFileSync(pkgSrc, 'utf8')
|
|
93
|
+
if (writeIfChanged(join(dstDir, 'package.json'), content)) result.created++
|
|
94
|
+
else result.skipped++
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return result
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Main ───────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const configDir = resolveConfigDir()
|
|
104
|
+
if (!configDir) {
|
|
105
|
+
// User hasn't run init yet — silent exit, no error
|
|
106
|
+
process.exit(0)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const tuiCopyDir = join(configDir, 'plugins', 'pantheon-tui')
|
|
110
|
+
if (!existsSync(tuiCopyDir)) {
|
|
111
|
+
// TUI plugin not installed — silent exit
|
|
112
|
+
process.exit(0)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Source: TUI plugin inside the installed package
|
|
116
|
+
const tuiSrcDir = join(ROOT, 'src', 'plugins', 'tui')
|
|
117
|
+
if (!existsSync(tuiSrcDir)) {
|
|
118
|
+
// Source not found (shouldn't happen in a proper install)
|
|
119
|
+
process.exit(0)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Compare versions before copy
|
|
123
|
+
const installedVersion = readVersion(join(tuiCopyDir, 'package.json'))
|
|
124
|
+
const sourceVersion = readVersion(join(tuiSrcDir, 'package.json'))
|
|
125
|
+
|
|
126
|
+
// Copy fresh files
|
|
127
|
+
const { created, skipped } = copyPluginFiles(tuiSrcDir, tuiCopyDir)
|
|
128
|
+
|
|
129
|
+
// Refresh dependencies in the copy
|
|
130
|
+
try {
|
|
131
|
+
execSync('npm install --omit=dev --no-audit', { cwd: tuiCopyDir, stdio: 'pipe' })
|
|
132
|
+
} catch {
|
|
133
|
+
// Non-fatal — deps may already be current
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Log result
|
|
137
|
+
if (installedVersion && sourceVersion && installedVersion === sourceVersion && created === 0) {
|
|
138
|
+
console.log(` TUI plugin already up to date (v${sourceVersion})`)
|
|
139
|
+
} else {
|
|
140
|
+
console.log(
|
|
141
|
+
` TUI plugin updated to v${sourceVersion || 'latest'}${installedVersion ? ` (was v${installedVersion})` : ''}`,
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
// Postinstall scripts must never break npm install
|
|
146
|
+
console.warn(`⚠️ TUI sync skipped: ${err.message}`)
|
|
147
|
+
process.exit(0)
|
|
148
|
+
}
|
package/scripts/uninstall.mjs
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/** Status/exit policy shared by installation validation and its tests. */
|
|
2
|
-
export const VALIDATION_STATUS = Object.freeze({
|
|
2
|
+
export const VALIDATION_STATUS = Object.freeze({
|
|
3
|
+
PASS: 'PASS',
|
|
4
|
+
WARN: 'WARN',
|
|
5
|
+
ERROR: 'ERROR',
|
|
6
|
+
SKIP: 'SKIP',
|
|
7
|
+
})
|
|
3
8
|
|
|
4
9
|
export function classifyDoctorExit(exitStatus, output = '') {
|
|
5
10
|
if (exitStatus === 0) return VALIDATION_STATUS.PASS
|
|
@@ -26,7 +26,11 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
|
26
26
|
export const MANIFESTS = [
|
|
27
27
|
{ name: 'pyproject.toml', file: 'pyproject.toml', kind: 'toml' },
|
|
28
28
|
{ name: 'plugin.json', file: 'plugin.json', kind: 'json' },
|
|
29
|
-
{
|
|
29
|
+
{
|
|
30
|
+
name: 'src/plugins/tui/package.json',
|
|
31
|
+
file: join('src', 'plugins', 'tui', 'package.json'),
|
|
32
|
+
kind: 'json',
|
|
33
|
+
},
|
|
30
34
|
]
|
|
31
35
|
|
|
32
36
|
function readTomlVersion(filePath) {
|
|
@@ -109,8 +113,7 @@ function printDivergenceTable(result) {
|
|
|
109
113
|
console.log('')
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
const isDirectRun =
|
|
113
|
-
process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href
|
|
116
|
+
const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href
|
|
114
117
|
|
|
115
118
|
if (isDirectRun) {
|
|
116
119
|
// npm scripts run from the package root, so the working directory is the
|
|
@@ -138,7 +141,9 @@ if (isDirectRun) {
|
|
|
138
141
|
console.log(`✓ Synced ${changed} manifest(s) to v${after.source}`)
|
|
139
142
|
process.exit(0)
|
|
140
143
|
}
|
|
141
|
-
console.error(
|
|
144
|
+
console.error(
|
|
145
|
+
`version-check: sync incomplete — ${after.manifests.filter((m) => !m.ok).length} manifest(s) still divergent`,
|
|
146
|
+
)
|
|
142
147
|
process.exit(1)
|
|
143
148
|
}
|
|
144
149
|
console.error('version-check: manifests out of sync (run with --fix to sync)')
|
package/src/agents/aphrodite.md
CHANGED
package/src/agents/demeter.md
CHANGED
package/src/agents/hephaestus.md
CHANGED
package/src/agents/hermes.md
CHANGED
package/src/agents/themis.md
CHANGED
|
@@ -97,6 +97,10 @@ Verificacoes rapidas de estilo e sanidade:
|
|
|
97
97
|
- [ ] Formatacao consistente (mesmo style guide)
|
|
98
98
|
- [ ] Nomes de variaveis/funcoes descritivos
|
|
99
99
|
- [ ] Comentarios TODO ou FIXME sem resolucao
|
|
100
|
+
- [ ] Comment Checker: codigo gerado indistinguivel de humano
|
|
101
|
+
- Flag comentarios obvios/excessivos (ex: `// increment i`, `# set x to 5`)
|
|
102
|
+
- Flag codigo morto comentado (blocos inteiros desativados)
|
|
103
|
+
- Flag cabecalhos boilerplate (autor/data/licenca repetitivos)
|
|
100
104
|
|
|
101
105
|
Tempo estimado: ~10% do esforco de review
|
|
102
106
|
|