supipowers 1.5.2 → 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 +149 -45
- 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 +19 -9
- 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 +298 -127
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +115 -14
- 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 +11 -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 +1401 -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/review/types.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import type { TSchema } from "@sinclair/typebox";
|
|
3
2
|
import { Value } from "@sinclair/typebox/value";
|
|
4
3
|
import type {
|
|
5
4
|
ConfiguredReviewAgent,
|
|
@@ -17,6 +16,7 @@ import type {
|
|
|
17
16
|
ReviewScopeStats,
|
|
18
17
|
ReviewSession,
|
|
19
18
|
ReviewSessionArtifacts,
|
|
19
|
+
ThinkingLevel,
|
|
20
20
|
} from "../types.js";
|
|
21
21
|
export type {
|
|
22
22
|
ConfiguredReviewAgent,
|
|
@@ -122,6 +122,16 @@ export const ReviewAgentConfigSchema = Type.Object(
|
|
|
122
122
|
enabled: Type.Boolean(),
|
|
123
123
|
data: Type.String({ minLength: 1 }),
|
|
124
124
|
model: Type.Union([Type.String({ minLength: 1 }), Type.Null()]),
|
|
125
|
+
thinkingLevel: Type.Optional(Type.Union([
|
|
126
|
+
Type.Literal("off"),
|
|
127
|
+
Type.Literal("minimal"),
|
|
128
|
+
Type.Literal("low"),
|
|
129
|
+
Type.Literal("medium"),
|
|
130
|
+
Type.Literal("high"),
|
|
131
|
+
Type.Literal("xhigh"),
|
|
132
|
+
Type.Null(),
|
|
133
|
+
])),
|
|
134
|
+
peerCoordination: Type.Optional(Type.Boolean()),
|
|
125
135
|
},
|
|
126
136
|
{ additionalProperties: false },
|
|
127
137
|
);
|
|
@@ -212,25 +222,6 @@ export const ReviewSessionSchema = Type.Object(
|
|
|
212
222
|
{ additionalProperties: false },
|
|
213
223
|
);
|
|
214
224
|
|
|
215
|
-
export interface ReviewValidationError {
|
|
216
|
-
path: string;
|
|
217
|
-
message: string;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function normalizeErrorPath(path: string): string {
|
|
221
|
-
return path.replace(/^\//, "").replace(/\//g, ".") || "(root)";
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export function collectReviewValidationErrors(schema: TSchema, data: unknown): ReviewValidationError[] {
|
|
225
|
-
return [...Value.Errors(schema, data)].map((error) => ({
|
|
226
|
-
path: normalizeErrorPath(error.path),
|
|
227
|
-
message: error.message,
|
|
228
|
-
}));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export function formatReviewValidationErrors(errors: ReviewValidationError[]): string[] {
|
|
232
|
-
return errors.map((error) => `${error.path}: ${error.message}`);
|
|
233
|
-
}
|
|
234
225
|
|
|
235
226
|
export function isReviewScopeFile(value: unknown): value is ReviewScopeFile {
|
|
236
227
|
return Value.Check(ReviewScopeFileSchema, value);
|
package/src/review/validator.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import reviewOutputSchema from "./prompts/review-output-schema.md" with { type: "text" };
|
|
2
1
|
import validationReviewPrompt from "./prompts/validation-review.md" with { type: "text" };
|
|
3
2
|
import type { GateExecutionContext, ReviewFinding, ReviewOutput, ReviewScope } from "../types.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { runWithOutputValidation, type ReliabilityReporter } from "../ai/structured-output.js";
|
|
4
|
+
import { renderSchemaText } from "../ai/schema-text.js";
|
|
5
|
+
import { explainReviewOutputFailure, parseReviewOutput } from "./output.js";
|
|
6
|
+
import { renderTemplate } from "../ai/template.js";
|
|
7
|
+
import { ReviewOutputSchema } from "./types.js";
|
|
8
|
+
|
|
9
|
+
const REVIEW_OUTPUT_SCHEMA_TEXT = renderSchemaText(ReviewOutputSchema);
|
|
6
10
|
|
|
7
11
|
export interface ReviewValidationInput {
|
|
8
12
|
cwd: string;
|
|
@@ -14,6 +18,7 @@ export interface ReviewValidationInput {
|
|
|
14
18
|
timeoutMs?: number;
|
|
15
19
|
validatorName?: string;
|
|
16
20
|
now?: () => Date;
|
|
21
|
+
reliability?: ReliabilityReporter;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export interface ReviewValidationResult {
|
|
@@ -80,12 +85,12 @@ export function buildValidationPrompt(
|
|
|
80
85
|
validatorName: string,
|
|
81
86
|
validatedAt: string,
|
|
82
87
|
): string {
|
|
83
|
-
return
|
|
88
|
+
return renderTemplate(validationReviewPrompt, {
|
|
84
89
|
scope,
|
|
85
90
|
findingsJson: JSON.stringify(findings, null, 2),
|
|
86
91
|
validatorName,
|
|
87
92
|
validatedAt,
|
|
88
|
-
outputSchema:
|
|
93
|
+
outputSchema: REVIEW_OUTPUT_SCHEMA_TEXT,
|
|
89
94
|
});
|
|
90
95
|
}
|
|
91
96
|
|
|
@@ -107,7 +112,7 @@ export async function validateReviewFindings(input: ReviewValidationInput): Prom
|
|
|
107
112
|
const result = await runWithOutputValidation(input.createAgentSession, {
|
|
108
113
|
cwd: input.cwd,
|
|
109
114
|
prompt: buildValidationPrompt(input.scope, input.findings, validatorName, validatedAt),
|
|
110
|
-
schema:
|
|
115
|
+
schema: REVIEW_OUTPUT_SCHEMA_TEXT,
|
|
111
116
|
parse(raw) {
|
|
112
117
|
const output = parseReviewOutput(raw);
|
|
113
118
|
return {
|
|
@@ -118,6 +123,7 @@ export async function validateReviewFindings(input: ReviewValidationInput): Prom
|
|
|
118
123
|
model: input.model,
|
|
119
124
|
thinkingLevel: input.thinkingLevel ?? null,
|
|
120
125
|
timeoutMs: input.timeoutMs ?? 120_000,
|
|
126
|
+
reliability: input.reliability,
|
|
121
127
|
});
|
|
122
128
|
|
|
123
129
|
if (result.status === "blocked") {
|
|
@@ -2,15 +2,17 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { FixPrSessionLedger } from "../fix-pr/types.js";
|
|
4
4
|
import type { PlatformPaths } from "../platform/types.js";
|
|
5
|
+
import type { WorkspaceTarget } from "../types.js";
|
|
6
|
+
import { getProjectTargetStatePath } from "../workspace/state-paths.js";
|
|
5
7
|
|
|
6
8
|
const SESSIONS_DIR = "fix-pr-sessions";
|
|
7
9
|
|
|
8
|
-
function getBaseDir(paths: PlatformPaths,
|
|
9
|
-
return paths
|
|
10
|
+
function getBaseDir(paths: PlatformPaths, target: WorkspaceTarget): string {
|
|
11
|
+
return getProjectTargetStatePath(paths, target, SESSIONS_DIR);
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
export function getSessionDir(paths: PlatformPaths,
|
|
13
|
-
return path.join(getBaseDir(paths,
|
|
14
|
+
export function getSessionDir(paths: PlatformPaths, target: WorkspaceTarget, sessionId: string): string {
|
|
15
|
+
return path.join(getBaseDir(paths, target), sessionId);
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export function generateFixPrSessionId(): string {
|
|
@@ -21,14 +23,14 @@ export function generateFixPrSessionId(): string {
|
|
|
21
23
|
return `fpr-${date}-${time}-${rand}`;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
export function createFixPrSession(paths: PlatformPaths,
|
|
25
|
-
const sessionDir = getSessionDir(paths,
|
|
26
|
+
export function createFixPrSession(paths: PlatformPaths, target: WorkspaceTarget, ledger: FixPrSessionLedger): void {
|
|
27
|
+
const sessionDir = getSessionDir(paths, target, ledger.id);
|
|
26
28
|
fs.mkdirSync(path.join(sessionDir, "snapshots"), { recursive: true });
|
|
27
29
|
fs.writeFileSync(path.join(sessionDir, "ledger.json"), JSON.stringify(ledger, null, 2));
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
export function loadFixPrSession(paths: PlatformPaths,
|
|
31
|
-
const ledgerPath = path.join(getSessionDir(paths,
|
|
32
|
+
export function loadFixPrSession(paths: PlatformPaths, target: WorkspaceTarget, sessionId: string): FixPrSessionLedger | null {
|
|
33
|
+
const ledgerPath = path.join(getSessionDir(paths, target, sessionId), "ledger.json");
|
|
32
34
|
if (!fs.existsSync(ledgerPath)) return null;
|
|
33
35
|
try {
|
|
34
36
|
return JSON.parse(fs.readFileSync(ledgerPath, "utf-8")) as FixPrSessionLedger;
|
|
@@ -37,14 +39,19 @@ export function loadFixPrSession(paths: PlatformPaths, cwd: string, sessionId: s
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
export function updateFixPrSession(paths: PlatformPaths,
|
|
41
|
-
const ledgerPath = path.join(getSessionDir(paths,
|
|
42
|
+
export function updateFixPrSession(paths: PlatformPaths, target: WorkspaceTarget, ledger: FixPrSessionLedger): void {
|
|
43
|
+
const ledgerPath = path.join(getSessionDir(paths, target, ledger.id), "ledger.json");
|
|
42
44
|
ledger.updatedAt = new Date().toISOString();
|
|
43
45
|
fs.writeFileSync(ledgerPath, JSON.stringify(ledger, null, 2));
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
export function findActiveFixPrSession(
|
|
47
|
-
|
|
48
|
+
export function findActiveFixPrSession(
|
|
49
|
+
paths: PlatformPaths,
|
|
50
|
+
target: WorkspaceTarget,
|
|
51
|
+
repo: string,
|
|
52
|
+
prNumber: number,
|
|
53
|
+
): FixPrSessionLedger | null {
|
|
54
|
+
const baseDir = getBaseDir(paths, target);
|
|
48
55
|
if (!fs.existsSync(baseDir)) return null;
|
|
49
56
|
|
|
50
57
|
const dirs = fs.readdirSync(baseDir)
|
|
@@ -53,8 +60,8 @@ export function findActiveFixPrSession(paths: PlatformPaths, cwd: string): FixPr
|
|
|
53
60
|
.reverse();
|
|
54
61
|
|
|
55
62
|
for (const dir of dirs) {
|
|
56
|
-
const ledger = loadFixPrSession(paths,
|
|
57
|
-
if (ledger && ledger.status === "running") return ledger;
|
|
63
|
+
const ledger = loadFixPrSession(paths, target, dir);
|
|
64
|
+
if (ledger && ledger.status === "running" && ledger.repo === repo && ledger.prNumber === prNumber) return ledger;
|
|
58
65
|
}
|
|
59
66
|
return null;
|
|
60
67
|
}
|
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;
|