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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { KnowledgeOwner } from "../../types.js";
|
|
1
2
|
import { type Chunk, chunkMarkdown } from "../knowledge/chunker.js";
|
|
2
3
|
import type { KnowledgeStore } from "../knowledge/store.js";
|
|
3
4
|
import { htmlToMarkdown } from "./html-to-md.js";
|
|
@@ -7,6 +8,8 @@ export interface FetchOptions {
|
|
|
7
8
|
source?: string;
|
|
8
9
|
/** Bypass 24h TTL cache. */
|
|
9
10
|
force?: boolean;
|
|
11
|
+
/** Ownership scope for indexed/cached content. Defaults to project-owned when omitted. */
|
|
12
|
+
owner?: KnowledgeOwner;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
export interface FetchResult {
|
|
@@ -28,15 +31,22 @@ export async function fetchAndIndex(
|
|
|
28
31
|
options?: FetchOptions,
|
|
29
32
|
): Promise<FetchResult> {
|
|
30
33
|
const source = options?.source ?? new URL(url).hostname;
|
|
34
|
+
const owner = options?.owner;
|
|
35
|
+
const resolvedOwner = resolveOwner(owner);
|
|
31
36
|
|
|
32
|
-
// Check cache unless forced
|
|
37
|
+
// Check cache unless forced. Explicit owners must be isolated exactly; the
|
|
38
|
+
// default project-owned path also accepts migrated legacy rows so upgraded
|
|
39
|
+
// stores do not refetch and duplicate visible search results.
|
|
33
40
|
if (!options?.force) {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
const cacheOwners = owner ? [resolvedOwner] : [resolvedOwner, { ownerScope: "legacy" as const, ownerId: "" }];
|
|
42
|
+
for (const cacheOwner of cacheOwners) {
|
|
43
|
+
const cached = store.db
|
|
44
|
+
.prepare("SELECT fetched_at FROM url_cache WHERE url = ? AND source = ? AND owner_scope = ? AND owner_id = ?")
|
|
45
|
+
.get(url, source, cacheOwner.ownerScope, cacheOwner.ownerId) as { fetched_at: number } | null;
|
|
37
46
|
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
if (cached && Date.now() - cached.fetched_at < TTL_MS) {
|
|
48
|
+
return buildCachedResult(store, source, cacheOwner);
|
|
49
|
+
}
|
|
40
50
|
}
|
|
41
51
|
}
|
|
42
52
|
|
|
@@ -51,11 +61,11 @@ export async function fetchAndIndex(
|
|
|
51
61
|
const markdown = toMarkdown(rawText, contentType);
|
|
52
62
|
|
|
53
63
|
const chunks = chunkMarkdown(markdown, source);
|
|
54
|
-
store.index(chunks, source);
|
|
64
|
+
store.index(chunks, source, owner);
|
|
55
65
|
|
|
56
66
|
store.db.run(
|
|
57
|
-
"INSERT OR REPLACE INTO url_cache (url, source, fetched_at) VALUES (?, ?, ?)",
|
|
58
|
-
[url, source, Date.now()],
|
|
67
|
+
"INSERT OR REPLACE INTO url_cache (url, source, owner_scope, owner_id, fetched_at) VALUES (?, ?, ?, ?, ?)",
|
|
68
|
+
[url, source, resolvedOwner.ownerScope, resolvedOwner.ownerId, Date.now()],
|
|
59
69
|
);
|
|
60
70
|
|
|
61
71
|
return {
|
|
@@ -102,11 +112,20 @@ function buildPreview(chunks: Chunk[]): string {
|
|
|
102
112
|
return out;
|
|
103
113
|
}
|
|
104
114
|
|
|
115
|
+
function resolveOwner(owner: KnowledgeOwner | undefined): Required<KnowledgeOwner> {
|
|
116
|
+
return {
|
|
117
|
+
ownerScope: owner?.ownerScope ?? "project",
|
|
118
|
+
ownerId: owner?.ownerId ?? "",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
105
122
|
/** Reconstruct a cached result by querying stored chunks. */
|
|
106
|
-
function buildCachedResult(store: KnowledgeStore, source: string): FetchResult {
|
|
123
|
+
function buildCachedResult(store: KnowledgeStore, source: string, owner: Required<KnowledgeOwner>): FetchResult {
|
|
107
124
|
const rows = store.db
|
|
108
|
-
.prepare(
|
|
109
|
-
|
|
125
|
+
.prepare(
|
|
126
|
+
"SELECT title, body, content_type AS contentType FROM content_chunks WHERE source = ? AND owner_scope = ? AND owner_id = ? ORDER BY id",
|
|
127
|
+
)
|
|
128
|
+
.all(source, owner.ownerScope, owner.ownerId) as Chunk[];
|
|
110
129
|
|
|
111
130
|
return {
|
|
112
131
|
preview: buildPreview(rows),
|
package/src/debug/logger.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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 { getProjectStatePath } from "../workspace/state-paths.js";
|
|
4
5
|
|
|
5
6
|
export type DebugSessionContext = {
|
|
6
7
|
cwd?: string;
|
|
@@ -74,7 +75,7 @@ export function createDebugLogger(
|
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
const filePath = paths
|
|
78
|
+
const filePath = getProjectStatePath(paths, cwd, "debug", `tool-${sanitizedTool}__session-${sessionId}.jsonl`);
|
|
78
79
|
|
|
79
80
|
const logger: DebugLogger = {
|
|
80
81
|
enabled: true,
|
package/src/deps/registry.ts
CHANGED
|
@@ -148,7 +148,7 @@ export const DEPENDENCIES: Dependency[] = [
|
|
|
148
148
|
binary: "playwright",
|
|
149
149
|
required: false,
|
|
150
150
|
category: "testing",
|
|
151
|
-
description: "Test runner
|
|
151
|
+
description: "Test runner used by the portable QA Bun entrypoints",
|
|
152
152
|
checkFn: (exec) => checkBinary(exec, "playwright"),
|
|
153
153
|
installCmd: null, // Compound command (&&) — not compatible with installDep's naive split
|
|
154
154
|
url: "https://playwright.dev",
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// src/discipline/failure-summarizer.ts
|
|
2
|
+
//
|
|
3
|
+
// Offline analyzer that walks stored reliability records and persisted
|
|
4
|
+
// session artifacts, classifies each failure via the failure taxonomy,
|
|
5
|
+
// and produces a compact deterministic report.
|
|
6
|
+
//
|
|
7
|
+
// The summarizer is pure: given the same input records it produces the
|
|
8
|
+
// same report. Every data source is optional — callers pass what they
|
|
9
|
+
// have, the summarizer copes with partial inputs.
|
|
10
|
+
//
|
|
11
|
+
// Phase 8 exit gate: recurring failures are aggregated so the next
|
|
12
|
+
// hardening target is evidence-driven, not anecdotal.
|
|
13
|
+
|
|
14
|
+
import type { PlatformPaths } from "../platform/types.js";
|
|
15
|
+
import type { ReliabilityRecord } from "../types.js";
|
|
16
|
+
import { readReliabilityRecords } from "../storage/reliability-metrics.js";
|
|
17
|
+
import {
|
|
18
|
+
FAILURE_CLASSES,
|
|
19
|
+
classifyFailure,
|
|
20
|
+
describeFailureClass,
|
|
21
|
+
type FailureClass,
|
|
22
|
+
} from "./failure-taxonomy.js";
|
|
23
|
+
|
|
24
|
+
export interface FailureOccurrence {
|
|
25
|
+
/** Timestamp of the underlying event. */
|
|
26
|
+
ts: string;
|
|
27
|
+
/** Command that produced the failure. */
|
|
28
|
+
command: string;
|
|
29
|
+
/** Specific operation (e.g. "commit-plan"), when known. */
|
|
30
|
+
operation?: string;
|
|
31
|
+
/** All classes that fired for this occurrence. */
|
|
32
|
+
classes: FailureClass[];
|
|
33
|
+
/** Truthful reason from the record. */
|
|
34
|
+
reason?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface FailureClassAggregate {
|
|
38
|
+
class: FailureClass;
|
|
39
|
+
description: string;
|
|
40
|
+
/** Total occurrences of this class in the input. */
|
|
41
|
+
count: number;
|
|
42
|
+
/** Count per command, sorted alphabetically. */
|
|
43
|
+
byCommand: Array<{ command: string; count: number }>;
|
|
44
|
+
/** Up to `exampleCount` representative records for review. */
|
|
45
|
+
examples: FailureOccurrence[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface FailureSummary {
|
|
49
|
+
/** Total non-ok records considered. */
|
|
50
|
+
totalFailures: number;
|
|
51
|
+
/** Failure classes that fired at least once, sorted by taxonomy order. */
|
|
52
|
+
aggregates: FailureClassAggregate[];
|
|
53
|
+
/** Non-ok records that did NOT match any taxonomy class. */
|
|
54
|
+
unclassified: FailureOccurrence[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface SummarizeOptions {
|
|
58
|
+
/** Number of example occurrences per class. Default 3. */
|
|
59
|
+
exampleCount?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isFailureRecord(record: ReliabilityRecord): boolean {
|
|
63
|
+
return record.outcome !== "ok";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function classifyRecord(record: ReliabilityRecord): FailureOccurrence {
|
|
67
|
+
const classes = classifyFailure({
|
|
68
|
+
outcome: record.outcome,
|
|
69
|
+
reason: record.reason,
|
|
70
|
+
// attempts used by unproductive-retry rule
|
|
71
|
+
attempts: record.attempts,
|
|
72
|
+
} as any);
|
|
73
|
+
return {
|
|
74
|
+
ts: record.ts,
|
|
75
|
+
command: record.command,
|
|
76
|
+
operation: record.operation,
|
|
77
|
+
classes,
|
|
78
|
+
reason: record.reason,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function aggregate(
|
|
83
|
+
occurrences: FailureOccurrence[],
|
|
84
|
+
exampleCount: number,
|
|
85
|
+
): FailureClassAggregate[] {
|
|
86
|
+
const map = new Map<FailureClass, FailureOccurrence[]>();
|
|
87
|
+
for (const occ of occurrences) {
|
|
88
|
+
for (const cls of occ.classes) {
|
|
89
|
+
const list = map.get(cls) ?? [];
|
|
90
|
+
list.push(occ);
|
|
91
|
+
map.set(cls, list);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const aggregates: FailureClassAggregate[] = [];
|
|
96
|
+
for (const cls of FAILURE_CLASSES) {
|
|
97
|
+
const list = map.get(cls);
|
|
98
|
+
if (!list || list.length === 0) continue;
|
|
99
|
+
const byCommandMap = new Map<string, number>();
|
|
100
|
+
for (const occ of list) {
|
|
101
|
+
byCommandMap.set(occ.command, (byCommandMap.get(occ.command) ?? 0) + 1);
|
|
102
|
+
}
|
|
103
|
+
const byCommand = [...byCommandMap.entries()]
|
|
104
|
+
.map(([command, count]) => ({ command, count }))
|
|
105
|
+
.sort((a, b) => a.command.localeCompare(b.command));
|
|
106
|
+
|
|
107
|
+
aggregates.push({
|
|
108
|
+
class: cls,
|
|
109
|
+
description: describeFailureClass(cls),
|
|
110
|
+
count: list.length,
|
|
111
|
+
byCommand,
|
|
112
|
+
examples: list.slice(0, Math.max(0, exampleCount)),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return aggregates;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Summarize an arbitrary list of reliability records. Pure — no filesystem.
|
|
121
|
+
* Callers can combine records from multiple sources before summarizing.
|
|
122
|
+
*/
|
|
123
|
+
export function summarizeFailures(
|
|
124
|
+
records: ReliabilityRecord[],
|
|
125
|
+
options: SummarizeOptions = {},
|
|
126
|
+
): FailureSummary {
|
|
127
|
+
const exampleCount = options.exampleCount ?? 3;
|
|
128
|
+
const failures = records.filter(isFailureRecord).map(classifyRecord);
|
|
129
|
+
|
|
130
|
+
const classified = failures.filter((f) => f.classes.length > 0);
|
|
131
|
+
const unclassified = failures.filter((f) => f.classes.length === 0);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
totalFailures: failures.length,
|
|
135
|
+
aggregates: aggregate(classified, exampleCount),
|
|
136
|
+
unclassified,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Convenience: load records from the per-cwd reliability store and
|
|
142
|
+
* summarize. Empty store produces an empty summary (no crashes).
|
|
143
|
+
*/
|
|
144
|
+
export function summarizeLocalFailures(
|
|
145
|
+
paths: PlatformPaths,
|
|
146
|
+
cwd: string,
|
|
147
|
+
options: SummarizeOptions = {},
|
|
148
|
+
): FailureSummary {
|
|
149
|
+
return summarizeFailures(readReliabilityRecords(paths, cwd), options);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Format a summary as readable lines. `[]` when there are no failures so
|
|
154
|
+
* callers can branch on length without a special case.
|
|
155
|
+
*/
|
|
156
|
+
export function formatFailureSummary(summary: FailureSummary): string[] {
|
|
157
|
+
if (summary.totalFailures === 0) return [];
|
|
158
|
+
|
|
159
|
+
const lines: string[] = [`Failure summary: ${summary.totalFailures} non-ok record(s)`];
|
|
160
|
+
for (const agg of summary.aggregates) {
|
|
161
|
+
lines.push(` [${agg.class}] ${agg.description} \u2014 ${agg.count} occurrence(s)`);
|
|
162
|
+
for (const per of agg.byCommand) {
|
|
163
|
+
lines.push(` \u00b7 ${per.command}: ${per.count}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (summary.unclassified.length > 0) {
|
|
167
|
+
lines.push(` [unclassified] ${summary.unclassified.length} record(s) did not match any taxonomy class`);
|
|
168
|
+
}
|
|
169
|
+
return lines;
|
|
170
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow failure taxonomy.
|
|
3
|
+
*
|
|
4
|
+
* Small, explicit set of failure classes used by summarizer and eval-promotion
|
|
5
|
+
* to turn raw reliability records + session notes into actionable categories.
|
|
6
|
+
*
|
|
7
|
+
* Classification is pure: regex / string checks only, no dynamic evaluation,
|
|
8
|
+
* deterministic for the same input, never throws.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const FAILURE_CLASSES = [
|
|
12
|
+
"premature-completion",
|
|
13
|
+
"wrong-tool-path",
|
|
14
|
+
"missing-artifact",
|
|
15
|
+
"verification-skipped",
|
|
16
|
+
"discovery-miss",
|
|
17
|
+
"unproductive-retry",
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
export type FailureClass = (typeof FAILURE_CLASSES)[number];
|
|
21
|
+
|
|
22
|
+
export interface FailureSignals {
|
|
23
|
+
/** Stored reliability record, optional. */
|
|
24
|
+
outcome?: "ok" | "blocked" | "retry-exhausted" | "fallback" | "agent-error";
|
|
25
|
+
/** Reason string from the reliability record or log, optional. */
|
|
26
|
+
reason?: string;
|
|
27
|
+
/** Tool call name if the failure involves a blocked/rerouted tool. */
|
|
28
|
+
toolName?: string;
|
|
29
|
+
/** Path of an artifact that was expected but not found. */
|
|
30
|
+
missingArtifactPath?: string;
|
|
31
|
+
/** Free-text description from debug traces or session notes. */
|
|
32
|
+
note?: string;
|
|
33
|
+
/** Attempt count from the reliability record, optional. */
|
|
34
|
+
attempts?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Tool names blocked by `routeToolCall` when context-mode is active.
|
|
38
|
+
// Keep in lock-step with `src/context-mode/routing.ts` — every native tool
|
|
39
|
+
// that the router redirects must classify as `wrong-tool-path` here.
|
|
40
|
+
const BLOCKED_TOOLS = new Set<string>([
|
|
41
|
+
"search",
|
|
42
|
+
"find",
|
|
43
|
+
"bash-grep",
|
|
44
|
+
"bash-find",
|
|
45
|
+
"curl",
|
|
46
|
+
"wget",
|
|
47
|
+
"fetch",
|
|
48
|
+
"web_fetch",
|
|
49
|
+
"WebFetch",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const DESCRIPTIONS: Record<FailureClass, string> = {
|
|
53
|
+
"premature-completion":
|
|
54
|
+
"Workflow claimed done before required artifact existed.",
|
|
55
|
+
"wrong-tool-path":
|
|
56
|
+
"Workflow reached for a blocked tool instead of the preferred ctx_* tool.",
|
|
57
|
+
"missing-artifact":
|
|
58
|
+
"Required output (plan file, session, findings.md) was never written.",
|
|
59
|
+
"verification-skipped":
|
|
60
|
+
"Agent skipped a mandatory verification step (test, typecheck, eval).",
|
|
61
|
+
"discovery-miss":
|
|
62
|
+
"Workflow wandered before finding the right entry point.",
|
|
63
|
+
"unproductive-retry":
|
|
64
|
+
"Retry loop spent attempts without making progress.",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Classify a failure based on signals. Returns one or more matching classes in
|
|
69
|
+
* priority order (matching `FAILURE_CLASSES` order); empty array when no class
|
|
70
|
+
* fires. Deterministic — never throws.
|
|
71
|
+
*/
|
|
72
|
+
export function classifyFailure(signals: FailureSignals): FailureClass[] {
|
|
73
|
+
const reason = signals.reason ?? "";
|
|
74
|
+
const note = signals.note ?? "";
|
|
75
|
+
const matches: FailureClass[] = [];
|
|
76
|
+
|
|
77
|
+
// premature-completion
|
|
78
|
+
if (
|
|
79
|
+
(signals.outcome === "ok" &&
|
|
80
|
+
/incomplete|partial|unresolved/i.test(reason)) ||
|
|
81
|
+
(signals.outcome === "fallback" &&
|
|
82
|
+
/never produced valid artifact/i.test(reason))
|
|
83
|
+
) {
|
|
84
|
+
matches.push("premature-completion");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// wrong-tool-path
|
|
88
|
+
if (
|
|
89
|
+
(signals.toolName && BLOCKED_TOOLS.has(signals.toolName)) ||
|
|
90
|
+
/ctx_/.test(reason)
|
|
91
|
+
) {
|
|
92
|
+
matches.push("wrong-tool-path");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// missing-artifact
|
|
96
|
+
if (
|
|
97
|
+
signals.missingArtifactPath !== undefined ||
|
|
98
|
+
(/missing/i.test(reason) && /plan|findings|session/i.test(reason))
|
|
99
|
+
) {
|
|
100
|
+
matches.push("missing-artifact");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// verification-skipped
|
|
104
|
+
if (
|
|
105
|
+
/without running (validator|tests|typecheck)/i.test(reason) ||
|
|
106
|
+
/skipped (verification|validation|test)/i.test(reason)
|
|
107
|
+
) {
|
|
108
|
+
matches.push("verification-skipped");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// discovery-miss
|
|
112
|
+
if (
|
|
113
|
+
/wandered/i.test(reason) ||
|
|
114
|
+
/wrong file/i.test(reason) ||
|
|
115
|
+
/searched broadly/i.test(note)
|
|
116
|
+
) {
|
|
117
|
+
matches.push("discovery-miss");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// unproductive-retry
|
|
121
|
+
if (signals.outcome === "retry-exhausted" && (signals.attempts ?? 0) >= 3) {
|
|
122
|
+
matches.push("unproductive-retry");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return matches;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Canonical human-friendly description per class. */
|
|
129
|
+
export function describeFailureClass(cls: FailureClass): string {
|
|
130
|
+
return DESCRIPTIONS[cls];
|
|
131
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// src/discipline/workflow-invariants.ts
|
|
2
|
+
//
|
|
3
|
+
// Runtime invariants that AI-heavy workflows must satisfy before reporting
|
|
4
|
+
// completion. When an invariant fails, the workflow yields a truthful
|
|
5
|
+
// blocker instead of silently claiming success. Used by plan/review/qa/fix-pr
|
|
6
|
+
// completion paths and by Phase 0 evals that test workflow boundaries.
|
|
7
|
+
//
|
|
8
|
+
// Design notes:
|
|
9
|
+
// - Invariants are pure functions over a workflow-specific context object.
|
|
10
|
+
// - Invariants return either `{ state: "satisfied" }` or a blocker with a
|
|
11
|
+
// human-readable `reason`. Keep reasons short and actionable.
|
|
12
|
+
// - `checkInvariants` returns the FIRST blocker, not a list. Workflows
|
|
13
|
+
// surface one blocker at a time so the user (or the model) can fix it
|
|
14
|
+
// and proceed, rather than being handed a noisy report.
|
|
15
|
+
// - This module owns only the generic abstraction. Workflow-specific
|
|
16
|
+
// invariant builders live next to their workflow (e.g. plan's PlanSpec
|
|
17
|
+
// validation in src/planning/approval-flow.ts).
|
|
18
|
+
|
|
19
|
+
export type InvariantState =
|
|
20
|
+
| { state: "satisfied" }
|
|
21
|
+
| { state: "blocked"; reason: string };
|
|
22
|
+
|
|
23
|
+
export interface WorkflowInvariant<TContext> {
|
|
24
|
+
/** Stable identifier. Used for logging and test assertions. */
|
|
25
|
+
name: string;
|
|
26
|
+
/**
|
|
27
|
+
* Evaluate this invariant against the workflow context. Return a blocker
|
|
28
|
+
* with a truthful reason if the invariant fails.
|
|
29
|
+
*/
|
|
30
|
+
check: (ctx: TContext) => InvariantState | Promise<InvariantState>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type InvariantCheckResult =
|
|
34
|
+
| { state: "satisfied" }
|
|
35
|
+
| { state: "blocked"; invariant: string; reason: string };
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Run invariants in order against `ctx`. Stop at the first blocker and
|
|
39
|
+
* return it. Returns `{ state: "satisfied" }` only when every invariant
|
|
40
|
+
* reports satisfied.
|
|
41
|
+
*/
|
|
42
|
+
export async function checkInvariants<TContext>(
|
|
43
|
+
invariants: readonly WorkflowInvariant<TContext>[],
|
|
44
|
+
ctx: TContext,
|
|
45
|
+
): Promise<InvariantCheckResult> {
|
|
46
|
+
for (const invariant of invariants) {
|
|
47
|
+
const result = await invariant.check(ctx);
|
|
48
|
+
if (result.state === "blocked") {
|
|
49
|
+
return { state: "blocked", invariant: invariant.name, reason: result.reason };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { state: "satisfied" };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Invariant builders — the common shapes workflows compose from
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build an invariant that is satisfied only when the predicate returns true.
|
|
61
|
+
*/
|
|
62
|
+
export function requireCondition<TContext>(
|
|
63
|
+
name: string,
|
|
64
|
+
predicate: (ctx: TContext) => boolean | Promise<boolean>,
|
|
65
|
+
reason: string,
|
|
66
|
+
): WorkflowInvariant<TContext> {
|
|
67
|
+
return {
|
|
68
|
+
name,
|
|
69
|
+
async check(ctx) {
|
|
70
|
+
const satisfied = await predicate(ctx);
|
|
71
|
+
return satisfied ? { state: "satisfied" } : { state: "blocked", reason };
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build an invariant that is satisfied only when the artifact exists. The
|
|
78
|
+
* caller supplies both the existence check and the artifact identifier used
|
|
79
|
+
* in the blocker reason.
|
|
80
|
+
*/
|
|
81
|
+
export function requireArtifact<TContext>(
|
|
82
|
+
name: string,
|
|
83
|
+
exists: (ctx: TContext) => boolean | Promise<boolean>,
|
|
84
|
+
artifactLabel: string,
|
|
85
|
+
): WorkflowInvariant<TContext> {
|
|
86
|
+
return requireCondition(
|
|
87
|
+
name,
|
|
88
|
+
exists,
|
|
89
|
+
`Required artifact is missing: ${artifactLabel}.`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build an invariant that blocks when the workflow still has pending work.
|
|
95
|
+
* Typical uses: outstanding todos, unresolved review comments, undispatched
|
|
96
|
+
* follow-up steps.
|
|
97
|
+
*/
|
|
98
|
+
export function requireNoPendingWork<TContext>(
|
|
99
|
+
name: string,
|
|
100
|
+
pending: (ctx: TContext) => number | Promise<number>,
|
|
101
|
+
workLabel: string,
|
|
102
|
+
): WorkflowInvariant<TContext> {
|
|
103
|
+
return {
|
|
104
|
+
name,
|
|
105
|
+
async check(ctx) {
|
|
106
|
+
const count = await pending(ctx);
|
|
107
|
+
return count === 0
|
|
108
|
+
? { state: "satisfied" }
|
|
109
|
+
: {
|
|
110
|
+
state: "blocked",
|
|
111
|
+
reason: `${count} ${workLabel} still pending \u2014 workflow cannot complete.`,
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Render a blocker result as a single line for notifications, logs, or
|
|
119
|
+
* prompts. `satisfied` states render as an empty string so callers can join
|
|
120
|
+
* multiple results without branching.
|
|
121
|
+
*/
|
|
122
|
+
export function formatInvariantResult(result: InvariantCheckResult): string {
|
|
123
|
+
if (result.state === "satisfied") return "";
|
|
124
|
+
return `[${result.invariant}] ${result.reason}`;
|
|
125
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/discovery/index.ts
|
|
2
|
+
//
|
|
3
|
+
// Deterministic repo-entry-point discovery. Given a workflow query (e.g.
|
|
4
|
+
// "fix the login bug", "review the latest commit") and context signals
|
|
5
|
+
// (changed files, workspace targets), rank likely-relevant files with a
|
|
6
|
+
// short rationale for each candidate.
|
|
7
|
+
//
|
|
8
|
+
// Used by /supi:review, /supi:plan, /supi:qa, and /supi:fix-pr to start
|
|
9
|
+
// from strong candidates rather than broad wandering.
|
|
10
|
+
//
|
|
11
|
+
// Non-goals:
|
|
12
|
+
// - Hosted search / vector database
|
|
13
|
+
// - Replacing native tools (grep/lsp). This layer *orchestrates* them.
|
|
14
|
+
//
|
|
15
|
+
// Phase 6 exit gate: fixture workspaces rank expected files first, every
|
|
16
|
+
// candidate carries rationale, behavior stays stable when inputs are empty.
|
|
17
|
+
|
|
18
|
+
export type {
|
|
19
|
+
DiscoveryCandidate,
|
|
20
|
+
DiscoveryInput,
|
|
21
|
+
DiscoveryResult,
|
|
22
|
+
DiscoverySource,
|
|
23
|
+
} from "./rank.js";
|
|
24
|
+
|
|
25
|
+
export { rankDiscoveryCandidates } from "./rank.js";
|
|
26
|
+
export { discoverFromSources } from "./sources.js";
|
|
27
|
+
|
|
28
|
+
export { rankWithLspAugmentation } from "./lsp.js";
|
|
29
|
+
export type { LspSymbolLocation, LspAugmentedResult } from "./lsp.js";
|
|
30
|
+
export { suggestCandidatesForWorkflow } from "./workflow.js";
|
|
31
|
+
export type { WorkflowDiscoveryInput, WorkflowDiscoveryResult } from "./workflow.js";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// src/discovery/lsp.ts
|
|
2
|
+
//
|
|
3
|
+
// LSP-assisted discovery: convert symbol search results into external
|
|
4
|
+
// signals the ranker consumes. When LSP is unavailable or the symbol
|
|
5
|
+
// lookup returns nothing, falls through cleanly — callers should never
|
|
6
|
+
// require LSP for discovery to work.
|
|
7
|
+
//
|
|
8
|
+
// This module deliberately does not talk to LSP directly. Callers pass in
|
|
9
|
+
// a `querySymbols` callback so the same function can be driven by:
|
|
10
|
+
// - the live platform LSP bridge in production
|
|
11
|
+
// - a deterministic fixture in tests
|
|
12
|
+
|
|
13
|
+
import type { DiscoveryCandidate } from "./rank.js";
|
|
14
|
+
import { rankDiscoveryCandidates, type DiscoveryInput } from "./rank.js";
|
|
15
|
+
|
|
16
|
+
export interface LspSymbolLocation {
|
|
17
|
+
/** Repo-relative path where the symbol is defined or referenced. */
|
|
18
|
+
path: string;
|
|
19
|
+
/** Short reason string attached to the candidate. */
|
|
20
|
+
reason: string;
|
|
21
|
+
/** Extra score beyond the baseline LSP boost. Optional. */
|
|
22
|
+
bonus?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface LspDiscoveryInput extends DiscoveryInput {
|
|
26
|
+
/**
|
|
27
|
+
* Called with the workflow `query`. Must return a (possibly empty) list
|
|
28
|
+
* of symbol locations. Any thrown error is caught and treated as
|
|
29
|
+
* "LSP unavailable" — discovery still returns a ranked list from the
|
|
30
|
+
* remaining sources.
|
|
31
|
+
*/
|
|
32
|
+
querySymbols: (query: string) => LspSymbolLocation[] | Promise<LspSymbolLocation[]>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface LspAugmentedResult {
|
|
36
|
+
candidates: DiscoveryCandidate[];
|
|
37
|
+
lspAvailable: boolean;
|
|
38
|
+
lspHitCount: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const WEIGHT_LSP = 6;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Run LSP symbol discovery against the query, fold the hits into external
|
|
45
|
+
* signals, and rank the combined candidate pool. When `querySymbols` throws
|
|
46
|
+
* or returns [], the result is still valid — discovery degrades, not fails.
|
|
47
|
+
*/
|
|
48
|
+
export async function rankWithLspAugmentation(
|
|
49
|
+
input: LspDiscoveryInput,
|
|
50
|
+
): Promise<LspAugmentedResult> {
|
|
51
|
+
let lspAvailable = true;
|
|
52
|
+
let lspHits: LspSymbolLocation[] = [];
|
|
53
|
+
|
|
54
|
+
if (input.query && input.query.trim().length > 0) {
|
|
55
|
+
try {
|
|
56
|
+
lspHits = await input.querySymbols(input.query);
|
|
57
|
+
} catch {
|
|
58
|
+
lspAvailable = false;
|
|
59
|
+
lspHits = [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const externalSignals: Record<string, { score: number; rationale: string }> = {
|
|
64
|
+
...(input.externalSignals ?? {}),
|
|
65
|
+
};
|
|
66
|
+
for (const hit of lspHits) {
|
|
67
|
+
const prior = externalSignals[hit.path];
|
|
68
|
+
const score = WEIGHT_LSP + (hit.bonus ?? 0);
|
|
69
|
+
externalSignals[hit.path] = prior
|
|
70
|
+
? {
|
|
71
|
+
score: prior.score + score,
|
|
72
|
+
rationale: `${prior.rationale}; ${hit.reason}`,
|
|
73
|
+
}
|
|
74
|
+
: { score, rationale: hit.reason };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const ranked = rankDiscoveryCandidates({
|
|
78
|
+
...input,
|
|
79
|
+
externalSignals,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
candidates: ranked.candidates,
|
|
84
|
+
lspAvailable,
|
|
85
|
+
lspHitCount: lspHits.length,
|
|
86
|
+
};
|
|
87
|
+
}
|