supipowers 1.5.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/bin/install.mjs +20 -5
- package/bin/install.ts +95 -0
- package/package.json +8 -4
- package/skills/context-mode/SKILL.md +17 -10
- package/skills/harness/SKILL.md +94 -0
- package/skills/ui-design/SKILL.md +63 -0
- package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
- package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
- package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
- package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
- package/skills/ultraplan-discover/SKILL.md +96 -0
- package/skills/ultraplan-intake/SKILL.md +89 -0
- package/skills/ultraplan-research/SKILL.md +129 -0
- package/skills/ultraplan-review/SKILL.md +86 -0
- package/skills/ultraplan-review-scope/SKILL.md +111 -0
- package/skills/ultraplan-review-structure/SKILL.md +120 -0
- package/skills/ultraplan-review-tdd/SKILL.md +142 -0
- package/skills/ultraplan-scout/SKILL.md +110 -0
- package/skills/ultraplan-synthesize/SKILL.md +124 -0
- package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
- package/src/ai/schema-text.ts +129 -0
- package/src/ai/structured-output.ts +274 -0
- package/src/ai/template.ts +27 -0
- package/src/bootstrap.ts +63 -28
- package/src/commands/agents.ts +131 -42
- package/src/commands/ai-review.ts +251 -30
- package/src/commands/clear.ts +434 -0
- package/src/commands/commit.ts +1 -0
- package/src/commands/config.ts +242 -44
- package/src/commands/context.ts +55 -28
- package/src/commands/doctor.ts +234 -6
- package/src/commands/fix-pr.ts +306 -131
- package/src/commands/generate.ts +111 -21
- package/src/commands/memory.ts +192 -0
- package/src/commands/model-picker.ts +28 -21
- package/src/commands/model.ts +18 -8
- package/src/commands/optimize-context.ts +408 -29
- package/src/commands/plan.ts +2 -0
- package/src/commands/qa.ts +312 -137
- package/src/commands/release.ts +259 -76
- package/src/commands/review.ts +293 -59
- package/src/commands/status.ts +200 -13
- package/src/commands/supi.ts +3 -35
- package/src/commands/ui-design.ts +394 -0
- package/src/commands/ultraplan.ts +1518 -0
- package/src/commands/update.ts +86 -0
- package/src/config/defaults.ts +62 -0
- package/src/config/loader.ts +448 -60
- package/src/config/schema.ts +108 -2
- package/src/context/optimizer.ts +25 -33
- package/src/context/rule-renderer.ts +223 -0
- package/src/context/savings.ts +258 -0
- package/src/context/startup-check.ts +380 -0
- package/src/context/startup-optimizer.ts +355 -0
- package/src/context/tokenignore.ts +146 -0
- package/src/context-mode/cache-handle.ts +49 -0
- package/src/context-mode/cache-preview.ts +71 -0
- package/src/context-mode/cache-store.ts +738 -0
- package/src/context-mode/compressor.ts +131 -26
- package/src/context-mode/dedup.ts +108 -0
- package/src/context-mode/detector.ts +35 -4
- package/src/context-mode/event-extractor.ts +14 -12
- package/src/context-mode/event-store.ts +91 -36
- package/src/context-mode/hooks.ts +798 -56
- package/src/context-mode/knowledge/store.ts +255 -11
- package/src/context-mode/memory-store.ts +325 -0
- package/src/context-mode/metrics-recorder.ts +158 -0
- package/src/context-mode/metrics-store.ts +765 -0
- package/src/context-mode/model.ts +24 -0
- package/src/context-mode/processor-keys.ts +29 -0
- package/src/context-mode/processors/build.ts +66 -0
- package/src/context-mode/processors/docker.ts +57 -0
- package/src/context-mode/processors/git.ts +111 -0
- package/src/context-mode/processors/json.ts +112 -0
- package/src/context-mode/processors/k8s.ts +67 -0
- package/src/context-mode/processors/lint.ts +67 -0
- package/src/context-mode/processors/log.ts +86 -0
- package/src/context-mode/processors/registry.ts +116 -0
- package/src/context-mode/processors/test-runner.ts +102 -0
- package/src/context-mode/processors/types.ts +20 -0
- package/src/context-mode/repomap.ts +400 -0
- package/src/context-mode/routing.ts +97 -24
- package/src/context-mode/sandbox/runners.ts +5 -1
- package/src/context-mode/snapshot-builder.ts +106 -11
- package/src/context-mode/source-hash.ts +173 -0
- package/src/context-mode/tool-name.ts +11 -0
- package/src/context-mode/tools.ts +654 -22
- package/src/context-mode/web/fetcher.ts +31 -12
- package/src/debug/logger.ts +2 -1
- package/src/deps/registry.ts +1 -1
- package/src/discipline/failure-summarizer.ts +170 -0
- package/src/discipline/failure-taxonomy.ts +131 -0
- package/src/discipline/workflow-invariants.ts +125 -0
- package/src/discovery/index.ts +31 -0
- package/src/discovery/lsp.ts +87 -0
- package/src/discovery/rank.ts +144 -0
- package/src/discovery/sources.ts +89 -0
- package/src/discovery/workflow.ts +87 -0
- package/src/docs/contracts.ts +39 -0
- package/src/docs/drift.ts +117 -87
- package/src/fix-pr/assessment.ts +200 -0
- package/src/fix-pr/contracts.ts +47 -0
- package/src/fix-pr/fetch-comments.ts +80 -0
- package/src/fix-pr/prompt-builder.ts +58 -40
- package/src/fix-pr/scripts/exec.ts +34 -0
- package/src/fix-pr/scripts/trigger-review.ts +106 -0
- package/src/fix-pr/scripts/wait-and-check.ts +108 -0
- package/src/fix-pr/types.ts +4 -0
- package/src/git/branch-finish.ts +5 -0
- package/src/git/commit-contract.ts +83 -0
- package/src/git/commit.ts +121 -184
- package/src/git/status.ts +62 -8
- package/src/harness/anti_slop/architecture-parser.ts +210 -0
- package/src/harness/anti_slop/backend-factory.ts +30 -0
- package/src/harness/anti_slop/backend.ts +140 -0
- package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
- package/src/harness/anti_slop/fallow-adapter.ts +305 -0
- package/src/harness/anti_slop/installer.ts +227 -0
- package/src/harness/anti_slop/queue.ts +216 -0
- package/src/harness/anti_slop/recommend.ts +84 -0
- package/src/harness/anti_slop/score.ts +180 -0
- package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
- package/src/harness/artifacts/agents-md.ts +88 -0
- package/src/harness/artifacts/checks-wiring.ts +57 -0
- package/src/harness/artifacts/docs-tree.ts +79 -0
- package/src/harness/artifacts/lint-configs.ts +136 -0
- package/src/harness/artifacts/review-agents.ts +67 -0
- package/src/harness/bare-entry.ts +108 -0
- package/src/harness/command.ts +1010 -0
- package/src/harness/default-agents/design.md +23 -0
- package/src/harness/default-agents/discover.md +18 -0
- package/src/harness/default-agents/implement.md +24 -0
- package/src/harness/default-agents/plan.md +19 -0
- package/src/harness/default-agents/research.md +21 -0
- package/src/harness/default-agents/validate.md +22 -0
- package/src/harness/gc/reporter.ts +28 -0
- package/src/harness/gc/runner.ts +136 -0
- package/src/harness/hooks/layer-context-inject.ts +155 -0
- package/src/harness/hooks/post-session-sweep.ts +130 -0
- package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
- package/src/harness/hooks/register.ts +118 -0
- package/src/harness/model.ts +117 -0
- package/src/harness/pipeline.ts +348 -0
- package/src/harness/project-paths.ts +235 -0
- package/src/harness/stage-runner.ts +107 -0
- package/src/harness/stages/design.ts +386 -0
- package/src/harness/stages/discover.ts +454 -0
- package/src/harness/stages/implement.ts +162 -0
- package/src/harness/stages/plan.ts +335 -0
- package/src/harness/stages/research.ts +263 -0
- package/src/harness/stages/validate.ts +684 -0
- package/src/harness/storage.ts +467 -0
- package/src/harness/tools.ts +426 -0
- package/src/lsp/bridge.ts +56 -95
- package/src/lsp/capabilities.ts +108 -0
- package/src/lsp/contracts.ts +35 -0
- package/src/lsp/detector.ts +8 -12
- package/src/markdown-frontmatter.ts +68 -0
- package/src/mempalace/bridge.ts +129 -0
- package/src/mempalace/config.ts +75 -0
- package/src/mempalace/format.ts +163 -0
- package/src/mempalace/hooks.ts +370 -0
- package/src/mempalace/installer-helper.ts +194 -0
- package/src/mempalace/python/mempalace_bridge.py +440 -0
- package/src/mempalace/runtime.ts +565 -0
- package/src/mempalace/schema.ts +264 -0
- package/src/mempalace/session-summary.ts +198 -0
- package/src/mempalace/tool.ts +186 -0
- package/src/mempalace/uv.ts +256 -0
- package/src/migrate/runner.ts +354 -0
- package/src/planning/approval-flow.ts +206 -9
- package/src/planning/plan-writer-prompt.ts +4 -3
- package/src/planning/planning-ask-tool.ts +39 -0
- package/src/planning/render-markdown.ts +74 -0
- package/src/planning/spec.ts +42 -0
- package/src/planning/system-prompt.ts +11 -8
- package/src/planning/validate.ts +84 -0
- package/src/platform/omp.ts +15 -2
- package/src/platform/system-prompt.ts +37 -0
- package/src/platform/test-utils.ts +3 -0
- package/src/platform/types.ts +6 -1
- package/src/qa/config.ts +12 -6
- package/src/qa/detect-app-type.ts +13 -6
- package/src/qa/matrix.ts +12 -6
- package/src/qa/prompt-builder.ts +28 -30
- package/src/qa/scripts/dev-server-utils.ts +72 -0
- package/src/qa/scripts/run-e2e-tests.ts +226 -0
- package/src/qa/scripts/start-dev-server.ts +138 -0
- package/src/qa/scripts/stop-dev-server.ts +77 -0
- package/src/qa/session.ts +13 -7
- package/src/quality/ai-setup.ts +27 -25
- package/src/quality/contracts.ts +34 -0
- package/src/quality/gates/ai-review.ts +20 -58
- package/src/quality/gates/command.ts +249 -46
- package/src/quality/review-gates.ts +18 -2
- package/src/quality/runner.ts +63 -22
- package/src/quality/schemas.ts +37 -2
- package/src/quality/setup.ts +96 -16
- package/src/release/changelog.ts +1 -1
- package/src/release/channels/custom.ts +13 -3
- package/src/release/channels/types.ts +5 -0
- package/src/release/contracts.ts +90 -0
- package/src/release/executor.ts +122 -45
- package/src/release/prompt.ts +18 -2
- package/src/release/targets.ts +86 -0
- package/src/release/version.ts +96 -71
- package/src/review/agent-loader.ts +221 -109
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +114 -13
- package/src/review/output.ts +12 -139
- package/src/review/runner.ts +12 -6
- package/src/review/scope.ts +144 -24
- package/src/review/types.ts +1 -20
- package/src/review/validator.ts +12 -6
- package/src/storage/fix-pr-sessions.ts +21 -14
- package/src/storage/plans.ts +14 -5
- package/src/storage/qa-sessions.ts +25 -19
- package/src/storage/reliability-metrics.ts +180 -0
- package/src/storage/reports.ts +8 -7
- package/src/storage/review-sessions.ts +55 -20
- package/src/tool-catalog/active-tool-controller.ts +164 -0
- package/src/tool-catalog/active-tool-planner.ts +212 -0
- package/src/tool-catalog/tool-groups.ts +102 -0
- package/src/types.ts +1399 -5
- package/src/ui-design/backend-adapter.ts +78 -0
- package/src/ui-design/backends/local-html.ts +82 -0
- package/src/ui-design/backends/pencil-mcp.ts +111 -0
- package/src/ui-design/components-scanner.ts +124 -0
- package/src/ui-design/config.ts +55 -0
- package/src/ui-design/pen-scanner.ts +95 -0
- package/src/ui-design/pen-selector.ts +72 -0
- package/src/ui-design/prompt-builder.ts +73 -0
- package/src/ui-design/scanner.ts +136 -0
- package/src/ui-design/session.ts +974 -0
- package/src/ui-design/system-prompt.ts +312 -0
- package/src/ui-design/tokens-scanner.ts +181 -0
- package/src/ui-design/types.ts +96 -0
- package/src/ultraplan/agent-catalog.ts +522 -0
- package/src/ultraplan/authoring/agent-catalog.ts +310 -0
- package/src/ultraplan/authoring/authoring-tools.ts +552 -0
- package/src/ultraplan/authoring/command-handlers.ts +339 -0
- package/src/ultraplan/authoring/markdown.ts +510 -0
- package/src/ultraplan/authoring/model.ts +162 -0
- package/src/ultraplan/authoring/pipeline.ts +319 -0
- package/src/ultraplan/authoring/stage-runner.ts +141 -0
- package/src/ultraplan/authoring/stages/approve.ts +249 -0
- package/src/ultraplan/authoring/stages/discover.ts +289 -0
- package/src/ultraplan/authoring/stages/intake.ts +203 -0
- package/src/ultraplan/authoring/stages/research.ts +399 -0
- package/src/ultraplan/authoring/stages/review.ts +333 -0
- package/src/ultraplan/authoring/stages/scout.ts +188 -0
- package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
- package/src/ultraplan/authoring/storage.ts +594 -0
- package/src/ultraplan/authoring/synth-gate.ts +165 -0
- package/src/ultraplan/authoring-draft.ts +653 -0
- package/src/ultraplan/authoring-persist.ts +180 -0
- package/src/ultraplan/authoring-tool.ts +608 -0
- package/src/ultraplan/authoring-wizard.ts +587 -0
- package/src/ultraplan/batch/merge.ts +98 -0
- package/src/ultraplan/batch/planner.ts +150 -0
- package/src/ultraplan/batch/presenter.ts +97 -0
- package/src/ultraplan/batch/storage.ts +420 -0
- package/src/ultraplan/batch/supervisor.ts +317 -0
- package/src/ultraplan/batch/worker.ts +26 -0
- package/src/ultraplan/batch/worktree.ts +110 -0
- package/src/ultraplan/contracts.ts +1593 -0
- package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
- package/src/ultraplan/default-agents/authoring/intake.md +12 -0
- package/src/ultraplan/default-agents/authoring/planner.md +12 -0
- package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
- package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/scout.md +12 -0
- package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
- package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-executor.md +10 -0
- package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-tester.md +10 -0
- package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-executor.md +10 -0
- package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-tester.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
- package/src/ultraplan/execution/contract.ts +71 -0
- package/src/ultraplan/execution/policy.ts +217 -0
- package/src/ultraplan/execution/runtime-tools.ts +107 -0
- package/src/ultraplan/execution/session-runner.ts +281 -0
- package/src/ultraplan/next-router.ts +85 -0
- package/src/ultraplan/presenter.ts +359 -0
- package/src/ultraplan/project-paths.ts +342 -0
- package/src/ultraplan/runtime/active-execution.ts +72 -0
- package/src/ultraplan/runtime/apply-mutation.ts +416 -0
- package/src/ultraplan/runtime/blockers.ts +243 -0
- package/src/ultraplan/runtime/hook-bridge.ts +486 -0
- package/src/ultraplan/runtime/launch-context.ts +207 -0
- package/src/ultraplan/runtime/migration.ts +524 -0
- package/src/ultraplan/runtime/normalize.ts +281 -0
- package/src/ultraplan/runtime/proof.ts +260 -0
- package/src/ultraplan/runtime/reducer.ts +416 -0
- package/src/ultraplan/runtime/repair.ts +251 -0
- package/src/ultraplan/runtime/tracker-storage.ts +368 -0
- package/src/ultraplan/session-selection.ts +291 -0
- package/src/ultraplan/storage.ts +374 -0
- package/src/utils/editor.ts +38 -0
- package/src/utils/executable.ts +80 -0
- package/src/utils/paths.ts +1 -20
- package/src/utils/shell.ts +31 -0
- package/src/visual/companion.ts +2 -1
- package/src/visual/scripts/frame-template.html +60 -0
- package/src/visual/scripts/index.js +59 -13
- package/src/visual/scripts/package.json +3 -0
- package/src/visual/start-server.ts +2 -1
- package/src/workspace/git-scope.ts +64 -0
- package/src/workspace/locks.ts +23 -0
- package/src/workspace/package-manager.ts +117 -0
- package/src/workspace/path-mapping.ts +75 -0
- package/src/workspace/project-slug.ts +92 -0
- package/src/workspace/repo-root.ts +137 -0
- package/src/workspace/selector.ts +115 -0
- package/src/workspace/state-paths.ts +118 -0
- package/src/workspace/targets.ts +313 -0
- package/src/fix-pr/scripts/diff-comments.sh +0 -33
- package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
- package/src/fix-pr/scripts/trigger-review.sh +0 -36
- package/src/fix-pr/scripts/wait-and-check.sh +0 -37
- package/src/qa/scripts/detect-app-type.sh +0 -68
- package/src/qa/scripts/discover-routes.sh +0 -143
- package/src/qa/scripts/run-e2e-tests.sh +0 -131
- package/src/qa/scripts/start-dev-server.sh +0 -46
- package/src/qa/scripts/stop-dev-server.sh +0 -36
- package/src/review/prompts/fix-output-schema.md +0 -18
- package/src/review/prompts/review-output-schema.md +0 -38
- package/src/review/template.ts +0 -15
- /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
package/src/platform/omp.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
ExecResult,
|
|
7
7
|
} from "./types.js";
|
|
8
8
|
import { createPaths } from "./types.js";
|
|
9
|
+
import { normalizeSystemPromptBlocks } from "./system-prompt.js";
|
|
9
10
|
|
|
10
11
|
async function execWithEnv(cmd: string, args: string[], opts: ExecOptions): Promise<ExecResult> {
|
|
11
12
|
const subprocess = Bun.spawn([cmd, ...args], {
|
|
@@ -50,6 +51,8 @@ export function createOmpAdapter(api: any): Platform {
|
|
|
50
51
|
registerCommand: (name, opts) => api.registerCommand(name, opts),
|
|
51
52
|
getCommands: () => api.getCommands(),
|
|
52
53
|
getActiveTools: () => api.getActiveTools(),
|
|
54
|
+
getAllTools:
|
|
55
|
+
typeof api.getAllTools === "function" ? () => api.getAllTools() : undefined,
|
|
53
56
|
exec: (cmd, args, opts) =>
|
|
54
57
|
opts?.env ? execWithEnv(cmd, args, opts) : api.exec(cmd, args, opts),
|
|
55
58
|
sendMessage: (content, opts) => {
|
|
@@ -105,14 +108,18 @@ export function createOmpAdapter(api: any): Platform {
|
|
|
105
108
|
// Fix: extract `model` from opts and pass it as `modelPattern` (string field
|
|
106
109
|
// that OMP resolves after extension models are registered).
|
|
107
110
|
const { model, ...restOpts } = opts;
|
|
111
|
+
const sessionOpts = { ...restOpts };
|
|
112
|
+
if ("systemPrompt" in sessionOpts) {
|
|
113
|
+
sessionOpts.systemPrompt = normalizeSystemPromptBlocks(sessionOpts.systemPrompt);
|
|
114
|
+
}
|
|
108
115
|
const { session } = await createAgentSession({
|
|
109
|
-
cwd:
|
|
116
|
+
cwd: sessionOpts.cwd ?? process.cwd(),
|
|
110
117
|
hasUI: false,
|
|
111
118
|
disableExtensionDiscovery: true,
|
|
112
119
|
skills: [],
|
|
113
120
|
promptTemplates: [],
|
|
114
121
|
slashCommands: [],
|
|
115
|
-
...
|
|
122
|
+
...sessionOpts,
|
|
116
123
|
// Only pass modelPattern when we have an explicit model override.
|
|
117
124
|
// When undefined, OMP uses its own default (user's configured model).
|
|
118
125
|
...(model ? { modelPattern: model } : {}),
|
|
@@ -126,6 +133,10 @@ export function createOmpAdapter(api: any): Platform {
|
|
|
126
133
|
},
|
|
127
134
|
|
|
128
135
|
registerTool: api.registerTool ? (definition: any) => api.registerTool(definition) : undefined,
|
|
136
|
+
setActiveTools:
|
|
137
|
+
typeof api.setActiveTools === "function"
|
|
138
|
+
? (names: string[]) => api.setActiveTools(names)
|
|
139
|
+
: undefined,
|
|
129
140
|
|
|
130
141
|
paths: createPaths(".omp"),
|
|
131
142
|
capabilities: {
|
|
@@ -133,6 +144,8 @@ export function createOmpAdapter(api: any): Platform {
|
|
|
133
144
|
compactionHooks: true,
|
|
134
145
|
customWidgets: true,
|
|
135
146
|
registerTool: typeof api.registerTool === "function",
|
|
147
|
+
activeToolFiltering:
|
|
148
|
+
typeof api.getAllTools === "function" && typeof api.setActiveTools === "function",
|
|
136
149
|
},
|
|
137
150
|
};
|
|
138
151
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type SystemPromptBlocks = string[];
|
|
2
|
+
|
|
3
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
4
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
5
|
+
? (value as Record<string, unknown>)
|
|
6
|
+
: null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function normalizeSystemPromptBlocks(value: unknown): SystemPromptBlocks {
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
return value.filter((block): block is string => typeof block === "string" && block.length > 0);
|
|
12
|
+
}
|
|
13
|
+
if (typeof value === "string") {
|
|
14
|
+
return value.length > 0 ? [value] : [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const record = asRecord(value);
|
|
18
|
+
if (record && "systemPrompt" in record) {
|
|
19
|
+
return normalizeSystemPromptBlocks(record.systemPrompt);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function systemPromptText(value: unknown): string {
|
|
26
|
+
return normalizeSystemPromptBlocks(value).join("\n\n");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function appendSystemPromptBlock(value: unknown, block: string): SystemPromptBlocks {
|
|
30
|
+
const blocks = normalizeSystemPromptBlocks(value);
|
|
31
|
+
return block.length > 0 ? [...blocks, block] : blocks;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function prependSystemPromptBlock(value: unknown, block: string): SystemPromptBlocks {
|
|
35
|
+
const blocks = normalizeSystemPromptBlocks(value);
|
|
36
|
+
return block.length > 0 ? [block, ...blocks] : blocks;
|
|
37
|
+
}
|
|
@@ -12,11 +12,13 @@ export function createMockPlatform(overrides?: Partial<Platform>): Platform {
|
|
|
12
12
|
registerCommand: mock(),
|
|
13
13
|
getCommands: mock(() => []),
|
|
14
14
|
getActiveTools: mock(() => []),
|
|
15
|
+
getAllTools: mock(() => []),
|
|
15
16
|
exec: mock(async () => ({ stdout: "", stderr: "", code: 0 })),
|
|
16
17
|
sendMessage: mock(),
|
|
17
18
|
sendUserMessage: mock(),
|
|
18
19
|
registerMessageRenderer: mock(),
|
|
19
20
|
on: mock(),
|
|
21
|
+
setActiveTools: mock(async () => {}),
|
|
20
22
|
createAgentSession: mock(async () => ({
|
|
21
23
|
subscribe: mock(() => () => {}),
|
|
22
24
|
prompt: mock(async () => {}),
|
|
@@ -29,6 +31,7 @@ export function createMockPlatform(overrides?: Partial<Platform>): Platform {
|
|
|
29
31
|
compactionHooks: true,
|
|
30
32
|
customWidgets: true,
|
|
31
33
|
registerTool: true,
|
|
34
|
+
activeToolFiltering: true,
|
|
32
35
|
},
|
|
33
36
|
...overrides,
|
|
34
37
|
};
|
package/src/platform/types.ts
CHANGED
|
@@ -37,6 +37,7 @@ export interface PlatformCapabilities {
|
|
|
37
37
|
compactionHooks: boolean;
|
|
38
38
|
customWidgets: boolean;
|
|
39
39
|
registerTool: boolean;
|
|
40
|
+
activeToolFiltering: boolean;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
// ── Agent Sessions ─────────────────────────────────────────
|
|
@@ -47,6 +48,9 @@ export interface AgentSessionOptions {
|
|
|
47
48
|
parentTaskPrefix?: string;
|
|
48
49
|
model?: string;
|
|
49
50
|
thinkingLevel?: string | null;
|
|
51
|
+
systemPrompt?: string[];
|
|
52
|
+
agentId?: string;
|
|
53
|
+
agentDisplayName?: string;
|
|
50
54
|
[key: string]: unknown;
|
|
51
55
|
}
|
|
52
56
|
|
|
@@ -130,10 +134,11 @@ export interface Platform {
|
|
|
130
134
|
|
|
131
135
|
// Introspection
|
|
132
136
|
getActiveTools(): string[];
|
|
137
|
+
getAllTools?(): string[];
|
|
133
138
|
|
|
134
139
|
// Tool registration
|
|
135
140
|
registerTool?(definition: any): void;
|
|
136
|
-
setActiveTools?(names: string[]): void
|
|
141
|
+
setActiveTools?(names: string[]): Promise<void>;
|
|
137
142
|
|
|
138
143
|
// Rendering
|
|
139
144
|
registerMessageRenderer<T>(type: string, renderer: any): void;
|
package/src/qa/config.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { E2eQaConfig } from "./types.js";
|
|
4
3
|
import type { PlatformPaths } from "../platform/types.js";
|
|
4
|
+
import type { WorkspaceTarget } from "../types.js";
|
|
5
|
+
import { getLocalTargetStatePath } from "../workspace/state-paths.js"
|
|
6
|
+
import type { E2eQaConfig } from "./types.js";
|
|
5
7
|
|
|
6
8
|
const CONFIG_FILENAME = "e2e-qa.json";
|
|
7
9
|
|
|
8
|
-
function getConfigPath(paths: PlatformPaths, cwd: string): string {
|
|
10
|
+
function getConfigPath(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): string {
|
|
11
|
+
if (target) {
|
|
12
|
+
return getLocalTargetStatePath(paths, target, CONFIG_FILENAME);
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
return paths.project(cwd, CONFIG_FILENAME);
|
|
10
16
|
}
|
|
11
17
|
|
|
@@ -26,8 +32,8 @@ export const DEFAULT_E2E_QA_CONFIG: E2eQaConfig = {
|
|
|
26
32
|
},
|
|
27
33
|
};
|
|
28
34
|
|
|
29
|
-
export function loadE2eQaConfig(paths: PlatformPaths, cwd: string): E2eQaConfig | null {
|
|
30
|
-
const configPath = getConfigPath(paths, cwd);
|
|
35
|
+
export function loadE2eQaConfig(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): E2eQaConfig | null {
|
|
36
|
+
const configPath = getConfigPath(paths, cwd, target);
|
|
31
37
|
if (!fs.existsSync(configPath)) return null;
|
|
32
38
|
try {
|
|
33
39
|
return JSON.parse(fs.readFileSync(configPath, "utf-8")) as E2eQaConfig;
|
|
@@ -36,8 +42,8 @@ export function loadE2eQaConfig(paths: PlatformPaths, cwd: string): E2eQaConfig
|
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
export function saveE2eQaConfig(paths: PlatformPaths, cwd: string, config: E2eQaConfig): void {
|
|
40
|
-
const configPath = getConfigPath(paths, cwd);
|
|
45
|
+
export function saveE2eQaConfig(paths: PlatformPaths, cwd: string, config: E2eQaConfig, target?: WorkspaceTarget): void {
|
|
46
|
+
const configPath = getConfigPath(paths, cwd, target);
|
|
41
47
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
42
48
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
43
49
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import type { AppType } from "./types.js";
|
|
3
4
|
|
|
4
5
|
export interface AppDetection {
|
|
5
|
-
type:
|
|
6
|
+
type: AppType;
|
|
6
7
|
devCommand: string;
|
|
7
8
|
port: number;
|
|
8
9
|
baseUrl: string;
|
|
10
|
+
isLikelyApp: boolean;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
function fileExists(cwd: string, ...segments: string[]): boolean {
|
|
@@ -29,9 +31,10 @@ function readJson(filePath: string): Record<string, unknown> | null {
|
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export function detectAppType(cwd: string): AppDetection {
|
|
32
|
-
let type = "generic";
|
|
34
|
+
let type: AppType = "generic";
|
|
33
35
|
let devCommand = "npm run dev";
|
|
34
36
|
let port = 3000;
|
|
37
|
+
let isLikelyApp = false;
|
|
35
38
|
|
|
36
39
|
const hasNextConfig =
|
|
37
40
|
fileExists(cwd, "next.config.js") ||
|
|
@@ -41,7 +44,6 @@ export function detectAppType(cwd: string): AppDetection {
|
|
|
41
44
|
const hasNextPagesDir = dirExists(cwd, "pages") || dirExists(cwd, "src", "pages");
|
|
42
45
|
|
|
43
46
|
if (hasNextConfig || hasNextAppDir || hasNextPagesDir) {
|
|
44
|
-
// Default nextjs-app; only switch to pages if pages dir exists and app dir does not
|
|
45
47
|
if (hasNextAppDir) {
|
|
46
48
|
type = "nextjs-app";
|
|
47
49
|
} else if (hasNextPagesDir) {
|
|
@@ -50,6 +52,7 @@ export function detectAppType(cwd: string): AppDetection {
|
|
|
50
52
|
type = "nextjs-app";
|
|
51
53
|
}
|
|
52
54
|
port = 3000;
|
|
55
|
+
isLikelyApp = true;
|
|
53
56
|
} else if (
|
|
54
57
|
fileExists(cwd, "vite.config.ts") ||
|
|
55
58
|
fileExists(cwd, "vite.config.js") ||
|
|
@@ -57,36 +60,40 @@ export function detectAppType(cwd: string): AppDetection {
|
|
|
57
60
|
) {
|
|
58
61
|
type = "vite";
|
|
59
62
|
port = 5173;
|
|
63
|
+
isLikelyApp = true;
|
|
60
64
|
} else if (fileExists(cwd, "angular.json")) {
|
|
61
65
|
type = "generic";
|
|
62
66
|
devCommand = "npm start";
|
|
63
67
|
port = 4200;
|
|
68
|
+
isLikelyApp = true;
|
|
64
69
|
} else if (fileExists(cwd, "package.json")) {
|
|
65
70
|
try {
|
|
66
71
|
const raw = fs.readFileSync(path.join(cwd, "package.json"), "utf8");
|
|
67
72
|
if (raw.includes('"express"')) {
|
|
68
73
|
type = "express";
|
|
69
74
|
port = 3000;
|
|
75
|
+
isLikelyApp = true;
|
|
70
76
|
}
|
|
71
77
|
} catch {
|
|
72
78
|
// proceed with defaults
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
// Override devCommand from package.json scripts
|
|
77
82
|
const pkgPath = path.join(cwd, "package.json");
|
|
78
83
|
const pkg = readJson(pkgPath);
|
|
79
84
|
if (pkg) {
|
|
80
85
|
const scripts = pkg.scripts as Record<string, string> | undefined;
|
|
81
86
|
if (scripts?.dev) {
|
|
82
87
|
devCommand = "npm run dev";
|
|
88
|
+
isLikelyApp = true;
|
|
83
89
|
} else if (scripts?.start) {
|
|
84
90
|
devCommand = "npm start";
|
|
91
|
+
isLikelyApp = true;
|
|
85
92
|
} else if (scripts?.serve) {
|
|
86
93
|
devCommand = "npm run serve";
|
|
94
|
+
isLikelyApp = true;
|
|
87
95
|
}
|
|
88
96
|
|
|
89
|
-
// Extract port from dev script text
|
|
90
97
|
const scriptText = scripts?.dev ?? scripts?.start ?? "";
|
|
91
98
|
if (scriptText) {
|
|
92
99
|
const portMatch = scriptText.match(/(?:--port|PORT=)\s*(\d+)/);
|
|
@@ -98,5 +105,5 @@ export function detectAppType(cwd: string): AppDetection {
|
|
|
98
105
|
|
|
99
106
|
const baseUrl = `http://localhost:${port}`;
|
|
100
107
|
|
|
101
|
-
return { type, devCommand, port, baseUrl };
|
|
108
|
+
return { type, devCommand, port, baseUrl, isLikelyApp };
|
|
102
109
|
}
|
package/src/qa/matrix.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { E2eMatrix, E2eFlowRecord, E2eTestResult, E2eRegression } from "./types.js";
|
|
4
3
|
import type { PlatformPaths } from "../platform/types.js";
|
|
4
|
+
import type { WorkspaceTarget } from "../types.js";
|
|
5
|
+
import { getLocalTargetStatePath } from "../workspace/state-paths.js"
|
|
6
|
+
import type { E2eFlowRecord, E2eMatrix, E2eRegression, E2eTestResult } from "./types.js";
|
|
5
7
|
|
|
6
8
|
const MATRIX_FILENAME = "e2e-matrix.json";
|
|
7
9
|
|
|
8
|
-
function getMatrixPath(paths: PlatformPaths, cwd: string): string {
|
|
10
|
+
function getMatrixPath(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): string {
|
|
11
|
+
if (target) {
|
|
12
|
+
return getLocalTargetStatePath(paths, target, MATRIX_FILENAME);
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
return paths.project(cwd, MATRIX_FILENAME);
|
|
10
16
|
}
|
|
11
17
|
|
|
@@ -18,8 +24,8 @@ export function createEmptyMatrix(appType: string): E2eMatrix {
|
|
|
18
24
|
};
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
export function loadE2eMatrix(paths: PlatformPaths, cwd: string): E2eMatrix | null {
|
|
22
|
-
const matrixPath = getMatrixPath(paths, cwd);
|
|
27
|
+
export function loadE2eMatrix(paths: PlatformPaths, cwd: string, target?: WorkspaceTarget): E2eMatrix | null {
|
|
28
|
+
const matrixPath = getMatrixPath(paths, cwd, target);
|
|
23
29
|
if (!fs.existsSync(matrixPath)) return null;
|
|
24
30
|
try {
|
|
25
31
|
return JSON.parse(fs.readFileSync(matrixPath, "utf-8")) as E2eMatrix;
|
|
@@ -28,8 +34,8 @@ export function loadE2eMatrix(paths: PlatformPaths, cwd: string): E2eMatrix | nu
|
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
export function saveE2eMatrix(paths: PlatformPaths, cwd: string, matrix: E2eMatrix): void {
|
|
32
|
-
const matrixPath = getMatrixPath(paths, cwd);
|
|
37
|
+
export function saveE2eMatrix(paths: PlatformPaths, cwd: string, matrix: E2eMatrix, target?: WorkspaceTarget): void {
|
|
38
|
+
const matrixPath = getMatrixPath(paths, cwd, target);
|
|
33
39
|
fs.mkdirSync(path.dirname(matrixPath), { recursive: true });
|
|
34
40
|
fs.writeFileSync(matrixPath, JSON.stringify(matrix, null, 2));
|
|
35
41
|
}
|
package/src/qa/prompt-builder.ts
CHANGED
|
@@ -13,7 +13,17 @@ export interface E2ePromptOptions {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function buildE2eOrchestratorPrompt(options: E2ePromptOptions): string {
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
cwd,
|
|
18
|
+
appType,
|
|
19
|
+
sessionDir,
|
|
20
|
+
scriptsDir,
|
|
21
|
+
config,
|
|
22
|
+
discoveredRoutes,
|
|
23
|
+
previousMatrix,
|
|
24
|
+
skillContent,
|
|
25
|
+
dotDirDisplay,
|
|
26
|
+
} = options;
|
|
17
27
|
const { playwright, execution } = config;
|
|
18
28
|
|
|
19
29
|
const headedFlag = playwright.headless ? "" : " --headed";
|
|
@@ -22,7 +32,7 @@ export function buildE2eOrchestratorPrompt(options: E2ePromptOptions): string {
|
|
|
22
32
|
"# E2E QA Pipeline — Autonomous Execution",
|
|
23
33
|
"",
|
|
24
34
|
`You are an autonomous E2E QA pipeline for a **${appType.type}** application.`,
|
|
25
|
-
"Run all phases sequentially without stopping. Use `playwright-cli` for browser interactions and the provided
|
|
35
|
+
"Run all phases sequentially without stopping. Use `playwright-cli` for browser interactions and the provided Bun runners for execution.",
|
|
26
36
|
"",
|
|
27
37
|
"## Session Context",
|
|
28
38
|
"",
|
|
@@ -37,7 +47,6 @@ export function buildE2eOrchestratorPrompt(options: E2ePromptOptions): string {
|
|
|
37
47
|
"",
|
|
38
48
|
];
|
|
39
49
|
|
|
40
|
-
// Previous matrix
|
|
41
50
|
if (previousMatrix) {
|
|
42
51
|
sections.push(
|
|
43
52
|
"## Previous Matrix",
|
|
@@ -53,7 +62,6 @@ export function buildE2eOrchestratorPrompt(options: E2ePromptOptions): string {
|
|
|
53
62
|
);
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
// Route hints (not authoritative — playwright-cli exploration is the source of truth)
|
|
57
65
|
sections.push(
|
|
58
66
|
"## Route Hints",
|
|
59
67
|
"",
|
|
@@ -65,7 +73,6 @@ export function buildE2eOrchestratorPrompt(options: E2ePromptOptions): string {
|
|
|
65
73
|
"",
|
|
66
74
|
);
|
|
67
75
|
|
|
68
|
-
// Skill content
|
|
69
76
|
if (skillContent) {
|
|
70
77
|
sections.push(
|
|
71
78
|
"## E2E Testing Methodology",
|
|
@@ -75,7 +82,6 @@ export function buildE2eOrchestratorPrompt(options: E2ePromptOptions): string {
|
|
|
75
82
|
);
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
// Step 1: Flow Discovery — interactive exploration via playwright-cli
|
|
79
85
|
sections.push(
|
|
80
86
|
"## Step 1: Flow Discovery",
|
|
81
87
|
"",
|
|
@@ -105,7 +111,6 @@ export function buildE2eOrchestratorPrompt(options: E2ePromptOptions): string {
|
|
|
105
111
|
"",
|
|
106
112
|
);
|
|
107
113
|
|
|
108
|
-
// Step 2: Test Generation
|
|
109
114
|
sections.push(
|
|
110
115
|
"## Step 2: Test Generation",
|
|
111
116
|
"",
|
|
@@ -118,7 +123,7 @@ export function buildE2eOrchestratorPrompt(options: E2ePromptOptions): string {
|
|
|
118
123
|
" - Use `expect(page).toHaveURL()`, `expect(locator).toBeVisible()` for assertions",
|
|
119
124
|
" - Use `page.waitForSelector()` or `expect(locator).toBeVisible()` before assertions — never use `networkidle` (unreliable for SPAs)",
|
|
120
125
|
" - Set meaningful test descriptions that describe the user journey",
|
|
121
|
-
|
|
126
|
+
"4. Import from `playwright/test`",
|
|
122
127
|
`5. Each test should start with \`await page.goto('${appType.baseUrl}/...')\``,
|
|
123
128
|
"",
|
|
124
129
|
"Example test structure:",
|
|
@@ -138,23 +143,22 @@ export function buildE2eOrchestratorPrompt(options: E2ePromptOptions): string {
|
|
|
138
143
|
"",
|
|
139
144
|
);
|
|
140
145
|
|
|
141
|
-
// Step 3: Execution
|
|
142
146
|
sections.push(
|
|
143
147
|
"## Step 3: Execution",
|
|
144
148
|
"",
|
|
145
149
|
"Run the generated tests:",
|
|
146
150
|
"",
|
|
147
|
-
|
|
148
|
-
"```
|
|
149
|
-
`
|
|
151
|
+
"1. Start the dev server:",
|
|
152
|
+
"```text",
|
|
153
|
+
`bun \"${scriptsDir}/start-dev-server.ts\" \"${cwd}\" \"${appType.devCommand}\" ${appType.port} 60 \"${sessionDir}\"`,
|
|
150
154
|
"```",
|
|
151
155
|
" Read the JSON output. If `ready: false`, stop and report the error.",
|
|
152
156
|
"",
|
|
153
|
-
"2. Run the tests — **IMPORTANT: never run playwright directly. Always use the
|
|
154
|
-
"```
|
|
155
|
-
`
|
|
157
|
+
"2. Run the tests — **IMPORTANT: never run playwright directly. Always use the runner:**",
|
|
158
|
+
"```text",
|
|
159
|
+
`bun \"${scriptsDir}/run-e2e-tests.ts\" \"${sessionDir}/tests\" \"${appType.baseUrl}\"`,
|
|
156
160
|
"```",
|
|
157
|
-
" Exit codes: 0 = all passed, 2 = some tests failed, 1 =
|
|
161
|
+
" Exit codes: 0 = all passed, 2 = some tests failed, 1 = runner/playwright error.",
|
|
158
162
|
" Read the JSON output. It contains `total`, `passed`, `failed`, `failures[]`.",
|
|
159
163
|
"",
|
|
160
164
|
`3. If there are failures and retries remain (max ${execution.maxRetries}):`,
|
|
@@ -164,13 +168,12 @@ export function buildE2eOrchestratorPrompt(options: E2ePromptOptions): string {
|
|
|
164
168
|
" - If real app bug: note it for the report",
|
|
165
169
|
"",
|
|
166
170
|
"4. Stop the dev server:",
|
|
167
|
-
"```
|
|
168
|
-
`
|
|
171
|
+
"```text",
|
|
172
|
+
`bun \"${scriptsDir}/stop-dev-server.ts\" \"${sessionDir}\"`,
|
|
169
173
|
"```",
|
|
170
174
|
"",
|
|
171
175
|
);
|
|
172
176
|
|
|
173
|
-
// Step 4: Regression Analysis & Reporting
|
|
174
177
|
sections.push(
|
|
175
178
|
"## Step 4: Regression Analysis & Reporting",
|
|
176
179
|
"",
|
|
@@ -195,33 +198,28 @@ export function buildE2eOrchestratorPrompt(options: E2ePromptOptions): string {
|
|
|
195
198
|
"",
|
|
196
199
|
);
|
|
197
200
|
|
|
198
|
-
// Script paths
|
|
199
201
|
sections.push(
|
|
200
|
-
"##
|
|
202
|
+
"## Runner Paths",
|
|
201
203
|
"",
|
|
202
|
-
`-
|
|
203
|
-
`-
|
|
204
|
-
`-
|
|
205
|
-
`- run-e2e-tests.sh: \`${scriptsDir}/run-e2e-tests.sh\``,
|
|
206
|
-
`- stop-dev-server.sh: \`${scriptsDir}/stop-dev-server.sh\``,
|
|
204
|
+
`- start-dev-server.ts: \`${scriptsDir}/start-dev-server.ts\``,
|
|
205
|
+
`- run-e2e-tests.ts: \`${scriptsDir}/run-e2e-tests.ts\``,
|
|
206
|
+
`- stop-dev-server.ts: \`${scriptsDir}/stop-dev-server.ts\``,
|
|
207
207
|
"",
|
|
208
208
|
);
|
|
209
209
|
|
|
210
|
-
// Token guidance
|
|
211
210
|
sections.push(
|
|
212
211
|
"## Token Guidance",
|
|
213
212
|
"",
|
|
214
213
|
"To minimize token usage:",
|
|
215
|
-
"- Always use `run-e2e-tests.
|
|
214
|
+
"- Always use `run-e2e-tests.ts` — never run playwright directly for test execution",
|
|
216
215
|
"- Use `playwright-cli snapshot` instead of `playwright-cli screenshot` when you only need element structure",
|
|
217
216
|
"- Only read the `failures` array from test results, skip passed tests",
|
|
218
217
|
"- Don't cat full test files when analyzing failures — read only the failing line range",
|
|
219
218
|
"- Write tests incrementally by flow group, run after each group to catch issues early",
|
|
220
|
-
"- Don't dump raw playwright output — the
|
|
219
|
+
"- Don't dump raw playwright output — the runner produces a compact JSON summary",
|
|
221
220
|
"",
|
|
222
221
|
);
|
|
223
222
|
|
|
224
|
-
// Troubleshooting
|
|
225
223
|
sections.push(
|
|
226
224
|
"## Troubleshooting",
|
|
227
225
|
"",
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { getShellInvocation } from "../../utils/shell.js";
|
|
3
|
+
|
|
4
|
+
export const DEV_SERVER_PID_FILENAME = "dev-server.pid";
|
|
5
|
+
|
|
6
|
+
const HEALTHCHECK_TIMEOUT_MS = 1_000;
|
|
7
|
+
const FORCE_KILL_WAIT_MS = 250;
|
|
8
|
+
|
|
9
|
+
export async function isServerReachable(port: number): Promise<boolean> {
|
|
10
|
+
try {
|
|
11
|
+
await fetch(`http://localhost:${port}`, {
|
|
12
|
+
signal: AbortSignal.timeout(HEALTHCHECK_TIMEOUT_MS),
|
|
13
|
+
});
|
|
14
|
+
return true;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function processExists(pid: number): boolean {
|
|
21
|
+
try {
|
|
22
|
+
process.kill(pid, 0);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function sleep(ms: number): Promise<void> {
|
|
30
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function killProcessTree(pid: number): Promise<void> {
|
|
34
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (process.platform === "win32") {
|
|
39
|
+
spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
40
|
+
stdio: "ignore",
|
|
41
|
+
});
|
|
42
|
+
await sleep(FORCE_KILL_WAIT_MS);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
process.kill(-pid, "SIGTERM");
|
|
48
|
+
} catch {
|
|
49
|
+
try {
|
|
50
|
+
process.kill(pid, "SIGTERM");
|
|
51
|
+
} catch {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await sleep(1_000);
|
|
57
|
+
if (!processExists(pid)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
process.kill(-pid, "SIGKILL");
|
|
63
|
+
} catch {
|
|
64
|
+
try {
|
|
65
|
+
process.kill(pid, "SIGKILL");
|
|
66
|
+
} catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { getShellInvocation };
|