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/quality/schemas.ts
CHANGED
|
@@ -9,18 +9,53 @@ export const LspDiagnosticsGateConfigSchema = Type.Object(
|
|
|
9
9
|
{ additionalProperties: false },
|
|
10
10
|
);
|
|
11
11
|
|
|
12
|
+
export const CommandGateRunTargetSchema = Type.Union([
|
|
13
|
+
Type.Object(
|
|
14
|
+
{
|
|
15
|
+
scope: Type.Literal("all-targets"),
|
|
16
|
+
},
|
|
17
|
+
{ additionalProperties: false },
|
|
18
|
+
),
|
|
19
|
+
Type.Object(
|
|
20
|
+
{
|
|
21
|
+
scope: Type.Literal("root"),
|
|
22
|
+
},
|
|
23
|
+
{ additionalProperties: false },
|
|
24
|
+
),
|
|
25
|
+
Type.Object(
|
|
26
|
+
{
|
|
27
|
+
scope: Type.Literal("all-workspaces"),
|
|
28
|
+
},
|
|
29
|
+
{ additionalProperties: false },
|
|
30
|
+
),
|
|
31
|
+
Type.Object(
|
|
32
|
+
{
|
|
33
|
+
scope: Type.Literal("workspace"),
|
|
34
|
+
relativeDir: Type.String({ minLength: 1 }),
|
|
35
|
+
},
|
|
36
|
+
{ additionalProperties: false },
|
|
37
|
+
),
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export const CommandGateRunSchema = Type.Object(
|
|
41
|
+
{
|
|
42
|
+
command: Type.String({ minLength: 1 }),
|
|
43
|
+
target: CommandGateRunTargetSchema,
|
|
44
|
+
},
|
|
45
|
+
{ additionalProperties: false },
|
|
46
|
+
);
|
|
47
|
+
|
|
12
48
|
export const CommandGateConfigSchema = Type.Union([
|
|
13
49
|
Type.Object(
|
|
14
50
|
{
|
|
15
51
|
enabled: Type.Literal(false),
|
|
16
|
-
command: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
17
52
|
},
|
|
18
53
|
{ additionalProperties: false },
|
|
19
54
|
),
|
|
20
55
|
Type.Object(
|
|
21
56
|
{
|
|
22
57
|
enabled: Type.Literal(true),
|
|
23
|
-
|
|
58
|
+
runs: Type.Array(CommandGateRunSchema, { minItems: 1 }),
|
|
24
59
|
},
|
|
25
60
|
{ additionalProperties: false },
|
|
26
61
|
),
|
package/src/quality/setup.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { Platform, PlatformContext } from "../platform/types.js";
|
|
|
4
4
|
import type {
|
|
5
5
|
ConfigScope,
|
|
6
6
|
ProjectFacts,
|
|
7
|
+
ProjectFactsTarget,
|
|
7
8
|
QualityGatesConfig,
|
|
8
9
|
SetupGatesResult,
|
|
9
10
|
SetupProposal,
|
|
@@ -11,8 +12,10 @@ import type {
|
|
|
11
12
|
import type { InspectionLoadResult } from "../config/schema.js";
|
|
12
13
|
import { validateQualityGates } from "../config/schema.js";
|
|
13
14
|
import { writeQualityGatesConfig } from "../config/loader.js";
|
|
15
|
+
import { resolvePackageManager } from "../workspace/package-manager.js";
|
|
16
|
+
import { discoverWorkspaceTargets } from "../workspace/targets.js";
|
|
14
17
|
import { CANONICAL_GATE_ORDER } from "./registry.js";
|
|
15
|
-
import { detectReviewGates } from "./review-gates.js";
|
|
18
|
+
import { collectReviewGateNotes, detectReviewGates } from "./review-gates.js";
|
|
16
19
|
import { suggestQualityGatesWithAi } from "./ai-setup.js";
|
|
17
20
|
|
|
18
21
|
export type GateSetupMode = "deterministic" | "ai-assisted";
|
|
@@ -63,17 +66,70 @@ function detectLockfiles(cwd: string): string[] {
|
|
|
63
66
|
.filter((file) => fs.existsSync(path.join(cwd, file)));
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
function normalizeScriptCommand(command: string | undefined): string | null {
|
|
70
|
+
const trimmed = command?.trim();
|
|
71
|
+
return trimmed ? trimmed : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function collectProjectTargets(cwd: string): ProjectFactsTarget[] {
|
|
75
|
+
const packageManager = resolvePackageManager(cwd).id;
|
|
76
|
+
const targets = discoverWorkspaceTargets(cwd, packageManager);
|
|
77
|
+
if (targets.length === 0) {
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
name: path.basename(cwd) || "root",
|
|
81
|
+
kind: "root",
|
|
82
|
+
relativeDir: ".",
|
|
83
|
+
packageScripts: readPackageScripts(cwd),
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return targets.map((target) => ({
|
|
89
|
+
name: target.name,
|
|
90
|
+
kind: target.kind,
|
|
91
|
+
relativeDir: target.relativeDir,
|
|
92
|
+
packageScripts: readPackageScripts(target.packageDir),
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function collectSharedPackageScripts(targets: ProjectFactsTarget[]): Record<string, string> {
|
|
97
|
+
if (targets.length === 0) {
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const shared = { ...targets[0].packageScripts };
|
|
102
|
+
for (const scriptName of Object.keys(shared)) {
|
|
103
|
+
const command = normalizeScriptCommand(shared[scriptName]);
|
|
104
|
+
if (!command) {
|
|
105
|
+
delete shared[scriptName];
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const matchesEveryTarget = targets.slice(1).every(
|
|
110
|
+
(target) => normalizeScriptCommand(target.packageScripts[scriptName]) === command,
|
|
111
|
+
);
|
|
112
|
+
if (!matchesEveryTarget) {
|
|
113
|
+
delete shared[scriptName];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return shared;
|
|
118
|
+
}
|
|
119
|
+
|
|
66
120
|
export function collectProjectFacts(
|
|
67
121
|
cwd: string,
|
|
68
122
|
inspection: InspectionLoadResult,
|
|
69
123
|
activeTools: string[],
|
|
70
124
|
): ProjectFacts {
|
|
125
|
+
const targets = collectProjectTargets(cwd);
|
|
71
126
|
return {
|
|
72
127
|
cwd,
|
|
73
|
-
packageScripts:
|
|
128
|
+
packageScripts: collectSharedPackageScripts(targets),
|
|
74
129
|
lockfiles: detectLockfiles(cwd),
|
|
75
130
|
activeTools,
|
|
76
131
|
existingGates: inspection.effectiveConfig?.quality.gates ?? {},
|
|
132
|
+
targets,
|
|
77
133
|
};
|
|
78
134
|
}
|
|
79
135
|
|
|
@@ -87,17 +143,34 @@ export function formatGateProposal(proposal: SetupProposal): string {
|
|
|
87
143
|
const entries = CANONICAL_GATE_ORDER
|
|
88
144
|
.filter((gateId) => proposal.gates[gateId] !== undefined)
|
|
89
145
|
.map((gateId) => [gateId, proposal.gates[gateId]] as const);
|
|
146
|
+
const sections: string[] = [];
|
|
147
|
+
|
|
90
148
|
if (entries.length === 0) {
|
|
91
|
-
|
|
149
|
+
sections.push("No gates suggested.");
|
|
150
|
+
} else {
|
|
151
|
+
sections.push(
|
|
152
|
+
entries
|
|
153
|
+
.map(([gateId, config]) => `${gateId}: ${JSON.stringify(config)}`)
|
|
154
|
+
.join("\n"),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (proposal.notes && proposal.notes.length > 0) {
|
|
159
|
+
sections.push([
|
|
160
|
+
"Notes:",
|
|
161
|
+
...proposal.notes.map((note) => `- ${note}`),
|
|
162
|
+
].join("\n"));
|
|
92
163
|
}
|
|
93
164
|
|
|
94
|
-
return
|
|
95
|
-
.map(([gateId, config]) => `${gateId}: ${JSON.stringify(config)}`)
|
|
96
|
-
.join("\n");
|
|
165
|
+
return sections.join("\n\n");
|
|
97
166
|
}
|
|
98
167
|
|
|
99
168
|
export function buildDeterministicSuggestion(projectFacts: ProjectFacts): SetupProposal {
|
|
100
|
-
|
|
169
|
+
const notes = collectReviewGateNotes(projectFacts);
|
|
170
|
+
return {
|
|
171
|
+
gates: detectReviewGates(projectFacts),
|
|
172
|
+
...(notes.length > 0 ? { notes } : {}),
|
|
173
|
+
};
|
|
101
174
|
}
|
|
102
175
|
|
|
103
176
|
export async function setupGates(
|
|
@@ -122,6 +195,7 @@ export async function setupGates(
|
|
|
122
195
|
projectFacts,
|
|
123
196
|
proposal,
|
|
124
197
|
}),
|
|
198
|
+
...(proposal.notes && proposal.notes.length > 0 ? { notes: proposal.notes } : {}),
|
|
125
199
|
};
|
|
126
200
|
options.onProgress?.({ type: "ai-analysis-completed" });
|
|
127
201
|
}
|
|
@@ -141,29 +215,32 @@ export async function setupGates(
|
|
|
141
215
|
};
|
|
142
216
|
}
|
|
143
217
|
|
|
144
|
-
function parseRevisedProposal(raw: string):
|
|
218
|
+
function parseRevisedProposal(raw: string): QualityGatesConfig {
|
|
145
219
|
const parsed = JSON.parse(raw) as QualityGatesConfig;
|
|
146
220
|
const validation = validateQualityGates(parsed);
|
|
147
221
|
if (!validation.valid) {
|
|
148
222
|
throw new Error(validation.errors.join("\n"));
|
|
149
223
|
}
|
|
150
224
|
|
|
151
|
-
return
|
|
225
|
+
return parsed;
|
|
152
226
|
}
|
|
153
227
|
|
|
154
228
|
function labelForScope(scope: ConfigScope): string {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
229
|
+
switch (scope) {
|
|
230
|
+
case "global":
|
|
231
|
+
return "Global (~/.omp/supipowers/config.json)";
|
|
232
|
+
case "root":
|
|
233
|
+
return "Repository (.omp/supipowers/config.json)";
|
|
234
|
+
}
|
|
158
235
|
}
|
|
159
236
|
|
|
160
237
|
async function selectSaveScope(ctx: PlatformContext): Promise<ConfigScope | null> {
|
|
161
238
|
const choice = await ctx.ui.select(
|
|
162
239
|
"Save quality gates to",
|
|
163
|
-
[labelForScope("
|
|
240
|
+
[labelForScope("root"), labelForScope("global"), "Cancel"],
|
|
164
241
|
{
|
|
165
242
|
initialIndex: 0,
|
|
166
|
-
helpText: "Choose whether review gates apply only to this
|
|
243
|
+
helpText: "Choose whether review gates apply only to this repository or all repositories.",
|
|
167
244
|
},
|
|
168
245
|
);
|
|
169
246
|
|
|
@@ -171,7 +248,7 @@ async function selectSaveScope(ctx: PlatformContext): Promise<ConfigScope | null
|
|
|
171
248
|
return null;
|
|
172
249
|
}
|
|
173
250
|
|
|
174
|
-
return choice === labelForScope("global") ? "global" : "
|
|
251
|
+
return choice === labelForScope("global") ? "global" : "root";
|
|
175
252
|
}
|
|
176
253
|
|
|
177
254
|
function buildProposalHelpText(proposal: SetupProposal, options?: GateSetupDialogOptions): string {
|
|
@@ -209,7 +286,10 @@ export async function interactivelySaveGateSetup(
|
|
|
209
286
|
}
|
|
210
287
|
|
|
211
288
|
try {
|
|
212
|
-
proposal =
|
|
289
|
+
proposal = {
|
|
290
|
+
...proposal,
|
|
291
|
+
gates: parseRevisedProposal(revised),
|
|
292
|
+
};
|
|
213
293
|
} catch (error) {
|
|
214
294
|
ctx.ui.notify((error as Error).message, "error");
|
|
215
295
|
}
|
package/src/release/changelog.ts
CHANGED
|
@@ -57,7 +57,7 @@ function parseGitLogWithFiles(gitLog: string): GitCommitWithFiles[] {
|
|
|
57
57
|
|
|
58
58
|
function isPathInReleaseScope(filePath: string, releaseScope: string[]): boolean {
|
|
59
59
|
const normalizedFile = normalizeReleasePath(filePath);
|
|
60
|
-
return releaseScope.some((scopePath) => normalizedFile === scopePath || normalizedFile.startsWith(`${scopePath}/`));
|
|
60
|
+
return releaseScope.some((scopePath) => scopePath === "." || normalizedFile === scopePath || normalizedFile.startsWith(`${scopePath}/`));
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/release/channels/custom.ts — Wraps user-defined custom channel config into a ChannelHandler
|
|
2
2
|
import type { CustomChannelConfig } from "../../types.js";
|
|
3
|
+
import { execShellCommand } from "../../utils/shell.js";
|
|
3
4
|
import type { ChannelHandler, ChannelPublishContext, ChannelStatus, ExecFn } from "./types.js";
|
|
4
5
|
|
|
5
6
|
|
|
@@ -13,7 +14,7 @@ export function createCustomHandler(id: string, config: CustomChannelConfig): Ch
|
|
|
13
14
|
return { channel: id, available: true, detail: "No detect command configured — assumed available" };
|
|
14
15
|
}
|
|
15
16
|
try {
|
|
16
|
-
const result = await exec
|
|
17
|
+
const result = await execShellCommand(exec, config.detectCommand, { cwd });
|
|
17
18
|
if (result.code === 0) {
|
|
18
19
|
return { channel: id, available: true, detail: `Detect command succeeded` };
|
|
19
20
|
}
|
|
@@ -27,9 +28,18 @@ export function createCustomHandler(id: string, config: CustomChannelConfig): Ch
|
|
|
27
28
|
try {
|
|
28
29
|
// `${tag}`/`${version}`/`${changelog}` stay in the shell template, but
|
|
29
30
|
// their values cross the shell boundary via environment variables.
|
|
30
|
-
const result = await exec
|
|
31
|
+
const result = await execShellCommand(exec, config.publishCommand, {
|
|
31
32
|
cwd: ctx.cwd,
|
|
32
|
-
env: {
|
|
33
|
+
env: {
|
|
34
|
+
tag: ctx.tag,
|
|
35
|
+
version: ctx.version,
|
|
36
|
+
changelog: ctx.changelog,
|
|
37
|
+
targetName: ctx.targetName,
|
|
38
|
+
targetId: ctx.targetId,
|
|
39
|
+
targetPath: ctx.targetPath,
|
|
40
|
+
manifestPath: ctx.manifestPath,
|
|
41
|
+
packageManager: ctx.packageManager,
|
|
42
|
+
},
|
|
33
43
|
});
|
|
34
44
|
if (result.code !== 0) {
|
|
35
45
|
return { success: false, error: result.stderr || result.stdout || `Custom channel '${id}' exited with code ${result.code}` };
|
|
@@ -17,6 +17,11 @@ export interface ChannelPublishContext {
|
|
|
17
17
|
tag: string;
|
|
18
18
|
changelog: string;
|
|
19
19
|
cwd: string;
|
|
20
|
+
targetName: string;
|
|
21
|
+
targetId: string;
|
|
22
|
+
targetPath: string;
|
|
23
|
+
manifestPath: string;
|
|
24
|
+
packageManager: string;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
export interface ChannelHandler {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/release/contracts.ts
|
|
2
|
+
//
|
|
3
|
+
// Schema-backed contracts for AI-assisted release subflows. The release
|
|
4
|
+
// command has two AI sessions that must not degrade into free-form text:
|
|
5
|
+
//
|
|
6
|
+
// 1. Release-note polish — the model rewrites the raw changelog into
|
|
7
|
+
// user-facing release notes. Returns a structured artifact rather
|
|
8
|
+
// than arbitrary markdown so the caller can reason about title,
|
|
9
|
+
// body, and highlights independently.
|
|
10
|
+
// 2. Release doc-fix — when doc-drift is detected before a release,
|
|
11
|
+
// the fixer agent applies documentation edits and reports back a
|
|
12
|
+
// structured summary of what it changed (or declares `blocked`).
|
|
13
|
+
//
|
|
14
|
+
// Both schemas flow through runWithOutputValidation so the retry loop
|
|
15
|
+
// hands validation errors back to the model rather than letting the
|
|
16
|
+
// release command publish on malformed output.
|
|
17
|
+
|
|
18
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
19
|
+
|
|
20
|
+
// ── Release-note polish ───────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export const RELEASE_NOTE_STATUSES = ["ok", "empty"] as const;
|
|
23
|
+
export type ReleaseNoteStatus = (typeof RELEASE_NOTE_STATUSES)[number];
|
|
24
|
+
|
|
25
|
+
export const ReleaseNotePolishOutputSchema = Type.Object(
|
|
26
|
+
{
|
|
27
|
+
title: Type.String({ minLength: 1 }),
|
|
28
|
+
body: Type.String(),
|
|
29
|
+
highlights: Type.Array(Type.String({ minLength: 1 })),
|
|
30
|
+
status: Type.Union(RELEASE_NOTE_STATUSES.map((value) => Type.Literal(value))),
|
|
31
|
+
},
|
|
32
|
+
{ additionalProperties: false },
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export type ReleaseNotePolishOutput = Static<typeof ReleaseNotePolishOutputSchema>;
|
|
36
|
+
|
|
37
|
+
// ── Release doc-fix ───────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
export const RELEASE_DOC_FIX_STATUSES = ["ok", "blocked"] as const;
|
|
40
|
+
export type ReleaseDocFixStatus = (typeof RELEASE_DOC_FIX_STATUSES)[number];
|
|
41
|
+
|
|
42
|
+
export const ReleaseDocFixEditSchema = Type.Object(
|
|
43
|
+
{
|
|
44
|
+
file: Type.String({ minLength: 1 }),
|
|
45
|
+
instructions: Type.String({ minLength: 1 }),
|
|
46
|
+
},
|
|
47
|
+
{ additionalProperties: false },
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
export const ReleaseDocFixOutputSchema = Type.Object(
|
|
51
|
+
{
|
|
52
|
+
edits: Type.Array(ReleaseDocFixEditSchema),
|
|
53
|
+
summary: Type.String({ minLength: 1 }),
|
|
54
|
+
status: Type.Union(RELEASE_DOC_FIX_STATUSES.map((value) => Type.Literal(value))),
|
|
55
|
+
},
|
|
56
|
+
{ additionalProperties: false },
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
export type ReleaseDocFixEdit = Static<typeof ReleaseDocFixEditSchema>;
|
|
60
|
+
export type ReleaseDocFixOutput = Static<typeof ReleaseDocFixOutputSchema>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Render a polished release-note artifact as markdown suitable for
|
|
64
|
+
* surface to the user and execution by the release pipeline. Produces a
|
|
65
|
+
* level-2 title, optional highlight bullets, and the grouped body.
|
|
66
|
+
*/
|
|
67
|
+
export function renderPolishedChangelog(output: ReleaseNotePolishOutput): string {
|
|
68
|
+
if (output.status === "empty") {
|
|
69
|
+
return output.body.trim().length > 0
|
|
70
|
+
? output.body.trim()
|
|
71
|
+
: "_No notable changes in this release._";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const parts: string[] = [];
|
|
75
|
+
parts.push(`## ${output.title}`);
|
|
76
|
+
if (output.highlights.length > 0) {
|
|
77
|
+
parts.push("");
|
|
78
|
+
parts.push("### Highlights");
|
|
79
|
+
parts.push("");
|
|
80
|
+
for (const h of output.highlights) {
|
|
81
|
+
parts.push(`- ${h}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const body = output.body.trim();
|
|
85
|
+
if (body.length > 0) {
|
|
86
|
+
parts.push("");
|
|
87
|
+
parts.push(body);
|
|
88
|
+
}
|
|
89
|
+
return parts.join("\n");
|
|
90
|
+
}
|
package/src/release/executor.ts
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
// src/release/executor.ts — Release execution: build, version bump, git ops, channel publishing
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import type {
|
|
4
|
+
CustomChannelConfig,
|
|
5
|
+
ReleaseChannel,
|
|
6
|
+
ReleaseResult,
|
|
7
|
+
ReleaseTarget,
|
|
8
|
+
} from "../types.js";
|
|
5
9
|
import { commitStaged } from "../git/commit.js";
|
|
6
10
|
import { formatTag } from "./version.js";
|
|
7
11
|
import { resolveChannelHandler } from "./channels/registry.js";
|
|
8
12
|
import type { ExecFn } from "./channels/types.js";
|
|
13
|
+
import { getRunScriptCommand } from "../workspace/package-manager.js";
|
|
9
14
|
|
|
10
15
|
/** Callback to report step progress during release execution. */
|
|
11
16
|
export type ReleaseProgressFn = (step: string, status: "active" | "done" | "error", detail?: string) => void;
|
|
12
17
|
|
|
13
18
|
export interface ExecuteReleaseOptions {
|
|
14
19
|
exec: ExecFn;
|
|
20
|
+
/** Repository root — git and channel publish steps run here. */
|
|
15
21
|
cwd: string;
|
|
22
|
+
/** Selected release target whose manifest/build should be mutated. */
|
|
23
|
+
target: ReleaseTarget;
|
|
16
24
|
version: string;
|
|
17
25
|
changelog: string;
|
|
18
26
|
channels: ReleaseChannel[];
|
|
@@ -20,7 +28,7 @@ export interface ExecuteReleaseOptions {
|
|
|
20
28
|
tagFormat: string;
|
|
21
29
|
/** User-defined custom channel configurations */
|
|
22
30
|
customChannels?: Record<string, CustomChannelConfig>;
|
|
23
|
-
/** Skip the
|
|
31
|
+
/** Skip the target manifest version write (version was already set locally). */
|
|
24
32
|
skipBump?: boolean;
|
|
25
33
|
/** Reuse/update the local git tag after the rebase step instead of creating a new one. */
|
|
26
34
|
skipTag?: boolean;
|
|
@@ -28,26 +36,79 @@ export interface ExecuteReleaseOptions {
|
|
|
28
36
|
onProgress?: ReleaseProgressFn;
|
|
29
37
|
}
|
|
30
38
|
|
|
39
|
+
interface TargetManifest {
|
|
40
|
+
scripts?: Record<string, string>;
|
|
41
|
+
version?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getTargetManifestGitPath(target: ReleaseTarget): string {
|
|
45
|
+
return target.relativeDir === "."
|
|
46
|
+
? "package.json"
|
|
47
|
+
: `${target.relativeDir}/package.json`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readTargetManifest(target: ReleaseTarget): TargetManifest {
|
|
51
|
+
return JSON.parse(fs.readFileSync(target.manifestPath, "utf-8")) as TargetManifest;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const PUSH_RETRY_LIMIT = 1;
|
|
55
|
+
|
|
56
|
+
export function isNonFastForwardPushError(detail: string | undefined): boolean {
|
|
57
|
+
if (!detail) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const normalized = detail.toLowerCase();
|
|
62
|
+
return normalized.includes("non-fast-forward") || normalized.includes("fetch first");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function refreshReleaseTag(
|
|
66
|
+
exec: ExecFn,
|
|
67
|
+
cwd: string,
|
|
68
|
+
tagName: string,
|
|
69
|
+
tagMessage: string,
|
|
70
|
+
progress: ReleaseProgressFn,
|
|
71
|
+
): Promise<{ success: true } | { success: false; error: string }> {
|
|
72
|
+
progress("git-tag", "active", `Refreshing ${tagName}`);
|
|
73
|
+
const gitTag = await exec("git", ["tag", "-a", "-f", tagName, "-m", tagMessage], { cwd });
|
|
74
|
+
if (gitTag.code !== 0) {
|
|
75
|
+
const detail = gitTag.stderr || gitTag.stdout || `exit code ${gitTag.code}`;
|
|
76
|
+
progress("git-tag", "error", detail);
|
|
77
|
+
return { success: false, error: `git tag: ${detail}` };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
progress("git-tag", "done", "Refreshed existing tag");
|
|
81
|
+
return { success: true };
|
|
82
|
+
}
|
|
83
|
+
|
|
31
84
|
/**
|
|
32
|
-
* Execute a full release: optional build,
|
|
85
|
+
* Execute a full release: optional build, target manifest version bump, git
|
|
33
86
|
* commit+tag+push, then per-channel publishing.
|
|
34
|
-
*
|
|
35
|
-
* - Build and git steps are fatal (throw or return early on non-zero exit).
|
|
36
|
-
* - Channel failures are non-fatal; each is tried independently and errors
|
|
37
|
-
* are recorded in the result.
|
|
38
|
-
* - In dry-run mode no exec calls are made; the result reflects what would
|
|
39
|
-
* happen (all flags true) so callers can preview without side-effects.
|
|
40
87
|
*/
|
|
41
88
|
export async function executeRelease(opts: ExecuteReleaseOptions): Promise<ReleaseResult> {
|
|
42
|
-
const {
|
|
89
|
+
const {
|
|
90
|
+
exec,
|
|
91
|
+
cwd,
|
|
92
|
+
target,
|
|
93
|
+
version,
|
|
94
|
+
changelog,
|
|
95
|
+
channels,
|
|
96
|
+
dryRun,
|
|
97
|
+
tagFormat,
|
|
98
|
+
skipBump,
|
|
99
|
+
skipTag,
|
|
100
|
+
onProgress,
|
|
101
|
+
customChannels = {},
|
|
102
|
+
} = opts;
|
|
43
103
|
const progress = onProgress ?? (() => {});
|
|
44
104
|
const tagName = formatTag(version, tagFormat);
|
|
45
105
|
const tagMessage = `Release ${tagName}\n\n${changelog}`;
|
|
106
|
+
const stagedPaths = [getTargetManifestGitPath(target)];
|
|
46
107
|
|
|
47
108
|
if (dryRun) {
|
|
48
|
-
console.log(`[dry-run] Would build (if scripts.build exists)`);
|
|
49
|
-
console.log(`[dry-run] Would bump
|
|
50
|
-
console.log(`[dry-run] Would git add
|
|
109
|
+
console.log(`[dry-run] Would build ${target.name} in ${target.packageDir} (if scripts.build exists)`);
|
|
110
|
+
console.log(`[dry-run] Would bump ${stagedPaths[0]} to ${version}`);
|
|
111
|
+
console.log(`[dry-run] Would git add -- ${stagedPaths.join(" ")}`);
|
|
51
112
|
console.log(`[dry-run] Would git commit -m "chore(release): ${tagName}"`);
|
|
52
113
|
console.log(`[dry-run] Would git pull --rebase origin`);
|
|
53
114
|
if (skipTag) {
|
|
@@ -56,8 +117,8 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
|
|
|
56
117
|
console.log(`[dry-run] Would git tag -a ${tagName}`);
|
|
57
118
|
}
|
|
58
119
|
console.log(`[dry-run] Would git push origin HEAD --follow-tags`);
|
|
59
|
-
for (const
|
|
60
|
-
console.log(`[dry-run] Would publish to channel: ${
|
|
120
|
+
for (const channel of channels) {
|
|
121
|
+
console.log(`[dry-run] Would publish to channel: ${channel}`);
|
|
61
122
|
}
|
|
62
123
|
return {
|
|
63
124
|
version,
|
|
@@ -67,15 +128,12 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
|
|
|
67
128
|
};
|
|
68
129
|
}
|
|
69
130
|
|
|
70
|
-
|
|
71
|
-
const pkgPath = path.join(cwd, "package.json");
|
|
72
|
-
const pkgRaw = fs.readFileSync(pkgPath, "utf-8");
|
|
73
|
-
const pkg = JSON.parse(pkgRaw) as Record<string, unknown>;
|
|
131
|
+
const manifest = readTargetManifest(target);
|
|
74
132
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const buildResult = await exec(
|
|
133
|
+
if (manifest.scripts?.build) {
|
|
134
|
+
progress("build", "active", `Running build script for ${target.name}`);
|
|
135
|
+
const buildCommand = getRunScriptCommand(target.packageManager, "build");
|
|
136
|
+
const buildResult = await exec(buildCommand.command, buildCommand.args, { cwd: target.packageDir });
|
|
79
137
|
if (buildResult.code !== 0) {
|
|
80
138
|
progress("build", "error", buildResult.stderr || "Non-zero exit");
|
|
81
139
|
throw new Error(`Build failed: ${buildResult.stderr || "non-zero exit"}`);
|
|
@@ -83,26 +141,21 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
|
|
|
83
141
|
progress("build", "done");
|
|
84
142
|
}
|
|
85
143
|
|
|
86
|
-
// ── 2. Bump version in package.json ──────────────────────────────────────
|
|
87
144
|
if (skipBump) {
|
|
88
145
|
progress("version-bump", "done", "Already set");
|
|
89
146
|
} else {
|
|
90
|
-
progress("version-bump", "active", `Bumping to ${version}`);
|
|
91
|
-
|
|
92
|
-
fs.writeFileSync(
|
|
147
|
+
progress("version-bump", "active", `Bumping ${stagedPaths[0]} to ${version}`);
|
|
148
|
+
manifest.version = version;
|
|
149
|
+
fs.writeFileSync(target.manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
93
150
|
progress("version-bump", "done");
|
|
94
151
|
}
|
|
95
152
|
|
|
96
|
-
// ── 3–6. Git operations ──────────────────────────────────────────────────
|
|
97
|
-
|
|
98
|
-
// When skipBump is true the version was already committed locally —
|
|
99
|
-
// there's nothing to stage or commit, so skip straight to tag+push.
|
|
100
153
|
if (skipBump) {
|
|
101
154
|
progress("git-add", "done", "Skipped (no changes)");
|
|
102
155
|
progress("git-commit", "done", "Skipped (already committed)");
|
|
103
156
|
} else {
|
|
104
|
-
progress("git-add", "active",
|
|
105
|
-
const gitAdd = await exec("git", ["add", "
|
|
157
|
+
progress("git-add", "active", `git add -- ${stagedPaths.join(" ")}`);
|
|
158
|
+
const gitAdd = await exec("git", ["add", "--", ...stagedPaths], { cwd });
|
|
106
159
|
if (gitAdd.code !== 0) {
|
|
107
160
|
const detail = gitAdd.stderr || gitAdd.stdout || `exit code ${gitAdd.code}`;
|
|
108
161
|
progress("git-add", "error", detail);
|
|
@@ -110,7 +163,7 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
|
|
|
110
163
|
}
|
|
111
164
|
progress("git-add", "done");
|
|
112
165
|
|
|
113
|
-
const commitMessage = `chore(release): ${
|
|
166
|
+
const commitMessage = `chore(release): ${tagName}`;
|
|
114
167
|
progress("git-commit", "active", commitMessage);
|
|
115
168
|
const commitResult = await commitStaged(exec, cwd, commitMessage);
|
|
116
169
|
if (!commitResult.success) {
|
|
@@ -144,19 +197,38 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
|
|
|
144
197
|
}
|
|
145
198
|
progress("git-tag", "done", skipTag ? "Refreshed existing tag" : undefined);
|
|
146
199
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
200
|
+
let pushAttempt = 0;
|
|
201
|
+
while (true) {
|
|
202
|
+
progress("git-push", "active", pushAttempt === 0 ? "Pushing to origin" : "Retrying push after rebase");
|
|
203
|
+
const gitPush = await exec("git", ["push", "origin", "HEAD", "--follow-tags"], { cwd });
|
|
204
|
+
if (gitPush.code === 0) {
|
|
205
|
+
progress("git-push", "done");
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
|
|
150
209
|
const detail = gitPush.stderr || gitPush.stdout || `exit code ${gitPush.code}`;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
210
|
+
if (pushAttempt >= PUSH_RETRY_LIMIT || !isNonFastForwardPushError(detail)) {
|
|
211
|
+
progress("git-push", "error", detail);
|
|
212
|
+
return { version, tagCreated: true, pushed: false, channels: [], error: `git push: ${detail}` };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
pushAttempt += 1;
|
|
216
|
+
progress("git-pull", "active", "Push rejected — rebasing before retry");
|
|
217
|
+
const retryPull = await exec("git", ["pull", "--rebase", "origin"], { cwd });
|
|
218
|
+
if (retryPull.code !== 0) {
|
|
219
|
+
const retryDetail = retryPull.stderr || retryPull.stdout || `exit code ${retryPull.code}`;
|
|
220
|
+
progress("git-pull", "error", retryDetail);
|
|
221
|
+
return { version, tagCreated: true, pushed: false, channels: [], error: `git pull: ${retryDetail}` };
|
|
222
|
+
}
|
|
223
|
+
progress("git-pull", "done", "Rebased after push rejection");
|
|
224
|
+
|
|
225
|
+
const refreshTag = await refreshReleaseTag(exec, cwd, tagName, tagMessage, progress);
|
|
226
|
+
if (!refreshTag.success) {
|
|
227
|
+
return { version, tagCreated: true, pushed: false, channels: [], error: refreshTag.error };
|
|
228
|
+
}
|
|
154
229
|
}
|
|
155
|
-
progress("git-push", "done");
|
|
156
230
|
|
|
157
|
-
// ── 7. Channel publishing ─────────────────────────────────────────────────
|
|
158
231
|
const channelResults: ReleaseResult["channels"] = [];
|
|
159
|
-
|
|
160
232
|
for (const channel of channels) {
|
|
161
233
|
progress(`publish-${channel}`, "active", `Publishing to ${channel}`);
|
|
162
234
|
|
|
@@ -170,9 +242,14 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
|
|
|
170
242
|
|
|
171
243
|
const result = await handler.publish(exec, {
|
|
172
244
|
version,
|
|
173
|
-
tag:
|
|
245
|
+
tag: tagName,
|
|
174
246
|
changelog,
|
|
175
247
|
cwd,
|
|
248
|
+
targetName: target.name,
|
|
249
|
+
targetId: target.id,
|
|
250
|
+
targetPath: target.relativeDir,
|
|
251
|
+
manifestPath: target.manifestPath,
|
|
252
|
+
packageManager: target.packageManager,
|
|
176
253
|
});
|
|
177
254
|
|
|
178
255
|
if (result.success) {
|