supipowers 1.5.3 → 2.0.0
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 +14 -8
- package/bin/install.mjs +20 -5
- package/bin/install.ts +95 -0
- package/package.json +8 -4
- package/skills/context-mode/SKILL.md +17 -10
- package/skills/harness/SKILL.md +94 -0
- package/skills/ui-design/SKILL.md +63 -0
- package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
- package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
- package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
- package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
- package/skills/ultraplan-discover/SKILL.md +96 -0
- package/skills/ultraplan-intake/SKILL.md +89 -0
- package/skills/ultraplan-research/SKILL.md +129 -0
- package/skills/ultraplan-review/SKILL.md +86 -0
- package/skills/ultraplan-review-scope/SKILL.md +111 -0
- package/skills/ultraplan-review-structure/SKILL.md +120 -0
- package/skills/ultraplan-review-tdd/SKILL.md +142 -0
- package/skills/ultraplan-scout/SKILL.md +110 -0
- package/skills/ultraplan-synthesize/SKILL.md +124 -0
- package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
- package/src/ai/schema-text.ts +129 -0
- package/src/ai/structured-output.ts +274 -0
- package/src/ai/template.ts +27 -0
- package/src/bootstrap.ts +63 -28
- package/src/commands/agents.ts +131 -42
- package/src/commands/ai-review.ts +251 -30
- package/src/commands/clear.ts +434 -0
- package/src/commands/commit.ts +1 -0
- package/src/commands/config.ts +242 -44
- package/src/commands/context.ts +55 -28
- package/src/commands/doctor.ts +234 -6
- package/src/commands/fix-pr.ts +306 -131
- package/src/commands/generate.ts +111 -21
- package/src/commands/memory.ts +192 -0
- package/src/commands/model-picker.ts +28 -21
- package/src/commands/model.ts +18 -8
- package/src/commands/optimize-context.ts +408 -29
- package/src/commands/plan.ts +2 -0
- package/src/commands/qa.ts +312 -137
- package/src/commands/release.ts +259 -76
- package/src/commands/review.ts +293 -59
- package/src/commands/status.ts +200 -13
- package/src/commands/supi.ts +3 -35
- package/src/commands/ui-design.ts +394 -0
- package/src/commands/ultraplan.ts +1518 -0
- package/src/commands/update.ts +86 -0
- package/src/config/defaults.ts +62 -0
- package/src/config/loader.ts +448 -60
- package/src/config/schema.ts +108 -2
- package/src/context/optimizer.ts +25 -33
- package/src/context/rule-renderer.ts +223 -0
- package/src/context/savings.ts +258 -0
- package/src/context/startup-check.ts +380 -0
- package/src/context/startup-optimizer.ts +355 -0
- package/src/context/tokenignore.ts +146 -0
- package/src/context-mode/cache-handle.ts +49 -0
- package/src/context-mode/cache-preview.ts +71 -0
- package/src/context-mode/cache-store.ts +738 -0
- package/src/context-mode/compressor.ts +131 -26
- package/src/context-mode/dedup.ts +108 -0
- package/src/context-mode/detector.ts +35 -4
- package/src/context-mode/event-extractor.ts +14 -12
- package/src/context-mode/event-store.ts +91 -36
- package/src/context-mode/hooks.ts +798 -56
- package/src/context-mode/knowledge/store.ts +255 -11
- package/src/context-mode/memory-store.ts +325 -0
- package/src/context-mode/metrics-recorder.ts +158 -0
- package/src/context-mode/metrics-store.ts +765 -0
- package/src/context-mode/model.ts +24 -0
- package/src/context-mode/processor-keys.ts +29 -0
- package/src/context-mode/processors/build.ts +66 -0
- package/src/context-mode/processors/docker.ts +57 -0
- package/src/context-mode/processors/git.ts +111 -0
- package/src/context-mode/processors/json.ts +112 -0
- package/src/context-mode/processors/k8s.ts +67 -0
- package/src/context-mode/processors/lint.ts +67 -0
- package/src/context-mode/processors/log.ts +86 -0
- package/src/context-mode/processors/registry.ts +116 -0
- package/src/context-mode/processors/test-runner.ts +102 -0
- package/src/context-mode/processors/types.ts +20 -0
- package/src/context-mode/repomap.ts +400 -0
- package/src/context-mode/routing.ts +97 -24
- package/src/context-mode/sandbox/runners.ts +5 -1
- package/src/context-mode/snapshot-builder.ts +106 -11
- package/src/context-mode/source-hash.ts +173 -0
- package/src/context-mode/tool-name.ts +11 -0
- package/src/context-mode/tools.ts +654 -22
- package/src/context-mode/web/fetcher.ts +31 -12
- package/src/debug/logger.ts +2 -1
- package/src/deps/registry.ts +1 -1
- package/src/discipline/failure-summarizer.ts +170 -0
- package/src/discipline/failure-taxonomy.ts +131 -0
- package/src/discipline/workflow-invariants.ts +125 -0
- package/src/discovery/index.ts +31 -0
- package/src/discovery/lsp.ts +87 -0
- package/src/discovery/rank.ts +144 -0
- package/src/discovery/sources.ts +89 -0
- package/src/discovery/workflow.ts +87 -0
- package/src/docs/contracts.ts +39 -0
- package/src/docs/drift.ts +117 -87
- package/src/fix-pr/assessment.ts +200 -0
- package/src/fix-pr/contracts.ts +47 -0
- package/src/fix-pr/fetch-comments.ts +80 -0
- package/src/fix-pr/prompt-builder.ts +58 -40
- package/src/fix-pr/scripts/exec.ts +34 -0
- package/src/fix-pr/scripts/trigger-review.ts +106 -0
- package/src/fix-pr/scripts/wait-and-check.ts +108 -0
- package/src/fix-pr/types.ts +4 -0
- package/src/git/branch-finish.ts +5 -0
- package/src/git/commit-contract.ts +83 -0
- package/src/git/commit.ts +121 -184
- package/src/git/status.ts +62 -8
- package/src/harness/anti_slop/architecture-parser.ts +210 -0
- package/src/harness/anti_slop/backend-factory.ts +30 -0
- package/src/harness/anti_slop/backend.ts +140 -0
- package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
- package/src/harness/anti_slop/fallow-adapter.ts +305 -0
- package/src/harness/anti_slop/installer.ts +227 -0
- package/src/harness/anti_slop/queue.ts +216 -0
- package/src/harness/anti_slop/recommend.ts +84 -0
- package/src/harness/anti_slop/score.ts +180 -0
- package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
- package/src/harness/artifacts/agents-md.ts +88 -0
- package/src/harness/artifacts/checks-wiring.ts +57 -0
- package/src/harness/artifacts/docs-tree.ts +79 -0
- package/src/harness/artifacts/lint-configs.ts +136 -0
- package/src/harness/artifacts/review-agents.ts +67 -0
- package/src/harness/bare-entry.ts +108 -0
- package/src/harness/command.ts +1010 -0
- package/src/harness/default-agents/design.md +23 -0
- package/src/harness/default-agents/discover.md +18 -0
- package/src/harness/default-agents/implement.md +24 -0
- package/src/harness/default-agents/plan.md +19 -0
- package/src/harness/default-agents/research.md +21 -0
- package/src/harness/default-agents/validate.md +22 -0
- package/src/harness/gc/reporter.ts +28 -0
- package/src/harness/gc/runner.ts +136 -0
- package/src/harness/hooks/layer-context-inject.ts +155 -0
- package/src/harness/hooks/post-session-sweep.ts +130 -0
- package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
- package/src/harness/hooks/register.ts +118 -0
- package/src/harness/model.ts +117 -0
- package/src/harness/pipeline.ts +348 -0
- package/src/harness/project-paths.ts +235 -0
- package/src/harness/stage-runner.ts +107 -0
- package/src/harness/stages/design.ts +386 -0
- package/src/harness/stages/discover.ts +454 -0
- package/src/harness/stages/implement.ts +162 -0
- package/src/harness/stages/plan.ts +335 -0
- package/src/harness/stages/research.ts +263 -0
- package/src/harness/stages/validate.ts +684 -0
- package/src/harness/storage.ts +467 -0
- package/src/harness/tools.ts +426 -0
- package/src/lsp/bridge.ts +56 -95
- package/src/lsp/capabilities.ts +108 -0
- package/src/lsp/contracts.ts +35 -0
- package/src/lsp/detector.ts +8 -12
- package/src/markdown-frontmatter.ts +68 -0
- package/src/mempalace/bridge.ts +129 -0
- package/src/mempalace/config.ts +75 -0
- package/src/mempalace/format.ts +163 -0
- package/src/mempalace/hooks.ts +370 -0
- package/src/mempalace/installer-helper.ts +194 -0
- package/src/mempalace/python/mempalace_bridge.py +440 -0
- package/src/mempalace/runtime.ts +565 -0
- package/src/mempalace/schema.ts +264 -0
- package/src/mempalace/session-summary.ts +198 -0
- package/src/mempalace/tool.ts +186 -0
- package/src/mempalace/uv.ts +256 -0
- package/src/migrate/runner.ts +354 -0
- package/src/planning/approval-flow.ts +206 -9
- package/src/planning/plan-writer-prompt.ts +4 -3
- package/src/planning/planning-ask-tool.ts +39 -0
- package/src/planning/render-markdown.ts +74 -0
- package/src/planning/spec.ts +42 -0
- package/src/planning/system-prompt.ts +11 -8
- package/src/planning/validate.ts +84 -0
- package/src/platform/omp.ts +15 -2
- package/src/platform/system-prompt.ts +37 -0
- package/src/platform/test-utils.ts +3 -0
- package/src/platform/types.ts +6 -1
- package/src/qa/config.ts +12 -6
- package/src/qa/detect-app-type.ts +13 -6
- package/src/qa/matrix.ts +12 -6
- package/src/qa/prompt-builder.ts +28 -30
- package/src/qa/scripts/dev-server-utils.ts +72 -0
- package/src/qa/scripts/run-e2e-tests.ts +226 -0
- package/src/qa/scripts/start-dev-server.ts +138 -0
- package/src/qa/scripts/stop-dev-server.ts +77 -0
- package/src/qa/session.ts +13 -7
- package/src/quality/ai-setup.ts +27 -25
- package/src/quality/contracts.ts +34 -0
- package/src/quality/gates/ai-review.ts +20 -58
- package/src/quality/gates/command.ts +249 -46
- package/src/quality/review-gates.ts +18 -2
- package/src/quality/runner.ts +63 -22
- package/src/quality/schemas.ts +37 -2
- package/src/quality/setup.ts +96 -16
- package/src/release/changelog.ts +1 -1
- package/src/release/channels/custom.ts +13 -3
- package/src/release/channels/types.ts +5 -0
- package/src/release/contracts.ts +90 -0
- package/src/release/executor.ts +122 -45
- package/src/release/prompt.ts +18 -2
- package/src/release/targets.ts +86 -0
- package/src/release/version.ts +96 -71
- package/src/review/agent-loader.ts +221 -109
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +114 -13
- package/src/review/output.ts +12 -139
- package/src/review/runner.ts +12 -6
- package/src/review/scope.ts +144 -24
- package/src/review/types.ts +1 -20
- package/src/review/validator.ts +12 -6
- package/src/storage/fix-pr-sessions.ts +21 -14
- package/src/storage/plans.ts +14 -5
- package/src/storage/qa-sessions.ts +25 -19
- package/src/storage/reliability-metrics.ts +180 -0
- package/src/storage/reports.ts +8 -7
- package/src/storage/review-sessions.ts +55 -20
- package/src/tool-catalog/active-tool-controller.ts +164 -0
- package/src/tool-catalog/active-tool-planner.ts +212 -0
- package/src/tool-catalog/tool-groups.ts +102 -0
- package/src/types.ts +1399 -5
- package/src/ui-design/backend-adapter.ts +78 -0
- package/src/ui-design/backends/local-html.ts +82 -0
- package/src/ui-design/backends/pencil-mcp.ts +111 -0
- package/src/ui-design/components-scanner.ts +124 -0
- package/src/ui-design/config.ts +55 -0
- package/src/ui-design/pen-scanner.ts +95 -0
- package/src/ui-design/pen-selector.ts +72 -0
- package/src/ui-design/prompt-builder.ts +73 -0
- package/src/ui-design/scanner.ts +136 -0
- package/src/ui-design/session.ts +974 -0
- package/src/ui-design/system-prompt.ts +312 -0
- package/src/ui-design/tokens-scanner.ts +181 -0
- package/src/ui-design/types.ts +96 -0
- package/src/ultraplan/agent-catalog.ts +522 -0
- package/src/ultraplan/authoring/agent-catalog.ts +310 -0
- package/src/ultraplan/authoring/authoring-tools.ts +552 -0
- package/src/ultraplan/authoring/command-handlers.ts +339 -0
- package/src/ultraplan/authoring/markdown.ts +510 -0
- package/src/ultraplan/authoring/model.ts +162 -0
- package/src/ultraplan/authoring/pipeline.ts +319 -0
- package/src/ultraplan/authoring/stage-runner.ts +141 -0
- package/src/ultraplan/authoring/stages/approve.ts +249 -0
- package/src/ultraplan/authoring/stages/discover.ts +289 -0
- package/src/ultraplan/authoring/stages/intake.ts +203 -0
- package/src/ultraplan/authoring/stages/research.ts +399 -0
- package/src/ultraplan/authoring/stages/review.ts +333 -0
- package/src/ultraplan/authoring/stages/scout.ts +188 -0
- package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
- package/src/ultraplan/authoring/storage.ts +594 -0
- package/src/ultraplan/authoring/synth-gate.ts +165 -0
- package/src/ultraplan/authoring-draft.ts +653 -0
- package/src/ultraplan/authoring-persist.ts +180 -0
- package/src/ultraplan/authoring-tool.ts +608 -0
- package/src/ultraplan/authoring-wizard.ts +587 -0
- package/src/ultraplan/batch/merge.ts +98 -0
- package/src/ultraplan/batch/planner.ts +150 -0
- package/src/ultraplan/batch/presenter.ts +97 -0
- package/src/ultraplan/batch/storage.ts +420 -0
- package/src/ultraplan/batch/supervisor.ts +317 -0
- package/src/ultraplan/batch/worker.ts +26 -0
- package/src/ultraplan/batch/worktree.ts +110 -0
- package/src/ultraplan/contracts.ts +1593 -0
- package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
- package/src/ultraplan/default-agents/authoring/intake.md +12 -0
- package/src/ultraplan/default-agents/authoring/planner.md +12 -0
- package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
- package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/scout.md +12 -0
- package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
- package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-executor.md +10 -0
- package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-tester.md +10 -0
- package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-executor.md +10 -0
- package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-tester.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
- package/src/ultraplan/execution/contract.ts +71 -0
- package/src/ultraplan/execution/policy.ts +217 -0
- package/src/ultraplan/execution/runtime-tools.ts +107 -0
- package/src/ultraplan/execution/session-runner.ts +281 -0
- package/src/ultraplan/next-router.ts +85 -0
- package/src/ultraplan/presenter.ts +359 -0
- package/src/ultraplan/project-paths.ts +342 -0
- package/src/ultraplan/runtime/active-execution.ts +72 -0
- package/src/ultraplan/runtime/apply-mutation.ts +416 -0
- package/src/ultraplan/runtime/blockers.ts +243 -0
- package/src/ultraplan/runtime/hook-bridge.ts +486 -0
- package/src/ultraplan/runtime/launch-context.ts +207 -0
- package/src/ultraplan/runtime/migration.ts +524 -0
- package/src/ultraplan/runtime/normalize.ts +281 -0
- package/src/ultraplan/runtime/proof.ts +260 -0
- package/src/ultraplan/runtime/reducer.ts +416 -0
- package/src/ultraplan/runtime/repair.ts +251 -0
- package/src/ultraplan/runtime/tracker-storage.ts +368 -0
- package/src/ultraplan/session-selection.ts +291 -0
- package/src/ultraplan/storage.ts +374 -0
- package/src/utils/editor.ts +38 -0
- package/src/utils/executable.ts +80 -0
- package/src/utils/paths.ts +1 -20
- package/src/utils/shell.ts +31 -0
- package/src/visual/companion.ts +2 -1
- package/src/visual/scripts/frame-template.html +60 -0
- package/src/visual/scripts/index.js +59 -13
- package/src/visual/scripts/package.json +3 -0
- package/src/visual/start-server.ts +2 -1
- package/src/workspace/git-scope.ts +64 -0
- package/src/workspace/locks.ts +23 -0
- package/src/workspace/package-manager.ts +117 -0
- package/src/workspace/path-mapping.ts +75 -0
- package/src/workspace/project-slug.ts +92 -0
- package/src/workspace/repo-root.ts +137 -0
- package/src/workspace/selector.ts +115 -0
- package/src/workspace/state-paths.ts +118 -0
- package/src/workspace/targets.ts +313 -0
- package/src/fix-pr/scripts/diff-comments.sh +0 -33
- package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
- package/src/fix-pr/scripts/trigger-review.sh +0 -36
- package/src/fix-pr/scripts/wait-and-check.sh +0 -37
- package/src/qa/scripts/detect-app-type.sh +0 -68
- package/src/qa/scripts/discover-routes.sh +0 -143
- package/src/qa/scripts/run-e2e-tests.sh +0 -131
- package/src/qa/scripts/start-dev-server.sh +0 -46
- package/src/qa/scripts/stop-dev-server.sh +0 -36
- package/src/review/prompts/fix-output-schema.md +0 -18
- package/src/review/prompts/review-output-schema.md +0 -38
- package/src/review/template.ts +0 -15
- /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
package/src/storage/plans.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { normalizeLineEndings } from "../text.js";
|
|
4
|
-
import type { Plan, PlanTask, TaskComplexity } from "../types.js";
|
|
4
|
+
import type { Plan, PlanTask, TaskComplexity, WorkspaceTarget } from "../types.js";
|
|
5
5
|
import type { PlatformPaths } from "../platform/types.js";
|
|
6
|
+
import { getProjectStatePath, getProjectTargetStatePath } from "../workspace/state-paths.js";
|
|
6
7
|
|
|
7
8
|
function getPlansDir(paths: PlatformPaths, cwd: string): string {
|
|
8
|
-
return paths
|
|
9
|
+
return getProjectStatePath(paths, cwd, "plans");
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
export function listPlans(paths: PlatformPaths, cwd: string): string[] {
|
|
13
|
-
const dir = getPlansDir(paths, cwd);
|
|
12
|
+
function listPlanFiles(dir: string): string[] {
|
|
14
13
|
if (!fs.existsSync(dir)) return [];
|
|
15
14
|
return fs
|
|
16
15
|
.readdirSync(dir)
|
|
@@ -19,6 +18,16 @@ export function listPlans(paths: PlatformPaths, cwd: string): string[] {
|
|
|
19
18
|
.reverse();
|
|
20
19
|
}
|
|
21
20
|
|
|
21
|
+
/** List all saved plans */
|
|
22
|
+
export function listPlans(paths: PlatformPaths, cwd: string): string[] {
|
|
23
|
+
return listPlanFiles(getPlansDir(paths, cwd));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** List all saved plans for a specific workspace target. */
|
|
27
|
+
export function listTargetPlans(paths: PlatformPaths, target: WorkspaceTarget): string[] {
|
|
28
|
+
return listPlanFiles(getProjectTargetStatePath(paths, target, "plans"));
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
/** Read a plan file by name */
|
|
23
32
|
export function readPlanFile(paths: PlatformPaths, cwd: string, name: string): string | null {
|
|
24
33
|
const filePath = path.join(getPlansDir(paths, cwd), name);
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { E2eSessionLedger } from "../qa/types.js";
|
|
4
3
|
import type { PlatformPaths } from "../platform/types.js";
|
|
4
|
+
import type { WorkspaceTarget } from "../types.js";
|
|
5
|
+
import { getProjectStatePath, getProjectTargetStatePath } from "../workspace/state-paths.js";
|
|
6
|
+
import type { E2eSessionLedger } from "../qa/types.js";
|
|
7
|
+
|
|
8
|
+
function getSessionsDir(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): string {
|
|
9
|
+
if (target) {
|
|
10
|
+
return getProjectTargetStatePath(paths, target, "qa-sessions");
|
|
11
|
+
}
|
|
5
12
|
|
|
6
|
-
|
|
7
|
-
return paths.project(cwd, "qa-sessions");
|
|
13
|
+
return getProjectStatePath(paths, cwd, "qa-sessions");
|
|
8
14
|
}
|
|
9
15
|
|
|
10
|
-
export function getSessionDir(paths: PlatformPaths, cwd: string, sessionId: string): string {
|
|
11
|
-
return path.join(getSessionsDir(paths, cwd), sessionId);
|
|
16
|
+
export function getSessionDir(paths: PlatformPaths, cwd: string, sessionId: string, target?: WorkspaceTarget): string {
|
|
17
|
+
return path.join(getSessionsDir(paths, cwd, target), sessionId);
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
/** Generate a unique QA session ID */
|
|
@@ -21,8 +27,8 @@ export function generateSessionId(): string {
|
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
/** Create a new QA session */
|
|
24
|
-
export function createSession(paths: PlatformPaths, cwd: string, ledger: E2eSessionLedger): void {
|
|
25
|
-
const sessionDir = getSessionDir(paths, cwd, ledger.id);
|
|
30
|
+
export function createSession(paths: PlatformPaths, cwd: string, ledger: E2eSessionLedger, target?: WorkspaceTarget): void {
|
|
31
|
+
const sessionDir = getSessionDir(paths, cwd, ledger.id, target);
|
|
26
32
|
fs.mkdirSync(sessionDir, { recursive: true });
|
|
27
33
|
fs.writeFileSync(
|
|
28
34
|
path.join(sessionDir, "ledger.json"),
|
|
@@ -31,8 +37,8 @@ export function createSession(paths: PlatformPaths, cwd: string, ledger: E2eSess
|
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
/** Load a QA session ledger */
|
|
34
|
-
export function loadSession(paths: PlatformPaths, cwd: string, sessionId: string): E2eSessionLedger | null {
|
|
35
|
-
const filePath = path.join(getSessionDir(paths, cwd, sessionId), "ledger.json");
|
|
40
|
+
export function loadSession(paths: PlatformPaths, cwd: string, sessionId: string, target?: WorkspaceTarget): E2eSessionLedger | null {
|
|
41
|
+
const filePath = path.join(getSessionDir(paths, cwd, sessionId, target), "ledger.json");
|
|
36
42
|
if (!fs.existsSync(filePath)) return null;
|
|
37
43
|
try {
|
|
38
44
|
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
@@ -42,14 +48,14 @@ export function loadSession(paths: PlatformPaths, cwd: string, sessionId: string
|
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
/** Update a QA session ledger */
|
|
45
|
-
export function updateSession(paths: PlatformPaths, cwd: string, ledger: E2eSessionLedger): void {
|
|
46
|
-
const filePath = path.join(getSessionDir(paths, cwd, ledger.id), "ledger.json");
|
|
51
|
+
export function updateSession(paths: PlatformPaths, cwd: string, ledger: E2eSessionLedger, target?: WorkspaceTarget): void {
|
|
52
|
+
const filePath = path.join(getSessionDir(paths, cwd, ledger.id, target), "ledger.json");
|
|
47
53
|
fs.writeFileSync(filePath, JSON.stringify(ledger, null, 2) + "\n");
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
/** List all QA sessions, newest first */
|
|
51
|
-
export function listSessions(paths: PlatformPaths, cwd: string): string[] {
|
|
52
|
-
const dir = getSessionsDir(paths, cwd);
|
|
57
|
+
export function listSessions(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): string[] {
|
|
58
|
+
const dir = getSessionsDir(paths, cwd, target);
|
|
53
59
|
if (!fs.existsSync(dir)) return [];
|
|
54
60
|
return fs
|
|
55
61
|
.readdirSync(dir)
|
|
@@ -59,9 +65,9 @@ export function listSessions(paths: PlatformPaths, cwd: string): string[] {
|
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
/** Find the latest session with incomplete phases */
|
|
62
|
-
export function findActiveSession(paths: PlatformPaths, cwd: string): E2eSessionLedger | null {
|
|
63
|
-
for (const sessionId of listSessions(paths, cwd)) {
|
|
64
|
-
const ledger = loadSession(paths, cwd, sessionId);
|
|
68
|
+
export function findActiveSession(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): E2eSessionLedger | null {
|
|
69
|
+
for (const sessionId of listSessions(paths, cwd, target)) {
|
|
70
|
+
const ledger = loadSession(paths, cwd, sessionId, target);
|
|
65
71
|
if (!ledger) continue;
|
|
66
72
|
const allCompleted = Object.values(ledger.phases).every(
|
|
67
73
|
(p) => p.status === "completed",
|
|
@@ -72,9 +78,9 @@ export function findActiveSession(paths: PlatformPaths, cwd: string): E2eSession
|
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
/** Find the latest session with failed test results */
|
|
75
|
-
export function findSessionWithFailures(paths: PlatformPaths, cwd: string): E2eSessionLedger | null {
|
|
76
|
-
for (const sessionId of listSessions(paths, cwd)) {
|
|
77
|
-
const ledger = loadSession(paths, cwd, sessionId);
|
|
81
|
+
export function findSessionWithFailures(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): E2eSessionLedger | null {
|
|
82
|
+
for (const sessionId of listSessions(paths, cwd, target)) {
|
|
83
|
+
const ledger = loadSession(paths, cwd, sessionId, target);
|
|
78
84
|
if (!ledger) continue;
|
|
79
85
|
if (ledger.results.some((r) => r.status === "fail")) return ledger;
|
|
80
86
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// src/storage/reliability-metrics.ts
|
|
2
|
+
//
|
|
3
|
+
// Local-first reliability metrics. Each AI-heavy command appends one
|
|
4
|
+
// ReliabilityRecord per attempt to .omp/supipowers/reliability/events.jsonl.
|
|
5
|
+
// /supi:status and /supi:doctor read these records to surface concrete
|
|
6
|
+
// numbers (parse-success rate, blocked rate, retries-per-run, fallback
|
|
7
|
+
// counts) instead of vibes.
|
|
8
|
+
//
|
|
9
|
+
// Storage format: append-only JSONL. One record per line. Robust to partial
|
|
10
|
+
// writes — readers skip malformed lines rather than aborting.
|
|
11
|
+
//
|
|
12
|
+
// Non-goals:
|
|
13
|
+
// - Hosted telemetry (records never leave the project)
|
|
14
|
+
// - Streaming queries (everything is in-memory after read)
|
|
15
|
+
// - Cross-cwd aggregation in a single read (callers pass the cwd they want)
|
|
16
|
+
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import type { PlatformPaths } from "../platform/types.js";
|
|
20
|
+
import { getProjectStatePath } from "../workspace/state-paths.js";
|
|
21
|
+
import type { ReliabilityOutcome, ReliabilityRecord, ReliabilitySummary } from "../types.js";
|
|
22
|
+
|
|
23
|
+
const EVENTS_FILE = "events.jsonl";
|
|
24
|
+
const RELIABILITY_DIR = "reliability";
|
|
25
|
+
|
|
26
|
+
function getReliabilityDir(paths: PlatformPaths, cwd: string): string {
|
|
27
|
+
return getProjectStatePath(paths, cwd, RELIABILITY_DIR);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getEventsPath(paths: PlatformPaths, cwd: string): string {
|
|
31
|
+
return path.join(getReliabilityDir(paths, cwd), EVENTS_FILE);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Append a single reliability record. Best-effort: failures are swallowed
|
|
36
|
+
* because metrics must never crash the workflow they observe.
|
|
37
|
+
*/
|
|
38
|
+
export function appendReliabilityRecord(
|
|
39
|
+
paths: PlatformPaths,
|
|
40
|
+
cwd: string,
|
|
41
|
+
record: ReliabilityRecord,
|
|
42
|
+
): void {
|
|
43
|
+
try {
|
|
44
|
+
fs.mkdirSync(getReliabilityDir(paths, cwd), { recursive: true });
|
|
45
|
+
fs.appendFileSync(getEventsPath(paths, cwd), JSON.stringify(record) + "\n");
|
|
46
|
+
} catch {
|
|
47
|
+
// Swallow — metrics observability must not break the workflow.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Read all reliability records for the given cwd. Malformed lines are
|
|
53
|
+
* skipped silently (best-effort recovery). Returns an empty array when the
|
|
54
|
+
* file does not exist yet.
|
|
55
|
+
*/
|
|
56
|
+
export function readReliabilityRecords(paths: PlatformPaths, cwd: string): ReliabilityRecord[] {
|
|
57
|
+
const file = getEventsPath(paths, cwd);
|
|
58
|
+
if (!fs.existsSync(file)) return [];
|
|
59
|
+
const records: ReliabilityRecord[] = [];
|
|
60
|
+
let raw: string;
|
|
61
|
+
try {
|
|
62
|
+
raw = fs.readFileSync(file, "utf-8");
|
|
63
|
+
} catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
for (const line of raw.split("\n")) {
|
|
67
|
+
if (!line.trim()) continue;
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(line) as ReliabilityRecord;
|
|
70
|
+
// Minimal shape check — drop records missing the required fields.
|
|
71
|
+
if (
|
|
72
|
+
typeof parsed?.ts === "string" &&
|
|
73
|
+
typeof parsed.command === "string" &&
|
|
74
|
+
typeof parsed.outcome === "string" &&
|
|
75
|
+
typeof parsed.attempts === "number"
|
|
76
|
+
) {
|
|
77
|
+
records.push(parsed);
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Skip malformed line.
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return records;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const ZERO_OUTCOME_COUNTS: Record<ReliabilityOutcome, number> = {
|
|
87
|
+
ok: 0,
|
|
88
|
+
blocked: 0,
|
|
89
|
+
"retry-exhausted": 0,
|
|
90
|
+
fallback: 0,
|
|
91
|
+
"agent-error": 0,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Aggregate records into per-command summaries. Returns one summary per
|
|
96
|
+
* distinct `command` that appears in `records`, sorted by command name.
|
|
97
|
+
*/
|
|
98
|
+
export function summarizeReliabilityRecords(records: ReliabilityRecord[]): ReliabilitySummary[] {
|
|
99
|
+
const buckets = new Map<string, ReliabilityRecord[]>();
|
|
100
|
+
for (const record of records) {
|
|
101
|
+
const list = buckets.get(record.command) ?? [];
|
|
102
|
+
list.push(record);
|
|
103
|
+
buckets.set(record.command, list);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const summaries: ReliabilitySummary[] = [];
|
|
107
|
+
for (const [command, list] of buckets) {
|
|
108
|
+
const byOutcome = { ...ZERO_OUTCOME_COUNTS };
|
|
109
|
+
let attemptsTotal = 0;
|
|
110
|
+
let lastRecordedAt: string | null = null;
|
|
111
|
+
for (const r of list) {
|
|
112
|
+
byOutcome[r.outcome] = (byOutcome[r.outcome] ?? 0) + 1;
|
|
113
|
+
attemptsTotal += r.attempts;
|
|
114
|
+
if (!lastRecordedAt || r.ts > lastRecordedAt) lastRecordedAt = r.ts;
|
|
115
|
+
}
|
|
116
|
+
summaries.push({
|
|
117
|
+
command,
|
|
118
|
+
total: list.length,
|
|
119
|
+
byOutcome,
|
|
120
|
+
avgAttempts: list.length > 0 ? attemptsTotal / list.length : 0,
|
|
121
|
+
lastRecordedAt,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
summaries.sort((a, b) => a.command.localeCompare(b.command));
|
|
126
|
+
return summaries;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Convenience: load + summarize in one call.
|
|
131
|
+
*/
|
|
132
|
+
export function loadReliabilitySummaries(
|
|
133
|
+
paths: PlatformPaths,
|
|
134
|
+
cwd: string,
|
|
135
|
+
): ReliabilitySummary[] {
|
|
136
|
+
return summarizeReliabilityRecords(readReliabilityRecords(paths, cwd));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const RELIABILITY_OUTCOMES: ReliabilityOutcome[] = [
|
|
140
|
+
"ok",
|
|
141
|
+
"blocked",
|
|
142
|
+
"retry-exhausted",
|
|
143
|
+
"fallback",
|
|
144
|
+
"agent-error",
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Render a concise, aligned reliability section suitable for TUI output.
|
|
149
|
+
* When no records exist yet, returns a single non-alarming empty-state line.
|
|
150
|
+
*/
|
|
151
|
+
export function formatReliabilitySection(summaries: ReliabilitySummary[]): string[] {
|
|
152
|
+
if (summaries.length === 0) {
|
|
153
|
+
return ["Reliability: no records yet (metrics appear after AI-heavy commands run)."];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const total = summaries.reduce((n, s) => n + s.total, 0);
|
|
157
|
+
const nameWidth = Math.max(...summaries.map((s) => s.command.length));
|
|
158
|
+
const colWidths: Record<ReliabilityOutcome, number> = { ...ZERO_OUTCOME_COUNTS };
|
|
159
|
+
for (const s of summaries) {
|
|
160
|
+
for (const outcome of RELIABILITY_OUTCOMES) {
|
|
161
|
+
const w = String(s.byOutcome[outcome] ?? 0).length;
|
|
162
|
+
if (w > colWidths[outcome]) colWidths[outcome] = w;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const outcome of RELIABILITY_OUTCOMES) {
|
|
166
|
+
if (colWidths[outcome] < 1) colWidths[outcome] = 1;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const lines: string[] = [`Reliability (last ${total} record${total === 1 ? "" : "s"})`];
|
|
170
|
+
for (const s of summaries) {
|
|
171
|
+
const name = s.command.padEnd(nameWidth);
|
|
172
|
+
const counts = RELIABILITY_OUTCOMES
|
|
173
|
+
.map((o) => `${o} ${String(s.byOutcome[o] ?? 0).padStart(colWidths[o])}`)
|
|
174
|
+
.join(" ");
|
|
175
|
+
const avg = s.avgAttempts.toFixed(1);
|
|
176
|
+
const last = s.lastRecordedAt ? s.lastRecordedAt.slice(0, 10) : "\u2014";
|
|
177
|
+
lines.push(`${name} ${counts} avg-attempts ${avg} last ${last}`);
|
|
178
|
+
}
|
|
179
|
+
return lines;
|
|
180
|
+
}
|
package/src/storage/reports.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { GateResult, GateStatus, ReviewReport } from "../types.js";
|
|
4
3
|
import type { PlatformPaths } from "../platform/types.js";
|
|
4
|
+
import type { GateResult, GateStatus, ReviewReport, WorkspaceTarget } from "../types.js";
|
|
5
|
+
import { getProjectTargetStatePath } from "../workspace/state-paths.js";
|
|
5
6
|
|
|
6
|
-
function getReportsDir(paths: PlatformPaths,
|
|
7
|
-
return paths
|
|
7
|
+
function getReportsDir(paths: PlatformPaths, target: WorkspaceTarget): string {
|
|
8
|
+
return getProjectTargetStatePath(paths, target, "reports");
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
function isGateStatus(value: unknown): value is GateStatus {
|
|
@@ -43,8 +44,8 @@ function isReviewReport(value: unknown): value is ReviewReport {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/** Save a review report */
|
|
46
|
-
export function saveReviewReport(paths: PlatformPaths,
|
|
47
|
-
const dir = getReportsDir(paths,
|
|
47
|
+
export function saveReviewReport(paths: PlatformPaths, target: WorkspaceTarget, report: ReviewReport): string {
|
|
48
|
+
const dir = getReportsDir(paths, target);
|
|
48
49
|
fs.mkdirSync(dir, { recursive: true });
|
|
49
50
|
const filename = `review-${report.timestamp.slice(0, 10)}.json`;
|
|
50
51
|
const filePath = path.join(dir, filename);
|
|
@@ -53,8 +54,8 @@ export function saveReviewReport(paths: PlatformPaths, cwd: string, report: Revi
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
/** Load the latest review report */
|
|
56
|
-
export function loadLatestReport(paths: PlatformPaths,
|
|
57
|
-
const dir = getReportsDir(paths,
|
|
57
|
+
export function loadLatestReport(paths: PlatformPaths, target: WorkspaceTarget): ReviewReport | null {
|
|
58
|
+
const dir = getReportsDir(paths, target);
|
|
58
59
|
if (!fs.existsSync(dir)) return null;
|
|
59
60
|
|
|
60
61
|
const files = fs
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { PlatformPaths } from "../platform/types.js";
|
|
4
|
-
import type { ReviewSession } from "../types.js";
|
|
5
4
|
import { isReviewSession } from "../review/types.js";
|
|
5
|
+
import type { ReviewSession, WorkspaceTarget } from "../types.js";
|
|
6
|
+
import { getProjectStatePath, getProjectTargetStatePath } from "../workspace/state-paths.js";
|
|
6
7
|
|
|
7
8
|
const SESSIONS_DIR = "reviews";
|
|
8
9
|
const ITERATIONS_DIR = "iterations";
|
|
9
10
|
const AGENTS_DIR = "agents";
|
|
10
11
|
|
|
11
|
-
function getSessionsDir(paths: PlatformPaths, cwd: string): string {
|
|
12
|
-
return
|
|
12
|
+
function getSessionsDir(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget | null): string {
|
|
13
|
+
return target
|
|
14
|
+
? getProjectTargetStatePath(paths, target, SESSIONS_DIR)
|
|
15
|
+
: getProjectStatePath(paths, cwd, SESSIONS_DIR);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
function ensureLayout(sessionDir: string): void {
|
|
@@ -17,8 +20,13 @@ function ensureLayout(sessionDir: string): void {
|
|
|
17
20
|
fs.mkdirSync(path.join(sessionDir, AGENTS_DIR), { recursive: true });
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
function getLedgerPath(
|
|
21
|
-
|
|
23
|
+
function getLedgerPath(
|
|
24
|
+
paths: PlatformPaths,
|
|
25
|
+
cwd: string,
|
|
26
|
+
sessionId: string,
|
|
27
|
+
target?: WorkspaceTarget | null,
|
|
28
|
+
): string {
|
|
29
|
+
return path.join(getReviewSessionDir(paths, cwd, sessionId, target), "session.json");
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
function resolveArtifactPath(sessionDir: string, relativePath: string): string {
|
|
@@ -35,8 +43,13 @@ function resolveArtifactPath(sessionDir: string, relativePath: string): string {
|
|
|
35
43
|
return resolved;
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
export function getReviewSessionDir(
|
|
39
|
-
|
|
46
|
+
export function getReviewSessionDir(
|
|
47
|
+
paths: PlatformPaths,
|
|
48
|
+
cwd: string,
|
|
49
|
+
sessionId: string,
|
|
50
|
+
target?: WorkspaceTarget | null,
|
|
51
|
+
): string {
|
|
52
|
+
return path.join(getSessionsDir(paths, cwd, target), sessionId);
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
export function generateReviewSessionId(now = new Date()): string {
|
|
@@ -47,14 +60,24 @@ export function generateReviewSessionId(now = new Date()): string {
|
|
|
47
60
|
return `review-${date}-${time}-${suffix}`;
|
|
48
61
|
}
|
|
49
62
|
|
|
50
|
-
export function createReviewSession(
|
|
51
|
-
|
|
63
|
+
export function createReviewSession(
|
|
64
|
+
paths: PlatformPaths,
|
|
65
|
+
cwd: string,
|
|
66
|
+
session: ReviewSession,
|
|
67
|
+
target?: WorkspaceTarget | null,
|
|
68
|
+
): void {
|
|
69
|
+
const sessionDir = getReviewSessionDir(paths, cwd, session.id, target);
|
|
52
70
|
ensureLayout(sessionDir);
|
|
53
|
-
fs.writeFileSync(getLedgerPath(paths, cwd, session.id), `${JSON.stringify(session, null, 2)}\n`);
|
|
71
|
+
fs.writeFileSync(getLedgerPath(paths, cwd, session.id, target), `${JSON.stringify(session, null, 2)}\n`);
|
|
54
72
|
}
|
|
55
73
|
|
|
56
|
-
export function loadReviewSession(
|
|
57
|
-
|
|
74
|
+
export function loadReviewSession(
|
|
75
|
+
paths: PlatformPaths,
|
|
76
|
+
cwd: string,
|
|
77
|
+
sessionId: string,
|
|
78
|
+
target?: WorkspaceTarget | null,
|
|
79
|
+
): ReviewSession | null {
|
|
80
|
+
const ledgerPath = getLedgerPath(paths, cwd, sessionId, target);
|
|
58
81
|
if (!fs.existsSync(ledgerPath)) {
|
|
59
82
|
return null;
|
|
60
83
|
}
|
|
@@ -67,15 +90,20 @@ export function loadReviewSession(paths: PlatformPaths, cwd: string, sessionId:
|
|
|
67
90
|
}
|
|
68
91
|
}
|
|
69
92
|
|
|
70
|
-
export function updateReviewSession(
|
|
93
|
+
export function updateReviewSession(
|
|
94
|
+
paths: PlatformPaths,
|
|
95
|
+
cwd: string,
|
|
96
|
+
session: ReviewSession,
|
|
97
|
+
target?: WorkspaceTarget | null,
|
|
98
|
+
): void {
|
|
71
99
|
session.updatedAt = new Date().toISOString();
|
|
72
|
-
const sessionDir = getReviewSessionDir(paths, cwd, session.id);
|
|
100
|
+
const sessionDir = getReviewSessionDir(paths, cwd, session.id, target);
|
|
73
101
|
ensureLayout(sessionDir);
|
|
74
|
-
fs.writeFileSync(getLedgerPath(paths, cwd, session.id), `${JSON.stringify(session, null, 2)}\n`);
|
|
102
|
+
fs.writeFileSync(getLedgerPath(paths, cwd, session.id, target), `${JSON.stringify(session, null, 2)}\n`);
|
|
75
103
|
}
|
|
76
104
|
|
|
77
|
-
export function listReviewSessions(paths: PlatformPaths, cwd: string): string[] {
|
|
78
|
-
const sessionsDir = getSessionsDir(paths, cwd);
|
|
105
|
+
export function listReviewSessions(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget | null): string[] {
|
|
106
|
+
const sessionsDir = getSessionsDir(paths, cwd, target);
|
|
79
107
|
if (!fs.existsSync(sessionsDir)) {
|
|
80
108
|
return [];
|
|
81
109
|
}
|
|
@@ -93,8 +121,9 @@ export function writeReviewArtifact(
|
|
|
93
121
|
sessionId: string,
|
|
94
122
|
relativePath: string,
|
|
95
123
|
payload: unknown,
|
|
124
|
+
target?: WorkspaceTarget | null,
|
|
96
125
|
): string {
|
|
97
|
-
const sessionDir = getReviewSessionDir(paths, cwd, sessionId);
|
|
126
|
+
const sessionDir = getReviewSessionDir(paths, cwd, sessionId, target);
|
|
98
127
|
ensureLayout(sessionDir);
|
|
99
128
|
const artifactPath = resolveArtifactPath(sessionDir, relativePath);
|
|
100
129
|
fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
|
|
@@ -106,8 +135,14 @@ export function writeReviewArtifact(
|
|
|
106
135
|
return artifactPath;
|
|
107
136
|
}
|
|
108
137
|
|
|
109
|
-
export function readReviewArtifact(
|
|
110
|
-
|
|
138
|
+
export function readReviewArtifact(
|
|
139
|
+
paths: PlatformPaths,
|
|
140
|
+
cwd: string,
|
|
141
|
+
sessionId: string,
|
|
142
|
+
relativePath: string,
|
|
143
|
+
target?: WorkspaceTarget | null,
|
|
144
|
+
): string | null {
|
|
145
|
+
const sessionDir = getReviewSessionDir(paths, cwd, sessionId, target);
|
|
111
146
|
const artifactPath = resolveArtifactPath(sessionDir, relativePath);
|
|
112
147
|
if (!fs.existsSync(artifactPath)) {
|
|
113
148
|
return null;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { getMetricsStore, getSessionId } from "../context-mode/hooks.js";
|
|
4
|
+
import { getProjectStateDir } from "../workspace/state-paths.js";
|
|
5
|
+
import type { Platform } from "../platform/types.js";
|
|
6
|
+
import { normalizeSystemPromptBlocks, systemPromptText } from "../platform/system-prompt.js";
|
|
7
|
+
import type { McpRegistry } from "../mcp/types.js";
|
|
8
|
+
import type { SupipowersConfig } from "../types.js";
|
|
9
|
+
import { planActiveTools } from "./active-tool-planner.js";
|
|
10
|
+
import { detectContextMode } from "../context-mode/detector.js";
|
|
11
|
+
import { getShadowedNativeTools } from "../context-mode/routing.js";
|
|
12
|
+
|
|
13
|
+
export interface ActiveToolControllerDeps {
|
|
14
|
+
loadMcpRegistryForCwd(cwd: string): McpRegistry;
|
|
15
|
+
consumePendingTags(): string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type BeforeAgentStartEventLike = {
|
|
19
|
+
prompt?: string;
|
|
20
|
+
systemPrompt?: string | string[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type BeforeAgentStartContextLike = {
|
|
24
|
+
cwd?: string;
|
|
25
|
+
getSystemPrompt?: () => unknown;
|
|
26
|
+
getContextUsage?: () => {
|
|
27
|
+
tokens?: number | null;
|
|
28
|
+
contextWindow?: number | null;
|
|
29
|
+
percent?: number | null;
|
|
30
|
+
} | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function registerActiveToolController(
|
|
34
|
+
platform: Platform,
|
|
35
|
+
config: SupipowersConfig,
|
|
36
|
+
_deps: ActiveToolControllerDeps,
|
|
37
|
+
): void {
|
|
38
|
+
if (!config.contextMode.enabled || !config.contextMode.lazyTools.enabled) return;
|
|
39
|
+
|
|
40
|
+
platform.on("before_agent_start", async (
|
|
41
|
+
event: BeforeAgentStartEventLike,
|
|
42
|
+
ctx: BeforeAgentStartContextLike = {},
|
|
43
|
+
) => {
|
|
44
|
+
if (typeof platform.getAllTools !== "function") return undefined;
|
|
45
|
+
if (typeof platform.setActiveTools !== "function") return undefined;
|
|
46
|
+
if (typeof ctx.getSystemPrompt !== "function") return undefined;
|
|
47
|
+
|
|
48
|
+
const cwd = typeof ctx.cwd === "string" && ctx.cwd.length > 0 ? ctx.cwd : process.cwd();
|
|
49
|
+
let registry: McpRegistry = { schemaVersion: 1, servers: {} };
|
|
50
|
+
try {
|
|
51
|
+
registry = _deps.loadMcpRegistryForCwd(cwd);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
(platform as any).logger?.warn?.("supi-lazy-tools: failed to load MCP registry", error);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let pendingTags: string[] = [];
|
|
57
|
+
try {
|
|
58
|
+
pendingTags = _deps.consumePendingTags();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
(platform as any).logger?.warn?.("supi-lazy-tools: failed to consume MCP tags", error);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let plan;
|
|
64
|
+
try {
|
|
65
|
+
plan = planActiveTools({
|
|
66
|
+
prompt: event.prompt ?? "",
|
|
67
|
+
currentActive: platform.getActiveTools(),
|
|
68
|
+
allTools: platform.getAllTools(),
|
|
69
|
+
lazyTools: config.contextMode.lazyTools,
|
|
70
|
+
mcpServers: registry.servers,
|
|
71
|
+
pendingTags,
|
|
72
|
+
cacheHandlesEnabled: config.contextMode.cacheHandles.enabled,
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
(platform as any).logger?.warn?.("supi-lazy-tools: active-tool planning failed", error);
|
|
76
|
+
return { systemPrompt: normalizeSystemPromptBlocks(event.systemPrompt) };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Hide native tools fully shadowed by an active ctx_* replacement.
|
|
80
|
+
// Without this, the LLM sees Search/Find/Fetch in the tool catalog and
|
|
81
|
+
// routinely tries them, only to receive routing-block errors. Filtering
|
|
82
|
+
// here removes them from the system prompt that OMP rebuilds below.
|
|
83
|
+
if (config.contextMode.enforceRouting) {
|
|
84
|
+
const status = detectContextMode(plan.activeTools);
|
|
85
|
+
const shadowed = new Set(getShadowedNativeTools(status));
|
|
86
|
+
if (shadowed.size > 0) {
|
|
87
|
+
plan.activeTools = plan.activeTools.filter((tool) => !shadowed.has(tool));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (plan.activeTools.length === 0 || arraysEqual(plan.activeTools, platform.getActiveTools())) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
await platform.setActiveTools(plan.activeTools);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
(platform as any).logger?.warn?.("supi-lazy-tools: setActiveTools failed", error);
|
|
99
|
+
return { systemPrompt: normalizeSystemPromptBlocks(event.systemPrompt) };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const rebuiltPrompt = ctx.getSystemPrompt();
|
|
103
|
+
const rebuiltPromptBlocks = normalizeSystemPromptBlocks(rebuiltPrompt);
|
|
104
|
+
recordLazyToolsMetric({
|
|
105
|
+
platform,
|
|
106
|
+
cwd,
|
|
107
|
+
beforePrompt: systemPromptText(event.systemPrompt),
|
|
108
|
+
afterPrompt: systemPromptText(rebuiltPrompt),
|
|
109
|
+
activeTools: plan.activeTools,
|
|
110
|
+
contextUsage: ctx.getContextUsage?.() ?? null,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return { systemPrompt: rebuiltPromptBlocks };
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function arraysEqual(a: readonly string[], b: readonly string[]): boolean {
|
|
118
|
+
if (a.length !== b.length) return false;
|
|
119
|
+
return a.every((value, index) => value === b[index]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
function recordLazyToolsMetric(opts: {
|
|
124
|
+
platform: Platform;
|
|
125
|
+
cwd: string;
|
|
126
|
+
beforePrompt: string;
|
|
127
|
+
afterPrompt: string;
|
|
128
|
+
activeTools: string[];
|
|
129
|
+
contextUsage: { tokens?: number | null; contextWindow?: number | null; percent?: number | null } | null;
|
|
130
|
+
}): void {
|
|
131
|
+
const store = getMetricsStore();
|
|
132
|
+
if (!store) return;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const projectSlug = basename(getProjectStateDir(opts.platform.paths, opts.cwd));
|
|
136
|
+
const sortedActiveTools = [...opts.activeTools].sort();
|
|
137
|
+
const sourceHash = createHash("sha256")
|
|
138
|
+
.update(projectSlug)
|
|
139
|
+
.update("\0")
|
|
140
|
+
.update(sortedActiveTools.join("\0"))
|
|
141
|
+
.digest("hex");
|
|
142
|
+
|
|
143
|
+
store.record({
|
|
144
|
+
session_id: getSessionId(),
|
|
145
|
+
ts: Date.now(),
|
|
146
|
+
layer: "L7",
|
|
147
|
+
tool: "(system)",
|
|
148
|
+
processor: "lazy-tools",
|
|
149
|
+
before_bytes: byteLength(opts.beforePrompt),
|
|
150
|
+
after_bytes: byteLength(opts.afterPrompt),
|
|
151
|
+
cache_hit: 0,
|
|
152
|
+
unique_source_hash: sourceHash,
|
|
153
|
+
context_tokens: opts.contextUsage?.tokens ?? null,
|
|
154
|
+
context_window: opts.contextUsage?.contextWindow ?? null,
|
|
155
|
+
context_percent: opts.contextUsage?.percent ?? null,
|
|
156
|
+
});
|
|
157
|
+
} catch (error) {
|
|
158
|
+
(opts.platform as any).logger?.warn?.("supi-lazy-tools: metrics record failed", error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function byteLength(value: string): number {
|
|
163
|
+
return new TextEncoder().encode(value).byteLength;
|
|
164
|
+
}
|