@supen-ai/cli 0.1.6
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 +235 -0
- package/daemon/dist/acp-client.d.ts +42 -0
- package/daemon/dist/acp-client.d.ts.map +1 -0
- package/daemon/dist/acp-client.js +149 -0
- package/daemon/dist/acp-client.js.map +1 -0
- package/daemon/dist/acp-types.d.ts +98 -0
- package/daemon/dist/acp-types.d.ts.map +1 -0
- package/daemon/dist/acp-types.js +2 -0
- package/daemon/dist/acp-types.js.map +1 -0
- package/daemon/dist/agent-sdk/app-server-approvals.d.ts +24 -0
- package/daemon/dist/agent-sdk/app-server-approvals.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/app-server-approvals.js +99 -0
- package/daemon/dist/agent-sdk/app-server-approvals.js.map +1 -0
- package/daemon/dist/agent-sdk/app-server-stream.d.ts +8 -0
- package/daemon/dist/agent-sdk/app-server-stream.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/app-server-stream.js +328 -0
- package/daemon/dist/agent-sdk/app-server-stream.js.map +1 -0
- package/daemon/dist/agent-sdk/driver-output-ui.d.ts +9 -0
- package/daemon/dist/agent-sdk/driver-output-ui.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/driver-output-ui.js +290 -0
- package/daemon/dist/agent-sdk/driver-output-ui.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.d.ts +21 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.js +488 -0
- package/daemon/dist/agent-sdk/drivers/acpx-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.d.ts +5 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.js +7 -0
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.d.ts +20 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.js +264 -0
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.d.ts +29 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.js +24 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.d.ts +25 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.js +58 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-events.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.d.ts +41 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.js +77 -0
- package/daemon/dist/agent-sdk/drivers/claude-code-items.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.d.ts +5 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.js +7 -0
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts +12 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js +484 -0
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.d.ts +28 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.js +219 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.d.ts +9 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.js +82 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.d.ts +9 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.js +86 -0
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/driver.d.ts +60 -0
- package/daemon/dist/agent-sdk/drivers/driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/driver.js +2 -0
- package/daemon/dist/agent-sdk/drivers/driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.d.ts +17 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.js +255 -0
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.js.map +1 -0
- package/daemon/dist/agent-sdk/drivers/registry.d.ts +21 -0
- package/daemon/dist/agent-sdk/drivers/registry.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/drivers/registry.js +167 -0
- package/daemon/dist/agent-sdk/drivers/registry.js.map +1 -0
- package/daemon/dist/agent-sdk/index.d.ts +24 -0
- package/daemon/dist/agent-sdk/index.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/index.js +19 -0
- package/daemon/dist/agent-sdk/index.js.map +1 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.d.ts +17 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.js +2 -0
- package/daemon/dist/agent-sdk/intelligence/contracts.js.map +1 -0
- package/daemon/dist/agent-sdk/memory/filesystem.d.ts +7 -0
- package/daemon/dist/agent-sdk/memory/filesystem.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/memory/filesystem.js +133 -0
- package/daemon/dist/agent-sdk/memory/filesystem.js.map +1 -0
- package/daemon/dist/agent-sdk/memory/subsystem.d.ts +54 -0
- package/daemon/dist/agent-sdk/memory/subsystem.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/memory/subsystem.js +106 -0
- package/daemon/dist/agent-sdk/memory/subsystem.js.map +1 -0
- package/daemon/dist/agent-sdk/session-events.d.ts +59 -0
- package/daemon/dist/agent-sdk/session-events.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/session-events.js +104 -0
- package/daemon/dist/agent-sdk/session-events.js.map +1 -0
- package/daemon/dist/agent-sdk/session-manager.d.ts +28 -0
- package/daemon/dist/agent-sdk/session-manager.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/session-manager.js +54 -0
- package/daemon/dist/agent-sdk/session-manager.js.map +1 -0
- package/daemon/dist/agent-sdk/types.d.ts +110 -0
- package/daemon/dist/agent-sdk/types.d.ts.map +1 -0
- package/daemon/dist/agent-sdk/types.js +2 -0
- package/daemon/dist/agent-sdk/types.js.map +1 -0
- package/daemon/dist/automation-event-listener.d.ts +27 -0
- package/daemon/dist/automation-event-listener.d.ts.map +1 -0
- package/daemon/dist/automation-event-listener.js +342 -0
- package/daemon/dist/automation-event-listener.js.map +1 -0
- package/daemon/dist/automation-runner.d.ts +17 -0
- package/daemon/dist/automation-runner.d.ts.map +1 -0
- package/daemon/dist/automation-runner.js +592 -0
- package/daemon/dist/automation-runner.js.map +1 -0
- package/daemon/dist/autonomy/feedback-memory.d.ts +8 -0
- package/daemon/dist/autonomy/feedback-memory.d.ts.map +1 -0
- package/daemon/dist/autonomy/feedback-memory.js +23 -0
- package/daemon/dist/autonomy/feedback-memory.js.map +1 -0
- package/daemon/dist/autonomy/intent-router.d.ts +10 -0
- package/daemon/dist/autonomy/intent-router.d.ts.map +1 -0
- package/daemon/dist/autonomy/intent-router.js +72 -0
- package/daemon/dist/autonomy/intent-router.js.map +1 -0
- package/daemon/dist/autonomy/memory-rules.d.ts +35 -0
- package/daemon/dist/autonomy/memory-rules.d.ts.map +1 -0
- package/daemon/dist/autonomy/memory-rules.js +117 -0
- package/daemon/dist/autonomy/memory-rules.js.map +1 -0
- package/daemon/dist/autonomy/proof-packet.d.ts +17 -0
- package/daemon/dist/autonomy/proof-packet.d.ts.map +1 -0
- package/daemon/dist/autonomy/proof-packet.js +28 -0
- package/daemon/dist/autonomy/proof-packet.js.map +1 -0
- package/daemon/dist/autonomy/session-autonomy.d.ts +18 -0
- package/daemon/dist/autonomy/session-autonomy.d.ts.map +1 -0
- package/daemon/dist/autonomy/session-autonomy.js +71 -0
- package/daemon/dist/autonomy/session-autonomy.js.map +1 -0
- package/daemon/dist/autonomy/source-inventory.d.ts +3 -0
- package/daemon/dist/autonomy/source-inventory.d.ts.map +1 -0
- package/daemon/dist/autonomy/source-inventory.js +84 -0
- package/daemon/dist/autonomy/source-inventory.js.map +1 -0
- package/daemon/dist/autonomy/types.d.ts +20 -0
- package/daemon/dist/autonomy/types.d.ts.map +1 -0
- package/daemon/dist/autonomy/types.js +2 -0
- package/daemon/dist/autonomy/types.js.map +1 -0
- package/daemon/dist/bin/mcp-os.d.ts +3 -0
- package/daemon/dist/bin/mcp-os.d.ts.map +1 -0
- package/daemon/dist/bin/mcp-os.js +63 -0
- package/daemon/dist/bin/mcp-os.js.map +1 -0
- package/daemon/dist/bin/mcp-scheduler.d.ts +3 -0
- package/daemon/dist/bin/mcp-scheduler.d.ts.map +1 -0
- package/daemon/dist/bin/mcp-scheduler.js +90 -0
- package/daemon/dist/bin/mcp-scheduler.js.map +1 -0
- package/daemon/dist/bin/supen-sys.d.ts +3 -0
- package/daemon/dist/bin/supen-sys.d.ts.map +1 -0
- package/daemon/dist/bin/supen-sys.js +38 -0
- package/daemon/dist/bin/supen-sys.js.map +1 -0
- package/daemon/dist/bootstrap/hub-bootstrap.d.ts +2 -0
- package/daemon/dist/bootstrap/hub-bootstrap.d.ts.map +1 -0
- package/daemon/dist/bootstrap/hub-bootstrap.js +409 -0
- package/daemon/dist/bootstrap/hub-bootstrap.js.map +1 -0
- package/daemon/dist/bootstrap/skill-bootstrap.d.ts +2 -0
- package/daemon/dist/bootstrap/skill-bootstrap.d.ts.map +1 -0
- package/daemon/dist/bootstrap/skill-bootstrap.js +92 -0
- package/daemon/dist/bootstrap/skill-bootstrap.js.map +1 -0
- package/daemon/dist/channels/acp.d.ts +23 -0
- package/daemon/dist/channels/acp.d.ts.map +1 -0
- package/daemon/dist/channels/acp.js +916 -0
- package/daemon/dist/channels/acp.js.map +1 -0
- package/daemon/dist/channels/base.d.ts +38 -0
- package/daemon/dist/channels/base.d.ts.map +1 -0
- package/daemon/dist/channels/base.js +56 -0
- package/daemon/dist/channels/base.js.map +1 -0
- package/daemon/dist/channels/http-routes.d.ts +44 -0
- package/daemon/dist/channels/http-routes.d.ts.map +1 -0
- package/daemon/dist/channels/http-routes.js +688 -0
- package/daemon/dist/channels/http-routes.js.map +1 -0
- package/daemon/dist/channels/http.d.ts +16 -0
- package/daemon/dist/channels/http.d.ts.map +1 -0
- package/daemon/dist/channels/http.js +84 -0
- package/daemon/dist/channels/http.js.map +1 -0
- package/daemon/dist/channels/index.d.ts +3 -0
- package/daemon/dist/channels/index.d.ts.map +1 -0
- package/daemon/dist/channels/index.js +7 -0
- package/daemon/dist/channels/index.js.map +1 -0
- package/daemon/dist/channels/registry.d.ts +18 -0
- package/daemon/dist/channels/registry.d.ts.map +1 -0
- package/daemon/dist/channels/registry.js +11 -0
- package/daemon/dist/channels/registry.js.map +1 -0
- package/daemon/dist/commands/builtin.d.ts +7 -0
- package/daemon/dist/commands/builtin.d.ts.map +1 -0
- package/daemon/dist/commands/builtin.js +111 -0
- package/daemon/dist/commands/builtin.js.map +1 -0
- package/daemon/dist/commands/catalog.d.ts +47 -0
- package/daemon/dist/commands/catalog.d.ts.map +1 -0
- package/daemon/dist/commands/catalog.js +487 -0
- package/daemon/dist/commands/catalog.js.map +1 -0
- package/daemon/dist/core/app.d.ts +18 -0
- package/daemon/dist/core/app.d.ts.map +1 -0
- package/daemon/dist/core/app.js +49 -0
- package/daemon/dist/core/app.js.map +1 -0
- package/daemon/dist/core/automation-timing.d.ts +10 -0
- package/daemon/dist/core/automation-timing.d.ts.map +1 -0
- package/daemon/dist/core/automation-timing.js +211 -0
- package/daemon/dist/core/automation-timing.js.map +1 -0
- package/daemon/dist/core/codex-subscription.d.ts +8 -0
- package/daemon/dist/core/codex-subscription.d.ts.map +1 -0
- package/daemon/dist/core/codex-subscription.js +150 -0
- package/daemon/dist/core/codex-subscription.js.map +1 -0
- package/daemon/dist/core/command-hub.d.ts +22 -0
- package/daemon/dist/core/command-hub.d.ts.map +1 -0
- package/daemon/dist/core/command-hub.js +59 -0
- package/daemon/dist/core/command-hub.js.map +1 -0
- package/daemon/dist/core/config.d.ts +146 -0
- package/daemon/dist/core/config.d.ts.map +1 -0
- package/daemon/dist/core/config.js +663 -0
- package/daemon/dist/core/config.js.map +1 -0
- package/daemon/dist/core/control-commands.d.ts +17 -0
- package/daemon/dist/core/control-commands.d.ts.map +1 -0
- package/daemon/dist/core/control-commands.js +35 -0
- package/daemon/dist/core/control-commands.js.map +1 -0
- package/daemon/dist/core/control-log.d.ts +17 -0
- package/daemon/dist/core/control-log.d.ts.map +1 -0
- package/daemon/dist/core/control-log.js +67 -0
- package/daemon/dist/core/control-log.js.map +1 -0
- package/daemon/dist/core/cortex.d.ts +53 -0
- package/daemon/dist/core/cortex.d.ts.map +1 -0
- package/daemon/dist/core/cortex.js +1690 -0
- package/daemon/dist/core/cortex.js.map +1 -0
- package/daemon/dist/core/daemon-lock.d.ts +16 -0
- package/daemon/dist/core/daemon-lock.d.ts.map +1 -0
- package/daemon/dist/core/daemon-lock.js +285 -0
- package/daemon/dist/core/daemon-lock.js.map +1 -0
- package/daemon/dist/core/dispatcher.d.ts +42 -0
- package/daemon/dist/core/dispatcher.d.ts.map +1 -0
- package/daemon/dist/core/dispatcher.js +173 -0
- package/daemon/dist/core/dispatcher.js.map +1 -0
- package/daemon/dist/core/enrollment.d.ts +41 -0
- package/daemon/dist/core/enrollment.d.ts.map +1 -0
- package/daemon/dist/core/enrollment.js +195 -0
- package/daemon/dist/core/enrollment.js.map +1 -0
- package/daemon/dist/core/env.d.ts +109 -0
- package/daemon/dist/core/env.d.ts.map +1 -0
- package/daemon/dist/core/env.js +329 -0
- package/daemon/dist/core/env.js.map +1 -0
- package/daemon/dist/core/gateway-config.d.ts +16 -0
- package/daemon/dist/core/gateway-config.d.ts.map +1 -0
- package/daemon/dist/core/gateway-config.js +103 -0
- package/daemon/dist/core/gateway-config.js.map +1 -0
- package/daemon/dist/core/gateway-protocol.d.ts +80 -0
- package/daemon/dist/core/gateway-protocol.d.ts.map +1 -0
- package/daemon/dist/core/gateway-protocol.js +2 -0
- package/daemon/dist/core/gateway-protocol.js.map +1 -0
- package/daemon/dist/core/gateway-routing-config.d.ts +21 -0
- package/daemon/dist/core/gateway-routing-config.d.ts.map +1 -0
- package/daemon/dist/core/gateway-routing-config.js +56 -0
- package/daemon/dist/core/gateway-routing-config.js.map +1 -0
- package/daemon/dist/core/gateway.d.ts +124 -0
- package/daemon/dist/core/gateway.d.ts.map +1 -0
- package/daemon/dist/core/gateway.js +887 -0
- package/daemon/dist/core/gateway.js.map +1 -0
- package/daemon/dist/core/hub-snapshot.d.ts +42 -0
- package/daemon/dist/core/hub-snapshot.d.ts.map +1 -0
- package/daemon/dist/core/hub-snapshot.js +125 -0
- package/daemon/dist/core/hub-snapshot.js.map +1 -0
- package/daemon/dist/core/interrupts.d.ts +7 -0
- package/daemon/dist/core/interrupts.d.ts.map +1 -0
- package/daemon/dist/core/interrupts.js +28 -0
- package/daemon/dist/core/interrupts.js.map +1 -0
- package/daemon/dist/core/logger.d.ts +3 -0
- package/daemon/dist/core/logger.d.ts.map +1 -0
- package/daemon/dist/core/logger.js +91 -0
- package/daemon/dist/core/logger.js.map +1 -0
- package/daemon/dist/core/loop-guard.d.ts +27 -0
- package/daemon/dist/core/loop-guard.d.ts.map +1 -0
- package/daemon/dist/core/loop-guard.js +47 -0
- package/daemon/dist/core/loop-guard.js.map +1 -0
- package/daemon/dist/core/observable-logging.d.ts +43 -0
- package/daemon/dist/core/observable-logging.d.ts.map +1 -0
- package/daemon/dist/core/observable-logging.js +77 -0
- package/daemon/dist/core/observable-logging.js.map +1 -0
- package/daemon/dist/core/pairing.d.ts +51 -0
- package/daemon/dist/core/pairing.d.ts.map +1 -0
- package/daemon/dist/core/pairing.js +207 -0
- package/daemon/dist/core/pairing.js.map +1 -0
- package/daemon/dist/core/progress.d.ts +32 -0
- package/daemon/dist/core/progress.d.ts.map +1 -0
- package/daemon/dist/core/progress.js +145 -0
- package/daemon/dist/core/progress.js.map +1 -0
- package/daemon/dist/core/protocol-adapter.d.ts +14 -0
- package/daemon/dist/core/protocol-adapter.d.ts.map +1 -0
- package/daemon/dist/core/protocol-adapter.js +472 -0
- package/daemon/dist/core/protocol-adapter.js.map +1 -0
- package/daemon/dist/core/sdk-wrapper.d.ts +36 -0
- package/daemon/dist/core/sdk-wrapper.d.ts.map +1 -0
- package/daemon/dist/core/sdk-wrapper.js +533 -0
- package/daemon/dist/core/sdk-wrapper.js.map +1 -0
- package/daemon/dist/core/security.d.ts +10 -0
- package/daemon/dist/core/security.d.ts.map +1 -0
- package/daemon/dist/core/security.js +95 -0
- package/daemon/dist/core/security.js.map +1 -0
- package/daemon/dist/core/space-env.d.ts +15 -0
- package/daemon/dist/core/space-env.d.ts.map +1 -0
- package/daemon/dist/core/space-env.js +182 -0
- package/daemon/dist/core/space-env.js.map +1 -0
- package/daemon/dist/core/status-inspector.d.ts +31 -0
- package/daemon/dist/core/status-inspector.d.ts.map +1 -0
- package/daemon/dist/core/status-inspector.js +35 -0
- package/daemon/dist/core/status-inspector.js.map +1 -0
- package/daemon/dist/core/storage-paths.d.ts +30 -0
- package/daemon/dist/core/storage-paths.d.ts.map +1 -0
- package/daemon/dist/core/storage-paths.js +84 -0
- package/daemon/dist/core/storage-paths.js.map +1 -0
- package/daemon/dist/core/store.d.ts +256 -0
- package/daemon/dist/core/store.d.ts.map +1 -0
- package/daemon/dist/core/store.js +2956 -0
- package/daemon/dist/core/store.js.map +1 -0
- package/daemon/dist/core/streaming.d.ts +24 -0
- package/daemon/dist/core/streaming.d.ts.map +1 -0
- package/daemon/dist/core/streaming.js +57 -0
- package/daemon/dist/core/streaming.js.map +1 -0
- package/daemon/dist/core/task-artifacts.d.ts +17 -0
- package/daemon/dist/core/task-artifacts.d.ts.map +1 -0
- package/daemon/dist/core/task-artifacts.js +104 -0
- package/daemon/dist/core/task-artifacts.js.map +1 -0
- package/daemon/dist/core/thread-event-log.d.ts +54 -0
- package/daemon/dist/core/thread-event-log.d.ts.map +1 -0
- package/daemon/dist/core/thread-event-log.js +218 -0
- package/daemon/dist/core/thread-event-log.js.map +1 -0
- package/daemon/dist/core/thread-runtime-state.d.ts +53 -0
- package/daemon/dist/core/thread-runtime-state.d.ts.map +1 -0
- package/daemon/dist/core/thread-runtime-state.js +271 -0
- package/daemon/dist/core/thread-runtime-state.js.map +1 -0
- package/daemon/dist/core/types.d.ts +552 -0
- package/daemon/dist/core/types.d.ts.map +1 -0
- package/daemon/dist/core/types.js +2 -0
- package/daemon/dist/core/types.js.map +1 -0
- package/daemon/dist/core/utils.d.ts +5 -0
- package/daemon/dist/core/utils.d.ts.map +1 -0
- package/daemon/dist/core/utils.js +35 -0
- package/daemon/dist/core/utils.js.map +1 -0
- package/daemon/dist/http/command-catalog.d.ts +3 -0
- package/daemon/dist/http/command-catalog.d.ts.map +1 -0
- package/daemon/dist/http/command-catalog.js +2 -0
- package/daemon/dist/http/command-catalog.js.map +1 -0
- package/daemon/dist/http/context.d.ts +18 -0
- package/daemon/dist/http/context.d.ts.map +1 -0
- package/daemon/dist/http/context.js +121 -0
- package/daemon/dist/http/context.js.map +1 -0
- package/daemon/dist/http/office-preview.d.ts +10 -0
- package/daemon/dist/http/office-preview.d.ts.map +1 -0
- package/daemon/dist/http/office-preview.js +234 -0
- package/daemon/dist/http/office-preview.js.map +1 -0
- package/daemon/dist/http/response.d.ts +5 -0
- package/daemon/dist/http/response.d.ts.map +1 -0
- package/daemon/dist/http/response.js +37 -0
- package/daemon/dist/http/response.js.map +1 -0
- package/daemon/dist/http/router.d.ts +4 -0
- package/daemon/dist/http/router.d.ts.map +1 -0
- package/daemon/dist/http/router.js +57 -0
- package/daemon/dist/http/router.js.map +1 -0
- package/daemon/dist/http/routes/agents.d.ts +4 -0
- package/daemon/dist/http/routes/agents.d.ts.map +1 -0
- package/daemon/dist/http/routes/agents.js +747 -0
- package/daemon/dist/http/routes/agents.js.map +1 -0
- package/daemon/dist/http/routes/automations.d.ts +6 -0
- package/daemon/dist/http/routes/automations.d.ts.map +1 -0
- package/daemon/dist/http/routes/automations.js +530 -0
- package/daemon/dist/http/routes/automations.js.map +1 -0
- package/daemon/dist/http/routes/autonomy.d.ts +4 -0
- package/daemon/dist/http/routes/autonomy.d.ts.map +1 -0
- package/daemon/dist/http/routes/autonomy.js +78 -0
- package/daemon/dist/http/routes/autonomy.js.map +1 -0
- package/daemon/dist/http/routes/chat-input.d.ts +18 -0
- package/daemon/dist/http/routes/chat-input.d.ts.map +1 -0
- package/daemon/dist/http/routes/chat-input.js +122 -0
- package/daemon/dist/http/routes/chat-input.js.map +1 -0
- package/daemon/dist/http/routes/plugins.d.ts +5 -0
- package/daemon/dist/http/routes/plugins.d.ts.map +1 -0
- package/daemon/dist/http/routes/plugins.js +221 -0
- package/daemon/dist/http/routes/plugins.js.map +1 -0
- package/daemon/dist/http/routes/rpc.d.ts +28 -0
- package/daemon/dist/http/routes/rpc.d.ts.map +1 -0
- package/daemon/dist/http/routes/rpc.js +790 -0
- package/daemon/dist/http/routes/rpc.js.map +1 -0
- package/daemon/dist/http/routes/sessions.d.ts +11 -0
- package/daemon/dist/http/routes/sessions.d.ts.map +1 -0
- package/daemon/dist/http/routes/sessions.js +963 -0
- package/daemon/dist/http/routes/sessions.js.map +1 -0
- package/daemon/dist/http/routes/skills.d.ts +5 -0
- package/daemon/dist/http/routes/skills.d.ts.map +1 -0
- package/daemon/dist/http/routes/skills.js +420 -0
- package/daemon/dist/http/routes/skills.js.map +1 -0
- package/daemon/dist/http/routes/system.d.ts +64 -0
- package/daemon/dist/http/routes/system.d.ts.map +1 -0
- package/daemon/dist/http/routes/system.js +2676 -0
- package/daemon/dist/http/routes/system.js.map +1 -0
- package/daemon/dist/http/stream.d.ts +11 -0
- package/daemon/dist/http/stream.d.ts.map +1 -0
- package/daemon/dist/http/stream.js +100 -0
- package/daemon/dist/http/stream.js.map +1 -0
- package/daemon/dist/http/thread-stream.d.ts +10 -0
- package/daemon/dist/http/thread-stream.d.ts.map +1 -0
- package/daemon/dist/http/thread-stream.js +50 -0
- package/daemon/dist/http/thread-stream.js.map +1 -0
- package/daemon/dist/http/thread-title.d.ts +12 -0
- package/daemon/dist/http/thread-title.d.ts.map +1 -0
- package/daemon/dist/http/thread-title.js +122 -0
- package/daemon/dist/http/thread-title.js.map +1 -0
- package/daemon/dist/http/utils.d.ts +2 -0
- package/daemon/dist/http/utils.d.ts.map +1 -0
- package/daemon/dist/http/utils.js +18 -0
- package/daemon/dist/http/utils.js.map +1 -0
- package/daemon/dist/http/websocket.d.ts +5 -0
- package/daemon/dist/http/websocket.d.ts.map +1 -0
- package/daemon/dist/http/websocket.js +100 -0
- package/daemon/dist/http/websocket.js.map +1 -0
- package/daemon/dist/index.d.ts +35 -0
- package/daemon/dist/index.d.ts.map +1 -0
- package/daemon/dist/index.js +1582 -0
- package/daemon/dist/index.js.map +1 -0
- package/daemon/dist/mcp/aggregate-config.d.ts +16 -0
- package/daemon/dist/mcp/aggregate-config.d.ts.map +1 -0
- package/daemon/dist/mcp/aggregate-config.js +97 -0
- package/daemon/dist/mcp/aggregate-config.js.map +1 -0
- package/daemon/dist/mcp/client.d.ts +94 -0
- package/daemon/dist/mcp/client.d.ts.map +1 -0
- package/daemon/dist/mcp/client.js +207 -0
- package/daemon/dist/mcp/client.js.map +1 -0
- package/daemon/dist/mcp/default-servers.d.ts +35 -0
- package/daemon/dist/mcp/default-servers.d.ts.map +1 -0
- package/daemon/dist/mcp/default-servers.js +209 -0
- package/daemon/dist/mcp/default-servers.js.map +1 -0
- package/daemon/dist/mcp/gateway-client.d.ts +58 -0
- package/daemon/dist/mcp/gateway-client.d.ts.map +1 -0
- package/daemon/dist/mcp/gateway-client.js +181 -0
- package/daemon/dist/mcp/gateway-client.js.map +1 -0
- package/daemon/dist/mcp/index.d.ts +26 -0
- package/daemon/dist/mcp/index.d.ts.map +1 -0
- package/daemon/dist/mcp/index.js +50 -0
- package/daemon/dist/mcp/index.js.map +1 -0
- package/daemon/dist/mcp/settings.d.ts +3 -0
- package/daemon/dist/mcp/settings.d.ts.map +1 -0
- package/daemon/dist/mcp/settings.js +60 -0
- package/daemon/dist/mcp/settings.js.map +1 -0
- package/daemon/dist/mcp/tools.d.ts +21 -0
- package/daemon/dist/mcp/tools.d.ts.map +1 -0
- package/daemon/dist/mcp/tools.js +136 -0
- package/daemon/dist/mcp/tools.js.map +1 -0
- package/daemon/dist/plugins/catalog.d.ts +10 -0
- package/daemon/dist/plugins/catalog.d.ts.map +1 -0
- package/daemon/dist/plugins/catalog.js +304 -0
- package/daemon/dist/plugins/catalog.js.map +1 -0
- package/daemon/dist/plugins/hub.d.ts +42 -0
- package/daemon/dist/plugins/hub.d.ts.map +1 -0
- package/daemon/dist/plugins/hub.js +812 -0
- package/daemon/dist/plugins/hub.js.map +1 -0
- package/daemon/dist/plugins/types.d.ts +144 -0
- package/daemon/dist/plugins/types.d.ts.map +1 -0
- package/daemon/dist/plugins/types.js +2 -0
- package/daemon/dist/plugins/types.js.map +1 -0
- package/daemon/dist/router.d.ts +13 -0
- package/daemon/dist/router.d.ts.map +1 -0
- package/daemon/dist/router.js +43 -0
- package/daemon/dist/router.js.map +1 -0
- package/daemon/dist/skills/adapter.d.ts +4 -0
- package/daemon/dist/skills/adapter.d.ts.map +1 -0
- package/daemon/dist/skills/adapter.js +141 -0
- package/daemon/dist/skills/adapter.js.map +1 -0
- package/daemon/dist/skills/allowlist.d.ts +20 -0
- package/daemon/dist/skills/allowlist.d.ts.map +1 -0
- package/daemon/dist/skills/allowlist.js +52 -0
- package/daemon/dist/skills/allowlist.js.map +1 -0
- package/daemon/dist/skills/catalog.d.ts +26 -0
- package/daemon/dist/skills/catalog.d.ts.map +1 -0
- package/daemon/dist/skills/catalog.js +274 -0
- package/daemon/dist/skills/catalog.js.map +1 -0
- package/daemon/dist/skills/claude_code.d.ts +25 -0
- package/daemon/dist/skills/claude_code.d.ts.map +1 -0
- package/daemon/dist/skills/claude_code.js +49 -0
- package/daemon/dist/skills/claude_code.js.map +1 -0
- package/daemon/dist/skills/commands.d.ts +3 -0
- package/daemon/dist/skills/commands.d.ts.map +1 -0
- package/daemon/dist/skills/commands.js +689 -0
- package/daemon/dist/skills/commands.js.map +1 -0
- package/daemon/dist/skills/enabled.d.ts +7 -0
- package/daemon/dist/skills/enabled.d.ts.map +1 -0
- package/daemon/dist/skills/enabled.js +37 -0
- package/daemon/dist/skills/enabled.js.map +1 -0
- package/daemon/dist/skills/hub.d.ts +35 -0
- package/daemon/dist/skills/hub.d.ts.map +1 -0
- package/daemon/dist/skills/hub.js +574 -0
- package/daemon/dist/skills/hub.js.map +1 -0
- package/daemon/dist/skills/loader.d.ts +24 -0
- package/daemon/dist/skills/loader.d.ts.map +1 -0
- package/daemon/dist/skills/loader.js +693 -0
- package/daemon/dist/skills/loader.js.map +1 -0
- package/daemon/dist/skills/mcp-config.d.ts +8 -0
- package/daemon/dist/skills/mcp-config.d.ts.map +1 -0
- package/daemon/dist/skills/mcp-config.js +110 -0
- package/daemon/dist/skills/mcp-config.js.map +1 -0
- package/daemon/dist/skills/parser.d.ts +3 -0
- package/daemon/dist/skills/parser.d.ts.map +1 -0
- package/daemon/dist/skills/parser.js +279 -0
- package/daemon/dist/skills/parser.js.map +1 -0
- package/daemon/dist/skills/registry.d.ts +2 -0
- package/daemon/dist/skills/registry.d.ts.map +1 -0
- package/daemon/dist/skills/registry.js +2 -0
- package/daemon/dist/skills/registry.js.map +1 -0
- package/daemon/dist/skills/runtime-contract.d.ts +3 -0
- package/daemon/dist/skills/runtime-contract.d.ts.map +1 -0
- package/daemon/dist/skills/runtime-contract.js +92 -0
- package/daemon/dist/skills/runtime-contract.js.map +1 -0
- package/daemon/dist/skills/runtime.d.ts +28 -0
- package/daemon/dist/skills/runtime.d.ts.map +1 -0
- package/daemon/dist/skills/runtime.js +286 -0
- package/daemon/dist/skills/runtime.js.map +1 -0
- package/daemon/dist/skills/types.d.ts +228 -0
- package/daemon/dist/skills/types.d.ts.map +1 -0
- package/daemon/dist/skills/types.js +2 -0
- package/daemon/dist/skills/types.js.map +1 -0
- package/daemon/dist/start.d.ts +6 -0
- package/daemon/dist/start.d.ts.map +1 -0
- package/daemon/dist/start.js +19 -0
- package/daemon/dist/start.js.map +1 -0
- package/daemon/dist/sub-agent.d.ts +46 -0
- package/daemon/dist/sub-agent.d.ts.map +1 -0
- package/daemon/dist/sub-agent.js +120 -0
- package/daemon/dist/sub-agent.js.map +1 -0
- package/daemon/dist/sync/supabase-sync.d.ts +16 -0
- package/daemon/dist/sync/supabase-sync.d.ts.map +1 -0
- package/daemon/dist/sync/supabase-sync.js +240 -0
- package/daemon/dist/sync/supabase-sync.js.map +1 -0
- package/daemon/dist/task-executor.d.ts +6 -0
- package/daemon/dist/task-executor.d.ts.map +1 -0
- package/daemon/dist/task-executor.js +31 -0
- package/daemon/dist/task-executor.js.map +1 -0
- package/daemon/dist/tools/automations.d.ts +57 -0
- package/daemon/dist/tools/automations.d.ts.map +1 -0
- package/daemon/dist/tools/automations.js +94 -0
- package/daemon/dist/tools/automations.js.map +1 -0
- package/daemon/dist/tools/built-ins.d.ts +112 -0
- package/daemon/dist/tools/built-ins.d.ts.map +1 -0
- package/daemon/dist/tools/built-ins.js +251 -0
- package/daemon/dist/tools/built-ins.js.map +1 -0
- package/daemon/dist/tools/index.d.ts +287 -0
- package/daemon/dist/tools/index.d.ts.map +1 -0
- package/daemon/dist/tools/index.js +86 -0
- package/daemon/dist/tools/index.js.map +1 -0
- package/daemon/dist/tools/shell.d.ts +15 -0
- package/daemon/dist/tools/shell.d.ts.map +1 -0
- package/daemon/dist/tools/shell.js +46 -0
- package/daemon/dist/tools/shell.js.map +1 -0
- package/daemon/dist/tools/skill-tools.d.ts +23 -0
- package/daemon/dist/tools/skill-tools.d.ts.map +1 -0
- package/daemon/dist/tools/skill-tools.js +64 -0
- package/daemon/dist/tools/skill-tools.js.map +1 -0
- package/daemon/dist/tools/system.d.ts +36 -0
- package/daemon/dist/tools/system.d.ts.map +1 -0
- package/daemon/dist/tools/system.js +54 -0
- package/daemon/dist/tools/system.js.map +1 -0
- package/daemon/dist/tools/types.d.ts +11 -0
- package/daemon/dist/tools/types.d.ts.map +1 -0
- package/daemon/dist/tools/types.js +2 -0
- package/daemon/dist/tools/types.js.map +1 -0
- package/daemon/scripts/browser-smoke.mjs +125 -0
- package/daemon/scripts/supen-daemon.js +15 -0
- package/dist/agent.d.ts +11 -0
- package/dist/agent.js +159 -0
- package/dist/agent.js.map +1 -0
- package/dist/auth/login.d.ts +20 -0
- package/dist/auth/login.js +151 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/logout.d.ts +5 -0
- package/dist/auth/logout.js +19 -0
- package/dist/auth/logout.js.map +1 -0
- package/dist/auth/store.d.ts +37 -0
- package/dist/auth/store.js +80 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/auth/whoami.d.ts +5 -0
- package/dist/auth/whoami.js +24 -0
- package/dist/auth/whoami.js.map +1 -0
- package/dist/backend.d.ts +8 -0
- package/dist/backend.js +148 -0
- package/dist/backend.js.map +1 -0
- package/dist/bootstrap.d.ts +13 -0
- package/dist/bootstrap.js +230 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/chat.d.ts +13 -0
- package/dist/chat.js +255 -0
- package/dist/chat.js.map +1 -0
- package/dist/commands.d.ts +48 -0
- package/dist/commands.js +273 -0
- package/dist/commands.js.map +1 -0
- package/dist/computer.d.ts +2 -0
- package/dist/computer.js +510 -0
- package/dist/computer.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +149 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon-manage.d.ts +29 -0
- package/dist/daemon-manage.js +255 -0
- package/dist/daemon-manage.js.map +1 -0
- package/dist/daemon.d.ts +15 -0
- package/dist/daemon.js +205 -0
- package/dist/daemon.js.map +1 -0
- package/dist/doctor.d.ts +13 -0
- package/dist/doctor.js +323 -0
- package/dist/doctor.js.map +1 -0
- package/dist/enroll.d.ts +7 -0
- package/dist/enroll.js +154 -0
- package/dist/enroll.js.map +1 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +363 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +141 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge.d.ts +3 -0
- package/dist/knowledge.js +1581 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.js +137 -0
- package/dist/mcp.js.map +1 -0
- package/dist/model.d.ts +8 -0
- package/dist/model.js +192 -0
- package/dist/model.js.map +1 -0
- package/dist/pairing.d.ts +21 -0
- package/dist/pairing.js +376 -0
- package/dist/pairing.js.map +1 -0
- package/dist/repl-events.d.ts +60 -0
- package/dist/repl-events.js +89 -0
- package/dist/repl-events.js.map +1 -0
- package/dist/repl-renderer.d.ts +37 -0
- package/dist/repl-renderer.js +140 -0
- package/dist/repl-renderer.js.map +1 -0
- package/dist/repl.d.ts +52 -0
- package/dist/repl.js +624 -0
- package/dist/repl.js.map +1 -0
- package/dist/service.d.ts +8 -0
- package/dist/service.js +238 -0
- package/dist/service.js.map +1 -0
- package/dist/skills.d.ts +14 -0
- package/dist/skills.js +423 -0
- package/dist/skills.js.map +1 -0
- package/dist/sse.d.ts +15 -0
- package/dist/sse.js +166 -0
- package/dist/sse.js.map +1 -0
- package/dist/thread.d.ts +9 -0
- package/dist/thread.js +152 -0
- package/dist/thread.js.map +1 -0
- package/dist/transport/computer-api.d.ts +1 -0
- package/dist/transport/computer-api.js +20 -0
- package/dist/transport/computer-api.js.map +1 -0
- package/dist/transport/gateway.d.ts +23 -0
- package/dist/transport/gateway.js +161 -0
- package/dist/transport/gateway.js.map +1 -0
- package/dist/transport/index.d.ts +27 -0
- package/dist/transport/index.js +77 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/local.d.ts +20 -0
- package/dist/transport/local.js +138 -0
- package/dist/transport/local.js.map +1 -0
- package/dist/transport/types.d.ts +42 -0
- package/dist/transport/types.js +11 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/ui/app.d.ts +7 -0
- package/dist/ui/app.js +192 -0
- package/dist/ui/app.js.map +1 -0
- package/dist/ui/history-item.d.ts +9 -0
- package/dist/ui/history-item.js +35 -0
- package/dist/ui/history-item.js.map +1 -0
- package/dist/ui/input-bar.d.ts +17 -0
- package/dist/ui/input-bar.js +67 -0
- package/dist/ui/input-bar.js.map +1 -0
- package/dist/ui/streaming-view.d.ts +6 -0
- package/dist/ui/streaming-view.js +6 -0
- package/dist/ui/streaming-view.js.map +1 -0
- package/dist/ui/thread-input-history.d.ts +18 -0
- package/dist/ui/thread-input-history.js +67 -0
- package/dist/ui/thread-input-history.js.map +1 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.js +80 -0
- package/dist/utils.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,1581 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import yaml from 'yaml';
|
|
6
|
+
import { computerApiPath } from './transport/computer-api.js';
|
|
7
|
+
const SUPEN_LLM_ENV_KEYS = [
|
|
8
|
+
'SUPEN_LLM_TOKEN',
|
|
9
|
+
'SUPEN_LLM_API_KEY',
|
|
10
|
+
'SUPEN_LLM_GATEWAY_URL',
|
|
11
|
+
'SUPEN_LLM_BASE_URL',
|
|
12
|
+
];
|
|
13
|
+
function daemonBaseUrl() {
|
|
14
|
+
const explicit = (process.env.SUPEN_DAEMON_URL || process.env.SUPEN_API_URL || '').trim().replace(/\/+$/, '');
|
|
15
|
+
if (explicit)
|
|
16
|
+
return explicit;
|
|
17
|
+
return `http://127.0.0.1:${process.env.SUPEN_DAEMON_PORT || process.env.PORT || '2756'}`;
|
|
18
|
+
}
|
|
19
|
+
function hasCompleteSupenLlmEnv(env) {
|
|
20
|
+
return Boolean((env.SUPEN_LLM_TOKEN || env.SUPEN_LLM_API_KEY) &&
|
|
21
|
+
(env.SUPEN_LLM_GATEWAY_URL || env.SUPEN_LLM_BASE_URL));
|
|
22
|
+
}
|
|
23
|
+
async function readSpaceLlmEnv() {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(`${daemonBaseUrl()}${computerApiPath('/llm-env')}`, {
|
|
28
|
+
signal: controller.signal,
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok)
|
|
31
|
+
return {};
|
|
32
|
+
const parsed = await response.json();
|
|
33
|
+
if (!parsed?.available || !parsed.env || typeof parsed.env !== 'object')
|
|
34
|
+
return {};
|
|
35
|
+
const env = {};
|
|
36
|
+
for (const key of SUPEN_LLM_ENV_KEYS) {
|
|
37
|
+
const value = parsed.env[key];
|
|
38
|
+
if (typeof value === 'string' && value.trim()) {
|
|
39
|
+
env[key] = value.trim();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return hasCompleteSupenLlmEnv(env) ? env : {};
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
clearTimeout(timeout);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function normalizeKnowledgeLlmEnv(env) {
|
|
52
|
+
const token = env.SUPEN_LLM_TOKEN || env.SUPEN_LLM_API_KEY || env.TIWATER_LLM_API_KEY || '';
|
|
53
|
+
const baseUrl = env.SUPEN_LLM_GATEWAY_URL || env.SUPEN_LLM_BASE_URL || env.TIWATER_LLM_BASE_URL || '';
|
|
54
|
+
if (!token || !baseUrl)
|
|
55
|
+
return env;
|
|
56
|
+
return {
|
|
57
|
+
...env,
|
|
58
|
+
SUPEN_LLM_TOKEN: env.SUPEN_LLM_TOKEN || token,
|
|
59
|
+
SUPEN_LLM_API_KEY: env.SUPEN_LLM_API_KEY || token,
|
|
60
|
+
SUPEN_LLM_GATEWAY_URL: env.SUPEN_LLM_GATEWAY_URL || baseUrl,
|
|
61
|
+
SUPEN_LLM_BASE_URL: env.SUPEN_LLM_BASE_URL || baseUrl,
|
|
62
|
+
TIWATER_LLM_API_KEY: env.TIWATER_LLM_API_KEY || token,
|
|
63
|
+
TIWATER_LLM_BASE_URL: env.TIWATER_LLM_BASE_URL || baseUrl,
|
|
64
|
+
SUPEN_KNOWLEDGE_LLM_ENV_AVAILABLE: '1',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async function resolveKnowledgeRuntimeEnv(runTests) {
|
|
68
|
+
if (!runTests)
|
|
69
|
+
return process.env;
|
|
70
|
+
if (hasCompleteSupenLlmEnv(process.env))
|
|
71
|
+
return normalizeKnowledgeLlmEnv(process.env);
|
|
72
|
+
const spaceLlmEnv = await readSpaceLlmEnv();
|
|
73
|
+
if (Object.keys(spaceLlmEnv).length === 0)
|
|
74
|
+
return process.env;
|
|
75
|
+
return normalizeKnowledgeLlmEnv({
|
|
76
|
+
...process.env,
|
|
77
|
+
...spaceLlmEnv,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function loadScenarios(packageRoot) {
|
|
81
|
+
const scenariosRoot = path.join(packageRoot, 'scenarios');
|
|
82
|
+
return walkMarkdownFiles(scenariosRoot)
|
|
83
|
+
.filter((filePath) => !filePath.endsWith(`${path.sep}questions.md`))
|
|
84
|
+
.filter((filePath) => !filePath.endsWith(`${path.sep}status-legend.md`))
|
|
85
|
+
.map((filePath) => parseScenario(packageRoot, filePath));
|
|
86
|
+
}
|
|
87
|
+
function normalizeScenarioSelector(value) {
|
|
88
|
+
if (typeof value !== 'string')
|
|
89
|
+
return [];
|
|
90
|
+
const normalized = value.trim().toUpperCase();
|
|
91
|
+
if (!normalized)
|
|
92
|
+
return [];
|
|
93
|
+
if (normalized.startsWith('SC-'))
|
|
94
|
+
return [normalized, normalized.slice(3)];
|
|
95
|
+
return [normalized, `SC-${normalized}`];
|
|
96
|
+
}
|
|
97
|
+
function resolvePackageRoot(input) {
|
|
98
|
+
const root = path.resolve(process.cwd(), input);
|
|
99
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
100
|
+
throw new Error(`Knowledge package path is not a directory: ${root}`);
|
|
101
|
+
}
|
|
102
|
+
return root;
|
|
103
|
+
}
|
|
104
|
+
function expandHomePath(inputPath) {
|
|
105
|
+
if (inputPath === '~')
|
|
106
|
+
return os.homedir();
|
|
107
|
+
if (inputPath.startsWith('~/'))
|
|
108
|
+
return path.join(os.homedir(), inputPath.slice(2));
|
|
109
|
+
return path.resolve(inputPath);
|
|
110
|
+
}
|
|
111
|
+
function readYamlObject(filePath) {
|
|
112
|
+
try {
|
|
113
|
+
if (!fs.existsSync(filePath))
|
|
114
|
+
return {};
|
|
115
|
+
const parsed = yaml.parse(fs.readFileSync(filePath, 'utf8'));
|
|
116
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function configuredRootValue(section) {
|
|
123
|
+
if (!section || typeof section !== 'object' || Array.isArray(section))
|
|
124
|
+
return null;
|
|
125
|
+
const root = section.root;
|
|
126
|
+
return typeof root === 'string' && root.trim() ? root.trim() : null;
|
|
127
|
+
}
|
|
128
|
+
function configuredStringValue(section, key) {
|
|
129
|
+
if (!section || typeof section !== 'object' || Array.isArray(section))
|
|
130
|
+
return null;
|
|
131
|
+
const value = section[key];
|
|
132
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
133
|
+
}
|
|
134
|
+
function resolveCliSpaceRoot() {
|
|
135
|
+
const explicit = process.env.SPACE_DIR || process.env.SPACES_DIR;
|
|
136
|
+
return explicit ? expandHomePath(explicit) : supenHomeDir();
|
|
137
|
+
}
|
|
138
|
+
function resolveConfiguredRoot(configured, baseRoot) {
|
|
139
|
+
if (configured === '~' || configured.startsWith('~/') || path.isAbsolute(configured)) {
|
|
140
|
+
return expandHomePath(configured);
|
|
141
|
+
}
|
|
142
|
+
return path.resolve(baseRoot, configured);
|
|
143
|
+
}
|
|
144
|
+
function readLocalSpaceConfig() {
|
|
145
|
+
const root = resolveCliSpaceRoot();
|
|
146
|
+
const candidates = [
|
|
147
|
+
{ configPath: path.join(root, 'space.yaml'), baseRoot: root },
|
|
148
|
+
{ configPath: path.join(root, 'space', 'config.yaml'), baseRoot: path.join(root, 'space') },
|
|
149
|
+
];
|
|
150
|
+
for (const candidate of candidates) {
|
|
151
|
+
const config = readYamlObject(candidate.configPath);
|
|
152
|
+
if (Object.keys(config).length > 0)
|
|
153
|
+
return { config, baseRoot: candidate.baseRoot };
|
|
154
|
+
}
|
|
155
|
+
return { config: {}, baseRoot: root };
|
|
156
|
+
}
|
|
157
|
+
function resolveMountedKnowledgeRoots() {
|
|
158
|
+
const { config, baseRoot } = readLocalSpaceConfig();
|
|
159
|
+
const knowledge = config.knowledge;
|
|
160
|
+
const configuredRoots = knowledge && typeof knowledge === 'object' && !Array.isArray(knowledge)
|
|
161
|
+
? knowledge.roots
|
|
162
|
+
: null;
|
|
163
|
+
if (Array.isArray(configuredRoots)) {
|
|
164
|
+
const roots = configuredRoots.flatMap((entry) => {
|
|
165
|
+
const id = configuredStringValue(entry, 'id');
|
|
166
|
+
const configured = configuredRootValue(entry);
|
|
167
|
+
if (!id || !configured)
|
|
168
|
+
return [];
|
|
169
|
+
return [{ id, root: resolveConfiguredRoot(configured, baseRoot) }];
|
|
170
|
+
});
|
|
171
|
+
if (roots.length > 0)
|
|
172
|
+
return roots;
|
|
173
|
+
}
|
|
174
|
+
return [{
|
|
175
|
+
id: 'default',
|
|
176
|
+
root: path.join(resolveCliSpaceRoot(), 'knowledge'),
|
|
177
|
+
}];
|
|
178
|
+
}
|
|
179
|
+
function readPluginName(pluginRoot) {
|
|
180
|
+
const manifestPath = path.join(pluginRoot, 'plugin.json');
|
|
181
|
+
try {
|
|
182
|
+
if (!fs.existsSync(manifestPath))
|
|
183
|
+
return null;
|
|
184
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
185
|
+
return typeof manifest.name === 'string' && manifest.name.trim() ? manifest.name.trim() : path.basename(pluginRoot);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return path.basename(pluginRoot);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function mountedPluginTargets() {
|
|
192
|
+
const targets = [];
|
|
193
|
+
for (const knowledgeRoot of resolveMountedKnowledgeRoots()) {
|
|
194
|
+
const pluginsRoot = path.join(knowledgeRoot.root, 'plugins');
|
|
195
|
+
if (!fs.existsSync(pluginsRoot) || !fs.statSync(pluginsRoot).isDirectory())
|
|
196
|
+
continue;
|
|
197
|
+
for (const entry of fs.readdirSync(pluginsRoot, { withFileTypes: true })) {
|
|
198
|
+
if (!entry.isDirectory() || entry.name.startsWith('.'))
|
|
199
|
+
continue;
|
|
200
|
+
const pluginRoot = path.join(pluginsRoot, entry.name);
|
|
201
|
+
const pluginName = readPluginName(pluginRoot);
|
|
202
|
+
if (!pluginName)
|
|
203
|
+
continue;
|
|
204
|
+
targets.push({
|
|
205
|
+
input: pluginName,
|
|
206
|
+
pluginName,
|
|
207
|
+
packageRoot: knowledgeRoot.root,
|
|
208
|
+
pluginRoot,
|
|
209
|
+
resolvedFrom: 'mounted-plugin',
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return targets.sort((left, right) => (left.pluginName || '').localeCompare(right.pluginName || ''));
|
|
214
|
+
}
|
|
215
|
+
function resolvePluginTarget(input) {
|
|
216
|
+
const asPath = path.resolve(process.cwd(), input);
|
|
217
|
+
if (fs.existsSync(asPath) && fs.statSync(asPath).isDirectory()) {
|
|
218
|
+
const manifestName = readPluginName(asPath);
|
|
219
|
+
if (manifestName) {
|
|
220
|
+
const parent = path.dirname(asPath);
|
|
221
|
+
return {
|
|
222
|
+
input,
|
|
223
|
+
pluginName: manifestName,
|
|
224
|
+
packageRoot: path.basename(parent) === 'plugins' ? path.dirname(parent) : asPath,
|
|
225
|
+
pluginRoot: asPath,
|
|
226
|
+
resolvedFrom: 'path',
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return { input, packageRoot: resolvePackageRoot(input), resolvedFrom: 'path' };
|
|
230
|
+
}
|
|
231
|
+
const matches = mountedPluginTargets().filter((target) => target.pluginName === input || path.basename(target.pluginRoot || '') === input);
|
|
232
|
+
if (matches.length === 1)
|
|
233
|
+
return { ...matches[0], input };
|
|
234
|
+
if (matches.length > 1) {
|
|
235
|
+
throw new Error(`Plugin name is ambiguous: ${input}. Matching roots: ${matches.map((target) => target.pluginRoot).join(', ')}`);
|
|
236
|
+
}
|
|
237
|
+
throw new Error(`Plugin is not mounted and path does not exist: ${input}`);
|
|
238
|
+
}
|
|
239
|
+
function resolveDefaultPluginTarget() {
|
|
240
|
+
const targets = mountedPluginTargets();
|
|
241
|
+
if (targets.length === 1)
|
|
242
|
+
return { ...targets[0], input: targets[0].pluginName || '' };
|
|
243
|
+
if (targets.length === 0) {
|
|
244
|
+
throw new Error('No mounted plugins found. Mount a knowledge root with plugins/<name>/plugin.json or pass a plugin/path explicitly.');
|
|
245
|
+
}
|
|
246
|
+
throw new Error(`Multiple plugins are mounted. Specify one: ${targets.map((target) => target.pluginName).filter(Boolean).map((name) => `supen verify ${name}`).join(', ')}`);
|
|
247
|
+
}
|
|
248
|
+
function resolveOptionalPluginTarget(input) {
|
|
249
|
+
return input && input.trim() ? resolvePluginTarget(input.trim()) : resolveDefaultPluginTarget();
|
|
250
|
+
}
|
|
251
|
+
function pluginNameFromRoot(pluginRoot) {
|
|
252
|
+
return pluginRoot ? path.basename(pluginRoot) : undefined;
|
|
253
|
+
}
|
|
254
|
+
function exists(filePath) {
|
|
255
|
+
return fs.existsSync(filePath);
|
|
256
|
+
}
|
|
257
|
+
function readText(filePath) {
|
|
258
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
259
|
+
}
|
|
260
|
+
function relativeToPackage(packageRoot, filePath) {
|
|
261
|
+
return path.relative(packageRoot, filePath);
|
|
262
|
+
}
|
|
263
|
+
function walkFiles(dir) {
|
|
264
|
+
if (!exists(dir))
|
|
265
|
+
return [];
|
|
266
|
+
const results = [];
|
|
267
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
268
|
+
const entryPath = path.join(dir, entry.name);
|
|
269
|
+
if (entry.isDirectory()) {
|
|
270
|
+
results.push(...walkFiles(entryPath));
|
|
271
|
+
}
|
|
272
|
+
else if (entry.isFile()) {
|
|
273
|
+
results.push(entryPath);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return results.sort((left, right) => left.localeCompare(right));
|
|
277
|
+
}
|
|
278
|
+
function walkMarkdownFiles(dir) {
|
|
279
|
+
return walkFiles(dir).filter((filePath) => filePath.endsWith('.md'));
|
|
280
|
+
}
|
|
281
|
+
function parseFrontmatter(raw) {
|
|
282
|
+
if (!raw.startsWith('---\n'))
|
|
283
|
+
return {};
|
|
284
|
+
const end = raw.indexOf('\n---', 4);
|
|
285
|
+
if (end < 0)
|
|
286
|
+
return {};
|
|
287
|
+
const data = {};
|
|
288
|
+
for (const line of raw.slice(4, end).split(/\r?\n/)) {
|
|
289
|
+
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*?)\s*$/);
|
|
290
|
+
if (match)
|
|
291
|
+
data[match[1]] = match[2];
|
|
292
|
+
}
|
|
293
|
+
return data;
|
|
294
|
+
}
|
|
295
|
+
function frontmatterBlock(raw) {
|
|
296
|
+
if (!raw.startsWith('---\n'))
|
|
297
|
+
return '';
|
|
298
|
+
const end = raw.indexOf('\n---', 4);
|
|
299
|
+
if (end < 0)
|
|
300
|
+
return '';
|
|
301
|
+
return raw.slice(4, end);
|
|
302
|
+
}
|
|
303
|
+
function parseFrontmatterList(raw, key) {
|
|
304
|
+
const block = frontmatterBlock(raw);
|
|
305
|
+
const lines = block.split(/\r?\n/);
|
|
306
|
+
const values = [];
|
|
307
|
+
let inList = false;
|
|
308
|
+
const keyPattern = new RegExp(`^\\s{2}${key}:\\s*$`);
|
|
309
|
+
const topLevelPattern = /^\s{0,2}[A-Za-z0-9_-]+:/;
|
|
310
|
+
for (const line of lines) {
|
|
311
|
+
if (keyPattern.test(line)) {
|
|
312
|
+
inList = true;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (inList && topLevelPattern.test(line) && !line.trim().startsWith('-'))
|
|
316
|
+
break;
|
|
317
|
+
if (inList) {
|
|
318
|
+
const match = line.match(/^\s{4}-\s+(.+?)\s*$/);
|
|
319
|
+
if (match)
|
|
320
|
+
values.push(match[1].trim());
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return values;
|
|
324
|
+
}
|
|
325
|
+
function parseSections(raw) {
|
|
326
|
+
const sections = new Map();
|
|
327
|
+
const lines = raw.split(/\r?\n/);
|
|
328
|
+
let current = null;
|
|
329
|
+
let bucket = [];
|
|
330
|
+
function flush() {
|
|
331
|
+
if (!current)
|
|
332
|
+
return;
|
|
333
|
+
sections.set(current, bucket.join('\n').trim());
|
|
334
|
+
}
|
|
335
|
+
for (const line of lines) {
|
|
336
|
+
const heading = line.match(/^##\s+(.+?)\s*$/);
|
|
337
|
+
if (heading) {
|
|
338
|
+
flush();
|
|
339
|
+
current = heading[1].trim();
|
|
340
|
+
bucket = [];
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (current)
|
|
344
|
+
bucket.push(line);
|
|
345
|
+
}
|
|
346
|
+
flush();
|
|
347
|
+
return sections;
|
|
348
|
+
}
|
|
349
|
+
function sectionHasContent(sections, name) {
|
|
350
|
+
const value = sections.get(name);
|
|
351
|
+
return typeof value === 'string' && value.replace(/[-\s]/g, '').length > 0;
|
|
352
|
+
}
|
|
353
|
+
function parseScenario(packageRoot, filePath) {
|
|
354
|
+
const raw = readText(filePath);
|
|
355
|
+
const frontmatter = parseFrontmatter(raw);
|
|
356
|
+
return {
|
|
357
|
+
filePath,
|
|
358
|
+
relativePath: relativeToPackage(packageRoot, filePath),
|
|
359
|
+
raw,
|
|
360
|
+
frontmatter,
|
|
361
|
+
sections: parseSections(raw),
|
|
362
|
+
id: frontmatter.id || '',
|
|
363
|
+
status: frontmatter.status || '',
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function finding(severity, code, target, message, extra = {}) {
|
|
367
|
+
return { severity, code, target, message, ...extra };
|
|
368
|
+
}
|
|
369
|
+
const REQUIRED_SCENARIO_SECTIONS = [
|
|
370
|
+
'目标 (Goal)',
|
|
371
|
+
'输入文件 (Input Files)',
|
|
372
|
+
'处理流程 (Processing Flow)',
|
|
373
|
+
'输出结果 (Output Result)',
|
|
374
|
+
'验收标准 (Acceptance Criteria)',
|
|
375
|
+
];
|
|
376
|
+
const READY_SCENARIO_SECTIONS = [
|
|
377
|
+
'参考示例文件 (Reference Sample Files)',
|
|
378
|
+
'注意点 (Points of Attention)',
|
|
379
|
+
];
|
|
380
|
+
const READY_STATUSES = new Set(['1-Data-Ready', '2-Clarified', '3-Developing']);
|
|
381
|
+
const INFRASTRUCTURE_SKILL_PATTERN = /(^|[-_])(runner|test|tests|validator)([-_]|$)/i;
|
|
382
|
+
function parseUnresolvedQuestions(packageRoot) {
|
|
383
|
+
const questionsPath = path.join(packageRoot, 'scenarios', 'questions.md');
|
|
384
|
+
const unresolvedByScenario = new Map();
|
|
385
|
+
if (!exists(questionsPath))
|
|
386
|
+
return unresolvedByScenario;
|
|
387
|
+
const raw = readText(questionsPath);
|
|
388
|
+
let currentScenario = 'general';
|
|
389
|
+
let currentQuestion = null;
|
|
390
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
391
|
+
const scenarioMatch = line.match(/^##\s+场景\s+\[\[.*?\|(.*?)\]\]/);
|
|
392
|
+
if (scenarioMatch) {
|
|
393
|
+
currentScenario = scenarioMatch[1].trim().toUpperCase();
|
|
394
|
+
currentQuestion = null;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (/^##\s+通用问题/.test(line)) {
|
|
398
|
+
currentScenario = 'general';
|
|
399
|
+
currentQuestion = null;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const questionMatch = line.match(/^-\s+\*\*\[(Q\d+)\]\s+(.+?)\*\*/);
|
|
403
|
+
if (questionMatch) {
|
|
404
|
+
currentQuestion = {
|
|
405
|
+
id: questionMatch[1],
|
|
406
|
+
text: questionMatch[2],
|
|
407
|
+
scenario: currentScenario,
|
|
408
|
+
status: '',
|
|
409
|
+
};
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const statusMatch = line.match(/状态\*\*:\s*(.+?)\s*$/);
|
|
413
|
+
if (statusMatch && currentQuestion) {
|
|
414
|
+
currentQuestion.status = statusMatch[1].trim();
|
|
415
|
+
if (!currentQuestion.status.includes('🟢')) {
|
|
416
|
+
const issue = finding('error', 'scenario.unresolved_questions', relativeToPackage(packageRoot, questionsPath), `Scenario ${currentQuestion.scenario} has unresolved clarification question ${currentQuestion.id}: ${currentQuestion.text}.`, { questions: [currentQuestion] });
|
|
417
|
+
const list = unresolvedByScenario.get(currentQuestion.scenario) || [];
|
|
418
|
+
list.push(issue);
|
|
419
|
+
unresolvedByScenario.set(currentQuestion.scenario, list);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return unresolvedByScenario;
|
|
424
|
+
}
|
|
425
|
+
function collectBuiltInScenarioFindings(packageRoot, scenarios) {
|
|
426
|
+
const findings = [];
|
|
427
|
+
const unresolvedByScenario = parseUnresolvedQuestions(packageRoot);
|
|
428
|
+
for (const scenario of scenarios) {
|
|
429
|
+
if (!scenario.id) {
|
|
430
|
+
findings.push(finding('error', 'scenario.missing_id', scenario.relativePath, 'Scenario frontmatter is missing `id`.'));
|
|
431
|
+
}
|
|
432
|
+
if (!scenario.status) {
|
|
433
|
+
findings.push(finding('error', 'scenario.missing_status', scenario.relativePath, 'Scenario frontmatter is missing `status`.'));
|
|
434
|
+
}
|
|
435
|
+
for (const section of REQUIRED_SCENARIO_SECTIONS) {
|
|
436
|
+
if (!sectionHasContent(scenario.sections, section)) {
|
|
437
|
+
findings.push(finding('error', 'scenario.missing_section', scenario.relativePath, `Scenario is missing required section content: ${section}.`));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (READY_STATUSES.has(scenario.status)) {
|
|
441
|
+
for (const section of READY_SCENARIO_SECTIONS) {
|
|
442
|
+
if (!sectionHasContent(scenario.sections, section)) {
|
|
443
|
+
findings.push(finding('error', 'scenario.ready_missing_section', scenario.relativePath, `Ready scenario is missing required section content: ${section}.`));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const samplesSection = scenario.sections.get('参考示例文件 (Reference Sample Files)') || '';
|
|
447
|
+
if (!samplesSection.includes('samples/')) {
|
|
448
|
+
findings.push(finding('warn', 'scenario.no_sample_links', scenario.relativePath, 'Ready scenario does not link any `samples/` fixtures.'));
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const scenarioSlug = scenario.id.replace(/^SC-/, '').toUpperCase();
|
|
452
|
+
for (const unresolved of unresolvedByScenario.get(scenarioSlug) || []) {
|
|
453
|
+
findings.push({
|
|
454
|
+
...unresolved,
|
|
455
|
+
severity: READY_STATUSES.has(scenario.status) ? 'error' : 'warn',
|
|
456
|
+
target: scenario.relativePath,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return findings;
|
|
461
|
+
}
|
|
462
|
+
function isPathInside(parent, child) {
|
|
463
|
+
const relative = path.relative(parent, child);
|
|
464
|
+
return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
465
|
+
}
|
|
466
|
+
function collectBuiltInPluginFindings(packageRoot, scenarios, targetPluginName) {
|
|
467
|
+
const findings = [];
|
|
468
|
+
const pluginsRoot = path.join(packageRoot, 'plugins');
|
|
469
|
+
if (!exists(pluginsRoot)) {
|
|
470
|
+
findings.push(finding('error', 'package.missing_plugins_dir', 'plugins', 'Knowledge package has no plugins directory.'));
|
|
471
|
+
return findings;
|
|
472
|
+
}
|
|
473
|
+
for (const filePath of walkFiles(pluginsRoot)) {
|
|
474
|
+
if (path.basename(filePath) === '.DS_Store') {
|
|
475
|
+
findings.push(finding('warn', 'package.os_metadata_file', relativeToPackage(packageRoot, filePath), 'Plugin package contains macOS metadata that should not be distributed.'));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const scenarioIds = new Set(scenarios.map((scenario) => scenario.id).filter(Boolean));
|
|
479
|
+
const contractScenarioIds = new Map();
|
|
480
|
+
const pluginDirs = fs.readdirSync(pluginsRoot, { withFileTypes: true })
|
|
481
|
+
.filter((entry) => entry.isDirectory())
|
|
482
|
+
.filter((entry) => !targetPluginName || entry.name === targetPluginName)
|
|
483
|
+
.map((entry) => path.join(pluginsRoot, entry.name))
|
|
484
|
+
.sort((left, right) => left.localeCompare(right));
|
|
485
|
+
if (targetPluginName && pluginDirs.length === 0) {
|
|
486
|
+
findings.push(finding('error', 'plugin.not_found', path.join('plugins', targetPluginName), `Mounted plugin ${targetPluginName} was not found under plugins/.`));
|
|
487
|
+
return findings;
|
|
488
|
+
}
|
|
489
|
+
for (const pluginRoot of pluginDirs) {
|
|
490
|
+
const pluginName = path.basename(pluginRoot);
|
|
491
|
+
const manifestPath = path.join(pluginRoot, 'plugin.json');
|
|
492
|
+
if (!exists(manifestPath)) {
|
|
493
|
+
findings.push(finding('error', 'plugin.missing_manifest', relativeToPackage(packageRoot, manifestPath), 'Plugin manifest is missing.'));
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
let manifest;
|
|
497
|
+
try {
|
|
498
|
+
manifest = JSON.parse(readText(manifestPath));
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
findings.push(finding('error', 'plugin.invalid_manifest_json', relativeToPackage(packageRoot, manifestPath), `Plugin manifest is not valid JSON: ${error.message}.`));
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
const pluginCommands = new Map();
|
|
505
|
+
if (!Array.isArray(manifest.commands)) {
|
|
506
|
+
findings.push(finding('warn', 'plugin.missing_commands', relativeToPackage(packageRoot, manifestPath), 'Plugin has no plugin-level `commands` declaration.'));
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
for (const [index, command] of manifest.commands.entries()) {
|
|
510
|
+
const target = `${relativeToPackage(packageRoot, manifestPath)}#commands[${index}]`;
|
|
511
|
+
if (!command?.command || typeof command.command !== 'string') {
|
|
512
|
+
findings.push(finding('error', 'plugin.command_missing_name', target, 'Plugin command entry is missing `command`.'));
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
if (pluginCommands.has(command.command)) {
|
|
516
|
+
findings.push(finding('error', 'plugin.duplicate_command', target, `Plugin command ${command.command} is declared more than once.`));
|
|
517
|
+
}
|
|
518
|
+
pluginCommands.set(command.command, target);
|
|
519
|
+
if (!command.description) {
|
|
520
|
+
findings.push(finding('warn', 'plugin.command_missing_description', target, `Plugin command ${command.command} is missing a description.`));
|
|
521
|
+
}
|
|
522
|
+
if (!command.handler || typeof command.handler !== 'string') {
|
|
523
|
+
findings.push(finding('error', 'plugin.command_missing_handler', target, `Plugin command ${command.command} is missing a handler path.`));
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
const handlerPath = path.resolve(pluginRoot, command.handler);
|
|
527
|
+
if (!isPathInside(pluginRoot, handlerPath)) {
|
|
528
|
+
findings.push(finding('error', 'plugin.command_handler_outside_plugin', target, `Plugin command ${command.command} handler points outside the plugin package.`));
|
|
529
|
+
}
|
|
530
|
+
else if (!exists(handlerPath)) {
|
|
531
|
+
findings.push(finding('error', 'plugin.command_handler_missing', relativeToPackage(packageRoot, handlerPath), `Plugin command ${command.command} handler file is missing.`));
|
|
532
|
+
}
|
|
533
|
+
else if (readText(handlerPath).trim().length === 0) {
|
|
534
|
+
findings.push(finding('error', 'plugin.command_handler_empty', relativeToPackage(packageRoot, handlerPath), `Plugin command ${command.command} handler file is empty.`));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const skillsRoot = path.join(pluginRoot, 'skills');
|
|
539
|
+
const skillNames = exists(skillsRoot)
|
|
540
|
+
? fs.readdirSync(skillsRoot, { withFileTypes: true })
|
|
541
|
+
.filter((entry) => entry.isDirectory())
|
|
542
|
+
.map((entry) => entry.name)
|
|
543
|
+
.sort()
|
|
544
|
+
: [];
|
|
545
|
+
const infraSkillNames = new Set(skillNames.filter((skillName) => INFRASTRUCTURE_SKILL_PATTERN.test(skillName)));
|
|
546
|
+
if (manifest.skills === './skills/' && infraSkillNames.size > 0) {
|
|
547
|
+
findings.push(finding('error', 'plugin.skills_glob_includes_infrastructure', relativeToPackage(packageRoot, manifestPath), '`skills: ./skills/` installs plugin infrastructure directories as end-user skills.'));
|
|
548
|
+
}
|
|
549
|
+
for (const skillName of infraSkillNames) {
|
|
550
|
+
const skillPath = path.join(skillsRoot, skillName, 'SKILL.md');
|
|
551
|
+
findings.push(finding('error', 'plugin.command_as_skill', relativeToPackage(packageRoot, skillPath), `${skillName} is plugin infrastructure and should not be installed as a scenario skill.`));
|
|
552
|
+
}
|
|
553
|
+
for (const commandFile of walkMarkdownFiles(skillsRoot).filter((filePath) => filePath.includes(`${path.sep}commands${path.sep}`))) {
|
|
554
|
+
const frontmatter = parseFrontmatter(readText(commandFile));
|
|
555
|
+
const commandName = frontmatter.command;
|
|
556
|
+
if (!commandName) {
|
|
557
|
+
findings.push(finding('warn', 'skill_command.missing_command_name', relativeToPackage(packageRoot, commandFile), 'Skill-level command file is missing command frontmatter.'));
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
if (pluginCommands.has(commandName)) {
|
|
561
|
+
findings.push(finding('error', 'plugin.command_shadowed_by_skill_command', relativeToPackage(packageRoot, commandFile), `Skill-level command ${commandName} duplicates a plugin-level command. Move the command to plugin.json and remove the skill command.`));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const mcpConfigPath = path.join(pluginRoot, '.mcp.json');
|
|
565
|
+
const mcpServerIds = new Set();
|
|
566
|
+
if (exists(mcpConfigPath)) {
|
|
567
|
+
try {
|
|
568
|
+
const mcpConfig = JSON.parse(readText(mcpConfigPath));
|
|
569
|
+
for (const serverId of Object.keys(mcpConfig.mcpServers || {}))
|
|
570
|
+
mcpServerIds.add(serverId);
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
findings.push(finding('error', 'plugin.invalid_mcp_json', relativeToPackage(packageRoot, mcpConfigPath), `MCP config is not valid JSON: ${error.message}.`));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
for (const skillName of skillNames) {
|
|
577
|
+
if (infraSkillNames.has(skillName))
|
|
578
|
+
continue;
|
|
579
|
+
const skillPath = path.join(skillsRoot, skillName, 'SKILL.md');
|
|
580
|
+
if (!exists(skillPath)) {
|
|
581
|
+
findings.push(finding('error', 'skill.missing_skill_md', path.join('plugins', pluginName, 'skills', skillName), `Scenario skill ${skillName} is missing SKILL.md.`));
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
const skillRaw = readText(skillPath);
|
|
585
|
+
const skillFrontmatter = parseFrontmatter(skillRaw);
|
|
586
|
+
if (skillFrontmatter.name && skillFrontmatter.name !== skillName) {
|
|
587
|
+
findings.push(finding('warn', 'skill.name_mismatch', relativeToPackage(packageRoot, skillPath), `Skill frontmatter name ${skillFrontmatter.name} does not match directory name ${skillName}.`));
|
|
588
|
+
}
|
|
589
|
+
if (!skillRaw.includes('## How to test')) {
|
|
590
|
+
findings.push(finding('warn', 'skill.missing_how_to_test', relativeToPackage(packageRoot, skillPath), 'Scenario skill is missing a `How to test` section.'));
|
|
591
|
+
}
|
|
592
|
+
for (const serverId of parseFrontmatterList(skillRaw, 'mcp_servers')) {
|
|
593
|
+
if (mcpServerIds.size > 0 && !mcpServerIds.has(serverId)) {
|
|
594
|
+
findings.push(finding('error', 'plugin.missing_mcp_server_for_skill', relativeToPackage(packageRoot, skillPath), `Skill references MCP server ${serverId}, but ${relativeToPackage(packageRoot, mcpConfigPath)} does not declare it.`));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const contractPath = path.join(skillsRoot, skillName, 'scenario.contract.json');
|
|
598
|
+
if (!exists(contractPath)) {
|
|
599
|
+
findings.push(finding('warn', 'skill.missing_contract', path.join('plugins', pluginName, 'skills', skillName), `Scenario skill ${skillName} is missing scenario.contract.json.`));
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
let contract;
|
|
603
|
+
try {
|
|
604
|
+
contract = JSON.parse(readText(contractPath));
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
findings.push(finding('error', 'skill.invalid_contract_json', relativeToPackage(packageRoot, contractPath), `Scenario contract is not valid JSON: ${error.message}.`));
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (!contract.contract_version) {
|
|
611
|
+
findings.push(finding('error', 'skill.contract_missing_version', relativeToPackage(packageRoot, contractPath), 'Scenario contract is missing `contract_version`.'));
|
|
612
|
+
}
|
|
613
|
+
if (contract.scenario_id) {
|
|
614
|
+
const existing = contractScenarioIds.get(contract.scenario_id) || [];
|
|
615
|
+
existing.push(`${pluginName}/${skillName}`);
|
|
616
|
+
contractScenarioIds.set(contract.scenario_id, existing);
|
|
617
|
+
}
|
|
618
|
+
if (!scenarioIds.has(contract.scenario_id)) {
|
|
619
|
+
findings.push(finding('error', 'skill.contract_without_scenario', relativeToPackage(packageRoot, contractPath), `Contract references ${contract.scenario_id}, but no matching scenario markdown was found.`));
|
|
620
|
+
}
|
|
621
|
+
const metadataScenarioId = skillRaw.match(/^\s{2}scenario_id:\s*(SC-[A-Z0-9]+)\s*$/m)?.[1];
|
|
622
|
+
if (metadataScenarioId && contract.scenario_id && metadataScenarioId !== contract.scenario_id) {
|
|
623
|
+
findings.push(finding('error', 'skill.contract_metadata_mismatch', relativeToPackage(packageRoot, contractPath), `Contract scenario ${contract.scenario_id} does not match SKILL metadata ${metadataScenarioId}.`));
|
|
624
|
+
}
|
|
625
|
+
if (contract.scenario_doc && !exists(path.resolve(path.dirname(contractPath), contract.scenario_doc))) {
|
|
626
|
+
findings.push(finding('error', 'skill.contract_scenario_doc_missing', relativeToPackage(packageRoot, contractPath), `Scenario contract points to missing scenario_doc: ${contract.scenario_doc}.`));
|
|
627
|
+
}
|
|
628
|
+
if (!Array.isArray(contract.sample_inputs) || contract.sample_inputs.length === 0) {
|
|
629
|
+
findings.push(finding('warn', 'skill.contract_missing_sample_inputs', relativeToPackage(packageRoot, contractPath), 'Scenario contract does not declare sample_inputs.'));
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
for (const [index, sample] of contract.sample_inputs.entries()) {
|
|
633
|
+
if (!sample.path) {
|
|
634
|
+
findings.push(finding('error', 'skill.contract_sample_missing_path', `${relativeToPackage(packageRoot, contractPath)}#sample_inputs[${index}]`, 'Sample input is missing `path`.'));
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (sample.required !== false && !exists(path.resolve(path.dirname(contractPath), sample.path))) {
|
|
638
|
+
findings.push(finding('error', 'skill.contract_required_sample_missing', relativeToPackage(packageRoot, path.resolve(path.dirname(contractPath), sample.path)), `Required sample input is missing for ${skillName}.`));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (!Array.isArray(contract.expected_outputs) || contract.expected_outputs.length === 0) {
|
|
643
|
+
findings.push(finding('error', 'skill.contract_missing_expected_outputs', relativeToPackage(packageRoot, contractPath), 'Scenario contract does not declare expected_outputs.'));
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
for (const [index, output] of contract.expected_outputs.entries()) {
|
|
647
|
+
if (!output.path) {
|
|
648
|
+
findings.push(finding('error', 'skill.contract_output_missing_path', `${relativeToPackage(packageRoot, contractPath)}#expected_outputs[${index}]`, 'Expected output is missing `path`.'));
|
|
649
|
+
}
|
|
650
|
+
if (!output.kind) {
|
|
651
|
+
findings.push(finding('warn', 'skill.contract_output_missing_kind', `${relativeToPackage(packageRoot, contractPath)}#expected_outputs[${index}]`, 'Expected output is missing `kind`.'));
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (contract.validation?.prompt_file && !exists(path.resolve(path.dirname(contractPath), contract.validation.prompt_file))) {
|
|
656
|
+
findings.push(finding('error', 'skill.contract_validation_prompt_missing', relativeToPackage(packageRoot, path.resolve(path.dirname(contractPath), contract.validation.prompt_file)), `Validation prompt is missing for ${skillName}.`));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
for (const [scenarioId, skills] of contractScenarioIds.entries()) {
|
|
661
|
+
if (skills.length > 1) {
|
|
662
|
+
findings.push(finding('warn', 'scenario.multiple_skill_contracts', 'plugins', `Scenario ${scenarioId} is implemented by multiple skills: ${skills.join(', ')}.`));
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
for (const scenario of scenarios) {
|
|
666
|
+
if (READY_STATUSES.has(scenario.status) && !contractScenarioIds.has(scenario.id)) {
|
|
667
|
+
findings.push(finding('error', 'scenario.ready_missing_skill_contract', scenario.relativePath, `Ready scenario ${scenario.id} has no scenario skill contract.`));
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return findings;
|
|
671
|
+
}
|
|
672
|
+
function runBuiltInAssessment(packageRoot, context) {
|
|
673
|
+
const findings = [];
|
|
674
|
+
if (!exists(path.join(packageRoot, 'AGENTS.md'))) {
|
|
675
|
+
findings.push(finding('error', 'package.missing_agents_md', 'AGENTS.md', 'Knowledge package is missing AGENTS.md guidance for agents and consumers.'));
|
|
676
|
+
}
|
|
677
|
+
const scenarios = loadScenarios(packageRoot);
|
|
678
|
+
if (scenarios.length === 0) {
|
|
679
|
+
findings.push(finding('warn', 'package.no_scenarios', 'scenarios', 'Knowledge package has no scenario markdown files.'));
|
|
680
|
+
}
|
|
681
|
+
findings.push(...collectBuiltInScenarioFindings(packageRoot, scenarios));
|
|
682
|
+
findings.push(...collectBuiltInPluginFindings(packageRoot, scenarios, pluginNameFromRoot(context.target?.pluginRoot)));
|
|
683
|
+
const summary = {
|
|
684
|
+
packageRoot,
|
|
685
|
+
scenarioCount: scenarios.length,
|
|
686
|
+
findings: findingCounts(findings),
|
|
687
|
+
};
|
|
688
|
+
const parsed = { summary, findings };
|
|
689
|
+
return {
|
|
690
|
+
name: 'supen:builtin-knowledge-package-assessment',
|
|
691
|
+
command: [...context.command, relativeToPackage(process.cwd(), packageRoot)],
|
|
692
|
+
cwd: process.cwd(),
|
|
693
|
+
exitCode: findings.some((item) => item.severity === 'error') ? 1 : 0,
|
|
694
|
+
stdout: `${JSON.stringify(parsed, null, 2)}\n`,
|
|
695
|
+
stderr: '',
|
|
696
|
+
parsed,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function validatorCoverageCategory(code) {
|
|
700
|
+
if (code.startsWith('scenario.'))
|
|
701
|
+
return 'scenario-readiness';
|
|
702
|
+
if (code.startsWith('plugin.command') || code === 'plugin.skills_glob_includes_infrastructure')
|
|
703
|
+
return 'plugin-command-surface';
|
|
704
|
+
if (code.startsWith('skill.contract') || code === 'skill.missing_contract' || code === 'scenario.ready_missing_skill_contract')
|
|
705
|
+
return 'skill-contracts';
|
|
706
|
+
if (code.startsWith('package.'))
|
|
707
|
+
return 'package-hygiene';
|
|
708
|
+
if (code.startsWith('plugin.missing_mcp') || code.startsWith('plugin.invalid_mcp'))
|
|
709
|
+
return 'plugin-runtime-declarations';
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
function collectValidatorCoverageFindings(packageRoot, builtInRun, packageRuns) {
|
|
713
|
+
const findings = [];
|
|
714
|
+
const builtInFindings = builtInRun.parsed?.findings || [];
|
|
715
|
+
if (packageRuns.length === 0) {
|
|
716
|
+
return [
|
|
717
|
+
finding('warn', 'validator.missing_package_validator', 'plugins/*/tests/validate-package.mjs', 'No package-owned validators were found. Supen can assess the package, but the knowledge package cannot repeat the checks on its own.'),
|
|
718
|
+
];
|
|
719
|
+
}
|
|
720
|
+
const packageFindingCodes = new Set(packageRuns.flatMap((run) => run.parsed?.findings || []).map((item) => item.code));
|
|
721
|
+
const categoryFindings = new Map();
|
|
722
|
+
for (const item of builtInFindings) {
|
|
723
|
+
const category = validatorCoverageCategory(item.code);
|
|
724
|
+
if (!category)
|
|
725
|
+
continue;
|
|
726
|
+
const list = categoryFindings.get(category) || [];
|
|
727
|
+
list.push(item);
|
|
728
|
+
categoryFindings.set(category, list);
|
|
729
|
+
}
|
|
730
|
+
for (const [category, categoryItems] of categoryFindings.entries()) {
|
|
731
|
+
const missedCodes = [...new Set(categoryItems.map((item) => item.code).filter((code) => !packageFindingCodes.has(code)))].sort();
|
|
732
|
+
if (missedCodes.length > 0) {
|
|
733
|
+
const missedItems = categoryItems.filter((item) => missedCodes.includes(item.code));
|
|
734
|
+
findings.push(finding('warn', 'validator.coverage_gap', 'plugins/*/tests/validate-package.mjs', `Package validators missed Supen-detected ${category} finding code(s): ${missedCodes.join(', ')}.`, {
|
|
735
|
+
category,
|
|
736
|
+
missedCodes,
|
|
737
|
+
missedTargets: missedItems.slice(0, 10).map((item) => item.target),
|
|
738
|
+
}));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const parsedFailures = packageRuns.filter((run) => run.stdout.trim() && !run.parsed);
|
|
742
|
+
for (const run of parsedFailures) {
|
|
743
|
+
findings.push(finding('warn', 'validator.non_json_output', run.name, 'Package validator produced stdout, but Supen could not parse it as structured JSON findings.'));
|
|
744
|
+
}
|
|
745
|
+
return findings;
|
|
746
|
+
}
|
|
747
|
+
function discoverValidators(packageRoot, targetPluginName) {
|
|
748
|
+
const pluginsRoot = path.join(packageRoot, 'plugins');
|
|
749
|
+
if (!fs.existsSync(pluginsRoot) || !fs.statSync(pluginsRoot).isDirectory()) {
|
|
750
|
+
return [];
|
|
751
|
+
}
|
|
752
|
+
return fs.readdirSync(pluginsRoot, { withFileTypes: true })
|
|
753
|
+
.filter((entry) => entry.isDirectory())
|
|
754
|
+
.filter((entry) => !targetPluginName || entry.name === targetPluginName)
|
|
755
|
+
.map((entry) => path.join(pluginsRoot, entry.name, 'tests', 'validate-package.mjs'))
|
|
756
|
+
.filter((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isFile())
|
|
757
|
+
.sort((left, right) => left.localeCompare(right));
|
|
758
|
+
}
|
|
759
|
+
function parseValidatorJson(stdout) {
|
|
760
|
+
const trimmed = stdout.trim();
|
|
761
|
+
if (!trimmed)
|
|
762
|
+
return undefined;
|
|
763
|
+
try {
|
|
764
|
+
const parsed = JSON.parse(trimmed);
|
|
765
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
766
|
+
return parsed;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
catch {
|
|
770
|
+
return undefined;
|
|
771
|
+
}
|
|
772
|
+
return undefined;
|
|
773
|
+
}
|
|
774
|
+
function runValidator(packageRoot, validatorPath, env) {
|
|
775
|
+
const relative = path.relative(packageRoot, validatorPath);
|
|
776
|
+
const result = spawnSync(process.execPath, [validatorPath], {
|
|
777
|
+
cwd: packageRoot,
|
|
778
|
+
encoding: 'utf8',
|
|
779
|
+
env,
|
|
780
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
781
|
+
});
|
|
782
|
+
return {
|
|
783
|
+
name: relative,
|
|
784
|
+
command: [process.execPath, relative],
|
|
785
|
+
cwd: packageRoot,
|
|
786
|
+
exitCode: result.status ?? 1,
|
|
787
|
+
stdout: result.stdout || '',
|
|
788
|
+
stderr: result.stderr || '',
|
|
789
|
+
parsed: parseValidatorJson(result.stdout || ''),
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
function discoverTestSuites(packageRoot, targetPluginName) {
|
|
793
|
+
const pluginsRoot = path.join(packageRoot, 'plugins');
|
|
794
|
+
if (!exists(pluginsRoot))
|
|
795
|
+
return [];
|
|
796
|
+
const searchRoot = targetPluginName ? path.join(pluginsRoot, targetPluginName) : pluginsRoot;
|
|
797
|
+
if (!exists(searchRoot))
|
|
798
|
+
return [];
|
|
799
|
+
return walkFiles(searchRoot)
|
|
800
|
+
.filter((filePath) => path.basename(filePath) === 'run-suite.mjs')
|
|
801
|
+
.filter((filePath) => {
|
|
802
|
+
const parts = filePath.split(path.sep);
|
|
803
|
+
const parent = parts.at(-2) || '';
|
|
804
|
+
const grandparent = parts.at(-3) || '';
|
|
805
|
+
return parent === 'tests' || (parent === 'scripts' && /test/i.test(grandparent));
|
|
806
|
+
})
|
|
807
|
+
.sort((left, right) => left.localeCompare(right));
|
|
808
|
+
}
|
|
809
|
+
function targetFromSkillName(packageRoot, skillsRoot, skillName) {
|
|
810
|
+
if (typeof skillName !== 'string')
|
|
811
|
+
return 'tests';
|
|
812
|
+
if (typeof skillsRoot === 'string') {
|
|
813
|
+
const skillPath = path.join(skillsRoot, skillName, 'SKILL.md');
|
|
814
|
+
if (isPathInside(packageRoot, skillPath)) {
|
|
815
|
+
return relativeToPackage(packageRoot, skillPath);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return `plugins/*/skills/${skillName}/SKILL.md`;
|
|
819
|
+
}
|
|
820
|
+
function collectReportedOutputs(record) {
|
|
821
|
+
const outputs = new Set();
|
|
822
|
+
const visit = (value) => {
|
|
823
|
+
if (!value)
|
|
824
|
+
return;
|
|
825
|
+
if (Array.isArray(value)) {
|
|
826
|
+
for (const item of value)
|
|
827
|
+
visit(item);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (typeof value === 'object') {
|
|
831
|
+
if (Array.isArray(value.outputs)) {
|
|
832
|
+
for (const output of value.outputs) {
|
|
833
|
+
if (typeof output === 'string')
|
|
834
|
+
outputs.add(output);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
for (const nested of Object.values(value))
|
|
838
|
+
visit(nested);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
visit(record);
|
|
843
|
+
return [...outputs].sort((left, right) => left.localeCompare(right));
|
|
844
|
+
}
|
|
845
|
+
function resolveReportedOutput(packageRoot, outputPath) {
|
|
846
|
+
if (outputPath.includes('$'))
|
|
847
|
+
return null;
|
|
848
|
+
return path.isAbsolute(outputPath) ? outputPath : path.resolve(packageRoot, outputPath);
|
|
849
|
+
}
|
|
850
|
+
function validateReportedOutput(outputPath) {
|
|
851
|
+
if (!exists(outputPath))
|
|
852
|
+
return [`missing: ${outputPath}`];
|
|
853
|
+
const stats = fs.statSync(outputPath);
|
|
854
|
+
if (!stats.isFile())
|
|
855
|
+
return [`not a file: ${outputPath}`];
|
|
856
|
+
if (stats.size === 0)
|
|
857
|
+
return [`empty file: ${outputPath}`];
|
|
858
|
+
const extension = path.extname(outputPath).toLowerCase();
|
|
859
|
+
if (extension === '.json') {
|
|
860
|
+
try {
|
|
861
|
+
const raw = readText(outputPath);
|
|
862
|
+
const parsed = JSON.parse(raw);
|
|
863
|
+
if (Array.isArray(parsed) && parsed.length === 0)
|
|
864
|
+
return [`empty JSON array: ${outputPath}`];
|
|
865
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Object.keys(parsed).length === 0) {
|
|
866
|
+
return [`empty JSON object: ${outputPath}`];
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
catch (error) {
|
|
870
|
+
return [`invalid JSON: ${outputPath}: ${error.message}`];
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (extension === '.docx' || extension === '.xlsx') {
|
|
874
|
+
const header = fs.readFileSync(outputPath, { flag: 'r' }).subarray(0, 2);
|
|
875
|
+
if (header[0] !== 0x50 || header[1] !== 0x4b) {
|
|
876
|
+
return [`invalid ${extension.slice(1).toUpperCase()} ZIP header: ${outputPath}`];
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (extension === '.pdf') {
|
|
880
|
+
const header = fs.readFileSync(outputPath, { flag: 'r' }).subarray(0, 4).toString('utf8');
|
|
881
|
+
if (header !== '%PDF')
|
|
882
|
+
return [`invalid PDF header: ${outputPath}`];
|
|
883
|
+
}
|
|
884
|
+
if (extension === '.md' && readText(outputPath).trim().length === 0) {
|
|
885
|
+
return [`empty Markdown file: ${outputPath}`];
|
|
886
|
+
}
|
|
887
|
+
return [];
|
|
888
|
+
}
|
|
889
|
+
function findingsFromTestSuite(packageRoot, runnerName, parsed) {
|
|
890
|
+
if (!parsed) {
|
|
891
|
+
return [finding('error', 'test.non_json_output', runnerName, 'Test suite did not emit structured JSON output.')];
|
|
892
|
+
}
|
|
893
|
+
const findings = [];
|
|
894
|
+
const results = Array.isArray(parsed.results) ? parsed.results : [];
|
|
895
|
+
const resultsByScenario = new Map();
|
|
896
|
+
if (results.length === 0) {
|
|
897
|
+
findings.push(finding('warn', 'test.no_results', runnerName, 'Test suite emitted no test results.'));
|
|
898
|
+
}
|
|
899
|
+
for (const result of results) {
|
|
900
|
+
if (!result || typeof result !== 'object')
|
|
901
|
+
continue;
|
|
902
|
+
const record = result;
|
|
903
|
+
for (const scenarioId of Array.isArray(record.scenarioIds) ? record.scenarioIds : []) {
|
|
904
|
+
for (const selector of normalizeScenarioSelector(scenarioId)) {
|
|
905
|
+
const list = resultsByScenario.get(selector) || [];
|
|
906
|
+
list.push(record);
|
|
907
|
+
resultsByScenario.set(selector, list);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const target = targetFromSkillName(packageRoot, parsed.skillsRoot, record.skillName);
|
|
911
|
+
if (record.finalStatus === 'failed') {
|
|
912
|
+
const failedParts = ['test', 'smoke', 'fixtureE2E', 'qaReview']
|
|
913
|
+
.map((key) => record[key])
|
|
914
|
+
.filter((entry) => entry && entry.status && !['passed', 'manual-review', 'not-automated'].includes(entry.status));
|
|
915
|
+
findings.push(finding('error', 'test.failed', target, `Test failed for ${record.skillName}: ${failedParts.map((entry) => entry.reason).filter(Boolean).join('; ') || 'no reason reported'}.`, { runner: runnerName, result: record }));
|
|
916
|
+
}
|
|
917
|
+
else if (record.finalStatus === 'blocked') {
|
|
918
|
+
const blockedParts = ['test', 'smoke', 'fixtureE2E', 'qaReview']
|
|
919
|
+
.map((key) => record[key])
|
|
920
|
+
.filter((entry) => entry && entry.status === 'blocked');
|
|
921
|
+
findings.push(finding('error', 'test.blocked_prerequisite', target, `Test for ${record.skillName} is blocked by a missing prerequisite: ${blockedParts.map((entry) => entry.reason).filter(Boolean).join('; ') || 'no blocker reported'}.`, { runner: runnerName, result: record }));
|
|
922
|
+
}
|
|
923
|
+
else if (record.finalStatus === 'incomplete') {
|
|
924
|
+
findings.push(finding('error', 'test.incomplete', target, `Test suite did not fully evaluate ${record.skillName}; declared expectations remain not automated or manual-only.`, { runner: runnerName, result: record }));
|
|
925
|
+
}
|
|
926
|
+
else if (record.finalStatus === 'passed') {
|
|
927
|
+
const outputPaths = collectReportedOutputs(record)
|
|
928
|
+
.map((outputPath) => resolveReportedOutput(packageRoot, outputPath))
|
|
929
|
+
.filter((outputPath) => typeof outputPath === 'string');
|
|
930
|
+
if (outputPaths.length === 0) {
|
|
931
|
+
findings.push(finding('error', 'test.no_verifiable_outputs', target, `Test passed for ${record.skillName}, but reported no concrete output artifacts for Supen to verify.`, { runner: runnerName, result: record }));
|
|
932
|
+
}
|
|
933
|
+
const outputIssues = outputPaths.flatMap(validateReportedOutput);
|
|
934
|
+
if (outputIssues.length > 0) {
|
|
935
|
+
findings.push(finding('error', 'test.output_invalid', target, `Test passed for ${record.skillName}, but Supen could not verify reported output artifacts: ${outputIssues.join('; ')}.`, { runner: runnerName, outputIssues, result: record }));
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
findings.push(finding('error', 'test.unknown_status', target, `Test result for ${record.skillName} did not report a recognized finalStatus.`, { runner: runnerName, result: record }));
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
for (const scenario of loadScenarios(packageRoot)) {
|
|
943
|
+
if (!READY_STATUSES.has(scenario.status))
|
|
944
|
+
continue;
|
|
945
|
+
const scenarioResults = resultsByScenario.get(scenario.id) || [];
|
|
946
|
+
if (scenarioResults.length === 0) {
|
|
947
|
+
findings.push(finding('error', 'test.missing_scenario_result', scenario.relativePath, `Ready scenario ${scenario.id} is a test todo item, but no test result was reported for it.`, { runner: runnerName, scenarioId: scenario.id }));
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (!scenarioResults.some((record) => record.finalStatus === 'passed')) {
|
|
951
|
+
findings.push(finding('error', 'test.scenario_not_passed', scenario.relativePath, `Ready scenario ${scenario.id} is a test todo item, but no reported test passed for it.`, { runner: runnerName, scenarioId: scenario.id, results: scenarioResults }));
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return findings;
|
|
955
|
+
}
|
|
956
|
+
function missingTestSuiteRun(packageRoot, context) {
|
|
957
|
+
const parsed = {
|
|
958
|
+
summary: {},
|
|
959
|
+
findings: [
|
|
960
|
+
finding('error', 'test.missing_runner', 'plugins', 'No package test suite runner was discovered. Supen cannot prove skill behavior without executable tests.'),
|
|
961
|
+
],
|
|
962
|
+
};
|
|
963
|
+
return {
|
|
964
|
+
name: 'supen:test-suite-discovery',
|
|
965
|
+
command: [...context.command, relativeToPackage(process.cwd(), packageRoot), '--run-tests'],
|
|
966
|
+
cwd: process.cwd(),
|
|
967
|
+
exitCode: 1,
|
|
968
|
+
stdout: `${JSON.stringify(parsed, null, 2)}\n`,
|
|
969
|
+
stderr: '',
|
|
970
|
+
parsed,
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function knowledgeTestTimeoutMs(explicit) {
|
|
974
|
+
if (explicit && Number.isFinite(explicit) && explicit > 0)
|
|
975
|
+
return explicit;
|
|
976
|
+
const fromEnv = Number.parseInt(process.env.SUPEN_KNOWLEDGE_TEST_TIMEOUT_MS || '', 10);
|
|
977
|
+
if (Number.isFinite(fromEnv) && fromEnv > 0)
|
|
978
|
+
return fromEnv;
|
|
979
|
+
return 10 * 60 * 1000;
|
|
980
|
+
}
|
|
981
|
+
function runTestSuite(packageRoot, testSuitePath, env, timeoutMs) {
|
|
982
|
+
const relative = path.relative(packageRoot, testSuitePath);
|
|
983
|
+
const timeout = knowledgeTestTimeoutMs(timeoutMs);
|
|
984
|
+
const result = spawnSync(process.execPath, [testSuitePath, 'all'], {
|
|
985
|
+
cwd: packageRoot,
|
|
986
|
+
encoding: 'utf8',
|
|
987
|
+
env,
|
|
988
|
+
timeout,
|
|
989
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
990
|
+
});
|
|
991
|
+
const parsedRaw = parseValidatorJson(result.stdout || '');
|
|
992
|
+
if (result.error && result.error.code === 'ETIMEDOUT' && !parsedRaw) {
|
|
993
|
+
const parsed = {
|
|
994
|
+
summary: {},
|
|
995
|
+
findings: [
|
|
996
|
+
finding('error', 'test.runner_timeout', relative, `Test suite exceeded the configured timeout of ${timeout}ms before producing complete structured results.`, {
|
|
997
|
+
timeoutMs: timeout,
|
|
998
|
+
stdout: result.stdout || '',
|
|
999
|
+
stderr: result.stderr || '',
|
|
1000
|
+
}),
|
|
1001
|
+
],
|
|
1002
|
+
};
|
|
1003
|
+
return {
|
|
1004
|
+
name: relative,
|
|
1005
|
+
command: [process.execPath, relative, 'all'],
|
|
1006
|
+
cwd: packageRoot,
|
|
1007
|
+
exitCode: 1,
|
|
1008
|
+
stdout: `${JSON.stringify(parsed, null, 2)}\n`,
|
|
1009
|
+
stderr: result.stderr || '',
|
|
1010
|
+
parsed,
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
const parsed = {
|
|
1014
|
+
summary: parsedRaw?.summary,
|
|
1015
|
+
findings: findingsFromTestSuite(packageRoot, relative, parsedRaw),
|
|
1016
|
+
...(parsedRaw || {}),
|
|
1017
|
+
};
|
|
1018
|
+
return {
|
|
1019
|
+
name: relative,
|
|
1020
|
+
command: [process.execPath, relative, 'all'],
|
|
1021
|
+
cwd: packageRoot,
|
|
1022
|
+
exitCode: result.status ?? 0,
|
|
1023
|
+
stdout: result.stdout || '',
|
|
1024
|
+
stderr: result.stderr || '',
|
|
1025
|
+
parsed,
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
function findingCounts(findings) {
|
|
1029
|
+
return findings.reduce((counts, finding) => {
|
|
1030
|
+
counts[finding.severity] = (counts[finding.severity] || 0) + 1;
|
|
1031
|
+
return counts;
|
|
1032
|
+
}, {});
|
|
1033
|
+
}
|
|
1034
|
+
function firstLine(value) {
|
|
1035
|
+
return String(value || '')
|
|
1036
|
+
.split(/\r?\n/)
|
|
1037
|
+
.map((line) => line.trim())
|
|
1038
|
+
.find(Boolean) || '';
|
|
1039
|
+
}
|
|
1040
|
+
function conciseFindingMessage(finding, maxLength = 240) {
|
|
1041
|
+
const result = asRecord(finding.result);
|
|
1042
|
+
const test = asRecord(result?.test);
|
|
1043
|
+
const rawReason = String(test?.reason || finding.message || '');
|
|
1044
|
+
const reason = rawReason.includes('You must install .NET to run this application.')
|
|
1045
|
+
? 'You must install .NET to run this application.'
|
|
1046
|
+
: rawReason.includes('Failed to resolve libhostfxr.so')
|
|
1047
|
+
? 'Failed to resolve libhostfxr.so; the .NET runtime is not available.'
|
|
1048
|
+
: firstLine(rawReason);
|
|
1049
|
+
if (!reason)
|
|
1050
|
+
return finding.message;
|
|
1051
|
+
return reason.length > maxLength ? `${reason.slice(0, maxLength - 1)}…` : reason;
|
|
1052
|
+
}
|
|
1053
|
+
function collectRunSuiteResults(runs) {
|
|
1054
|
+
return runs.flatMap((run) => {
|
|
1055
|
+
const results = Array.isArray(run.parsed?.results) ? run.parsed.results : [];
|
|
1056
|
+
return results.filter((result) => !!result && typeof result === 'object' && !Array.isArray(result));
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
function buildRoundSummary(runs, findings, taskArtifacts) {
|
|
1060
|
+
const testResults = collectRunSuiteResults(runs);
|
|
1061
|
+
const testsByStatus = testResults.reduce((counts, result) => {
|
|
1062
|
+
const status = typeof result.finalStatus === 'string' && result.finalStatus.trim()
|
|
1063
|
+
? result.finalStatus.trim()
|
|
1064
|
+
: 'unknown';
|
|
1065
|
+
counts[status] = (counts[status] || 0) + 1;
|
|
1066
|
+
return counts;
|
|
1067
|
+
}, {});
|
|
1068
|
+
const blockedPrerequisites = Array.from(new Set(findings
|
|
1069
|
+
.filter((finding) => finding.code === 'test.blocked_prerequisite')
|
|
1070
|
+
.map((finding) => conciseFindingMessage(finding, 160))
|
|
1071
|
+
.filter(Boolean)));
|
|
1072
|
+
return {
|
|
1073
|
+
validators: runs.length,
|
|
1074
|
+
findings: {
|
|
1075
|
+
total: findings.length,
|
|
1076
|
+
bySeverity: findingCounts(findings),
|
|
1077
|
+
},
|
|
1078
|
+
tests: {
|
|
1079
|
+
total: testResults.length,
|
|
1080
|
+
byStatus: testsByStatus,
|
|
1081
|
+
passed: testResults
|
|
1082
|
+
.filter((result) => result.finalStatus === 'passed')
|
|
1083
|
+
.map((result) => result.skillName)
|
|
1084
|
+
.filter(Boolean),
|
|
1085
|
+
blocked: testResults
|
|
1086
|
+
.filter((result) => result.finalStatus === 'blocked')
|
|
1087
|
+
.map((result) => result.skillName)
|
|
1088
|
+
.filter(Boolean),
|
|
1089
|
+
},
|
|
1090
|
+
primaryBlockers: blockedPrerequisites.slice(0, 5),
|
|
1091
|
+
artifacts: taskArtifacts
|
|
1092
|
+
? {
|
|
1093
|
+
root: taskArtifacts.root,
|
|
1094
|
+
files: taskArtifacts.files.length,
|
|
1095
|
+
}
|
|
1096
|
+
: undefined,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
function uniqueFindings(findings) {
|
|
1100
|
+
const seen = new Set();
|
|
1101
|
+
const unique = [];
|
|
1102
|
+
for (const finding of findings) {
|
|
1103
|
+
const key = finding.code === 'scenario.ready_missing_section' || finding.code === 'validator.coverage_gap'
|
|
1104
|
+
? `${finding.severity}\0${finding.code}\0${finding.target}\0${finding.message}`
|
|
1105
|
+
: `${finding.severity}\0${finding.code}\0${finding.target}`;
|
|
1106
|
+
if (seen.has(key))
|
|
1107
|
+
continue;
|
|
1108
|
+
seen.add(key);
|
|
1109
|
+
unique.push(finding);
|
|
1110
|
+
}
|
|
1111
|
+
return unique;
|
|
1112
|
+
}
|
|
1113
|
+
function recommendedAction(finding) {
|
|
1114
|
+
switch (finding.code) {
|
|
1115
|
+
case 'package.missing_agents_md':
|
|
1116
|
+
return 'Add AGENTS.md describing the package purpose, structure, consumer expectations, and evolution rules.';
|
|
1117
|
+
case 'package.os_metadata_file':
|
|
1118
|
+
return 'Remove the metadata file from the package and prevent it from being committed again.';
|
|
1119
|
+
case 'scenario.ready_missing_section':
|
|
1120
|
+
return 'Update the scenario markdown with the missing ready-state section, or downgrade its lifecycle status until the requirement is actually ready.';
|
|
1121
|
+
case 'scenario.unresolved_questions':
|
|
1122
|
+
return 'Resolve or explicitly defer the linked clarification question before implementation continues.';
|
|
1123
|
+
case 'scenario.no_sample_links':
|
|
1124
|
+
return 'Link the concrete fixture under samples/, or mark the scenario as blocked on fixture collection.';
|
|
1125
|
+
case 'plugin.command_as_skill':
|
|
1126
|
+
return 'Move plugin infrastructure out of normal scenario skills and expose it through plugin-level commands or package tests.';
|
|
1127
|
+
case 'plugin.command_shadowed_by_skill_command':
|
|
1128
|
+
return 'Delete or migrate the stale skill-level command so the plugin-level command is the single command surface.';
|
|
1129
|
+
case 'plugin.skills_glob_includes_infrastructure':
|
|
1130
|
+
return 'Change plugin skill discovery so infrastructure directories are excluded from installable scenario skills.';
|
|
1131
|
+
case 'plugin.missing_commands':
|
|
1132
|
+
return 'Declare package operations in plugin.json commands and back them with plugin command handlers.';
|
|
1133
|
+
case 'plugin.command_handler_missing':
|
|
1134
|
+
case 'plugin.command_handler_empty':
|
|
1135
|
+
return 'Add a real handler file for the plugin-level command and rerun command catalog validation.';
|
|
1136
|
+
case 'skill.missing_contract':
|
|
1137
|
+
return 'Add a scenario.contract.json for the skill or remove it from scenario-skill discovery.';
|
|
1138
|
+
case 'skill.contract_required_sample_missing':
|
|
1139
|
+
return 'Add the missing required fixture or mark the sample optional only if the scenario can still be validated without it.';
|
|
1140
|
+
case 'skill.contract_validation_prompt_missing':
|
|
1141
|
+
return 'Add the validation prompt file referenced by the contract.';
|
|
1142
|
+
case 'scenario.ready_missing_skill_contract':
|
|
1143
|
+
return 'Create a scenario skill contract for the ready scenario, or downgrade the scenario status until implementation is planned.';
|
|
1144
|
+
case 'validator.missing_package_validator':
|
|
1145
|
+
return 'Add a package-owned validator so the knowledge package can repeat Supen-discovered checks without bespoke manual review.';
|
|
1146
|
+
case 'validator.coverage_gap':
|
|
1147
|
+
return 'Extend the package validator to cover the missed category, then rerun Supen to prove the gap is closed.';
|
|
1148
|
+
case 'validator.non_json_output':
|
|
1149
|
+
return 'Emit structured JSON with summary and findings so Supen can compare validation evidence across rounds.';
|
|
1150
|
+
case 'test.non_json_output':
|
|
1151
|
+
return 'Update the test runner to emit structured JSON so Supen can record test evidence.';
|
|
1152
|
+
case 'test.no_results':
|
|
1153
|
+
return 'Update the test runner to discover and report scenario test results.';
|
|
1154
|
+
case 'test.missing_scenario_result':
|
|
1155
|
+
return 'Use the scenarios folder as the test todo list: add a test result for this ready scenario.';
|
|
1156
|
+
case 'test.scenario_not_passed':
|
|
1157
|
+
return 'Fix or complete the scenario test until this ready scenario has a passing test result.';
|
|
1158
|
+
case 'test.failed':
|
|
1159
|
+
return 'Fix the failing test or remove the target from test discovery until it has a valid executable test.';
|
|
1160
|
+
case 'test.blocked_prerequisite':
|
|
1161
|
+
return 'Install or configure the missing prerequisite, or explicitly mark the scenario blocked until the prerequisite is available.';
|
|
1162
|
+
case 'test.incomplete':
|
|
1163
|
+
return 'Translate the declared scenario expectations into executable checks so the test can produce a pass/fail decision.';
|
|
1164
|
+
case 'test.no_verifiable_outputs':
|
|
1165
|
+
return 'Report concrete output artifact paths from the test runner so Supen can independently verify them.';
|
|
1166
|
+
case 'test.output_invalid':
|
|
1167
|
+
return 'Fix the test or implementation so every reported output artifact exists, is non-empty, and has the expected file structure.';
|
|
1168
|
+
case 'test.unknown_status':
|
|
1169
|
+
return 'Update the test runner to report finalStatus as passed, failed, blocked, or incomplete for every target.';
|
|
1170
|
+
case 'test.missing_runner':
|
|
1171
|
+
return 'Add a package test runner that executes scenario skill tests and emits structured JSON results.';
|
|
1172
|
+
case 'test.runner_timeout':
|
|
1173
|
+
return 'Add per-target timeouts/progress reporting to the package test runner, or increase the timeout only after proving the long-running step is bounded.';
|
|
1174
|
+
default:
|
|
1175
|
+
return 'Repair the cited package surface, rerun validation, and attach before/after evidence.';
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
function renderKnowledgeIssueMarkdown(round) {
|
|
1179
|
+
const counts = findingCounts(round.findings);
|
|
1180
|
+
const lines = [
|
|
1181
|
+
'# Knowledge Evolution Round',
|
|
1182
|
+
'',
|
|
1183
|
+
`- Package: ${round.packageRoot}`,
|
|
1184
|
+
`- Created: ${round.createdAt}`,
|
|
1185
|
+
`- Status: ${round.status}`,
|
|
1186
|
+
`- Findings: ${round.findings.length} (${Object.entries(counts).map(([severity, count]) => `${severity}: ${count}`).join(', ') || 'none'})`,
|
|
1187
|
+
'',
|
|
1188
|
+
'## Validators',
|
|
1189
|
+
'',
|
|
1190
|
+
...round.validators.flatMap((validator) => [
|
|
1191
|
+
`- ${validator.name}`,
|
|
1192
|
+
` - Exit code: ${validator.exitCode}`,
|
|
1193
|
+
` - Command: ${validator.command.map((part) => JSON.stringify(part)).join(' ')}`,
|
|
1194
|
+
]),
|
|
1195
|
+
'',
|
|
1196
|
+
'## Knowledge Issues',
|
|
1197
|
+
'',
|
|
1198
|
+
];
|
|
1199
|
+
if (round.findings.length === 0) {
|
|
1200
|
+
lines.push('No issues detected.');
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
for (const [index, finding] of round.findings.entries()) {
|
|
1204
|
+
lines.push(`### KI-${String(index + 1).padStart(3, '0')} ${finding.code}`, '', `- Severity: ${finding.severity}`, `- Target: ${finding.target}`, `- Message: ${finding.message}`, `- Recommended repair: ${recommendedAction(finding)}`, '');
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
lines.push('## Promotion Decision', '', round.status === 'pass'
|
|
1208
|
+
? 'Promote: all validators passed.'
|
|
1209
|
+
: 'Reject promotion: validation produced blocking findings. Apply a repair capsule, then rerun this command.', '');
|
|
1210
|
+
return `${lines.join('\n')}\n`;
|
|
1211
|
+
}
|
|
1212
|
+
function renderRepairCapsuleMarkdown(round) {
|
|
1213
|
+
const grouped = new Map();
|
|
1214
|
+
for (const finding of round.findings) {
|
|
1215
|
+
const list = grouped.get(finding.code) || [];
|
|
1216
|
+
list.push(finding);
|
|
1217
|
+
grouped.set(finding.code, list);
|
|
1218
|
+
}
|
|
1219
|
+
const lines = [
|
|
1220
|
+
'# Repair Capsule',
|
|
1221
|
+
'',
|
|
1222
|
+
`- Package: ${round.packageRoot}`,
|
|
1223
|
+
`- Created: ${round.createdAt}`,
|
|
1224
|
+
`- Validation status: ${round.status}`,
|
|
1225
|
+
`- Promotion decision: ${round.status === 'pass' ? 'promote' : 'reject until repaired'}`,
|
|
1226
|
+
'',
|
|
1227
|
+
'## Proposed Repairs',
|
|
1228
|
+
'',
|
|
1229
|
+
];
|
|
1230
|
+
if (round.findings.length === 0) {
|
|
1231
|
+
lines.push('No repairs proposed. Validation passed.', '');
|
|
1232
|
+
}
|
|
1233
|
+
else {
|
|
1234
|
+
for (const [code, findings] of grouped.entries()) {
|
|
1235
|
+
lines.push(`### ${code}`, '', `Affected targets: ${findings.length}`, '');
|
|
1236
|
+
const sampleTargets = findings.slice(0, 8).map((finding) => `- ${finding.target}`);
|
|
1237
|
+
lines.push(...sampleTargets);
|
|
1238
|
+
if (findings.length > sampleTargets.length) {
|
|
1239
|
+
lines.push(`- ... ${findings.length - sampleTargets.length} more`);
|
|
1240
|
+
}
|
|
1241
|
+
lines.push('', `Repair action: ${recommendedAction(findings[0])}`, '');
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
lines.push('## Validation Required Before Promotion', '', ...round.validators.map((validator) => `- Rerun ${validator.command.map((part) => JSON.stringify(part)).join(' ')} from ${validator.cwd}.`), '', '## Rollback Path', '', 'Do not promote partial repairs. Revert the repair diff and keep this capsule as rejected evidence if validation still reports blocking findings.', '');
|
|
1245
|
+
return `${lines.join('\n')}\n`;
|
|
1246
|
+
}
|
|
1247
|
+
function defaultEvolutionRoot(packageRoot) {
|
|
1248
|
+
return path.join(packageRoot, '.supen', 'evolution');
|
|
1249
|
+
}
|
|
1250
|
+
function createOutputDir(packageRoot, createdAt, outputRoot) {
|
|
1251
|
+
const safeTimestamp = createdAt.replace(/[:.]/g, '-');
|
|
1252
|
+
const baseDir = outputRoot
|
|
1253
|
+
? path.resolve(process.cwd(), outputRoot)
|
|
1254
|
+
: defaultEvolutionRoot(packageRoot);
|
|
1255
|
+
const outputDir = path.join(baseDir, safeTimestamp);
|
|
1256
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1257
|
+
return outputDir;
|
|
1258
|
+
}
|
|
1259
|
+
function writeRoundEvidence(round, outputRoot) {
|
|
1260
|
+
const outputDir = createOutputDir(round.packageRoot, round.createdAt, outputRoot);
|
|
1261
|
+
const withOutput = { ...round, outputDir };
|
|
1262
|
+
fs.writeFileSync(path.join(outputDir, 'evidence.json'), `${JSON.stringify(withOutput, null, 2)}\n`, 'utf8');
|
|
1263
|
+
fs.writeFileSync(path.join(outputDir, 'knowledge-issues.md'), renderKnowledgeIssueMarkdown(withOutput), 'utf8');
|
|
1264
|
+
fs.writeFileSync(path.join(outputDir, 'repair-capsule.md'), renderRepairCapsuleMarkdown(withOutput), 'utf8');
|
|
1265
|
+
for (const validator of round.validators) {
|
|
1266
|
+
const baseName = validator.name.replace(/[^A-Za-z0-9_.-]+/g, '_');
|
|
1267
|
+
fs.writeFileSync(path.join(outputDir, `${baseName}.stdout.txt`), validator.stdout, 'utf8');
|
|
1268
|
+
fs.writeFileSync(path.join(outputDir, `${baseName}.stderr.txt`), validator.stderr, 'utf8');
|
|
1269
|
+
}
|
|
1270
|
+
return withOutput;
|
|
1271
|
+
}
|
|
1272
|
+
function supenHomeDir() {
|
|
1273
|
+
return process.env.SUPEN_HOME || path.join(os.homedir(), '.supen');
|
|
1274
|
+
}
|
|
1275
|
+
function safeTaskSegment(value, fallback) {
|
|
1276
|
+
const normalized = (value || '').trim().replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
1277
|
+
return normalized || fallback;
|
|
1278
|
+
}
|
|
1279
|
+
function taskArtifactRoot() {
|
|
1280
|
+
return path.join(supenHomeDir(), 'tasks');
|
|
1281
|
+
}
|
|
1282
|
+
function taskArtifactFolderName(createdAt, packageRoot) {
|
|
1283
|
+
const packageName = path.basename(packageRoot) || 'knowledge';
|
|
1284
|
+
const timestamp = createdAt.replace(/[:.]/g, '-');
|
|
1285
|
+
return `task-knowledge-evolve-${safeTaskSegment(packageName, 'package')}-${timestamp}`;
|
|
1286
|
+
}
|
|
1287
|
+
function asRecord(value) {
|
|
1288
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
1289
|
+
? value
|
|
1290
|
+
: null;
|
|
1291
|
+
}
|
|
1292
|
+
function collectTestOutputArtifacts(round) {
|
|
1293
|
+
const artifacts = [];
|
|
1294
|
+
for (const validator of round.validators) {
|
|
1295
|
+
const parsed = asRecord(validator.parsed);
|
|
1296
|
+
const results = Array.isArray(parsed?.results) ? parsed.results : [];
|
|
1297
|
+
for (const rawResult of results) {
|
|
1298
|
+
const result = asRecord(rawResult);
|
|
1299
|
+
if (!result)
|
|
1300
|
+
continue;
|
|
1301
|
+
const skillName = typeof result.skillName === 'string'
|
|
1302
|
+
? result.skillName
|
|
1303
|
+
: safeTaskSegment(String(result.skillName || ''), 'unknown-skill');
|
|
1304
|
+
const test = asRecord(result.test);
|
|
1305
|
+
const outputs = Array.isArray(test?.outputs) ? test.outputs : [];
|
|
1306
|
+
for (const output of outputs) {
|
|
1307
|
+
if (typeof output !== 'string' || !output.trim())
|
|
1308
|
+
continue;
|
|
1309
|
+
const source = path.resolve(output);
|
|
1310
|
+
if (!fs.existsSync(source) || !fs.statSync(source).isFile())
|
|
1311
|
+
continue;
|
|
1312
|
+
artifacts.push({ skillName, source });
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
return artifacts;
|
|
1317
|
+
}
|
|
1318
|
+
function publishTaskArtifacts(round) {
|
|
1319
|
+
const outputs = collectTestOutputArtifacts(round);
|
|
1320
|
+
const taskFolder = taskArtifactFolderName(round.createdAt, round.packageRoot);
|
|
1321
|
+
const root = path.join(taskArtifactRoot(), taskFolder);
|
|
1322
|
+
fs.mkdirSync(root, { recursive: true });
|
|
1323
|
+
const files = [];
|
|
1324
|
+
for (const output of outputs) {
|
|
1325
|
+
const skillSegment = safeTaskSegment(output.skillName, 'unknown-skill');
|
|
1326
|
+
const targetDir = path.join(root, skillSegment);
|
|
1327
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
1328
|
+
const target = path.join(targetDir, path.basename(output.source));
|
|
1329
|
+
fs.copyFileSync(output.source, target);
|
|
1330
|
+
const relative = path.relative(root, target);
|
|
1331
|
+
files.push({
|
|
1332
|
+
skillName: output.skillName,
|
|
1333
|
+
source: output.source,
|
|
1334
|
+
path: relative,
|
|
1335
|
+
size: fs.statSync(target).size,
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
const published = { root, taskFolder, files };
|
|
1339
|
+
const withArtifacts = { ...round, taskArtifacts: published };
|
|
1340
|
+
fs.writeFileSync(path.join(root, 'artifact-index.json'), `${JSON.stringify(published, null, 2)}\n`, 'utf8');
|
|
1341
|
+
fs.writeFileSync(path.join(root, 'evidence.json'), `${JSON.stringify(withArtifacts, null, 2)}\n`, 'utf8');
|
|
1342
|
+
fs.writeFileSync(path.join(root, 'knowledge-issues.md'), renderKnowledgeIssueMarkdown(withArtifacts), 'utf8');
|
|
1343
|
+
fs.writeFileSync(path.join(root, 'repair-capsule.md'), renderRepairCapsuleMarkdown(withArtifacts), 'utf8');
|
|
1344
|
+
const logsRoot = path.join(root, 'logs');
|
|
1345
|
+
fs.mkdirSync(logsRoot, { recursive: true });
|
|
1346
|
+
for (const validator of round.validators) {
|
|
1347
|
+
const baseName = validator.name.replace(/[^A-Za-z0-9_.-]+/g, '_');
|
|
1348
|
+
fs.writeFileSync(path.join(logsRoot, `${baseName}.stdout.txt`), validator.stdout, 'utf8');
|
|
1349
|
+
fs.writeFileSync(path.join(logsRoot, `${baseName}.stderr.txt`), validator.stderr, 'utf8');
|
|
1350
|
+
}
|
|
1351
|
+
return withArtifacts;
|
|
1352
|
+
}
|
|
1353
|
+
async function runEvolution(packagePath, options) {
|
|
1354
|
+
const target = options.target;
|
|
1355
|
+
const packageRoot = target?.packageRoot || resolvePackageRoot(packagePath);
|
|
1356
|
+
const context = {
|
|
1357
|
+
command: options.command || ['supen', 'knowledge', 'evolve'],
|
|
1358
|
+
title: options.title || 'Knowledge evolution round',
|
|
1359
|
+
subjectLabel: options.subjectLabel || 'Package',
|
|
1360
|
+
target,
|
|
1361
|
+
};
|
|
1362
|
+
const runtimeEnv = await resolveKnowledgeRuntimeEnv(options.runTests);
|
|
1363
|
+
const testTimeoutMs = options.testTimeoutMs ? Number.parseInt(options.testTimeoutMs, 10) : undefined;
|
|
1364
|
+
const targetPluginName = pluginNameFromRoot(target?.pluginRoot);
|
|
1365
|
+
const packageValidators = discoverValidators(packageRoot, targetPluginName);
|
|
1366
|
+
const builtInRun = runBuiltInAssessment(packageRoot, context);
|
|
1367
|
+
const packageRuns = packageValidators.map((validatorPath) => runValidator(packageRoot, validatorPath, runtimeEnv));
|
|
1368
|
+
const testSuitePaths = options.runTests ? discoverTestSuites(packageRoot, targetPluginName) : [];
|
|
1369
|
+
const validatorCoverageRun = {
|
|
1370
|
+
name: 'supen:validator-coverage-assessment',
|
|
1371
|
+
command: [...context.command, relativeToPackage(process.cwd(), packageRoot)],
|
|
1372
|
+
cwd: process.cwd(),
|
|
1373
|
+
exitCode: 0,
|
|
1374
|
+
stdout: '',
|
|
1375
|
+
stderr: '',
|
|
1376
|
+
parsed: {
|
|
1377
|
+
summary: {},
|
|
1378
|
+
findings: collectValidatorCoverageFindings(packageRoot, builtInRun, packageRuns),
|
|
1379
|
+
},
|
|
1380
|
+
};
|
|
1381
|
+
validatorCoverageRun.exitCode = (validatorCoverageRun.parsed?.findings || []).some((item) => item.severity === 'error') ? 1 : 0;
|
|
1382
|
+
validatorCoverageRun.stdout = `${JSON.stringify(validatorCoverageRun.parsed, null, 2)}\n`;
|
|
1383
|
+
const runs = [
|
|
1384
|
+
builtInRun,
|
|
1385
|
+
...packageRuns,
|
|
1386
|
+
validatorCoverageRun,
|
|
1387
|
+
...(options.runTests
|
|
1388
|
+
? (testSuitePaths.length > 0
|
|
1389
|
+
? testSuitePaths.map((testSuitePath) => runTestSuite(packageRoot, testSuitePath, runtimeEnv, testTimeoutMs))
|
|
1390
|
+
: [missingTestSuiteRun(packageRoot, context)])
|
|
1391
|
+
: []),
|
|
1392
|
+
];
|
|
1393
|
+
const findings = uniqueFindings(runs.flatMap((run) => run.parsed?.findings || []));
|
|
1394
|
+
const status = runs.every((run) => run.exitCode === 0) && findings.every((finding) => finding.severity !== 'error')
|
|
1395
|
+
? 'pass'
|
|
1396
|
+
: 'fail';
|
|
1397
|
+
let round = {
|
|
1398
|
+
packageRoot,
|
|
1399
|
+
createdAt: new Date().toISOString(),
|
|
1400
|
+
status,
|
|
1401
|
+
validators: runs,
|
|
1402
|
+
findings,
|
|
1403
|
+
};
|
|
1404
|
+
if (options.runTests && options.publishTaskArtifacts !== false) {
|
|
1405
|
+
round = publishTaskArtifacts(round);
|
|
1406
|
+
}
|
|
1407
|
+
round = {
|
|
1408
|
+
...round,
|
|
1409
|
+
summary: buildRoundSummary(runs, findings, round.taskArtifacts),
|
|
1410
|
+
};
|
|
1411
|
+
if (round.taskArtifacts) {
|
|
1412
|
+
fs.writeFileSync(path.join(round.taskArtifacts.root, 'evidence.json'), `${JSON.stringify(round, null, 2)}\n`, 'utf8');
|
|
1413
|
+
fs.writeFileSync(path.join(round.taskArtifacts.root, 'knowledge-issues.md'), renderKnowledgeIssueMarkdown(round), 'utf8');
|
|
1414
|
+
fs.writeFileSync(path.join(round.taskArtifacts.root, 'repair-capsule.md'), renderRepairCapsuleMarkdown(round), 'utf8');
|
|
1415
|
+
}
|
|
1416
|
+
if (options.write !== false) {
|
|
1417
|
+
round = writeRoundEvidence(round, options.output);
|
|
1418
|
+
}
|
|
1419
|
+
if (options.json) {
|
|
1420
|
+
console.log(JSON.stringify(round, null, 2));
|
|
1421
|
+
if (status === 'fail')
|
|
1422
|
+
process.exitCode = 1;
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const counts = findingCounts(findings);
|
|
1426
|
+
const testSummary = asRecord(round.summary?.tests);
|
|
1427
|
+
const testsByStatus = asRecord(testSummary?.byStatus);
|
|
1428
|
+
console.log(`\n${context.title}\n`);
|
|
1429
|
+
console.log(` ${context.subjectLabel}: ${target?.pluginName || packageRoot}`);
|
|
1430
|
+
if (target?.pluginRoot) {
|
|
1431
|
+
console.log(` Plugin dir: ${target.pluginRoot}`);
|
|
1432
|
+
console.log(` Root: ${packageRoot}`);
|
|
1433
|
+
}
|
|
1434
|
+
console.log(` Status: ${status}`);
|
|
1435
|
+
console.log(` Validators: ${runs.length}`);
|
|
1436
|
+
console.log(` Findings: ${findings.length} (${Object.entries(counts).map(([severity, count]) => `${severity}: ${count}`).join(', ') || 'none'})`);
|
|
1437
|
+
if (testSummary && Object.keys(testsByStatus || {}).length > 0) {
|
|
1438
|
+
console.log(` Tests: ${Object.entries(testsByStatus || {}).map(([testStatus, count]) => `${count} ${testStatus}`).join(', ')}`);
|
|
1439
|
+
}
|
|
1440
|
+
if (round.outputDir) {
|
|
1441
|
+
console.log(` Evidence: ${round.outputDir}`);
|
|
1442
|
+
}
|
|
1443
|
+
if (round.taskArtifacts) {
|
|
1444
|
+
console.log(` Task files: ${round.taskArtifacts.root}`);
|
|
1445
|
+
}
|
|
1446
|
+
const primaryBlockers = Array.isArray(round.summary?.primaryBlockers) ? round.summary.primaryBlockers.filter(Boolean) : [];
|
|
1447
|
+
if (primaryBlockers.length > 0) {
|
|
1448
|
+
console.log('\nPrimary blockers\n');
|
|
1449
|
+
for (const blocker of primaryBlockers) {
|
|
1450
|
+
console.log(` - ${blocker}`);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
const blocking = findings.filter((finding) => finding.severity === 'error').slice(0, 8);
|
|
1454
|
+
if (blocking.length > 0) {
|
|
1455
|
+
console.log('\nBlocking issues\n');
|
|
1456
|
+
for (const finding of blocking) {
|
|
1457
|
+
console.log(` - [${finding.code}] ${finding.target}`);
|
|
1458
|
+
console.log(` ${conciseFindingMessage(finding)}`);
|
|
1459
|
+
console.log(` ${recommendedAction(finding)}`);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
if (round.taskArtifacts) {
|
|
1463
|
+
console.log('\nEvidence files\n');
|
|
1464
|
+
console.log(` - ${path.join(round.taskArtifacts.root, 'evidence.json')}`);
|
|
1465
|
+
console.log(` - ${path.join(round.taskArtifacts.root, 'knowledge-issues.md')}`);
|
|
1466
|
+
console.log(` - ${path.join(round.taskArtifacts.root, 'repair-capsule.md')}`);
|
|
1467
|
+
}
|
|
1468
|
+
console.log('');
|
|
1469
|
+
if (status === 'fail')
|
|
1470
|
+
process.exitCode = 1;
|
|
1471
|
+
}
|
|
1472
|
+
function handleCliError(err, json) {
|
|
1473
|
+
if (json) {
|
|
1474
|
+
console.log(JSON.stringify({ error: err?.message || String(err) }));
|
|
1475
|
+
}
|
|
1476
|
+
else {
|
|
1477
|
+
console.error(`\n Error: ${err?.message || String(err)}\n`);
|
|
1478
|
+
}
|
|
1479
|
+
process.exit(1);
|
|
1480
|
+
}
|
|
1481
|
+
async function runPluginVerify(targetInput, options, command = ['supen', 'plugin', 'verify']) {
|
|
1482
|
+
const target = resolveOptionalPluginTarget(targetInput);
|
|
1483
|
+
await runEvolution(target.packageRoot, {
|
|
1484
|
+
json: options.json,
|
|
1485
|
+
write: false,
|
|
1486
|
+
runTests: true,
|
|
1487
|
+
testTimeoutMs: options.testTimeoutMs,
|
|
1488
|
+
publishTaskArtifacts: true,
|
|
1489
|
+
target,
|
|
1490
|
+
command,
|
|
1491
|
+
title: 'Plugin verification round',
|
|
1492
|
+
subjectLabel: 'Plugin',
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
async function runKnowledgeEvolve(focusInput, options, command = ['supen', 'evolve']) {
|
|
1496
|
+
const focusTarget = focusInput && focusInput.trim() ? resolvePluginTarget(focusInput.trim()) : resolveDefaultPluginTarget();
|
|
1497
|
+
await runEvolution(focusTarget.packageRoot, {
|
|
1498
|
+
...options,
|
|
1499
|
+
target: focusInput && focusInput.trim() ? focusTarget : undefined,
|
|
1500
|
+
command,
|
|
1501
|
+
title: 'Knowledge evolution round',
|
|
1502
|
+
subjectLabel: 'Knowledge root',
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
export function registerVerifyEvolveCommands(program) {
|
|
1506
|
+
program
|
|
1507
|
+
.command('verify [plugin]')
|
|
1508
|
+
.description('Verify the current mounted plugin, or a specified plugin/path')
|
|
1509
|
+
.option('--json', 'Output the full verification evidence as JSON')
|
|
1510
|
+
.option('--test-timeout-ms <ms>', 'Timeout for each discovered plugin test suite (default: 600000 or SUPEN_KNOWLEDGE_TEST_TIMEOUT_MS)')
|
|
1511
|
+
.action(async (plugin, options) => {
|
|
1512
|
+
try {
|
|
1513
|
+
await runPluginVerify(plugin, options, ['supen', 'verify']);
|
|
1514
|
+
}
|
|
1515
|
+
catch (err) {
|
|
1516
|
+
handleCliError(err, options.json);
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
program
|
|
1520
|
+
.command('evolve [focus]')
|
|
1521
|
+
.description('Evolve the current mounted knowledge context, optionally focused on a plugin/path')
|
|
1522
|
+
.option('--json', 'Output the full evolution evidence as JSON')
|
|
1523
|
+
.option('--no-write', 'Do not write evidence files into the knowledge root')
|
|
1524
|
+
.option('--output <dir>', 'Directory for evolution evidence (default: .supen/evolution under the knowledge root)')
|
|
1525
|
+
.option('--run-tests', 'Run discovered plugin test suites and include results as evolution evidence')
|
|
1526
|
+
.option('--test-timeout-ms <ms>', 'Timeout for each discovered plugin test suite (default: 600000 or SUPEN_KNOWLEDGE_TEST_TIMEOUT_MS)')
|
|
1527
|
+
.option('--no-publish-task-artifacts', 'Do not copy generated test outputs into Files / Tasks')
|
|
1528
|
+
.action(async (plugin, options) => {
|
|
1529
|
+
try {
|
|
1530
|
+
await runKnowledgeEvolve(plugin, options, ['supen', 'evolve']);
|
|
1531
|
+
}
|
|
1532
|
+
catch (err) {
|
|
1533
|
+
handleCliError(err, options.json);
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
export function registerPluginCommand(program) {
|
|
1538
|
+
const command = program
|
|
1539
|
+
.command('plugin')
|
|
1540
|
+
.description('Inspect and verify mounted Supen plugins');
|
|
1541
|
+
command
|
|
1542
|
+
.command('list')
|
|
1543
|
+
.description('List mounted plugins Supen can verify')
|
|
1544
|
+
.option('--json', 'Output mounted plugins as JSON')
|
|
1545
|
+
.action((options) => {
|
|
1546
|
+
const plugins = mountedPluginTargets().map((target) => ({
|
|
1547
|
+
name: target.pluginName,
|
|
1548
|
+
root: target.pluginRoot,
|
|
1549
|
+
knowledgeRoot: target.packageRoot,
|
|
1550
|
+
sourceMode: target.resolvedFrom,
|
|
1551
|
+
}));
|
|
1552
|
+
if (options.json) {
|
|
1553
|
+
console.log(JSON.stringify({ plugins }, null, 2));
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
if (plugins.length === 0) {
|
|
1557
|
+
console.log('\n No mounted plugins found.\n');
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
console.log('\nMounted plugins\n');
|
|
1561
|
+
for (const plugin of plugins) {
|
|
1562
|
+
console.log(` - ${plugin.name}`);
|
|
1563
|
+
console.log(` Root: ${plugin.root}`);
|
|
1564
|
+
}
|
|
1565
|
+
console.log('');
|
|
1566
|
+
});
|
|
1567
|
+
command
|
|
1568
|
+
.command('verify <plugin>')
|
|
1569
|
+
.description('Verify a mounted plugin by running validators, scenario tests, and artifact checks')
|
|
1570
|
+
.option('--json', 'Output the full verification evidence as JSON')
|
|
1571
|
+
.option('--test-timeout-ms <ms>', 'Timeout for each discovered plugin test suite (default: 600000 or SUPEN_KNOWLEDGE_TEST_TIMEOUT_MS)')
|
|
1572
|
+
.action(async (plugin, options) => {
|
|
1573
|
+
try {
|
|
1574
|
+
await runPluginVerify(plugin, options);
|
|
1575
|
+
}
|
|
1576
|
+
catch (err) {
|
|
1577
|
+
handleCliError(err, options.json);
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
//# sourceMappingURL=knowledge.js.map
|