peaks-cli 1.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.
Files changed (143) hide show
  1. package/LICENSE +52 -0
  2. package/README.md +417 -0
  3. package/bin/peaks.js +2 -0
  4. package/dist/src/cli/cli-helpers.d.ts +25 -0
  5. package/dist/src/cli/cli-helpers.js +78 -0
  6. package/dist/src/cli/commands/capability-commands.d.ts +5 -0
  7. package/dist/src/cli/commands/capability-commands.js +46 -0
  8. package/dist/src/cli/commands/capability-worker-config-sc-commands.d.ts +3 -0
  9. package/dist/src/cli/commands/capability-worker-config-sc-commands.js +10 -0
  10. package/dist/src/cli/commands/config-commands.d.ts +3 -0
  11. package/dist/src/cli/commands/config-commands.js +212 -0
  12. package/dist/src/cli/commands/core-artifact-commands.d.ts +3 -0
  13. package/dist/src/cli/commands/core-artifact-commands.js +200 -0
  14. package/dist/src/cli/commands/sc-commands.d.ts +3 -0
  15. package/dist/src/cli/commands/sc-commands.js +37 -0
  16. package/dist/src/cli/commands/worker-commands.d.ts +3 -0
  17. package/dist/src/cli/commands/worker-commands.js +52 -0
  18. package/dist/src/cli/commands/workflow-commands.d.ts +3 -0
  19. package/dist/src/cli/commands/workflow-commands.js +257 -0
  20. package/dist/src/cli/index.d.ts +1 -0
  21. package/dist/src/cli/index.js +14 -0
  22. package/dist/src/cli/program.d.ts +4 -0
  23. package/dist/src/cli/program.js +13 -0
  24. package/dist/src/services/artifacts/artifact-service.d.ts +43 -0
  25. package/dist/src/services/artifacts/artifact-service.js +97 -0
  26. package/dist/src/services/artifacts/workspace-service.d.ts +33 -0
  27. package/dist/src/services/artifacts/workspace-service.js +254 -0
  28. package/dist/src/services/config/config-service.d.ts +29 -0
  29. package/dist/src/services/config/config-service.js +501 -0
  30. package/dist/src/services/config/config-types.d.ts +63 -0
  31. package/dist/src/services/config/config-types.js +16 -0
  32. package/dist/src/services/config/model-routing.d.ts +4 -0
  33. package/dist/src/services/config/model-routing.js +15 -0
  34. package/dist/src/services/doctor/doctor-service.d.ts +18 -0
  35. package/dist/src/services/doctor/doctor-service.js +68 -0
  36. package/dist/src/services/memory/project-memory-service.d.ts +79 -0
  37. package/dist/src/services/memory/project-memory-service.js +306 -0
  38. package/dist/src/services/profiles/profile-service.d.ts +6 -0
  39. package/dist/src/services/profiles/profile-service.js +19 -0
  40. package/dist/src/services/providers/minimax-provider-service.d.ts +24 -0
  41. package/dist/src/services/providers/minimax-provider-service.js +143 -0
  42. package/dist/src/services/providers/minimax-worker-service.d.ts +21 -0
  43. package/dist/src/services/providers/minimax-worker-service.js +80 -0
  44. package/dist/src/services/proxy/proxy-service.d.ts +7 -0
  45. package/dist/src/services/proxy/proxy-service.js +31 -0
  46. package/dist/src/services/rd/rd-service.d.ts +88 -0
  47. package/dist/src/services/rd/rd-service.js +370 -0
  48. package/dist/src/services/recommendations/capability-availability.d.ts +5 -0
  49. package/dist/src/services/recommendations/capability-availability.js +40 -0
  50. package/dist/src/services/recommendations/capability-map-service.d.ts +7 -0
  51. package/dist/src/services/recommendations/capability-map-service.js +131 -0
  52. package/dist/src/services/recommendations/capability-seed-items.d.ts +2 -0
  53. package/dist/src/services/recommendations/capability-seed-items.js +131 -0
  54. package/dist/src/services/recommendations/capability-seed-mappings.d.ts +2 -0
  55. package/dist/src/services/recommendations/capability-seed-mappings.js +42 -0
  56. package/dist/src/services/recommendations/capability-seed-sources.d.ts +2 -0
  57. package/dist/src/services/recommendations/capability-seed-sources.js +35 -0
  58. package/dist/src/services/recommendations/recommendation-service.d.ts +8 -0
  59. package/dist/src/services/recommendations/recommendation-service.js +106 -0
  60. package/dist/src/services/recommendations/recommendation-types.d.ts +129 -0
  61. package/dist/src/services/recommendations/recommendation-types.js +1 -0
  62. package/dist/src/services/recommendations/seed-capability-catalog.d.ts +3 -0
  63. package/dist/src/services/recommendations/seed-capability-catalog.js +3 -0
  64. package/dist/src/services/refactor/refactor-service.d.ts +9 -0
  65. package/dist/src/services/refactor/refactor-service.js +33 -0
  66. package/dist/src/services/sc/index.d.ts +1 -0
  67. package/dist/src/services/sc/index.js +1 -0
  68. package/dist/src/services/sc/sc-service.d.ts +79 -0
  69. package/dist/src/services/sc/sc-service.js +223 -0
  70. package/dist/src/services/skills/skill-registry.d.ts +17 -0
  71. package/dist/src/services/skills/skill-registry.js +40 -0
  72. package/dist/src/services/standards/project-standards-service.d.ts +82 -0
  73. package/dist/src/services/standards/project-standards-service.js +383 -0
  74. package/dist/src/services/tech/tech-service.d.ts +69 -0
  75. package/dist/src/services/tech/tech-service.js +236 -0
  76. package/dist/src/services/workflow/workflow-autonomous-service.d.ts +99 -0
  77. package/dist/src/services/workflow/workflow-autonomous-service.js +526 -0
  78. package/dist/src/services/workflow/workflow-router-service.d.ts +85 -0
  79. package/dist/src/services/workflow/workflow-router-service.js +213 -0
  80. package/dist/src/shared/change-id.d.ts +15 -0
  81. package/dist/src/shared/change-id.js +76 -0
  82. package/dist/src/shared/frontmatter.d.ts +6 -0
  83. package/dist/src/shared/frontmatter.js +47 -0
  84. package/dist/src/shared/fs-utils.d.ts +4 -0
  85. package/dist/src/shared/fs-utils.js +16 -0
  86. package/dist/src/shared/fs.d.ts +4 -0
  87. package/dist/src/shared/fs.js +26 -0
  88. package/dist/src/shared/path-utils.d.ts +13 -0
  89. package/dist/src/shared/path-utils.js +56 -0
  90. package/dist/src/shared/paths.d.ts +6 -0
  91. package/dist/src/shared/paths.js +40 -0
  92. package/dist/src/shared/planner-response.d.ts +21 -0
  93. package/dist/src/shared/planner-response.js +26 -0
  94. package/dist/src/shared/platform.d.ts +6 -0
  95. package/dist/src/shared/platform.js +11 -0
  96. package/dist/src/shared/process.d.ts +5 -0
  97. package/dist/src/shared/process.js +12 -0
  98. package/dist/src/shared/result.d.ts +13 -0
  99. package/dist/src/shared/result.js +32 -0
  100. package/package.json +49 -0
  101. package/schemas/approval-record.schema.json +14 -0
  102. package/schemas/artifact-manifest.schema.json +16 -0
  103. package/schemas/artifact-retention-report.schema.json +17 -0
  104. package/schemas/artifact-workspace.schema.json +22 -0
  105. package/schemas/capability-availability.schema.json +36 -0
  106. package/schemas/capability-item.schema.json +37 -0
  107. package/schemas/capability-source.schema.json +30 -0
  108. package/schemas/change-impact.schema.json +15 -0
  109. package/schemas/context-capsule.schema.json +16 -0
  110. package/schemas/recommendation-plan.schema.json +37 -0
  111. package/schemas/refactor-slice-spec.schema.json +19 -0
  112. package/scripts/clean-dist.mjs +8 -0
  113. package/scripts/install-skills.mjs +76 -0
  114. package/scripts/watch.mjs +389 -0
  115. package/skills/peaks-prd/SKILL.md +42 -0
  116. package/skills/peaks-prd/references/artifact-contracts.md +3 -0
  117. package/skills/peaks-prd/references/command-migration.md +3 -0
  118. package/skills/peaks-prd/references/workflow.md +11 -0
  119. package/skills/peaks-qa/SKILL.md +45 -0
  120. package/skills/peaks-qa/references/artifact-contracts.md +3 -0
  121. package/skills/peaks-qa/references/command-migration.md +3 -0
  122. package/skills/peaks-qa/references/regression-gates.md +16 -0
  123. package/skills/peaks-rd/SKILL.md +56 -0
  124. package/skills/peaks-rd/references/artifact-contracts.md +3 -0
  125. package/skills/peaks-rd/references/command-migration.md +3 -0
  126. package/skills/peaks-rd/references/refactor-workflow.md +31 -0
  127. package/skills/peaks-sc/SKILL.md +30 -0
  128. package/skills/peaks-sc/references/artifact-contracts.md +3 -0
  129. package/skills/peaks-sc/references/artifact-retention.md +14 -0
  130. package/skills/peaks-sc/references/command-migration.md +3 -0
  131. package/skills/peaks-solo/SKILL.md +63 -0
  132. package/skills/peaks-solo/references/artifact-contracts.md +3 -0
  133. package/skills/peaks-solo/references/command-migration.md +3 -0
  134. package/skills/peaks-solo/references/refactor-mode.md +22 -0
  135. package/skills/peaks-solo/references/workflow.md +14 -0
  136. package/skills/peaks-txt/SKILL.md +48 -0
  137. package/skills/peaks-txt/references/artifact-contracts.md +3 -0
  138. package/skills/peaks-txt/references/command-migration.md +3 -0
  139. package/skills/peaks-txt/references/context-capsule.md +20 -0
  140. package/skills/peaks-ui/SKILL.md +35 -0
  141. package/skills/peaks-ui/references/artifact-contracts.md +3 -0
  142. package/skills/peaks-ui/references/command-migration.md +3 -0
  143. package/skills/peaks-ui/references/workflow.md +11 -0
