pantheon-opencode 1.5.2 → 1.6.0-beta.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/.pantheon/code-mode/README.md +4 -2
- package/.pantheon/code-mode/checkpoint_session.py +138 -102
- package/.pantheon/code-mode/compress-inline.py +102 -57
- package/.pantheon/code-mode/manifest.json +2 -8
- package/AGENTS.global.md +182 -0
- package/AGENTS.md +10 -891
- package/CHANGELOG.md +343 -0
- package/README.md +577 -79
- package/README.pt-BR.md +345 -60
- package/bin/pantheon-init.mjs +26 -3
- package/commands/pantheon-audit.md +7 -1
- package/commands/pantheon-consolidate.md +1 -1
- package/commands/pantheon-deepwork.md +14 -3
- package/commands/pantheon-focus.md +17 -0
- package/commands/pantheon-optimize.md +2 -2
- package/commands/pantheon-vision.md +61 -12
- package/package.json +30 -11
- package/plugin.json +2 -2
- package/pyproject.toml +11 -16
- package/scripts/build-agents-md.mjs +376 -58
- package/scripts/changelog-merge.mjs +161 -0
- package/scripts/check-plugin-v2-coverage.mjs +213 -0
- package/scripts/ci-validate-yaml.py +7 -5
- package/scripts/{code_mode_server.py → code_mode.py} +119 -27
- package/scripts/cost.mjs +244 -41
- package/scripts/doctor.mjs +236 -29
- package/scripts/install/agents-md.mjs +4 -4
- package/scripts/install/cli-ui.mjs +1 -1
- package/scripts/install/config-migration.mjs +319 -6
- package/scripts/install/health-check.mjs +26 -37
- package/scripts/install/interactive.mjs +7 -7
- package/scripts/install/migrate.mjs +1 -1
- package/scripts/install/opencode-version.mjs +246 -4
- package/scripts/install/opencode.mjs +627 -179
- package/scripts/install/plugin.mjs +43 -9
- package/scripts/install/shared.mjs +120 -12
- package/scripts/install/vision-prompt.mjs +161 -0
- package/scripts/install-mcp.mjs +28 -9
- package/scripts/{mcp_persistence_server.py → mcp_persistence.py} +392 -78
- package/{src/mcp/mcp_resources_server.py → scripts/mcp_resources.py} +2 -2
- package/scripts/{memory_mcp_server.py → memory_mcp.py} +372 -198
- package/scripts/postinstall.mjs +0 -7
- package/scripts/probe-context-rehydrate.mjs +36 -13
- package/scripts/probe-pantheon-cost.mjs +71 -15
- package/scripts/prune.mjs +2 -2
- package/scripts/run-node-tests.mjs +189 -0
- package/scripts/sync-artifacts.mjs +26 -13
- package/scripts/sync-tui.mjs +24 -3
- package/scripts/test-opencode-v2-sandbox.sh +1913 -0
- package/scripts/validate-routing.mjs +43 -3
- package/scripts/validate-sandbox-install.mjs +138 -0
- package/scripts/versioning.mjs +13 -1
- package/scripts/vision-status.mjs +682 -0
- package/skills-lock.json +15 -16
- package/src/agents/aphrodite.md +20 -37
- package/src/agents/apollo.md +7 -3
- package/src/agents/athena.md +63 -35
- package/src/agents/demeter.md +8 -28
- package/src/agents/gaia.md +1 -1
- package/src/agents/hephaestus.md +10 -21
- package/src/agents/hermes.md +15 -38
- package/src/agents/iris.md +1 -1
- package/src/agents/mnemosyne.md +23 -60
- package/src/agents/nyx.md +1 -1
- package/src/agents/prometheus.md +16 -20
- package/src/agents/talos.md +10 -2
- package/src/agents/themis.md +5 -5
- package/src/agents/zeus.md +99 -130
- package/src/instructions/agent-return-format.instructions.md +18 -3
- package/src/instructions/backend-standards.instructions.md +2 -0
- package/src/instructions/frontend-standards.instructions.md +3 -1
- package/src/instructions/memory-protocol.instructions.md +11 -110
- package/src/instructions/zeus-anti-stall.instructions.md +14 -61
- package/src/instructions/zeus-communication-rules.instructions.md +0 -1
- package/src/instructions/zeus-council-synthesis.instructions.md +32 -260
- package/src/instructions/zeus-memory-operations.instructions.md +26 -0
- package/src/instructions/zeus-timeout-retry.instructions.md +40 -87
- package/src/mcp/{code_mode_server.py → code_mode.py} +119 -27
- package/src/mcp/eval_store.py +2 -2
- package/src/mcp/mcp_codemap_module.py +559 -129
- package/src/mcp/{mcp_persistence_server.py → mcp_persistence.py} +392 -78
- package/{scripts/mcp_resources_server.py → src/mcp/mcp_resources.py} +2 -2
- package/src/mcp/{memory_mcp_server.py → memory_mcp.py} +373 -250
- package/src/mcp/{pantheon_vision_server.py → pantheon_vision.py} +396 -40
- package/src/mcp/requirements-mcp.txt +10 -6
- package/src/mcp/requirements-vision.txt +4 -2
- package/src/pantheon/cost-command.ts +399 -101
- package/src/pantheon/delegation-enforce.ts +10 -1
- package/src/pantheon/model-command.ts +18 -7
- package/src/pantheon/presets.d.mts +0 -8
- package/src/pantheon/presets.mjs +5 -32
- package/src/pantheon/todo-enforcer.ts +1 -1
- package/src/pantheon/v2-bounded-read.ts +640 -0
- package/src/pantheon/v2-delegation-enforce.ts +214 -0
- package/src/pantheon/v2-execute-after.ts +186 -0
- package/src/pantheon/v2-tools.ts +429 -0
- package/src/pantheon/v2-unsupported.d.mts +11 -0
- package/src/pantheon/v2-unsupported.mjs +90 -0
- package/src/pantheon/v2-vision.ts +622 -0
- package/src/pantheon/vision-local.ts +531 -0
- package/src/pantheon/vision.ts +1161 -116
- package/src/plugin-v2.ts +1244 -288
- package/src/plugin.ts +12 -9
- package/src/plugins/tui/dist/server.d.ts +1 -2
- package/src/plugins/tui/dist/server.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui-v2.d.ts +154 -0
- package/src/plugins/tui/dist/tui-v2.d.ts.map +1 -0
- package/src/plugins/tui/dist/tui-v2.js +659 -0
- package/src/plugins/tui/dist/tui-v2.js.map +1 -0
- package/src/plugins/tui/dist/tui.d.ts +85 -82
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +72 -59
- package/src/plugins/tui/index-v2.ts +6 -0
- package/src/plugins/tui/package-lock.json +5504 -1341
- package/src/plugins/tui/package.json +25 -15
- package/src/plugins/tui/src/index-v2.tsx +734 -0
- package/src/plugins/tui/src/index.tsx +106 -85
- package/src/plugins/tui/src/opencode-v2.d.ts +13 -0
- package/src/plugins/tui/tsconfig.build.json +5 -0
- package/src/plugins/tui/tsconfig.typecheck.json +5 -0
- package/src/plugins/tui/tsdown.config.ts +22 -2
- package/src/plugins/tui/tui-v2.ts +2 -0
- package/src/plugins/tui/tui.ts +3 -0
- package/src/routing.yml +11 -22
- package/src/skills/README.md +34 -39
- package/src/skills/agent-coordination/SKILL.md +7 -2
- package/src/skills/artifact-management/SKILL.md +12 -5
- package/src/skills/auto-continue/SKILL.md +54 -95
- package/src/skills/code-review-checklist/SKILL.md +5 -5
- package/src/skills/context-compression/SKILL.md +26 -879
- package/src/skills/session-goal/SKILL.md +14 -5
- package/src/skills/spec-driven-development/SKILL.md +3 -1
- package/src/skills/tdd-with-agents/SKILL.md +18 -10
- package/.pantheon/code-mode/eval-llm-judge.py +0 -255
- package/.pantheon/code-mode/eval-monte-carlo.py +0 -368
- package/.pantheon/code-mode/eval-run.py +0 -197
- package/.pantheon/code-mode/eval-static.py +0 -321
- package/.pantheon/code-mode/session-end-save.py +0 -186
- package/.pantheon/code-mode/session-end-save.sh +0 -18
- package/opencode.json +0 -58
- package/scripts/recover-zenodo-deposition.mjs +0 -146
- package/scripts/test-opencode-v1-v2-sandbox.sh +0 -862
- package/scripts/validate-zenodo-release.mjs +0 -154
- package/scripts/zenodo-artifact.mjs +0 -94
- package/scripts/zenodo-release-state.mjs +0 -53
- package/src/skills/orchestration-workflow/SKILL.md +0 -311
package/scripts/doctor.mjs
CHANGED
|
@@ -29,6 +29,8 @@ import { homedir } from 'node:os'
|
|
|
29
29
|
import { basename, dirname, isAbsolute, join, resolve } from 'node:path'
|
|
30
30
|
import { fileURLToPath } from 'node:url'
|
|
31
31
|
|
|
32
|
+
import { listV2UnsupportedFeatures } from '../src/pantheon/v2-unsupported.mjs'
|
|
33
|
+
|
|
32
34
|
// ---------------------------------------------------------------------------
|
|
33
35
|
// Paths
|
|
34
36
|
// ---------------------------------------------------------------------------
|
|
@@ -88,15 +90,25 @@ function parseArgs(argv) {
|
|
|
88
90
|
args.target = process.cwd()
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
if (args.profile === 'auto') {
|
|
92
|
-
args.profile = args.target.includes('pantheon-sandbox') ? 'sandbox' : 'global'
|
|
93
|
-
}
|
|
94
|
-
|
|
95
93
|
// Resolve to absolute
|
|
96
94
|
if (!args.target.startsWith('/')) {
|
|
97
95
|
args.target = join(process.cwd(), args.target)
|
|
98
96
|
}
|
|
99
97
|
|
|
98
|
+
if (args.profile === 'auto') {
|
|
99
|
+
const target = resolve(args.target)
|
|
100
|
+
// A checkout is not a managed OpenCode installation: its repository
|
|
101
|
+
// config intentionally contains only development MCPs. Use the lite
|
|
102
|
+
// policy for `npm run doctor` so a fresh checkout does not require a
|
|
103
|
+
// user's global MCP registry or pre-existing venv.
|
|
104
|
+
args.profile =
|
|
105
|
+
target === resolve(ROOT)
|
|
106
|
+
? 'lite'
|
|
107
|
+
: args.target.includes('pantheon-sandbox')
|
|
108
|
+
? 'sandbox'
|
|
109
|
+
: 'global'
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
return args
|
|
101
113
|
}
|
|
102
114
|
|
|
@@ -159,7 +171,7 @@ function showHelp() {
|
|
|
159
171
|
C. Permission Checks — frontmatter validation
|
|
160
172
|
D. OpenCode Status — OpenCode artifact layout
|
|
161
173
|
E. Git Status — uncommitted changes
|
|
162
|
-
|
|
174
|
+
F. Runtime Layer — runtime Python + pinned pip dependencies
|
|
163
175
|
H3. Plugin Drift — registered plugin version vs this package
|
|
164
176
|
K. Node Runtime — node:sqlite availability for pantheon_cost
|
|
165
177
|
|
|
@@ -818,12 +830,14 @@ function checkGitStatus(args) {
|
|
|
818
830
|
}
|
|
819
831
|
|
|
820
832
|
// ---------------------------------------------------------------------------
|
|
821
|
-
// Check F: Runtime Layer —
|
|
833
|
+
// Check F: Runtime Layer — Runtime Python & pinned dependencies
|
|
822
834
|
// ---------------------------------------------------------------------------
|
|
823
835
|
// The MCP servers run on the venv Python under ~/.config/opencode/.venv
|
|
824
|
-
// (same resolution as scripts/install-mcp.mjs)
|
|
825
|
-
//
|
|
826
|
-
//
|
|
836
|
+
// (same resolution as scripts/install-mcp.mjs) after installation. A
|
|
837
|
+
// repository checkout/CI job is not an installed target, however, so it may
|
|
838
|
+
// intentionally have no venv. In that case the doctor uses PANTHEON_PYTHON or
|
|
839
|
+
// python3 and still validates the exact pins. Missing or mismatched packages
|
|
840
|
+
// remain blocking errors; the fallback never makes the runtime check advisory
|
|
827
841
|
// (issue #18 / #21).
|
|
828
842
|
|
|
829
843
|
/** Parse `pip freeze` output into a normalized name → version map. */
|
|
@@ -906,31 +920,53 @@ function checkRequirementLine(line, installed) {
|
|
|
906
920
|
}
|
|
907
921
|
}
|
|
908
922
|
|
|
923
|
+
/** Classify whether a required runtime file is available to the doctor. */
|
|
924
|
+
export function classifyRequiredRuntimeFile(exists) {
|
|
925
|
+
return exists ? 'present' : 'error'
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/** Validate a required runtime requirements file without downgrading absence. */
|
|
929
|
+
export function validateRequiredRuntimeFile(filePath) {
|
|
930
|
+
const present = classifyRequiredRuntimeFile(existsSync(filePath)) === 'present'
|
|
931
|
+
return {
|
|
932
|
+
ok: present,
|
|
933
|
+
message: present
|
|
934
|
+
? `requirements file present: ${filePath}`
|
|
935
|
+
: `requirements file not found: ${filePath}`,
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
909
939
|
function checkVenvLayer(args) {
|
|
910
|
-
section('F. Runtime Layer —
|
|
940
|
+
section('F. Runtime Layer — Runtime Python & Dependencies')
|
|
911
941
|
|
|
912
|
-
const
|
|
942
|
+
const runtimePython = resolveRuntimePython(args)
|
|
913
943
|
|
|
914
|
-
if (!existsSync(
|
|
915
|
-
if (
|
|
916
|
-
info(
|
|
944
|
+
if (!isAbsolute(runtimePython) || !existsSync(runtimePython)) {
|
|
945
|
+
if (runtimePython === 'python3') {
|
|
946
|
+
info('No dedicated .venv found — validating the Python interpreter on PATH')
|
|
917
947
|
} else {
|
|
918
|
-
error(
|
|
948
|
+
error(
|
|
949
|
+
`Runtime Python not found: ${runtimePython} (set PANTHEON_PYTHON or run \`pantheon-opencode init\` to create a .venv)`,
|
|
950
|
+
)
|
|
951
|
+
return
|
|
919
952
|
}
|
|
920
|
-
return
|
|
921
953
|
}
|
|
922
|
-
|
|
954
|
+
if (isAbsolute(runtimePython) && existsSync(runtimePython)) {
|
|
955
|
+
pass(`Runtime Python exists: ${runtimePython}`)
|
|
956
|
+
}
|
|
923
957
|
|
|
924
|
-
const probe = spawn(
|
|
958
|
+
const probe = spawn(runtimePython, ['-c', 'import sys; print(sys.version.split()[0])'])
|
|
925
959
|
if (probe.status !== 0) {
|
|
926
|
-
error(
|
|
960
|
+
error(
|
|
961
|
+
`Runtime Python does not respond (exit ${probe.status}): ${probe.stderr || 'interpreter is unavailable'}`,
|
|
962
|
+
)
|
|
927
963
|
return
|
|
928
964
|
}
|
|
929
|
-
pass(`
|
|
965
|
+
pass(`Runtime Python responds (Python ${probe.stdout})`)
|
|
930
966
|
|
|
931
|
-
const freeze = spawn(
|
|
967
|
+
const freeze = spawn(runtimePython, ['-m', 'pip', 'freeze'])
|
|
932
968
|
if (freeze.status !== 0) {
|
|
933
|
-
|
|
969
|
+
error(`pip freeze failed (exit ${freeze.status}) — cannot verify pinned versions`)
|
|
934
970
|
return
|
|
935
971
|
}
|
|
936
972
|
const installed = parsePipFreeze(freeze.stdout)
|
|
@@ -940,8 +976,9 @@ function checkVenvLayer(args) {
|
|
|
940
976
|
join(ROOT, 'src', 'mcp', 'requirements-vision.txt'),
|
|
941
977
|
]
|
|
942
978
|
for (const reqFile of reqFiles) {
|
|
943
|
-
|
|
944
|
-
|
|
979
|
+
const requirement = validateRequiredRuntimeFile(reqFile)
|
|
980
|
+
if (!requirement.ok) {
|
|
981
|
+
error(requirement.message)
|
|
945
982
|
continue
|
|
946
983
|
}
|
|
947
984
|
const base = basename(reqFile)
|
|
@@ -965,7 +1002,7 @@ function checkVenvLayer(args) {
|
|
|
965
1002
|
/**
|
|
966
1003
|
* Return whether *dir* contains an executable code-mode target.
|
|
967
1004
|
*
|
|
968
|
-
* Mirrors `_has_usable_scripts` in `scripts/
|
|
1005
|
+
* Mirrors `_has_usable_scripts` in `scripts/code_mode.py`: a directory
|
|
969
1006
|
* is only a valid resolver candidate when it is a real (non-symlink) directory
|
|
970
1007
|
* containing a regular `.py`/`.sh` direct child. This prevents an empty project
|
|
971
1008
|
* overlay from masking a lower-priority installation that actually ships
|
|
@@ -1073,7 +1110,9 @@ export function validateCodeModeManifest(codeModeDir) {
|
|
|
1073
1110
|
|
|
1074
1111
|
const actualScripts = readdirSync(codeModeDir).filter(
|
|
1075
1112
|
(name) =>
|
|
1076
|
-
|
|
1113
|
+
!name.startsWith('eval-') &&
|
|
1114
|
+
(name.endsWith('.py') || name.endsWith('.sh')) &&
|
|
1115
|
+
statSync(join(codeModeDir, name)).isFile(),
|
|
1077
1116
|
)
|
|
1078
1117
|
const missing = listed.filter((name) => !existsSync(join(codeModeDir, name)))
|
|
1079
1118
|
const extra = actualScripts.filter((name) => !Object.hasOwn(data.scripts, name))
|
|
@@ -1104,12 +1143,32 @@ export function checkCodeModeManifest(args) {
|
|
|
1104
1143
|
pass(`✅ Code-mode manifest valid: ${result.count}/${result.total} scripts verified`)
|
|
1105
1144
|
}
|
|
1106
1145
|
|
|
1107
|
-
/**
|
|
1146
|
+
/**
|
|
1147
|
+
* Resolve the Python used by the runtime checks.
|
|
1148
|
+
*
|
|
1149
|
+
* Installed targets use the venv created by init. A checkout in CI does not
|
|
1150
|
+
* have that installation state, however, so the explicit PANTHEON_PYTHON
|
|
1151
|
+
* override and the PATH fallback make the contract reproducible without
|
|
1152
|
+
* silently skipping dependency validation.
|
|
1153
|
+
*/
|
|
1108
1154
|
export function resolveRuntimePython(args) {
|
|
1155
|
+
const env = args.env ?? process.env
|
|
1156
|
+
if (typeof env.PANTHEON_PYTHON === 'string' && env.PANTHEON_PYTHON.trim() !== '') {
|
|
1157
|
+
return env.PANTHEON_PYTHON.trim()
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1109
1160
|
const target = resolve(args.target)
|
|
1110
1161
|
const isProject = existsSync(join(target, '.opencode'))
|
|
1111
|
-
const runtimeRoot = isProject ? target : resolveOpenCodeConfigDir(
|
|
1112
|
-
|
|
1162
|
+
const runtimeRoot = isProject ? target : resolveOpenCodeConfigDir(env)
|
|
1163
|
+
const venvCandidates =
|
|
1164
|
+
process.platform === 'win32'
|
|
1165
|
+
? [
|
|
1166
|
+
join(runtimeRoot, '.venv', 'Scripts', 'python.exe'),
|
|
1167
|
+
join(runtimeRoot, '.venv', 'Scripts', 'python'),
|
|
1168
|
+
]
|
|
1169
|
+
: [join(runtimeRoot, '.venv', 'bin', 'python3'), join(runtimeRoot, '.venv', 'bin', 'python')]
|
|
1170
|
+
const existing = venvCandidates.find((candidate) => existsSync(candidate))
|
|
1171
|
+
return existing ?? 'python3'
|
|
1113
1172
|
}
|
|
1114
1173
|
|
|
1115
1174
|
/** Return relative script arguments as absolute paths using an MCP entry cwd. */
|
|
@@ -1373,6 +1432,153 @@ export function checkPluginVersionDrift(args) {
|
|
|
1373
1432
|
return 'sync'
|
|
1374
1433
|
}
|
|
1375
1434
|
|
|
1435
|
+
// ---------------------------------------------------------------------------
|
|
1436
|
+
// Check H4: V2 feature reduction (the surface a migrating user loses)
|
|
1437
|
+
// ---------------------------------------------------------------------------
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* Whether an opencode.json registers the V2 generation of the Pantheon plugin.
|
|
1441
|
+
*
|
|
1442
|
+
* The V2 contract is the PLURAL `plugins` key; V1 is the singular `plugin` key.
|
|
1443
|
+
* So the key alone is not the test — a config can legitimately carry a
|
|
1444
|
+
* third-party entry in `plugins`. What identifies the generation is a Pantheon
|
|
1445
|
+
* V2 entry inside it. Matched on the `plugin-v2` marker so all three real
|
|
1446
|
+
* spellings are recognised: the canonical DIRECTORY (`src/plugin-v2`), an
|
|
1447
|
+
* absolute path to it, and the package export (`pantheon-opencode/plugin-v2`).
|
|
1448
|
+
*
|
|
1449
|
+
* @param {unknown} ref
|
|
1450
|
+
* @returns {boolean}
|
|
1451
|
+
*/
|
|
1452
|
+
function isV2PluginRef(ref) {
|
|
1453
|
+
const candidate =
|
|
1454
|
+
typeof ref === 'string'
|
|
1455
|
+
? ref
|
|
1456
|
+
: ref && typeof ref === 'object'
|
|
1457
|
+
? (ref.path ?? ref.source ?? ref.id ?? ref.name)
|
|
1458
|
+
: undefined
|
|
1459
|
+
return typeof candidate === 'string' && candidate.includes('plugin-v2')
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
/**
|
|
1463
|
+
* Whether any reachable config registers the V2 generation of the Pantheon
|
|
1464
|
+
* plugin. Split out of {@link collectV2UnsupportedFeatures} because "is the V2
|
|
1465
|
+
* generation registered" and "does the seed list anything" are TWO facts: the
|
|
1466
|
+
* first is about the user's config, the second about this repository's seed. A
|
|
1467
|
+
* single `features.length === 0` cannot tell them apart, and conflating them
|
|
1468
|
+
* let an emptied seed print "no V2 generation registered — nothing reduced".
|
|
1469
|
+
*
|
|
1470
|
+
* @param {{path:string, data:object}[]} configs
|
|
1471
|
+
* @returns {boolean}
|
|
1472
|
+
*/
|
|
1473
|
+
export function isV2GenerationRegistered(configs) {
|
|
1474
|
+
return configs.some((cfg) => {
|
|
1475
|
+
const list = cfg.data?.plugins
|
|
1476
|
+
return Array.isArray(list) && list.some(isV2PluginRef)
|
|
1477
|
+
})
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
/**
|
|
1481
|
+
* The V2 feature-reduction markers to report for a set of collected configs.
|
|
1482
|
+
*
|
|
1483
|
+
* Returns the shared seed verbatim when ANY reachable config registers the V2
|
|
1484
|
+
* generation, and an empty list otherwise. The strings come from
|
|
1485
|
+
* `src/pantheon/v2-unsupported.mjs` — the same seed `getUnsupportedFeatures()`
|
|
1486
|
+
* is built from — so doctor cannot report a reduction the plugin does not, or
|
|
1487
|
+
* miss one it does. Nothing is reworded here.
|
|
1488
|
+
*
|
|
1489
|
+
* An empty result is ambiguous on its own and deliberately not interpreted
|
|
1490
|
+
* here: ask {@link isV2GenerationRegistered} for the registration fact, or
|
|
1491
|
+
* {@link describeV2Reduction} to get both at once.
|
|
1492
|
+
*
|
|
1493
|
+
* @param {{path:string, data:object}[]} configs
|
|
1494
|
+
* @returns {string[]}
|
|
1495
|
+
*/
|
|
1496
|
+
export function collectV2UnsupportedFeatures(configs) {
|
|
1497
|
+
return isV2GenerationRegistered(configs) ? listV2UnsupportedFeatures() : []
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
/**
|
|
1501
|
+
* Decide what H4 prints, from the two distinct facts rather than from one list
|
|
1502
|
+
* length.
|
|
1503
|
+
*
|
|
1504
|
+
* Three cases, and the middle one used to be silently reported as the first:
|
|
1505
|
+
*
|
|
1506
|
+
* - not registered → info. A V1 install is a supported configuration with
|
|
1507
|
+
* nothing to reduce, and saying so plainly is correct.
|
|
1508
|
+
* - registered, seed EMPTY → warn. This is a packaging fault in THIS repo,
|
|
1509
|
+
* not an absence of reduction: the V2 surface really is reduced and the
|
|
1510
|
+
* report is missing. It must never claim "nothing reduced".
|
|
1511
|
+
* - registered, seed populated → warn, listing every marker.
|
|
1512
|
+
*
|
|
1513
|
+
* Pure and exported so the empty-seed branch is testable without stubbing the
|
|
1514
|
+
* frozen seed module. `status` is returned alongside the text so a caller never
|
|
1515
|
+
* has to recover the case by matching on the message.
|
|
1516
|
+
*
|
|
1517
|
+
* @param {{registered:boolean, features:string[]}} input
|
|
1518
|
+
* @returns {{status:'reported'|'empty-seed'|'no-v2-generation', level:'info'|'warn', message:string}}
|
|
1519
|
+
*/
|
|
1520
|
+
export function describeV2Reduction({ registered, features }) {
|
|
1521
|
+
if (!registered) {
|
|
1522
|
+
return {
|
|
1523
|
+
status: 'no-v2-generation',
|
|
1524
|
+
level: 'info',
|
|
1525
|
+
message: 'No V2 plugin generation registered — nothing reduced',
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (features.length === 0) {
|
|
1529
|
+
return {
|
|
1530
|
+
status: 'empty-seed',
|
|
1531
|
+
level: 'warn',
|
|
1532
|
+
message:
|
|
1533
|
+
'Pantheon IS registered on the V2 plugin generation, but the shared ' +
|
|
1534
|
+
'unsupported-feature seed is empty, so the reduction cannot be reported. ' +
|
|
1535
|
+
"That is a fault in this installation's reporting, not an absence of " +
|
|
1536
|
+
'reduction: the V2 surface still drops the 3 goal tools and the ' +
|
|
1537
|
+
'caller/target delegation matrix.',
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
return {
|
|
1541
|
+
status: 'reported',
|
|
1542
|
+
level: 'warn',
|
|
1543
|
+
message:
|
|
1544
|
+
'Pantheon is registered on the V2 plugin generation, which registers 3 tools ' +
|
|
1545
|
+
"(hashline_edit, pantheon_cost, pantheon_model) instead of the V1 surface's 6 plus " +
|
|
1546
|
+
`the BackgroundJobBoard and the caller/target delegation matrix. Unsupported: ${features.join(', ')}`,
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* Report the V2 feature reduction through doctor's own output shape
|
|
1552
|
+
* (section + warn/info), so it lands in the health summary like every other
|
|
1553
|
+
* finding and never changes the exit code: a V2 install is a supported
|
|
1554
|
+
* configuration, not a broken one.
|
|
1555
|
+
*
|
|
1556
|
+
* @param {{target:string, env?:object}} args
|
|
1557
|
+
* @returns {'reported'|'empty-seed'|'no-v2-generation'|'no-config'}
|
|
1558
|
+
*/
|
|
1559
|
+
export function checkV2UnsupportedFeatures(args) {
|
|
1560
|
+
section('H4. V2 Feature Reduction')
|
|
1561
|
+
|
|
1562
|
+
const configs = collectMcpConfigs(args)
|
|
1563
|
+
if (configs.length === 0) {
|
|
1564
|
+
info('No opencode.json was found — V2 feature reduction check skipped')
|
|
1565
|
+
return 'no-config'
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// Branch on REGISTRATION, not on the marker count: an empty seed on a
|
|
1569
|
+
// registered V2 generation is a reporting fault and must not be reported as
|
|
1570
|
+
// the reassuring "nothing reduced".
|
|
1571
|
+
const registered = isV2GenerationRegistered(configs)
|
|
1572
|
+
const verdict = describeV2Reduction({
|
|
1573
|
+
registered,
|
|
1574
|
+
features: collectV2UnsupportedFeatures(configs),
|
|
1575
|
+
})
|
|
1576
|
+
if (verdict.level === 'info') info(verdict.message)
|
|
1577
|
+
else warn(verdict.message)
|
|
1578
|
+
|
|
1579
|
+
return verdict.status
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1376
1582
|
// ---------------------------------------------------------------------------
|
|
1377
1583
|
// Check G: AGENTS.md freshness
|
|
1378
1584
|
// ---------------------------------------------------------------------------
|
|
@@ -1745,6 +1951,7 @@ async function main() {
|
|
|
1745
1951
|
checkAgentsMdFreshness(args)
|
|
1746
1952
|
checkSubagentDepthPlacement(args)
|
|
1747
1953
|
checkPermissionTaskPresence(args)
|
|
1954
|
+
checkV2UnsupportedFeatures(args)
|
|
1748
1955
|
|
|
1749
1956
|
printSummary(args.target)
|
|
1750
1957
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* agents-md.mjs — Dynamic AGENTS.md generator from canonical agent frontmatter
|
|
4
4
|
*
|
|
5
|
-
* Reads all agents/*.
|
|
5
|
+
* Reads all canonical src/agents/*.md files, extracts name + description from frontmatter,
|
|
6
6
|
* and generates AGENTS.md content dynamically. Eliminates hardcoded agent tables
|
|
7
7
|
* in platform installers (claude.mjs, cursor.mjs, windsurf.mjs, cline.mjs, etc.).
|
|
8
8
|
*
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
22
22
|
import { dirname, join } from 'node:path'
|
|
23
23
|
import { fileURLToPath } from 'node:url'
|
|
24
|
-
import
|
|
24
|
+
import { load } from 'js-yaml'
|
|
25
25
|
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
27
|
// Paths
|
|
@@ -47,7 +47,7 @@ function parseAgentFrontmatter(content) {
|
|
|
47
47
|
if (!match) return null
|
|
48
48
|
|
|
49
49
|
try {
|
|
50
|
-
const fm =
|
|
50
|
+
const fm = load(match[1])
|
|
51
51
|
if (!fm || typeof fm !== 'object' || !fm.name) return null
|
|
52
52
|
return {
|
|
53
53
|
name: String(fm.name),
|
|
@@ -64,7 +64,7 @@ function parseAgentFrontmatter(content) {
|
|
|
64
64
|
// ---------------------------------------------------------------------------
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Read all canonical agents/*.
|
|
67
|
+
* Read all canonical src/agents/*.md files, parse frontmatter,
|
|
68
68
|
* and return an array of agent metadata objects sorted alphabetically by name.
|
|
69
69
|
*
|
|
70
70
|
* Skips files that don't match the `*.agent.md` pattern, files without
|