@planu/cli 4.1.3 → 4.2.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/CHANGELOG.md +23 -1
- package/dist/engine/code-scanner/layer-scanner.js +29 -116
- package/dist/engine/core-bridge.d.ts +6 -6
- package/dist/engine/crash-shield/index.js +26 -63
- package/dist/engine/detect-duplication.js +63 -53
- package/dist/engine/drift-monitor.js +1 -1
- package/dist/engine/onboarding/new-project-resolver.d.ts +7 -0
- package/dist/engine/onboarding/new-project-resolver.js +265 -0
- package/dist/engine/reviewer-tokens/signer.js +4 -22
- package/dist/engine/scan-project/module-discoverer.js +9 -11
- package/dist/engine/spec-migrator/strict-planu-cleanup.js +2 -1
- package/dist/engine/validator/analyzer.js +14 -11
- package/dist/engine/validator/deep-code-checker.js +4 -8
- package/dist/storage/base-store.js +2 -6
- package/dist/storage/gaps-log.js +38 -32
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/technology-selection-store.d.ts +5 -0
- package/dist/storage/technology-selection-store.js +42 -0
- package/dist/tools/create-spec.js +23 -3
- package/dist/tools/facilitate.js +39 -29
- package/dist/tools/init-project/handler.js +59 -12
- package/dist/tools/register-spec-tools/core-spec-tools.js +33 -1
- package/dist/tools/tool-registry/core-tools.js +35 -1
- package/dist/tools/update-status/batch.js +4 -1
- package/dist/types/facilitator.d.ts +13 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/project/inputs.d.ts +12 -0
- package/dist/types/technology-selection.d.ts +77 -0
- package/dist/types/technology-selection.js +3 -0
- package/package.json +22 -8
|
@@ -2,6 +2,7 @@ import { checkGate } from '../engine/clarification-gate/gate.js';
|
|
|
2
2
|
import { upsertToken, hashQuestions } from '../engine/clarification-gate/token-store.js';
|
|
3
3
|
import { ti } from '../i18n/index.js';
|
|
4
4
|
import { knowledgeStore, specStore } from '../storage/index.js';
|
|
5
|
+
import { readTechnologySelectionContract } from '../storage/technology-selection-store.js';
|
|
5
6
|
import { formatSuccess, addNextSteps, toolResult, interactiveResult } from './response-helpers.js';
|
|
6
7
|
import { writeFile, mkdir, rm, readFile, stat } from 'node:fs/promises';
|
|
7
8
|
import { createHash } from 'node:crypto';
|
|
@@ -515,17 +516,36 @@ export async function handleCreateSpec(inputParams, server) {
|
|
|
515
516
|
await measureStep('mkdir-specDir', () => mkdir(specDir, { recursive: true }));
|
|
516
517
|
// SPEC-586: Filter suggested criteria by spec tags/target/scope before injection
|
|
517
518
|
const filteredCriteria = await filterCriteriaByTags(autopilot.suggestedCriteria, spec.tags, spec.target, spec.scope).catch(() => autopilot.suggestedCriteria);
|
|
519
|
+
const technologyContract = await readTechnologySelectionContract(params.projectPath ?? '');
|
|
520
|
+
const contractNote = technologyContract
|
|
521
|
+
? [
|
|
522
|
+
'',
|
|
523
|
+
'Technology Contract:',
|
|
524
|
+
`- Mode: ${technologyContract.mode}`,
|
|
525
|
+
technologyContract.language ? `- Language: ${technologyContract.language}` : '',
|
|
526
|
+
technologyContract.framework
|
|
527
|
+
? `- Framework: ${technologyContract.framework}`
|
|
528
|
+
: '- Framework: none or not selected',
|
|
529
|
+
technologyContract.platform ? `- Platform: ${technologyContract.platform}` : '',
|
|
530
|
+
technologyContract.projectType
|
|
531
|
+
? `- Project type: ${technologyContract.projectType}`
|
|
532
|
+
: '',
|
|
533
|
+
'- Agents must not choose a different stack unless the user approves a new contract.',
|
|
534
|
+
]
|
|
535
|
+
.filter(Boolean)
|
|
536
|
+
.join('\n')
|
|
537
|
+
: '';
|
|
518
538
|
const specGenerator = new FallbackGenerator();
|
|
519
539
|
const generatedSpec = await measureStep('generateSpecBody', () => specGenerator.generate({
|
|
520
540
|
title: spec.title,
|
|
521
|
-
description
|
|
541
|
+
description: `${description}${contractNote}`,
|
|
522
542
|
type: spec.type,
|
|
523
543
|
scope: spec.scope,
|
|
524
544
|
target: spec.target,
|
|
525
545
|
acFormat: params.acFormat,
|
|
526
546
|
projectContext: {
|
|
527
|
-
language: knowledge?.language ?? undefined,
|
|
528
|
-
framework: knowledge?.framework ?? undefined,
|
|
547
|
+
language: technologyContract?.language ?? knowledge?.language ?? undefined,
|
|
548
|
+
framework: technologyContract?.framework ?? knowledge?.framework ?? undefined,
|
|
529
549
|
architecture: knowledge?.architecture.primary ?? undefined,
|
|
530
550
|
},
|
|
531
551
|
}));
|
package/dist/tools/facilitate.js
CHANGED
|
@@ -7,6 +7,8 @@ import { loadContext } from '../engine/facilitate/context-loader.js';
|
|
|
7
7
|
import { route } from '../engine/facilitate/router.js';
|
|
8
8
|
import { parsePlanContent } from '../engine/facilitate/plan-parser.js';
|
|
9
9
|
import { compactJson, compactResult, compactError } from './output-formatter.js';
|
|
10
|
+
import { interactiveResult } from './response-helpers.js';
|
|
11
|
+
import { resolveNewProjectOnboarding } from '../engine/onboarding/new-project-resolver.js';
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
11
13
|
// Scenario detection (SPEC-037 legacy — kept for backward compatibility)
|
|
12
14
|
// ---------------------------------------------------------------------------
|
|
@@ -31,29 +33,6 @@ function detectScenario(description) {
|
|
|
31
33
|
return 'new_project';
|
|
32
34
|
}
|
|
33
35
|
// ---------------------------------------------------------------------------
|
|
34
|
-
// Step questions for new_project scenario
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
const NEW_PROJECT_STEPS = [
|
|
37
|
-
'What is the main purpose of your app? What problem does it solve and for whom?',
|
|
38
|
-
'What platform will it run on? (web app, mobile iOS/Android, REST API, desktop app, CLI tool)',
|
|
39
|
-
'Do you have a preferred tech stack? (language, framework, database — skip if unsure)',
|
|
40
|
-
];
|
|
41
|
-
function buildNewProjectResponse(step, collectedInfo) {
|
|
42
|
-
if (step < NEW_PROJECT_STEPS.length) {
|
|
43
|
-
const question = NEW_PROJECT_STEPS[step] ?? NEW_PROJECT_STEPS[0];
|
|
44
|
-
const intro = step === 0
|
|
45
|
-
? "Welcome to Planu! I'll guide you with a few questions to generate a precise spec.\n\n"
|
|
46
|
-
: `Got it! ${Object.values(collectedInfo).join(', ')}\n\n`;
|
|
47
|
-
return `${intro}Step ${step + 1}/3: ${question}`;
|
|
48
|
-
}
|
|
49
|
-
const info = Object.values(collectedInfo).join(' | ');
|
|
50
|
-
return (`Great — I have enough context to generate your spec.\n\n` +
|
|
51
|
-
`Summary: ${info}\n\n` +
|
|
52
|
-
`Next step: Call \`create_spec\` with this description:\n` +
|
|
53
|
-
`"${Object.values(collectedInfo).join('. ')}."\n\n` +
|
|
54
|
-
`Or call \`clarify_requirements\` first for deeper requirement analysis.`);
|
|
55
|
-
}
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
36
|
// Existing project scenario
|
|
58
37
|
// ---------------------------------------------------------------------------
|
|
59
38
|
function buildExistingProjectResponse(description) {
|
|
@@ -206,7 +185,7 @@ async function runSmartOrchestrator(description, projectId, planContent) {
|
|
|
206
185
|
// Main handler
|
|
207
186
|
// ---------------------------------------------------------------------------
|
|
208
187
|
export async function handleFacilitate(params, server) {
|
|
209
|
-
const { description, projectId, sessionId, scenario: explicitScenario,
|
|
188
|
+
const { description, projectId, sessionId, scenario: explicitScenario, step = 0, targetTool, checkFocus = false, changeFocus, paradigmReport, planContent, } = params;
|
|
210
189
|
const sessionKey = sessionId ?? projectId ?? 'default';
|
|
211
190
|
// SPEC-264: When planContent provided OR no explicit scenario/control params given,
|
|
212
191
|
// use the smart orchestrator path.
|
|
@@ -355,11 +334,42 @@ export async function handleFacilitate(params, server) {
|
|
|
355
334
|
let guidance;
|
|
356
335
|
switch (scenario) {
|
|
357
336
|
case 'new_project': {
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
:
|
|
361
|
-
|
|
362
|
-
|
|
337
|
+
const resolution = await resolveNewProjectOnboarding({
|
|
338
|
+
description,
|
|
339
|
+
parentWorkspacePath: params.parentWorkspacePath,
|
|
340
|
+
projectPath: params.projectId,
|
|
341
|
+
appName: params.appName,
|
|
342
|
+
appSlug: params.appSlug,
|
|
343
|
+
projectType: params.projectType,
|
|
344
|
+
platform: params.platform,
|
|
345
|
+
language: params.language,
|
|
346
|
+
framework: params.framework,
|
|
347
|
+
database: params.database,
|
|
348
|
+
createDirectory: params.createDirectory,
|
|
349
|
+
clarificationAnswers: params.clarificationAnswers,
|
|
350
|
+
});
|
|
351
|
+
if (!resolution.ready) {
|
|
352
|
+
const response = interactiveResult(resolution.questions, 'New project onboarding needs a target app folder and technology choices before Planu can initialize anything. Do not call init_project on the parent workspace yet.', 'universal');
|
|
353
|
+
response.structuredContent = {
|
|
354
|
+
...response.structuredContent,
|
|
355
|
+
mode: 'new_project',
|
|
356
|
+
parentWorkspacePath: resolution.parentWorkspacePath,
|
|
357
|
+
resolvedProjectPath: resolution.resolvedProjectPath,
|
|
358
|
+
appSlug: resolution.appSlug,
|
|
359
|
+
errors: resolution.errors,
|
|
360
|
+
nextAction: 'Ask the user these questions, then call facilitate again with clarificationAnswers or explicit fields. Only call init_project with the resolved child projectPath.',
|
|
361
|
+
};
|
|
362
|
+
return response;
|
|
363
|
+
}
|
|
364
|
+
return ok({
|
|
365
|
+
action: 'facilitate',
|
|
366
|
+
scenario,
|
|
367
|
+
mode: 'new_project',
|
|
368
|
+
readyToInitialize: true,
|
|
369
|
+
resolvedProjectPath: resolution.resolvedProjectPath,
|
|
370
|
+
technologySelectionContract: resolution.contract,
|
|
371
|
+
nextAction: 'Call init_project with mode="new_project", projectPath set to resolvedProjectPath, createDirectory=true, and technologySelectionContract from this response.',
|
|
372
|
+
});
|
|
363
373
|
}
|
|
364
374
|
case 'existing_project':
|
|
365
375
|
guidance = buildExistingProjectResponse(description);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { elicitOrFallback, questionsToFormSchema } from '../../engine/elicitation/elicit-helper.js';
|
|
2
2
|
import { ti } from '../../i18n/index.js';
|
|
3
3
|
import { AutopilotSummaryCollector } from '../../engine/autopilot/summary-collector.js';
|
|
4
|
-
import { hashProjectPath, knowledgeStore, globalStore, licenseStore } from '../../storage/index.js';
|
|
4
|
+
import { hashProjectPath, knowledgeStore, globalStore, licenseStore, technologySelectionStore, } from '../../storage/index.js';
|
|
5
5
|
import { addProject } from '../../storage/global-projects-store.js';
|
|
6
6
|
import { getCurrentTier } from '../license-gate.js';
|
|
7
7
|
import { checkLimits } from '../../engine/license-validator.js';
|
|
@@ -24,7 +24,7 @@ import { readAutoInstallFlag, orchestrateSkillInstalls, runHealthCheckWithBaseli
|
|
|
24
24
|
import { injectProactiveRules } from '../../engine/claude-md-injector/index.js';
|
|
25
25
|
import { discoverSkillsForInit } from '../../engine/skill-bootstrap/registry-fetcher.js';
|
|
26
26
|
import { join } from 'node:path';
|
|
27
|
-
import { stat } from 'node:fs/promises';
|
|
27
|
+
import { mkdir, stat } from 'node:fs/promises';
|
|
28
28
|
import { generateAgentTeamsRulesIfMissing, generateWorkflowRulesIfMissing, } from './rules-generator.js';
|
|
29
29
|
import { installUniversalRules } from '../../engine/universal-rules/installer.js';
|
|
30
30
|
import { detectHost } from '../../engine/host-detection/detect-host.js';
|
|
@@ -37,6 +37,8 @@ import { applyStackBasedGroupActivations } from './tool-group-activator.js';
|
|
|
37
37
|
import { checkGate } from '../../engine/clarification-gate/gate.js';
|
|
38
38
|
import { upsertToken, hashQuestions } from '../../engine/clarification-gate/token-store.js';
|
|
39
39
|
import { reconcileInteractiveQuestionHooks } from '../reconcile-interactive-question-hooks.js';
|
|
40
|
+
import { resolveNewProjectOnboarding } from '../../engine/onboarding/new-project-resolver.js';
|
|
41
|
+
import { interactiveResult } from '../response-helpers.js';
|
|
40
42
|
/** Frontend framework groups — mutually exclusive. Two or more detected → multi-stack conflict. */
|
|
41
43
|
const FRONTEND_FRAMEWORK_GROUPS = [
|
|
42
44
|
['react', 'next', 'next.js', 'nextjs', 'remix', 'preact'],
|
|
@@ -81,8 +83,29 @@ export async function handleInitProject(params, server) {
|
|
|
81
83
|
}
|
|
82
84
|
// eslint-disable-next-line max-lines-per-function, complexity
|
|
83
85
|
return trackCost(params.projectPath, 'init_project', async () => {
|
|
84
|
-
const { projectPath, locale, hourlyRate, experienceLevel, userProfile, autoInstallSkills, workMode, } = params;
|
|
86
|
+
const { projectPath, mode, locale, hourlyRate, experienceLevel, userProfile, autoInstallSkills, workMode, } = params;
|
|
85
87
|
try {
|
|
88
|
+
let approvedTechnologyContract = params.technologySelectionContract ?? null;
|
|
89
|
+
if (mode === 'new_project' && approvedTechnologyContract === null) {
|
|
90
|
+
const resolution = await resolveNewProjectOnboarding({
|
|
91
|
+
description: params.appName ?? params.projectType ?? 'New project',
|
|
92
|
+
parentWorkspacePath: params.parentWorkspacePath,
|
|
93
|
+
projectPath,
|
|
94
|
+
appName: params.appName,
|
|
95
|
+
appSlug: params.appSlug,
|
|
96
|
+
projectType: params.projectType,
|
|
97
|
+
platform: params.platform,
|
|
98
|
+
language: params.language,
|
|
99
|
+
framework: params.framework,
|
|
100
|
+
database: params.database,
|
|
101
|
+
createDirectory: params.createDirectory,
|
|
102
|
+
clarificationAnswers: params.clarificationAnswers,
|
|
103
|
+
});
|
|
104
|
+
if (!resolution.ready) {
|
|
105
|
+
return interactiveResult(resolution.questions, 'init_project new_project mode needs a confirmed child app folder and technology choices before writing project files.', 'universal');
|
|
106
|
+
}
|
|
107
|
+
approvedTechnologyContract = resolution.contract ?? null;
|
|
108
|
+
}
|
|
86
109
|
// Validate projectPath exists and is a directory before any I/O
|
|
87
110
|
try {
|
|
88
111
|
const pathStat = await stat(projectPath);
|
|
@@ -99,15 +122,20 @@ export async function handleInitProject(params, server) {
|
|
|
99
122
|
}
|
|
100
123
|
}
|
|
101
124
|
catch {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
125
|
+
if (mode === 'new_project' && params.createDirectory === true) {
|
|
126
|
+
await mkdir(projectPath, { recursive: true });
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: 'text',
|
|
133
|
+
text: `Project directory does not exist: "${projectPath}". Create the directory first and try again.`,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
isError: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
111
139
|
}
|
|
112
140
|
const projectId = hashProjectPath(projectPath);
|
|
113
141
|
// SPEC-1007: migrate legacy <cwd>/data/projects/{id} into ~/.planu/data/
|
|
@@ -147,6 +175,19 @@ export async function handleInitProject(params, server) {
|
|
|
147
175
|
const effectiveExperience = experienceLevel ?? existing?.experienceLevel ?? globalConfig.defaultExperienceLevel;
|
|
148
176
|
// Run full project analysis
|
|
149
177
|
const knowledge = await analyzeProject(projectPath, projectId, effectiveLocale, effectiveExperience);
|
|
178
|
+
if (approvedTechnologyContract !== null) {
|
|
179
|
+
knowledge.language = approvedTechnologyContract.language ?? knowledge.language;
|
|
180
|
+
knowledge.framework =
|
|
181
|
+
approvedTechnologyContract.framework !== undefined
|
|
182
|
+
? approvedTechnologyContract.framework
|
|
183
|
+
: knowledge.framework;
|
|
184
|
+
const stackAdditions = [
|
|
185
|
+
approvedTechnologyContract.language,
|
|
186
|
+
approvedTechnologyContract.framework ?? undefined,
|
|
187
|
+
approvedTechnologyContract.database ?? undefined,
|
|
188
|
+
].filter((item) => typeof item === 'string' && item.length > 0);
|
|
189
|
+
knowledge.stack = Array.from(new Set([...knowledge.stack, ...stackAdditions]));
|
|
190
|
+
}
|
|
150
191
|
// Apply overrides
|
|
151
192
|
if (userProfile) {
|
|
152
193
|
knowledge.userProfile = userProfile;
|
|
@@ -214,6 +255,12 @@ export async function handleInitProject(params, server) {
|
|
|
214
255
|
}
|
|
215
256
|
// Save initial project knowledge
|
|
216
257
|
await knowledgeStore.saveKnowledge(projectId, knowledge);
|
|
258
|
+
if (approvedTechnologyContract !== null) {
|
|
259
|
+
await technologySelectionStore.writeTechnologySelectionContract(projectPath, {
|
|
260
|
+
...approvedTechnologyContract,
|
|
261
|
+
projectPath,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
217
264
|
// SPEC-789: Register project in global workspace registry so workspace_health
|
|
218
265
|
// and list_registered_projects can discover it without manual registration.
|
|
219
266
|
addProject(projectPath).catch(() => {
|
|
@@ -18,6 +18,34 @@ import { handleTypeSafetyGate } from '../type-safety-gate.js';
|
|
|
18
18
|
/** init_project inputSchema — extracted to keep registerCoreSpecTools within line budget. */
|
|
19
19
|
const INIT_PROJECT_INPUT_SCHEMA = {
|
|
20
20
|
projectPath: z.string().max(4096).describe('Absolute path to the project root'),
|
|
21
|
+
mode: z
|
|
22
|
+
.enum(['existing_project', 'new_project'])
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Onboarding mode. Defaults to existing_project for backwards compatibility.'),
|
|
25
|
+
parentWorkspacePath: z
|
|
26
|
+
.string()
|
|
27
|
+
.max(4096)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Parent workspace path for explicit new_project onboarding'),
|
|
30
|
+
appName: z.string().max(500).optional().describe('Human-readable app name for new_project'),
|
|
31
|
+
appSlug: z
|
|
32
|
+
.string()
|
|
33
|
+
.max(200)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Safe folder slug for new_project; must not contain path separators'),
|
|
36
|
+
projectType: z.string().max(500).optional().describe('Type of project being initialized'),
|
|
37
|
+
platform: z.string().max(500).optional().describe('Runtime/deployment platform'),
|
|
38
|
+
language: z.string().max(500).optional().describe('User-confirmed language'),
|
|
39
|
+
framework: z.string().max(500).nullable().optional().describe('User-confirmed framework'),
|
|
40
|
+
database: z.string().max(500).nullable().optional().describe('User-confirmed database'),
|
|
41
|
+
createDirectory: z
|
|
42
|
+
.boolean()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe('When true with mode=new_project, create only the resolved projectPath directory'),
|
|
45
|
+
technologySelectionContract: z
|
|
46
|
+
.record(z.string(), z.unknown())
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Approved TechnologySelectionContract returned by facilitate new_project flow'),
|
|
21
49
|
locale: SupportedLocaleEnum.optional().describe('Preferred locale for this project'),
|
|
22
50
|
hourlyRate: z.number().optional().describe('Developer hourly rate in USD for cost estimation'),
|
|
23
51
|
experienceLevel: ExperienceLevelEnum.optional().describe('Developer experience level: beginner, intermediate, or expert'),
|
|
@@ -317,7 +345,11 @@ export function registerCoreSpecTools(server) {
|
|
|
317
345
|
.describe('Absolute project root. Preferred when projectId is unknown.'),
|
|
318
346
|
status: SpecStatusEnum.describe('New status for all specs'),
|
|
319
347
|
dryRun: z.boolean().optional().describe('Preview the batch without mutating any spec.'),
|
|
320
|
-
reviewNotes: z
|
|
348
|
+
reviewNotes: z
|
|
349
|
+
.string()
|
|
350
|
+
.max(10_000)
|
|
351
|
+
.optional()
|
|
352
|
+
.describe('Optional notes for each transition.'),
|
|
321
353
|
},
|
|
322
354
|
}, safeTracked('update_status_batch', async (args) => handleUpdateStatusBatch(args)));
|
|
323
355
|
// 8. estimate
|
|
@@ -456,11 +456,45 @@ const coreToolsRegistry = [
|
|
|
456
456
|
.record(z.string().max(500), z.string().max(10_000))
|
|
457
457
|
.optional()
|
|
458
458
|
.describe('Answers collected in previous steps'),
|
|
459
|
+
parentWorkspacePath: z
|
|
460
|
+
.string()
|
|
461
|
+
.max(4096)
|
|
462
|
+
.optional()
|
|
463
|
+
.describe('Parent workspace path that should contain a new project folder'),
|
|
464
|
+
appName: z.string().max(500).optional().describe('Human-readable new app name'),
|
|
465
|
+
appSlug: z
|
|
466
|
+
.string()
|
|
467
|
+
.max(200)
|
|
468
|
+
.optional()
|
|
469
|
+
.describe('Safe folder name for the new app; must not contain path separators'),
|
|
470
|
+
projectType: z.string().max(500).optional().describe('Kind of project to create'),
|
|
471
|
+
platform: z.string().max(500).optional().describe('Runtime or deployment platform'),
|
|
472
|
+
language: z.string().max(500).optional().describe('User-confirmed programming language'),
|
|
473
|
+
framework: z
|
|
474
|
+
.string()
|
|
475
|
+
.max(500)
|
|
476
|
+
.nullable()
|
|
477
|
+
.optional()
|
|
478
|
+
.describe('User-confirmed framework, null for no framework'),
|
|
479
|
+
database: z
|
|
480
|
+
.string()
|
|
481
|
+
.max(500)
|
|
482
|
+
.nullable()
|
|
483
|
+
.optional()
|
|
484
|
+
.describe('User-confirmed database, null for no database'),
|
|
485
|
+
createDirectory: z
|
|
486
|
+
.boolean()
|
|
487
|
+
.optional()
|
|
488
|
+
.describe('When true in new_project flow, create only the resolved child project folder'),
|
|
489
|
+
clarificationAnswers: z
|
|
490
|
+
.record(z.string().max(500), z.string().max(10_000))
|
|
491
|
+
.optional()
|
|
492
|
+
.describe('Answers to interactiveQuestions emitted by facilitate'),
|
|
459
493
|
step: z
|
|
460
494
|
.number()
|
|
461
495
|
.int()
|
|
462
496
|
.min(0)
|
|
463
|
-
.max(
|
|
497
|
+
.max(10)
|
|
464
498
|
.optional()
|
|
465
499
|
.describe('Current step index for new_project flow (0–2)'),
|
|
466
500
|
targetTool: z
|
|
@@ -8,7 +8,10 @@ function firstText(result) {
|
|
|
8
8
|
export async function handleUpdateStatusBatch(input) {
|
|
9
9
|
const uniqueSpecIds = [...new Set(input.specIds)].sort();
|
|
10
10
|
if (uniqueSpecIds.length === 0) {
|
|
11
|
-
return {
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: 'text', text: 'update_status_batch requires at least one specId.' }],
|
|
13
|
+
isError: true,
|
|
14
|
+
};
|
|
12
15
|
}
|
|
13
16
|
const resolved = await resolveProjectIdOrAutoDetect({
|
|
14
17
|
projectId: input.projectId,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ParadigmReport } from './paradigm.js';
|
|
2
|
+
import type { TechnologySelectionContract } from './technology-selection.js';
|
|
2
3
|
/** Ambiguity level for a tool request. */
|
|
3
4
|
export type AmbiguityLevel = 'none' | 'low' | 'medium' | 'critical';
|
|
4
5
|
/** Dimensions evaluated during ambiguity scoring. */
|
|
@@ -68,6 +69,18 @@ export interface FacilitateInput {
|
|
|
68
69
|
scenario?: FacilitateScenario;
|
|
69
70
|
/** Cumulative answers from previous steps. */
|
|
70
71
|
collectedInfo?: Record<string, string>;
|
|
72
|
+
/** Parent workspace path for new-project onboarding. */
|
|
73
|
+
parentWorkspacePath?: string;
|
|
74
|
+
appName?: string;
|
|
75
|
+
appSlug?: string;
|
|
76
|
+
projectType?: string;
|
|
77
|
+
platform?: string;
|
|
78
|
+
language?: string;
|
|
79
|
+
framework?: string | null;
|
|
80
|
+
database?: string | null;
|
|
81
|
+
createDirectory?: boolean;
|
|
82
|
+
clarificationAnswers?: Record<string, string>;
|
|
83
|
+
technologySelectionContract?: TechnologySelectionContract;
|
|
71
84
|
/** Step index (0-based). Incremented by the caller. */
|
|
72
85
|
step?: number;
|
|
73
86
|
/** Target tool for ambiguity check (optional). */
|
package/dist/types/index.d.ts
CHANGED
|
@@ -212,6 +212,7 @@ export * from './steering.js';
|
|
|
212
212
|
export * from './sentry.js';
|
|
213
213
|
export * from './supabase.js';
|
|
214
214
|
export * from './skill-bootstrap.js';
|
|
215
|
+
export * from './technology-selection.js';
|
|
215
216
|
export * from './compliance-gate.js';
|
|
216
217
|
export * from './schema-parity.js';
|
|
217
218
|
export * from './auto-update.js';
|
package/dist/types/index.js
CHANGED
|
@@ -209,6 +209,7 @@ export * from './steering.js';
|
|
|
209
209
|
export * from './sentry.js';
|
|
210
210
|
export * from './supabase.js';
|
|
211
211
|
export * from './skill-bootstrap.js';
|
|
212
|
+
export * from './technology-selection.js';
|
|
212
213
|
export * from './compliance-gate.js';
|
|
213
214
|
export * from './schema-parity.js';
|
|
214
215
|
export * from './auto-update.js';
|
|
@@ -2,12 +2,24 @@ import type { SupportedLocale, ExperienceLevel, WorkMode } from '../common/index
|
|
|
2
2
|
import type { ConstitutionPrinciple } from './core.js';
|
|
3
3
|
import type { PermissionsMode } from '../permissions-config.js';
|
|
4
4
|
import type { PluginInstallMode } from '../plugin-install.js';
|
|
5
|
+
import type { ProjectOnboardingMode, TechnologySelectionContract } from '../technology-selection.js';
|
|
5
6
|
export interface SetLocaleInput {
|
|
6
7
|
projectId?: string;
|
|
7
8
|
locale: SupportedLocale;
|
|
8
9
|
}
|
|
9
10
|
export interface InitProjectInput {
|
|
10
11
|
projectPath: string;
|
|
12
|
+
mode?: ProjectOnboardingMode;
|
|
13
|
+
parentWorkspacePath?: string;
|
|
14
|
+
appName?: string;
|
|
15
|
+
appSlug?: string;
|
|
16
|
+
projectType?: string;
|
|
17
|
+
platform?: string;
|
|
18
|
+
language?: string;
|
|
19
|
+
framework?: string | null;
|
|
20
|
+
database?: string | null;
|
|
21
|
+
createDirectory?: boolean;
|
|
22
|
+
technologySelectionContract?: TechnologySelectionContract;
|
|
11
23
|
locale?: SupportedLocale;
|
|
12
24
|
hourlyRate?: number;
|
|
13
25
|
experienceLevel?: ExperienceLevel;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { InteractiveQuestion } from './clarification.js';
|
|
2
|
+
export type ProjectOnboardingMode = 'existing_project' | 'new_project';
|
|
3
|
+
export type TechnologyDecisionSource = 'detected' | 'user-confirmed' | 'suggested-confirmed';
|
|
4
|
+
export type TechnologyVerificationStatus = 'verified' | 'unverified';
|
|
5
|
+
export interface TechnologyDocsVerification {
|
|
6
|
+
name: string;
|
|
7
|
+
url: string;
|
|
8
|
+
status: TechnologyVerificationStatus;
|
|
9
|
+
reason?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface TechnologySelectionContract {
|
|
12
|
+
mode: ProjectOnboardingMode;
|
|
13
|
+
projectPath: string;
|
|
14
|
+
parentWorkspacePath?: string;
|
|
15
|
+
appName?: string;
|
|
16
|
+
appSlug?: string;
|
|
17
|
+
projectType?: string;
|
|
18
|
+
platform?: string;
|
|
19
|
+
language?: string;
|
|
20
|
+
framework?: string | null;
|
|
21
|
+
database?: string | null;
|
|
22
|
+
source: TechnologyDecisionSource;
|
|
23
|
+
evidence: string[];
|
|
24
|
+
officialDocs: TechnologyDocsVerification[];
|
|
25
|
+
verifiedAt?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface NewProjectOnboardingInput {
|
|
28
|
+
description: string;
|
|
29
|
+
parentWorkspacePath?: string;
|
|
30
|
+
projectPath?: string;
|
|
31
|
+
appName?: string;
|
|
32
|
+
appSlug?: string;
|
|
33
|
+
projectType?: string;
|
|
34
|
+
platform?: string;
|
|
35
|
+
language?: string;
|
|
36
|
+
framework?: string | null;
|
|
37
|
+
database?: string | null;
|
|
38
|
+
createDirectory?: boolean;
|
|
39
|
+
clarificationAnswers?: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
export interface NewProjectResolution {
|
|
42
|
+
ready: boolean;
|
|
43
|
+
parentWorkspacePath?: string;
|
|
44
|
+
resolvedProjectPath?: string;
|
|
45
|
+
appSlug?: string;
|
|
46
|
+
questions: InteractiveQuestion[];
|
|
47
|
+
contract?: TechnologySelectionContract;
|
|
48
|
+
errors: string[];
|
|
49
|
+
}
|
|
50
|
+
export type NewProjectQuestionState = Partial<{
|
|
51
|
+
parentWorkspacePath: string;
|
|
52
|
+
appSlug: string;
|
|
53
|
+
projectType: string;
|
|
54
|
+
platform: string;
|
|
55
|
+
language: string;
|
|
56
|
+
framework: string | null;
|
|
57
|
+
languageAnswered: boolean;
|
|
58
|
+
frameworkAnswered: boolean;
|
|
59
|
+
createDirectoryAnswered: boolean;
|
|
60
|
+
}>;
|
|
61
|
+
export interface NewProjectResolvedFields {
|
|
62
|
+
parentWorkspacePath?: string;
|
|
63
|
+
appName?: string;
|
|
64
|
+
appSlug?: string;
|
|
65
|
+
projectType?: string;
|
|
66
|
+
platform?: string;
|
|
67
|
+
language?: string;
|
|
68
|
+
framework?: string | null;
|
|
69
|
+
database?: string | null;
|
|
70
|
+
languageAnswered: boolean;
|
|
71
|
+
frameworkAnswered: boolean;
|
|
72
|
+
createDirectoryAnswered: boolean;
|
|
73
|
+
createDirectory: boolean;
|
|
74
|
+
resolvedProjectPath?: string;
|
|
75
|
+
errors: string[];
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=technology-selection.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planu/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Planu — MCP Server for Spec Driven Development with native Rust acceleration for hot paths. Cross-platform (Linux/macOS/Windows, x64/arm64, glibc/musl).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"packageName": "@planu/core"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@planu/core-darwin-arm64": "4.
|
|
36
|
-
"@planu/core-darwin-x64": "4.
|
|
37
|
-
"@planu/core-linux-arm64-gnu": "4.
|
|
38
|
-
"@planu/core-linux-arm64-musl": "4.
|
|
39
|
-
"@planu/core-linux-x64-gnu": "4.
|
|
40
|
-
"@planu/core-linux-x64-musl": "4.
|
|
35
|
+
"@planu/core-darwin-arm64": "4.2.0",
|
|
36
|
+
"@planu/core-darwin-x64": "4.2.0",
|
|
37
|
+
"@planu/core-linux-arm64-gnu": "4.2.0",
|
|
38
|
+
"@planu/core-linux-arm64-musl": "4.2.0",
|
|
39
|
+
"@planu/core-linux-x64-gnu": "4.2.0",
|
|
40
|
+
"@planu/core-linux-x64-musl": "4.2.0"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=24.0.0"
|
|
@@ -143,6 +143,20 @@
|
|
|
143
143
|
"hono": ">=4.12.14",
|
|
144
144
|
"postcss": ">=8.5.10",
|
|
145
145
|
"fast-uri": ">=3.1.2"
|
|
146
|
+
},
|
|
147
|
+
"supportedArchitectures": {
|
|
148
|
+
"os": [
|
|
149
|
+
"darwin",
|
|
150
|
+
"linux"
|
|
151
|
+
],
|
|
152
|
+
"cpu": [
|
|
153
|
+
"arm64",
|
|
154
|
+
"x64"
|
|
155
|
+
],
|
|
156
|
+
"libc": [
|
|
157
|
+
"glibc",
|
|
158
|
+
"musl"
|
|
159
|
+
]
|
|
146
160
|
}
|
|
147
161
|
},
|
|
148
162
|
"devDependencies": {
|
|
@@ -171,7 +185,7 @@
|
|
|
171
185
|
"eslint-plugin-import": "^2.32.0",
|
|
172
186
|
"happy-dom": "^20.9.0",
|
|
173
187
|
"husky": "^9.1.7",
|
|
174
|
-
"javascript-obfuscator": "^5.4.
|
|
188
|
+
"javascript-obfuscator": "^5.4.3",
|
|
175
189
|
"knip": "^6.14.1",
|
|
176
190
|
"lint-staged": "^17.0.5",
|
|
177
191
|
"madge": "^8.0.0",
|