@@ -0,0 +1,85 @@
1
+ import { type ModelProviderConfig, type WorkspaceConfig } from '../config/config-types.js';
2
+ import { type RdPlanResult } from '../rd/rd-service.js';
3
+ import { type TechPlanResult, type TechStatus } from '../tech/tech-service.js';
4
+ export type WorkflowMode = 'solo' | 'team';
5
+ export type SoloMode = 'full-auto' | 'guided' | 'rnd';
6
+ export type ModelTier = 'top-tier' | 'mid-tier';
7
+ export type ModelRole = 'strongest' | 'execution';
8
+ export type WorkflowRoutePolicy = 'solo-broad-multi-model' | 'team-rd-limited-multi-model';
9
+ export type WorkflowStepStage = 'product-direction' | 'design-direction' | 'tech-direction' | 'tech-review' | 'rd-planning' | 'coding-execution' | 'unit-test-execution' | 'quality-review';
10
+ export type WorkflowStepOwner = 'peaks-solo' | 'peaks-rd' | 'peaks-tech' | 'human';
11
+ export type WorkflowRouterRequest = {
12
+ changeId: string;
13
+ goal: string;
14
+ mode: WorkflowMode;
15
+ soloMode?: SoloMode;
16
+ maxWorkers?: number;
17
+ dryRun: true;
18
+ artifactWorkspacePath?: string;
19
+ workspace?: WorkspaceConfig;
20
+ config?: {
21
+ economyMode?: boolean;
22
+ swarmMode?: boolean;
23
+ providers?: ModelProviderConfig;
24
+ };
25
+ };
26
+ export type WorkflowRouterStep = {
27
+ readonly id: string;
28
+ readonly stage: WorkflowStepStage;
29
+ readonly owner: WorkflowStepOwner;
30
+ readonly modelTier: ModelTier;
31
+ readonly modelRole: ModelRole;
32
+ readonly modelId: string;
33
+ readonly reason: string;
34
+ readonly dryRunOnly: true;
35
+ readonly invokesAgents: false;
36
+ readonly writesArtifacts: false;
37
+ readonly dependsOn: readonly string[];
38
+ };
39
+ export type WorkflowModelRouting = {
40
+ readonly strongestModel: {
41
+ readonly modelId: 'claude-opus-4-7';
42
+ readonly uses: readonly WorkflowStepStage[];
43
+ };
44
+ readonly executionModel: {
45
+ readonly modelId: string;
46
+ readonly uses: readonly WorkflowStepStage[];
47
+ };
48
+ };
49
+ export type WorkflowModelAssignment = {
50
+ readonly stage: WorkflowStepStage;
51
+ readonly owner: WorkflowStepOwner;
52
+ readonly modelTier: ModelTier;
53
+ readonly modelRole: ModelRole;
54
+ readonly modelId: string;
55
+ };
56
+ export type WorkflowModeStatus = {
57
+ readonly economyModeEnabled: boolean;
58
+ readonly swarmModeEnabled: boolean;
59
+ readonly executionModelId: string;
60
+ readonly executionModelSource: string;
61
+ readonly summary: string;
62
+ };
63
+ export type WorkflowRouterPlan = {
64
+ readonly changeId: string;
65
+ readonly goal: string;
66
+ readonly mode: WorkflowMode;
67
+ readonly soloMode?: SoloMode;
68
+ readonly executionMode: 'autonomous';
69
+ readonly decisionProfile: string;
70
+ readonly dryRun: true;
71
+ readonly routePolicy: WorkflowRoutePolicy;
72
+ readonly modelRouting: WorkflowModelRouting;
73
+ readonly modelAssignments: readonly WorkflowModelAssignment[];
74
+ readonly modeStatus: WorkflowModeStatus;
75
+ readonly techStatus: TechStatus;
76
+ readonly techPlan: TechPlanResult;
77
+ readonly rdPlan: RdPlanResult;
78
+ readonly steps: readonly WorkflowRouterStep[];
79
+ readonly blockedReasons: readonly string[];
80
+ readonly nextActions: readonly string[];
81
+ readonly constraints: readonly string[];
82
+ };
83
+ export declare function isWorkflowMode(mode: string): mode is WorkflowMode;
84
+ export declare function isSoloMode(value: string): value is SoloMode;
85
+ export declare function createWorkflowRouterPlan(request: WorkflowRouterRequest): WorkflowRouterPlan;
@@ -0,0 +1,213 @@
1
+ import { DEFAULT_CONFIG } from '../config/config-types.js';
2
+ import { getConfiguredExecutionModelId, STRONGEST_MODEL_ID } from '../config/model-routing.js';
3
+ import { getLocalArtifactPath } from '../artifacts/workspace-service.js';
4
+ import { createRdSwarmPlan } from '../rd/rd-service.js';
5
+ import { createTechPlan, getTechStatus } from '../tech/tech-service.js';
6
+ import { validateChangeIdOrThrow } from '../../shared/change-id.js';
7
+ import { WORKSPACE_UNAVAILABLE_NEXT_ACTIONS } from '../../shared/planner-response.js';
8
+ const WORKFLOW_CONSTRAINTS = Object.freeze([
9
+ 'dry-run-only',
10
+ 'requires-swarm-execution-for-rd-and-qa-when-enabled',
11
+ 'execution-model-from-config-providers',
12
+ 'do-not-launch-agents',
13
+ 'do-not-write-artifacts',
14
+ 'do-not-mutate-target-repo'
15
+ ]);
16
+ const EXECUTION_STAGES = ['coding-execution', 'unit-test-execution'];
17
+ const GUIDED_DECISION_STAGES = ['product-direction', 'design-direction'];
18
+ const GOVERNED_DECISION_STAGES = ['product-direction', 'design-direction', 'tech-direction', 'tech-review'];
19
+ export function isWorkflowMode(mode) {
20
+ return mode === 'solo' || mode === 'team';
21
+ }
22
+ function assertSupportedMode(mode) {
23
+ if (!isWorkflowMode(mode)) {
24
+ throw new Error('Unsupported workflow mode');
25
+ }
26
+ }
27
+ function normalizeGoal(goal) {
28
+ const normalized = goal.trim();
29
+ if (!normalized) {
30
+ throw new Error('Goal must be non-empty');
31
+ }
32
+ return normalized;
33
+ }
34
+ function assertSoloModeAllowed(mode, soloMode) {
35
+ if (mode !== 'solo' && soloMode !== undefined) {
36
+ throw new Error('soloMode requires solo workflow mode');
37
+ }
38
+ }
39
+ function step(input, executionModelId) {
40
+ const modelRole = EXECUTION_STAGES.includes(input.stage) ? 'execution' : 'strongest';
41
+ return {
42
+ ...input,
43
+ modelRole,
44
+ modelId: modelRole === 'execution' ? executionModelId : STRONGEST_MODEL_ID,
45
+ dryRunOnly: true,
46
+ invokesAgents: false,
47
+ writesArtifacts: false
48
+ };
49
+ }
50
+ export function isSoloMode(value) {
51
+ return value === 'full-auto' || value === 'guided' || value === 'rnd';
52
+ }
53
+ function getDecisionProfileSummary(mode, soloMode) {
54
+ if (mode === 'team') {
55
+ return 'Team mode keeps product and design governance on a human-controlled path while RD execution follows recommended defaults.';
56
+ }
57
+ if (soloMode === 'guided') {
58
+ return 'Guided mode keeps the user in the decision loop for the early recommended defaults, while later execution remains bounded by the routing plan.';
59
+ }
60
+ if (soloMode === 'rnd') {
61
+ return 'R&D mode asks for technical confirmation up front, then applies recommended defaults for implementation, testing, review, and safety checks.';
62
+ }
63
+ return 'Full-auto mode applies recommended defaults for product, design, and tech decisions, then runs the engineering pipeline end to end under routing gates.';
64
+ }
65
+ function annotateSteps(steps, soloMode) {
66
+ const decisionStages = soloMode === 'guided'
67
+ ? GUIDED_DECISION_STAGES
68
+ : GOVERNED_DECISION_STAGES;
69
+ return steps.map((currentStep) => {
70
+ const isDecisionStage = decisionStages.includes(currentStep.stage);
71
+ const reasonPrefix = isDecisionStage
72
+ ? `[${soloMode}] decision stage`
73
+ : '[routed] execution stage';
74
+ return {
75
+ ...currentStep,
76
+ reason: `${reasonPrefix}: ${currentStep.reason}`
77
+ };
78
+ });
79
+ }
80
+ function createSoloSteps(executionModelId) {
81
+ return [
82
+ step({ id: 'solo-product-direction', stage: 'product-direction', owner: 'peaks-solo', modelTier: 'top-tier', reason: 'Product direction needs strong judgment before execution work is delegated.', dependsOn: [] }, executionModelId),
83
+ step({ id: 'solo-design-direction', stage: 'design-direction', owner: 'peaks-solo', modelTier: 'top-tier', reason: 'Design direction uses the recommended default before cheaper implementation work.', dependsOn: ['solo-product-direction'] }, executionModelId),
84
+ step({ id: 'solo-tech-direction', stage: 'tech-direction', owner: 'peaks-tech', modelTier: 'top-tier', reason: 'Technical boundaries and approval gates use the recommended default with high-confidence planning.', dependsOn: ['solo-design-direction'] }, executionModelId),
85
+ step({ id: 'solo-tech-review', stage: 'tech-review', owner: 'peaks-tech', modelTier: 'top-tier', reason: 'Tech artifacts and gate decisions require strong review and a recommended default path.', dependsOn: ['solo-tech-direction'] }, executionModelId),
86
+ step({ id: 'solo-rd-planning', stage: 'rd-planning', owner: 'peaks-rd', modelTier: 'top-tier', reason: 'RD task decomposition and acceptance criteria use the recommended default before execution delegation.', dependsOn: ['solo-tech-review'] }, executionModelId),
87
+ step({ id: 'solo-coding-execution', stage: 'coding-execution', owner: 'peaks-rd', modelTier: executionModelId === STRONGEST_MODEL_ID ? 'top-tier' : 'mid-tier', reason: `Coding and routine refactoring must use the configured execution worker model ${executionModelId}.`, dependsOn: ['solo-rd-planning'] }, executionModelId),
88
+ step({ id: 'solo-unit-test-execution', stage: 'unit-test-execution', owner: 'peaks-rd', modelTier: executionModelId === STRONGEST_MODEL_ID ? 'top-tier' : 'mid-tier', reason: `Unit test authoring and focused test runs must use the configured execution worker model ${executionModelId}.`, dependsOn: ['solo-coding-execution'] }, executionModelId),
89
+ step({ id: 'solo-quality-review', stage: 'quality-review', owner: 'peaks-solo', modelTier: 'top-tier', reason: 'Reducer and final quality gates need strong synthesis and risk review.', dependsOn: ['solo-unit-test-execution'] }, executionModelId)
90
+ ];
91
+ }
92
+ function createSoloStepsForMode(soloMode, executionModelId) {
93
+ return annotateSteps(createSoloSteps(executionModelId), soloMode);
94
+ }
95
+ function createTeamSteps(executionModelId) {
96
+ return [
97
+ step({ id: 'team-product-direction', stage: 'product-direction', owner: 'human', modelTier: 'top-tier', reason: 'Team product direction should stay on the governed planning path.', dependsOn: [] }, executionModelId),
98
+ step({ id: 'team-design-direction', stage: 'design-direction', owner: 'human', modelTier: 'top-tier', reason: 'Team design direction should preserve reviewability and accountability.', dependsOn: ['team-product-direction'] }, executionModelId),
99
+ step({ id: 'team-tech-direction', stage: 'tech-direction', owner: 'peaks-tech', modelTier: 'top-tier', reason: 'Team technical plans should remain strongly governed before RD execution.', dependsOn: ['team-design-direction'] }, executionModelId),
100
+ step({ id: 'team-tech-review', stage: 'tech-review', owner: 'peaks-tech', modelTier: 'top-tier', reason: 'Team tech approval requires strong review before execution.', dependsOn: ['team-tech-direction'] }, executionModelId),
101
+ step({ id: 'team-rd-planning', stage: 'rd-planning', owner: 'peaks-rd', modelTier: 'top-tier', reason: 'Team RD task decomposition remains on the governed strongest-model path.', dependsOn: ['team-tech-review'] }, executionModelId),
102
+ step({ id: 'team-coding-execution', stage: 'coding-execution', owner: 'peaks-rd', modelTier: executionModelId === STRONGEST_MODEL_ID ? 'top-tier' : 'mid-tier', reason: `Bounded coding tasks must use the configured execution worker model ${executionModelId}.`, dependsOn: ['team-rd-planning'] }, executionModelId),
103
+ step({ id: 'team-unit-test-execution', stage: 'unit-test-execution', owner: 'peaks-rd', modelTier: executionModelId === STRONGEST_MODEL_ID ? 'top-tier' : 'mid-tier', reason: `Unit-test tasks must use the configured execution worker model ${executionModelId}.`, dependsOn: ['team-coding-execution'] }, executionModelId),
104
+ step({ id: 'team-quality-review', stage: 'quality-review', owner: 'peaks-rd', modelTier: 'top-tier', reason: 'Team RD outputs still need reducer and quality review gates.', dependsOn: ['team-unit-test-execution'] }, executionModelId)
105
+ ];
106
+ }
107
+ function uniqueStrings(values) {
108
+ return [...new Set(values)];
109
+ }
110
+ function createModelRouting(steps, executionModelId) {
111
+ return {
112
+ strongestModel: {
113
+ modelId: STRONGEST_MODEL_ID,
114
+ uses: steps.filter((step) => step.modelRole === 'strongest').map((step) => step.stage)
115
+ },
116
+ executionModel: {
117
+ modelId: executionModelId,
118
+ uses: steps.filter((step) => step.modelRole === 'execution').map((step) => step.stage)
119
+ }
120
+ };
121
+ }
122
+ function createModelAssignments(steps) {
123
+ return steps.map((step) => ({
124
+ stage: step.stage,
125
+ owner: step.owner,
126
+ modelTier: step.modelTier,
127
+ modelRole: step.modelRole,
128
+ modelId: step.modelId
129
+ }));
130
+ }
131
+ function getTechPlanBlockedReasons(techPlan) {
132
+ return techPlan.available ? techPlan.blockedReasons : techPlan.preview.blockedReasons;
133
+ }
134
+ function getTechPlanNextActions(techPlan) {
135
+ return [...techPlan.nextActions];
136
+ }
137
+ function createModeStatus(economyMode, swarmMode, executionModelId, executionModelSource) {
138
+ const economySummary = economyMode
139
+ ? `Economy mode enabled: code worker and test worker strictly use ${executionModelId} from config providers.`
140
+ : `Economy mode disabled: code worker and test worker use ${STRONGEST_MODEL_ID}, matching planner/reviewer.`;
141
+ const swarmSummary = swarmMode
142
+ ? 'Swarm mode enabled: peaks-rd coding, unit-test, and peaks-qa quality work must be represented as swarm worker tasks.'
143
+ : 'Swarm mode disabled: swarm worker graph generation is bypassed.';
144
+ return {
145
+ economyModeEnabled: economyMode,
146
+ swarmModeEnabled: swarmMode,
147
+ executionModelId,
148
+ executionModelSource,
149
+ summary: `${economySummary} ${swarmSummary}`
150
+ };
151
+ }
152
+ function getSoloMode(mode, soloMode) {
153
+ if (mode !== 'solo') {
154
+ return undefined;
155
+ }
156
+ if (soloMode === undefined) {
157
+ return 'full-auto';
158
+ }
159
+ if (!isSoloMode(soloMode)) {
160
+ throw new Error('Unsupported solo mode');
161
+ }
162
+ return soloMode;
163
+ }
164
+ export function createWorkflowRouterPlan(request) {
165
+ assertSupportedMode(request.mode);
166
+ assertSoloModeAllowed(request.mode, request.soloMode);
167
+ validateChangeIdOrThrow(request.changeId);
168
+ const goal = normalizeGoal(request.goal);
169
+ const maxWorkers = request.maxWorkers ?? 40;
170
+ const economyMode = request.config?.economyMode ?? DEFAULT_CONFIG.economyMode;
171
+ const swarmMode = request.config?.swarmMode ?? DEFAULT_CONFIG.swarmMode;
172
+ const executionModelId = economyMode ? getConfiguredExecutionModelId(request.config?.providers) : STRONGEST_MODEL_ID;
173
+ const modeStatus = createModeStatus(economyMode, swarmMode, executionModelId, economyMode ? 'config.providers' : 'planner-reviewer-strongest-model');
174
+ const soloMode = getSoloMode(request.mode, request.soloMode);
175
+ const decisionProfile = getDecisionProfileSummary(request.mode, soloMode);
176
+ const artifactWorkspacePath = request.artifactWorkspacePath ?? (request.workspace ? getLocalArtifactPath(request.workspace) : undefined);
177
+ const sharedWorkspaceOptions = {
178
+ ...(artifactWorkspacePath ? { artifactWorkspacePath } : {}),
179
+ ...(request.workspace ? { workspace: request.workspace } : {})
180
+ };
181
+ const techStatus = getTechStatus({ changeId: request.changeId, ...sharedWorkspaceOptions });
182
+ const techPlan = createTechPlan({ changeId: request.changeId, goal, swarm: swarmMode, dryRun: true, ...sharedWorkspaceOptions });
183
+ const rdPlan = createRdSwarmPlan({ skill: 'rd', changeId: request.changeId, goal, maxWorkers, swarmMode, executionModelId, dryRun: true, ...sharedWorkspaceOptions });
184
+ const steps = soloMode ? createSoloStepsForMode(soloMode, executionModelId) : createTeamSteps(executionModelId);
185
+ const blockedReasons = uniqueStrings([
186
+ ...techStatus.blockedReasons,
187
+ ...getTechPlanBlockedReasons(techPlan),
188
+ ...rdPlan.blockedReasons
189
+ ]);
190
+ const nextActions = blockedReasons.includes('artifact-workspace-unavailable')
191
+ ? [...WORKSPACE_UNAVAILABLE_NEXT_ACTIONS]
192
+ : uniqueStrings([...techStatus.nextActions, ...getTechPlanNextActions(techPlan), ...rdPlan.nextActions]);
193
+ return {
194
+ changeId: request.changeId,
195
+ goal,
196
+ mode: request.mode,
197
+ ...(soloMode ? { soloMode } : {}),
198
+ executionMode: 'autonomous',
199
+ decisionProfile,
200
+ dryRun: true,
201
+ routePolicy: request.mode === 'solo' ? 'solo-broad-multi-model' : 'team-rd-limited-multi-model',
202
+ modelRouting: createModelRouting(steps, executionModelId),
203
+ modelAssignments: createModelAssignments(steps),
204
+ modeStatus,
205
+ techStatus,
206
+ techPlan,
207
+ rdPlan,
208
+ steps,
209
+ blockedReasons,
210
+ nextActions,
211
+ constraints: [...WORKFLOW_CONSTRAINTS]
212
+ };
213
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared change-id validation and artifact path helpers.
3
+ * All Peaks planner commands must use these to prevent path traversal
4
+ * and keep artifacts inside the Peaks artifact workspace.
5
+ */
6
+ export declare function isValidChangeId(changeId: string): boolean;
7
+ export declare function isUnsafePathInput(input: string): boolean;
8
+ export declare function validateChangeIdOrThrow(changeId: string): void;
9
+ export declare class ChangeIdValidationError extends Error {
10
+ readonly changeId: string;
11
+ constructor(changeId: string);
12
+ }
13
+ export declare function isUnsafeArtifactPath(path: string): boolean;
14
+ export declare function buildArtifactRelativePath(changeId: string, ...segments: string[]): string;
15
+ export declare function isPathInsideArtifactRoot(path: string, artifactRoot: string): boolean;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared change-id validation and artifact path helpers.
3
+ * All Peaks planner commands must use these to prevent path traversal
4
+ * and keep artifacts inside the Peaks artifact workspace.
5
+ */
6
+ import { posix } from 'node:path';
7
+ const CHANGE_ID_PATTERN = /^[A-Za-z0-9._-]+$/;
8
+ function normalizeForwardSlashes(input) {
9
+ return input.replace(/\\/g, '/');
10
+ }
11
+ function hasUnsafePathShape(input) {
12
+ const normalized = normalizeForwardSlashes(input);
13
+ if (input.includes('\\'))
14
+ return true;
15
+ if (!normalized || normalized === '.' || normalized === '..')
16
+ return true;
17
+ if (normalized.startsWith('/') || normalized.startsWith('//'))
18
+ return true;
19
+ if (/^[A-Za-z]:/.test(normalized))
20
+ return true;
21
+ if (normalized.includes('://'))
22
+ return true;
23
+ if (/^[^@\s]+@[^:\s]+:.+/.test(normalized))
24
+ return true;
25
+ return normalized.split('/').some((segment) => segment.length === 0 || segment === '.' || segment === '..');
26
+ }
27
+ function normalizeArtifactPath(input) {
28
+ const normalized = posix.normalize(normalizeForwardSlashes(input));
29
+ return normalized.replace(/\/$/, '');
30
+ }
31
+ export function isValidChangeId(changeId) {
32
+ if (!changeId || changeId.length === 0)
33
+ return false;
34
+ if (changeId === '.' || changeId === '..')
35
+ return false;
36
+ if (changeId.includes('..'))
37
+ return false;
38
+ if (!CHANGE_ID_PATTERN.test(changeId))
39
+ return false;
40
+ return !hasUnsafePathShape(changeId);
41
+ }
42
+ export function isUnsafePathInput(input) {
43
+ return hasUnsafePathShape(input);
44
+ }
45
+ export function validateChangeIdOrThrow(changeId) {
46
+ if (!isValidChangeId(changeId)) {
47
+ throw new ChangeIdValidationError(changeId);
48
+ }
49
+ }
50
+ export class ChangeIdValidationError extends Error {
51
+ changeId;
52
+ constructor(changeId) {
53
+ super(`Invalid change-id: "${changeId}". Change-id must contain only letters, numbers, dots, underscores, or dashes, and must not be "." or "..".`);
54
+ this.name = 'ChangeIdValidationError';
55
+ this.changeId = changeId;
56
+ }
57
+ }
58
+ export function isUnsafeArtifactPath(path) {
59
+ return isUnsafePathInput(path);
60
+ }
61
+ export function buildArtifactRelativePath(changeId, ...segments) {
62
+ validateChangeIdOrThrow(changeId);
63
+ const joined = segments.map((segment) => normalizeForwardSlashes(segment)).join('/');
64
+ const candidatePath = `.peaks/changes/${changeId}/${joined}`;
65
+ if (isUnsafeArtifactPath(joined) || isUnsafeArtifactPath(candidatePath)) {
66
+ throw new ChangeIdValidationError(changeId);
67
+ }
68
+ return normalizeArtifactPath(candidatePath);
69
+ }
70
+ export function isPathInsideArtifactRoot(path, artifactRoot) {
71
+ if (!path || !artifactRoot)
72
+ return false;
73
+ const normalizedPath = normalizeArtifactPath(path);
74
+ const normalizedRoot = normalizeArtifactPath(artifactRoot);
75
+ return normalizedPath === normalizedRoot || normalizedPath.startsWith(`${normalizedRoot}/`);
76
+ }
@@ -0,0 +1,6 @@
1
+ export type SkillFrontmatter = {
2
+ name: string;
3
+ description: string;
4
+ [key: string]: string;
5
+ };
6
+ export declare function parseFrontmatter(markdown: string): SkillFrontmatter;
@@ -0,0 +1,47 @@
1
+ export function parseFrontmatter(markdown) {
2
+ const lines = markdown.split(/\r?\n/);
3
+ if (lines[0] !== '---') {
4
+ throw new Error('Missing YAML frontmatter opening marker');
5
+ }
6
+ const endIndex = lines.findIndex((line, index) => index > 0 && line === '---');
7
+ if (endIndex === -1) {
8
+ throw new Error('Missing YAML frontmatter closing marker');
9
+ }
10
+ const metadata = {};
11
+ const frontmatterLines = lines.slice(1, endIndex);
12
+ for (let index = 0; index < frontmatterLines.length; index += 1) {
13
+ const line = frontmatterLines[index];
14
+ if (!line || line.trim().length === 0) {
15
+ continue;
16
+ }
17
+ const blockMatch = line.match(/^([A-Za-z0-9_-]+):\s*[|>]\s*$/);
18
+ if (blockMatch?.[1]) {
19
+ const key = blockMatch[1];
20
+ const blockLines = [];
21
+ index += 1;
22
+ while (index < frontmatterLines.length) {
23
+ const blockLine = frontmatterLines[index];
24
+ if (blockLine && /^\S[^:]*:/.test(blockLine)) {
25
+ index -= 1;
26
+ break;
27
+ }
28
+ blockLines.push(blockLine?.replace(/^\s{2}/, '') ?? '');
29
+ index += 1;
30
+ }
31
+ metadata[key] = blockLines.join('\n').trim();
32
+ continue;
33
+ }
34
+ const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
35
+ if (!match?.[1]) {
36
+ throw new Error(`Invalid frontmatter line: ${line}`);
37
+ }
38
+ metadata[match[1]] = (match[2] ?? '').trim().replace(/^['"]|['"]$/g, '');
39
+ }
40
+ if (!metadata.name) {
41
+ throw new Error('Missing required frontmatter field: name');
42
+ }
43
+ if (!metadata.description) {
44
+ throw new Error('Missing required frontmatter field: description');
45
+ }
46
+ return metadata;
47
+ }
@@ -0,0 +1,4 @@
1
+ import type { Platform } from './platform.js';
2
+ export declare function getDirectoryLinkType(targetPlatform?: Platform): 'junction' | 'dir';
3
+ export declare function createDirectoryLinkSync(target: string, linkPath: string): void;
4
+ export declare function readDirectoryLinkTarget(linkPath: string): string | null;
@@ -0,0 +1,16 @@
1
+ import { symlinkSync as nodeSymlinkSync, readlinkSync } from 'node:fs';
2
+ import { platform } from './platform.js';
3
+ export function getDirectoryLinkType(targetPlatform = platform) {
4
+ return targetPlatform === 'win32' ? 'junction' : 'dir';
5
+ }
6
+ export function createDirectoryLinkSync(target, linkPath) {
7
+ nodeSymlinkSync(target, linkPath, getDirectoryLinkType());
8
+ }
9
+ export function readDirectoryLinkTarget(linkPath) {
10
+ try {
11
+ return readlinkSync(linkPath);
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
@@ -0,0 +1,4 @@
1
+ export declare function pathExists(path: string): Promise<boolean>;
2
+ export declare function readText(path: string): Promise<string>;
3
+ export declare function listDirectories(path: string): Promise<string[]>;
4
+ export declare function isDirectory(path: string): Promise<boolean>;
@@ -0,0 +1,26 @@
1
+ import { access, readdir, readFile, stat } from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
3
+ export async function pathExists(path) {
4
+ try {
5
+ await access(path, constants.F_OK);
6
+ return true;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ export async function readText(path) {
13
+ return readFile(path, 'utf8');
14
+ }
15
+ export async function listDirectories(path) {
16
+ const entries = await readdir(path, { withFileTypes: true });
17
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
18
+ }
19
+ export async function isDirectory(path) {
20
+ try {
21
+ return (await stat(path)).isDirectory();
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
@@ -0,0 +1,13 @@
1
+ import { type Platform } from './platform.js';
2
+ export declare const SEP: "/" | "\\";
3
+ export declare function normalizePath(p: string): string;
4
+ export declare function pathsEqual(a: string, b: string): boolean;
5
+ export declare function localPath(p: string, targetPlatform?: Platform): string;
6
+ export declare function getTempDir(options?: {
7
+ env?: NodeJS.ProcessEnv;
8
+ }): string;
9
+ export declare function isInsidePath(childPath: string, parentPath: string): boolean;
10
+ export declare function isWindowsAbsolutePath(path: string): boolean;
11
+ export declare function resolveInputPath(path: string): string;
12
+ export declare function stableRealPath(path: string): string;
13
+ export declare function stablePath(path: string): string;
@@ -0,0 +1,56 @@
1
+ import { existsSync, realpathSync } from 'node:fs';
2
+ import { isAbsolute, parse, relative, resolve, sep } from 'node:path';
3
+ import { tmpdir } from 'node:os';
4
+ import { platform } from './platform.js';
5
+ export const SEP = sep;
6
+ const localPathConverters = {
7
+ win32: (p) => p.replace(/\//g, '\\'),
8
+ darwin: (p) => p,
9
+ linux: (p) => p
10
+ };
11
+ export function normalizePath(p) {
12
+ return p.replace(/\\/g, '/');
13
+ }
14
+ export function pathsEqual(a, b) {
15
+ return normalizePath(a) === normalizePath(b);
16
+ }
17
+ export function localPath(p, targetPlatform = platform) {
18
+ return localPathConverters[targetPlatform](p);
19
+ }
20
+ export function getTempDir(options) {
21
+ const env = options?.env ?? process.env;
22
+ if (env.TEMP)
23
+ return env.TEMP;
24
+ if (env.TMP)
25
+ return env.TMP;
26
+ return tmpdir();
27
+ }
28
+ export function isInsidePath(childPath, parentPath) {
29
+ const relativePath = relative(parentPath, childPath);
30
+ return relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath));
31
+ }
32
+ export function isWindowsAbsolutePath(path) {
33
+ return /^[A-Za-z]:[\\/]/.test(path);
34
+ }
35
+ export function resolveInputPath(path) {
36
+ return isWindowsAbsolutePath(path) ? normalizePath(path) : resolve(path);
37
+ }
38
+ export function stableRealPath(path) {
39
+ return realpathSync(resolveInputPath(path));
40
+ }
41
+ export function stablePath(path) {
42
+ const resolvedPath = resolveInputPath(path);
43
+ if (existsSync(resolvedPath)) {
44
+ return stableRealPath(resolvedPath);
45
+ }
46
+ const parsedPath = parse(resolvedPath);
47
+ const missingSegments = [];
48
+ let currentPath = resolvedPath;
49
+ while (!existsSync(currentPath) && currentPath !== parsedPath.root) {
50
+ const parsedCurrent = parse(currentPath);
51
+ missingSegments.unshift(parsedCurrent.base);
52
+ currentPath = parsedCurrent.dir;
53
+ }
54
+ const realExistingPath = existsSync(currentPath) ? stableRealPath(currentPath) : parsedPath.root;
55
+ return resolve(realExistingPath, ...missingSegments);
56
+ }
@@ -0,0 +1,6 @@
1
+ export declare const repoRoot: string;
2
+ export declare const skillsDir: string;
3
+ export declare const schemasDir: string;
4
+ export declare const templatesDir: string;
5
+ export declare const requiredSkillNames: readonly ["peaks-solo", "peaks-prd", "peaks-ui", "peaks-rd", "peaks-qa", "peaks-sc", "peaks-txt"];
6
+ export declare const requiredSchemaFiles: readonly ["artifact-manifest.schema.json", "context-capsule.schema.json", "approval-record.schema.json", "change-impact.schema.json", "refactor-slice-spec.schema.json", "artifact-retention-report.schema.json", "capability-source.schema.json", "capability-item.schema.json", "capability-availability.schema.json", "recommendation-plan.schema.json", "artifact-workspace.schema.json"];
@@ -0,0 +1,40 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ function findRepoRoot(startPath) {
5
+ let currentPath = startPath;
6
+ while (currentPath !== dirname(currentPath)) {
7
+ if (existsSync(resolve(currentPath, 'package.json')) && existsSync(resolve(currentPath, 'skills'))) {
8
+ return currentPath;
9
+ }
10
+ currentPath = dirname(currentPath);
11
+ }
12
+ throw new Error(`Unable to locate Peaks repository root from ${startPath}`);
13
+ }
14
+ const currentFile = fileURLToPath(import.meta.url);
15
+ export const repoRoot = findRepoRoot(dirname(currentFile));
16
+ export const skillsDir = resolve(repoRoot, 'skills');
17
+ export const schemasDir = resolve(repoRoot, 'schemas');
18
+ export const templatesDir = resolve(repoRoot, 'templates');
19
+ export const requiredSkillNames = [
20
+ 'peaks-solo',
21
+ 'peaks-prd',
22
+ 'peaks-ui',
23
+ 'peaks-rd',
24
+ 'peaks-qa',
25
+ 'peaks-sc',
26
+ 'peaks-txt'
27
+ ];
28
+ export const requiredSchemaFiles = [
29
+ 'artifact-manifest.schema.json',
30
+ 'context-capsule.schema.json',
31
+ 'approval-record.schema.json',
32
+ 'change-impact.schema.json',
33
+ 'refactor-slice-spec.schema.json',
34
+ 'artifact-retention-report.schema.json',
35
+ 'capability-source.schema.json',
36
+ 'capability-item.schema.json',
37
+ 'capability-availability.schema.json',
38
+ 'recommendation-plan.schema.json',
39
+ 'artifact-workspace.schema.json'
40
+ ];
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shared response types for planner commands.
3
+ * These types ensure consistent error/status reporting across
4
+ * tech and RD swarm dry-run planner commands.
5
+ */
6
+ export type WorkspaceUnavailableBehavior = 'preview' | 'blocked';
7
+ export type ArtifactWorkspaceUnavailableResponse = {
8
+ available: false;
9
+ behavior: WorkspaceUnavailableBehavior;
10
+ reason: string;
11
+ nextActions: readonly string[];
12
+ };
13
+ export type ArtifactWorkspaceAvailableResponse<T> = {
14
+ available: true;
15
+ data: T;
16
+ };
17
+ export type ArtifactWorkspaceResponse<T> = ArtifactWorkspaceUnavailableResponse | ArtifactWorkspaceAvailableResponse<T>;
18
+ export declare const WORKSPACE_UNAVAILABLE_NEXT_ACTIONS: readonly string[];
19
+ export declare function makeUnavailableResponse(behavior: WorkspaceUnavailableBehavior, reason: string): ArtifactWorkspaceUnavailableResponse;
20
+ export declare function makeAvailableResponse<T>(data: T): ArtifactWorkspaceAvailableResponse<T>;
21
+ export declare function isUnavailableResponse<T>(resp: ArtifactWorkspaceResponse<T>): resp is ArtifactWorkspaceUnavailableResponse;