@polymorphism-tech/morph-spec 9.8.0 → 10.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/CLAUDE.md +10 -10
- package/README.md +4 -4
- package/bin/morph-spec.js +40 -43
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +24 -30
- package/framework/MORPH.md +47 -123
- package/framework/agents.json +6 -374
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-archive.md +4 -3
- package/framework/commands/morph-hotfix.md +8 -30
- package/framework/commands/morph-infra.md +2 -2
- package/framework/commands/morph-media.md +10 -9
- package/framework/commands/morph-review.md +15 -0
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +83 -94
- package/framework/evals/review-checklist.md +45 -53
- package/framework/hooks/README.md +4 -4
- package/framework/hooks/claude-code/core/README.md +1 -1
- package/framework/hooks/claude-code/core/denylist.json +4 -4
- package/framework/hooks/claude-code/core/gate-guard.js +9 -0
- package/framework/hooks/claude-code/core/masked-exit.js +49 -0
- package/framework/hooks/claude-code/core/state-sync.js +4 -1
- package/framework/hooks/claude-code/core/telemetry-log.js +1 -2
- package/framework/hooks/claude-code/core/test-budget.js +10 -70
- package/framework/hooks/claude-code/core/validate-completion.js +11 -24
- package/framework/hooks/claude-code/core/worktree-context.js +5 -22
- package/framework/hooks/claude-code/core/worktree-create.js +6 -45
- package/framework/hooks/claude-code/core/worktree-env-inject.js +2 -10
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/gate-event.js +2 -2
- package/framework/hooks/shared/graph-refresh.js +20 -3
- package/framework/hooks/shared/heavy-command.js +32 -1
- package/framework/hooks/shared/heavy-config.js +1 -1
- package/framework/hooks/shared/next-step.js +63 -24
- package/framework/hooks/shared/phase-utils.js +6 -9
- package/framework/hooks/shared/report-progress.js +86 -0
- package/framework/hooks/shared/state-reader.js +3 -2
- package/framework/hooks/statusline.js +332 -0
- package/framework/phases.json +32 -10
- package/framework/rules/ai-agents-standards.md +9 -1
- package/framework/rules/csharp-standards.md +4 -0
- package/framework/rules/ef-standards.md +20 -0
- package/framework/rules/frontend-standards.md +1 -1
- package/framework/rules/implement-report.md +25 -0
- package/framework/rules/infrastructure-standards.md +2 -0
- package/framework/rules/nextjs-standards.md +7 -0
- package/framework/rules/plan-grammar.md +53 -0
- package/framework/rules/ui-design.md +27 -0
- package/framework/schemas/config.schema.json +11 -18
- package/framework/schemas/feature.schema.json +7 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/create-umbraco-lp/references/umbraco-lp-patterns.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +2 -2
- package/framework/skills/core/morph-apply/SKILL.md +38 -166
- package/framework/skills/core/morph-apply/references/debugging.md +8 -0
- package/framework/skills/core/morph-apply/references/hotfix.md +42 -0
- package/framework/skills/core/morph-apply/references/machine.md +29 -0
- package/framework/skills/core/{morph-media/SKILL.md → morph-apply/references/media.md} +11 -74
- package/framework/skills/core/morph-apply/references/review.md +65 -0
- package/framework/skills/core/morph-plan/SKILL.md +42 -108
- package/framework/skills/core/morph-plan/references/critique.md +40 -0
- package/framework/skills/core/morph-plan/references/interview.md +25 -0
- package/framework/skills/core/morph-uiux/SKILL.md +47 -64
- package/framework/skills/core/morph-uiux/surfaces.json +32 -0
- package/framework/standards/STANDARDS.json +32 -5
- package/framework/standards/STANDARDS.meta.json +5 -1
- package/framework/standards/ai-agents/agent-session.md +2 -3
- package/framework/standards/ai-agents/context-providers.md +2 -2
- package/framework/standards/ai-agents/durable-workflows-hitl.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +2 -2
- package/framework/standards/ai-agents/rag-custom-pgvector.md +6 -6
- package/framework/standards/ai-agents/setup.md +1 -1
- package/framework/standards/ai-agents/tool-adapters.md +2 -2
- package/framework/standards/architecture/stack-baseline.md +47 -0
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -4
- package/framework/standards/frontend/design-system/ai-image-generation.md +51 -12
- package/framework/standards/frontend/design-system/ai-video-generation.md +50 -4
- package/framework/standards/frontend/design-system/art-direction.md +2 -2
- package/framework/standards/frontend/scroll-driven/frame-scrub.md +12 -2
- package/framework/standards/frontend/scroll-driven/scroll-components.md +76 -4
- package/framework/templates/REGISTRY.json +1 -2
- package/framework/templates/context/ARCHITECTURE.md +1 -1
- package/framework/templates/docs/plan.md +12 -12
- package/framework/templates/docs/ui-mockups.md +17 -105
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +88 -23
- package/src/commands/graph/diff.js +74 -0
- package/src/commands/graph/status.js +1 -1
- package/src/commands/observability/cost.js +2 -4
- package/src/commands/observability/telemetry.js +1 -1
- package/src/commands/project/doctor.js +23 -39
- package/src/commands/project/fleet.js +5 -6
- package/src/commands/project/init.js +63 -36
- package/src/commands/project/stack-scan.js +3 -42
- package/src/commands/project/stack-set.js +26 -0
- package/src/commands/project/status.js +12 -13
- package/src/commands/project/update.js +39 -3
- package/src/commands/project/worktree.js +3 -14
- package/src/commands/state/advance.js +17 -17
- package/src/commands/state/approve.js +0 -6
- package/src/commands/state/archive.js +27 -1
- package/src/commands/state/auto-approval.js +1 -1
- package/src/commands/state/create.js +0 -5
- package/src/commands/state/gate-check.js +37 -30
- package/src/commands/state/gate-decision.js +3 -2
- package/src/commands/state/next.js +1 -0
- package/src/commands/state/recap.js +67 -0
- package/src/commands/state/report-check.js +36 -178
- package/src/commands/validation/plan-check.js +16 -11
- package/src/commands/validation/validate-feature.js +3 -3
- package/src/commands/validation/verify.js +3 -6
- package/src/core/paths/output-schema.js +3 -3
- package/src/core/state/state-manager.js +2 -4
- package/src/dashboard/app/components/CommandPalette.jsx +2 -5
- package/src/dashboard/app/components/GatePauses.jsx +116 -0
- package/src/dashboard/app/components/SessionMap.jsx +77 -0
- package/src/dashboard/app/pages/Activity.jsx +4 -0
- package/src/dashboard/app/pages/AgentMap.jsx +1 -2
- package/src/dashboard/app/pages/FeatureDetail.jsx +129 -186
- package/src/dashboard/app/pages/Features.jsx +7 -7
- package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
- package/src/dashboard/app/pages/Portfolio.jsx +40 -5
- package/src/dashboard/scanner/activity-scanner.js +29 -0
- package/src/dashboard/scanner/agents-scanner.js +11 -14
- package/src/dashboard/scanner/features-scanner.js +45 -24
- package/src/dashboard/scanner/project-api.js +11 -2
- package/src/dashboard/scanner/skills-scanner.js +11 -1
- package/src/lib/e2e/applicability.js +3 -1
- package/src/lib/git/tree-state.js +1 -16
- package/src/lib/graph/diff-tree.js +127 -0
- package/src/lib/graph/graphify-runtime.js +12 -5
- package/src/lib/installers/memory-installer.js +2 -2
- package/src/lib/installers/rules-installer.js +57 -0
- package/src/lib/installers/stack-assets.js +17 -0
- package/src/lib/installers/stack-sync.js +219 -0
- package/src/lib/plan/checks.js +55 -110
- package/src/lib/plan/critique.js +28 -0
- package/src/lib/plan/media.js +42 -0
- package/src/lib/plan/plan-html.js +125 -0
- package/src/lib/plan/plan-parser.js +34 -45
- package/src/lib/recap/piper.js +36 -0
- package/src/lib/recap/recap.js +232 -0
- package/src/lib/review-reports.js +28 -4
- package/src/lib/risk-detector.js +90 -11
- package/src/lib/specs/screen-checklist.js +33 -48
- package/src/lib/stack/dotnet-build-ignores.js +8 -9
- package/src/lib/telemetry/metrics.js +27 -29
- package/src/lib/validators/reuse/reuse-validator.js +1 -9
- package/src/lib/validators/verify-runner.js +0 -7
- package/src/lib/worktree/fleet-scanner.js +7 -20
- package/src/lib/worktree/worktree-audit.js +0 -37
- package/src/scripts/setup-infra.js +15 -50
- package/src/utils/agents-installer.js +27 -201
- package/src/utils/hooks-installer.js +11 -95
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +0 -23
- package/framework/hooks/claude-code/statusline.py +0 -699
- package/framework/hooks/claude-code/statusline.sh +0 -11
- package/framework/hooks/shared/agent-reap.js +0 -74
- package/framework/hooks/shared/slice-progress.js +0 -110
- package/framework/skills/core/morph-hotfix/SKILL.md +0 -133
- package/framework/skills/core/morph-machine/SKILL.md +0 -46
- package/framework/skills/core/morph-review/SKILL.md +0 -129
- package/framework/skills/core/morph-standards/SKILL.md +0 -52
- package/framework/standards/PROVENANCE.json +0 -119
- package/src/commands/project/worktree-reap.js +0 -34
- package/src/commands/state/handoff.js +0 -65
- package/src/commands/validation/mutate.js +0 -263
- package/src/commands/validation/scout-check.js +0 -28
- package/src/lib/generators/handoff-generator.js +0 -172
- package/src/lib/mutation/adapters.js +0 -79
- package/src/lib/mutation/commands.js +0 -61
- package/src/lib/mutation/dotnet-introspection.js +0 -281
- package/src/lib/mutation/manifest.js +0 -144
- package/src/lib/mutation/plan.js +0 -44
- package/src/lib/mutation/report.js +0 -68
- package/src/lib/mutation/runner.js +0 -301
- package/src/lib/mutation/schemata.js +0 -60
- package/src/lib/mutation/snapshot.js +0 -65
- package/src/lib/mutation/subset.js +0 -109
- package/src/lib/mutation/trx.js +0 -50
- package/src/lib/scout/scout-check.js +0 -293
- package/src/lib/validators/traceability-check.js +0 -103
- package/src/lib/worktree/agent-reaper.js +0 -247
|
@@ -16,8 +16,9 @@ import {
|
|
|
16
16
|
getInstalledCLIVersion
|
|
17
17
|
} from '../../utils/version-checker.js';
|
|
18
18
|
import { resetMorphSettings } from '../../utils/claude-settings-manager.js';
|
|
19
|
-
import { checkMissingAgents, checkAgentsDrift, maxTurnsError
|
|
19
|
+
import { checkMissingAgents, checkAgentsDrift, maxTurnsError } from '../../utils/agents-installer.js';
|
|
20
20
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
21
|
+
import { stackRuleGaps } from '../../lib/installers/stack-sync.js';
|
|
21
22
|
import { auditGraph } from '../../lib/graph/graph-health.js';
|
|
22
23
|
import { auditFeatureLifecycle } from '../../core/state/lifecycle-audit.js';
|
|
23
24
|
import { validateTestNodes, morphConfigPath, discoverFrontendPackages } from '../../lib/tasks/test-nodes.js';
|
|
@@ -114,27 +115,27 @@ export function validateAgentsJson(agentsData) {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
const version = agentsData.version || 'unknown';
|
|
117
|
-
const
|
|
118
|
+
const isCurrent = version.startsWith('6.');
|
|
118
119
|
const v4Fields = [];
|
|
119
|
-
const
|
|
120
|
+
const missingFields = [];
|
|
120
121
|
|
|
121
122
|
for (const [id, data] of agentEntries) {
|
|
122
123
|
if (data.tier !== undefined) v4Fields.push(`${id}.tier`);
|
|
123
124
|
if (data.active_phases !== undefined) v4Fields.push(`${id}.active_phases`);
|
|
124
125
|
if (data.reports_to !== undefined) v4Fields.push(`${id}.reports_to`);
|
|
125
126
|
if (data.teammate !== undefined) v4Fields.push(`${id}.teammate`);
|
|
126
|
-
if (!data.title)
|
|
127
|
-
if (!Array.isArray(data.domains))
|
|
128
|
-
if (typeof data.persona !== 'string' || !data.persona)
|
|
127
|
+
if (!data.title) missingFields.push(`${id}.title`);
|
|
128
|
+
if (!Array.isArray(data.domains)) missingFields.push(`${id}.domains`);
|
|
129
|
+
if (typeof data.persona !== 'string' || !data.persona) missingFields.push(`${id}.persona`);
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
if (
|
|
132
|
+
if (isCurrent) {
|
|
132
133
|
checks.push({ name: `agents.json schema (v${version})`, status: 'ok' });
|
|
133
134
|
} else {
|
|
134
135
|
checks.push({
|
|
135
136
|
name: `agents.json schema (v${version})`,
|
|
136
137
|
status: 'warn',
|
|
137
|
-
msg:
|
|
138
|
+
msg: "expected v6.x — run 'morph-spec update'"
|
|
138
139
|
});
|
|
139
140
|
}
|
|
140
141
|
|
|
@@ -155,26 +156,12 @@ export function validateAgentsJson(agentsData) {
|
|
|
155
156
|
? { name: 'agents.json maxTurns', status: 'error', msg: maxTurnsErrors.join(' | ') }
|
|
156
157
|
: { name: 'agents.json maxTurns', status: 'ok' });
|
|
157
158
|
|
|
158
|
-
|
|
159
|
-
.
|
|
160
|
-
.filter(Boolean);
|
|
161
|
-
checks.push(surfacesErrors.length > 0
|
|
162
|
-
? { name: 'agents.json surfaces', status: 'error', msg: surfacesErrors.join(' | ') }
|
|
163
|
-
: { name: 'agents.json surfaces', status: 'ok' });
|
|
164
|
-
|
|
165
|
-
const effortErrors = agentEntries
|
|
166
|
-
.map(([id, data]) => effortError(id, data?.effort))
|
|
167
|
-
.filter(Boolean);
|
|
168
|
-
checks.push(effortErrors.length > 0
|
|
169
|
-
? { name: 'agents.json effort', status: 'error', msg: effortErrors.join(' | ') }
|
|
170
|
-
: { name: 'agents.json effort', status: 'ok' });
|
|
171
|
-
|
|
172
|
-
if (missingV5Fields.length > 0 && isV5) {
|
|
173
|
-
const sample = missingV5Fields.slice(0, 3).join(', ');
|
|
159
|
+
if (missingFields.length > 0 && isCurrent) {
|
|
160
|
+
const sample = missingFields.slice(0, 3).join(', ');
|
|
174
161
|
checks.push({
|
|
175
|
-
name: 'agents.json
|
|
162
|
+
name: 'agents.json required fields',
|
|
176
163
|
status: 'warn',
|
|
177
|
-
msg: `${
|
|
164
|
+
msg: `${missingFields.length} missing: ${sample}${missingFields.length > 3 ? '...' : ''}`
|
|
178
165
|
});
|
|
179
166
|
}
|
|
180
167
|
|
|
@@ -194,7 +181,6 @@ const REQUIRED_AGENTS = [
|
|
|
194
181
|
'verifier',
|
|
195
182
|
'reviewer',
|
|
196
183
|
'security',
|
|
197
|
-
'scout'
|
|
198
184
|
];
|
|
199
185
|
|
|
200
186
|
async function doctorFullCommand(frameworkRoot) {
|
|
@@ -589,15 +575,6 @@ export async function doctorWorktreesCommand(targetPath, options = {}) {
|
|
|
589
575
|
}
|
|
590
576
|
}
|
|
591
577
|
|
|
592
|
-
const { reapAgentWorktrees } = await import('../../lib/worktree/agent-reaper.js');
|
|
593
|
-
const reap = await reapAgentWorktrees(targetPath);
|
|
594
|
-
if (reap.ok) {
|
|
595
|
-
const gone = reap.reaped.map((r) => r.path ?? r.branch);
|
|
596
|
-
prune.agentReap = { reaped: gone, branchesDeleted: reap.branchesDeleted, preserved: reap.preserved, ...(reap.busy ? { busy: true } : {}) };
|
|
597
|
-
if (!options.json && gone.length) logger.success(`sobras de sub-agent removidas: ${gone.join(', ')}`);
|
|
598
|
-
if (!options.json && reap.busy) logger.warn('reaper de sub-agent já em execução em outro processo; nada foi feito nesta rodada');
|
|
599
|
-
}
|
|
600
|
-
|
|
601
578
|
const { resolvePrimaryRoot } = await import('./worktree.js');
|
|
602
579
|
const { applyEditorSettings } = await import('../../lib/worktree/vscode-settings.js');
|
|
603
580
|
const editor = applyEditorSettings(resolvePrimaryRoot(targetPath));
|
|
@@ -660,9 +637,6 @@ export async function doctorWorktreesCommand(targetPath, options = {}) {
|
|
|
660
637
|
if (problems.some(prunable) && !options.prune) {
|
|
661
638
|
logger.dim('\n Rode `morph-spec doctor --worktrees --prune` para limpar os registros órfãos e os diretórios vazios.');
|
|
662
639
|
}
|
|
663
|
-
if (issues.some((i) => i.type === 'agent-worktree-leftover' && i.reapable) && !options.prune) {
|
|
664
|
-
logger.dim('\n Rode `morph-spec worktree reap` (ou `doctor --worktrees --prune`) para remover as sobras de sub-agent que já pararam.');
|
|
665
|
-
}
|
|
666
640
|
if (!problems.length) {
|
|
667
641
|
logger.success('Nenhuma inconsistência — os itens acima são informativos.');
|
|
668
642
|
return true;
|
|
@@ -1058,6 +1032,16 @@ export async function doctorCommand(options = {}) {
|
|
|
1058
1032
|
hasWarnings = true;
|
|
1059
1033
|
}
|
|
1060
1034
|
|
|
1035
|
+
const gaps = stackRuleGaps(targetPath);
|
|
1036
|
+
if (gaps?.stackMissing) {
|
|
1037
|
+
checks.push({ name: 'project.stack', status: 'warn', msg: 'ausente/unknown no config.json — nenhuma regra de stack instalada; morph-spec stack-set <stack> (a do Morph: standard architecture-stack-baseline), antes da sessão que implementa' });
|
|
1038
|
+
hasWarnings = true;
|
|
1039
|
+
}
|
|
1040
|
+
if (gaps?.missing.length) {
|
|
1041
|
+
checks.push({ name: 'stack rules', status: 'warn', msg: `faltando para [${gaps.tags.join(', ')}]: ${gaps.missing.join(', ')} — rode morph-spec verify ou update` });
|
|
1042
|
+
hasWarnings = true;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1061
1045
|
const agentsPathClaude = join(claudePath, 'agents');
|
|
1062
1046
|
if (await pathExists(agentsPathClaude)) {
|
|
1063
1047
|
const agentFiles = await fs.readdir(agentsPathClaude);
|
|
@@ -32,17 +32,16 @@ export function fleetCommand(options = {}) {
|
|
|
32
32
|
|
|
33
33
|
console.log(chalk.white.bold(
|
|
34
34
|
// PHASE at 18 fits `aguardando-merge`; narrower misaligns every column after it.
|
|
35
|
-
col('FEATURE', 20) + col('TYPE', 9) + col('PHASE', 18) + col('
|
|
35
|
+
col('FEATURE', 20) + col('TYPE', 9) + col('PHASE', 18) + col('CENÁRIOS', 10)
|
|
36
36
|
+ col('GATES PEND.', 20) + col('WHERE', 24) + col('REMOTO', 16) + col('CLAIM', 16)
|
|
37
|
-
+ col('PORTS', 12) +
|
|
37
|
+
+ col('PORTS', 12) + 'ATIVIDADE',
|
|
38
38
|
));
|
|
39
39
|
for (const e of fleet.entries) {
|
|
40
|
-
const
|
|
40
|
+
const scenarios = e.scenarios ? `${e.scenarios.done}/${e.scenarios.total}` : '-';
|
|
41
41
|
const gates = e.pendingGates.length ? e.pendingGates.join(',') : '-';
|
|
42
42
|
// pr-pending exists only on its branch, never in a worktree or the root.
|
|
43
43
|
const where = (e.location === 'worktree' || e.location === 'pr-pending') ? e.branch : 'raiz';
|
|
44
44
|
const ports = e.env ? `${e.env.range[0]}-${e.env.range[1]}` : '-';
|
|
45
|
-
const handoff = e.handoff?.exists ? 'sim' : '-';
|
|
46
45
|
const activity = [
|
|
47
46
|
e.lastCommit ? `commit ${e.lastCommit}` : null,
|
|
48
47
|
Number.isInteger(e.dirty) && e.dirty > 0 ? chalk.yellow(`${e.dirty} dirty`) : null,
|
|
@@ -51,10 +50,10 @@ export function fleetCommand(options = {}) {
|
|
|
51
50
|
// length and append the padding by hand.
|
|
52
51
|
const claimPlain = e.claim ? `${e.claim.live ? 'sessão' : 'morta'} (${claimAge(e.claim)})` : '-';
|
|
53
52
|
console.log(
|
|
54
|
-
col(e.feature, 20) + col(e.workType || '-', 9) + col(e.phase, 18) + col(
|
|
53
|
+
col(e.feature, 20) + col(e.workType || '-', 9) + col(e.phase, 18) + col(scenarios, 10)
|
|
55
54
|
+ col(gates, 20) + col(where, 24) + col(remoteCell(e.remote), 16)
|
|
56
55
|
+ claimCell(e.claim) + ' '.repeat(Math.max(1, 16 - claimPlain.length))
|
|
57
|
-
+ col(ports, 12) +
|
|
56
|
+
+ col(ports, 12) + activity,
|
|
58
57
|
);
|
|
59
58
|
}
|
|
60
59
|
const claimed = fleet.entries.filter((e) => e.claim?.live);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { join,
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
1
|
+
import { join, resolve } from 'path';
|
|
3
2
|
import { homedir } from 'os';
|
|
4
3
|
import fs from 'fs-extra';
|
|
5
4
|
import ora from 'ora';
|
|
@@ -28,13 +27,43 @@ import {
|
|
|
28
27
|
setupStitch,
|
|
29
28
|
loadMcpRegistry
|
|
30
29
|
} from '../../lib/installers/mcp-installer.js';
|
|
31
|
-
import { setupInfra
|
|
32
|
-
import { installSkills } from '../../utils/skills-installer.js';
|
|
33
|
-
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
30
|
+
import { setupInfra } from '../../scripts/setup-infra.js';
|
|
34
31
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
35
32
|
import { updateCommand } from './update.js';
|
|
36
33
|
import { resolvePrimaryRoot } from './worktree.js';
|
|
37
34
|
import { registerProject } from '../../lib/projects/registry.js';
|
|
35
|
+
import { setProjectStack } from '../../lib/installers/stack-sync.js';
|
|
36
|
+
import { installStackAssets } from '../../lib/installers/stack-assets.js';
|
|
37
|
+
|
|
38
|
+
const GREENFIELD_CHOICES = [
|
|
39
|
+
{ name: 'dotnet+nextjs — stack do Morph: .NET 10 + EF Core/Neon + Next.js/Shadcn (recomendada)', value: { stack: 'dotnet+nextjs', integrations: ['neon'] } },
|
|
40
|
+
{ name: 'dotnet — só a API .NET (EF Core/Neon)', value: { stack: 'dotnet', integrations: ['neon'] } },
|
|
41
|
+
{ name: 'nextjs — só o app web Next.js', value: { stack: 'nextjs', integrations: [] } },
|
|
42
|
+
{ name: 'Decidir depois (o /morph-plan pergunta)', value: null },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/** Empty project: `--stack` wins; a terminal is asked, the Morph stack first; no terminal leaves it to the plan. */
|
|
46
|
+
export async function chooseGreenfieldStack(options = {}, { isTTY = process.stdin.isTTY, prompt = inquirer.prompt } = {}) {
|
|
47
|
+
if (options.stack) {
|
|
48
|
+
const integrations = String(options.with ?? '').split(',').map((t) => t.trim()).filter(Boolean);
|
|
49
|
+
return { stack: options.stack, integrations };
|
|
50
|
+
}
|
|
51
|
+
if (!isTTY) return null;
|
|
52
|
+
const { stackChoice } = await prompt([{
|
|
53
|
+
type: 'list',
|
|
54
|
+
name: 'stackChoice',
|
|
55
|
+
message: 'Projeto novo: qual stack?',
|
|
56
|
+
choices: GREENFIELD_CHOICES,
|
|
57
|
+
}]);
|
|
58
|
+
return stackChoice;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function reinstallForStack(targetPath, stackTags, spinner) {
|
|
62
|
+
if (stackTags.length === 0) return;
|
|
63
|
+
spinner.start('Re-installing stack-filtered assets...');
|
|
64
|
+
await installStackAssets(targetPath, stackTags);
|
|
65
|
+
spinner.succeed(`Stack-filtered assets reinstalled for [${stackTags.join(', ')}]`);
|
|
66
|
+
}
|
|
38
67
|
|
|
39
68
|
/**
|
|
40
69
|
* A linked worktree junctions `.claude/` and `.morph/framework/` but not `.morph/config`/state:
|
|
@@ -196,15 +225,17 @@ export async function initCommand(options) {
|
|
|
196
225
|
if (structure?.uiLibrary) logger.dim(`UI Library: ${structure.uiLibrary}`);
|
|
197
226
|
if (structure?.integrations?.length > 0) logger.dim(`Integrations: ${structure.integrations.join(', ')}`);
|
|
198
227
|
|
|
199
|
-
const { standardsChoice } =
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
228
|
+
const { standardsChoice } = process.stdin.isTTY
|
|
229
|
+
? await inquirer.prompt([{
|
|
230
|
+
type: 'list',
|
|
231
|
+
name: 'standardsChoice',
|
|
232
|
+
message: 'How should morph-spec handle your project standards?',
|
|
233
|
+
choices: [
|
|
234
|
+
{ name: 'Keep morph-spec defaults (recommended)', value: 'morph-spec' },
|
|
235
|
+
{ name: 'Detect project-specific standards', value: 'detect' }
|
|
236
|
+
]
|
|
237
|
+
}])
|
|
238
|
+
: { standardsChoice: 'morph-spec' };
|
|
208
239
|
|
|
209
240
|
if (standardsChoice === 'detect') {
|
|
210
241
|
spinner.start('Generating project-specific standards...');
|
|
@@ -215,30 +246,27 @@ export async function initCommand(options) {
|
|
|
215
246
|
logger.dim('Review and edit .morph/context/standards.md as needed.');
|
|
216
247
|
}
|
|
217
248
|
|
|
218
|
-
|
|
219
|
-
if (stackTags.length > 0) {
|
|
220
|
-
spinner.start('Re-installing stack-filtered assets...');
|
|
221
|
-
const frameworkDir = join(targetPath, '.morph', 'framework');
|
|
222
|
-
const fwSrcDir = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'framework');
|
|
223
|
-
|
|
224
|
-
const rulesSrc = join(fwSrcDir, 'rules');
|
|
225
|
-
const rulesDest = join(targetPath, '.claude', 'rules');
|
|
226
|
-
installRulesForStack(rulesSrc, rulesDest, stackTags);
|
|
227
|
-
installCommandsForStack(join(fwSrcDir, 'commands'), join(targetPath, '.claude', 'commands'), stackTags);
|
|
228
|
-
|
|
229
|
-
await installSkills(targetPath, { projectTags: stackTags });
|
|
230
|
-
|
|
231
|
-
await installAgents(targetPath, fwSrcDir, { projectTags: stackTags });
|
|
232
|
-
await installDomainAgents(targetPath, fwSrcDir, { projectTags: stackTags });
|
|
233
|
-
|
|
234
|
-
spinner.succeed(`Stack-filtered assets reinstalled for [${stackTags.join(', ')}]`);
|
|
235
|
-
}
|
|
236
|
-
|
|
249
|
+
await reinstallForStack(targetPath, getProjectTags(config), spinner);
|
|
237
250
|
spinner.start('Continuing...');
|
|
238
251
|
}
|
|
239
252
|
} catch (error) {
|
|
240
253
|
logger.dim(` Stack detection skipped: ${error.message}`);
|
|
241
254
|
}
|
|
255
|
+
} else {
|
|
256
|
+
const choice = await chooseGreenfieldStack(options);
|
|
257
|
+
if (choice) {
|
|
258
|
+
spinner.stop();
|
|
259
|
+
const result = setProjectStack(targetPath, choice.stack, { integrations: choice.integrations, log: (line) => logger.dim(line) });
|
|
260
|
+
if (result.error) {
|
|
261
|
+
logger.warn(`Stack não declarada: ${result.error}`);
|
|
262
|
+
} else {
|
|
263
|
+
config = await readJson(configPath);
|
|
264
|
+
await reinstallForStack(targetPath, getProjectTags(config), spinner);
|
|
265
|
+
}
|
|
266
|
+
spinner.start('Continuing...');
|
|
267
|
+
} else {
|
|
268
|
+
logger.dim(' Stack: não declarada — o /morph-plan pergunta antes de planejar (recomendada: a do Morph)');
|
|
269
|
+
}
|
|
242
270
|
}
|
|
243
271
|
|
|
244
272
|
let mcpSetupResult = null;
|
|
@@ -460,13 +488,12 @@ export async function initCommand(options) {
|
|
|
460
488
|
|
|
461
489
|
const agents = infraResult?.agents;
|
|
462
490
|
if (agents) {
|
|
463
|
-
|
|
464
|
-
logger.dim(` ✓ .claude/agents/ (${total} domain personas installed)`);
|
|
491
|
+
logger.dim(` ✓ .claude/agents/ (${agents.installed ?? 0} Gate 3 agents installed)`);
|
|
465
492
|
} else {
|
|
466
493
|
logger.dim(' ✓ .claude/agents/ (native subagents installed)');
|
|
467
494
|
}
|
|
468
495
|
|
|
469
|
-
logger.dim(' ✓ ~/.claude/statusline.
|
|
496
|
+
logger.dim(' ✓ ~/.claude/statusline.js (global statusline installed)');
|
|
470
497
|
|
|
471
498
|
logger.blank();
|
|
472
499
|
const globalSettingsPath = join(homedir(), '.claude', 'settings.local.json');
|
|
@@ -1,56 +1,17 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
import { getFeatureDir } from '../../core/paths/output-schema.js';
|
|
5
1
|
import { readMorphConfig } from '../../lib/tasks/test-runner.js';
|
|
6
|
-
import {
|
|
7
|
-
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
2
|
+
import { scanStack, renderStackScan } from '../../lib/stack/stack-scan.js';
|
|
8
3
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/** Scan the tree at `cwd`; with a feature, write `2-plan/stack-scan.md`. */
|
|
4
|
+
/** Scan the tree at `cwd`; prints, never writes — the plan reads stdout. */
|
|
12
5
|
export function runStackScan(featureName, cwd = process.cwd()) {
|
|
13
6
|
const result = scanStack(cwd, readMorphConfig(cwd));
|
|
14
|
-
|
|
15
|
-
if (!featureName) return { ...result, markdown, written: null, exit: 0 };
|
|
16
|
-
|
|
17
|
-
const featureDir = join(cwd, getFeatureDir(featureName));
|
|
18
|
-
if (!existsSync(featureDir)) return { ...result, markdown, written: null, exit: 2, error: 'feature-not-found' };
|
|
19
|
-
const planDir = join(featureDir, '2-plan');
|
|
20
|
-
mkdirSync(planDir, { recursive: true });
|
|
21
|
-
const written = join(planDir, STACK_SCAN_FILE);
|
|
22
|
-
writeFileSync(written, markdown, 'utf8');
|
|
23
|
-
return { ...result, markdown, written, exit: 0 };
|
|
7
|
+
return { ...result, markdown: renderStackScan(result, featureName ?? 'projeto'), exit: 0 };
|
|
24
8
|
}
|
|
25
9
|
|
|
26
10
|
export function stackScanCommand(featureName, options = {}) {
|
|
27
|
-
if (featureName) {
|
|
28
|
-
const owner = assertOwnerTree(featureName, process.cwd());
|
|
29
|
-
if (owner.blocked) {
|
|
30
|
-
for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec stack-scan ${featureName}`)) {
|
|
31
|
-
console.error(chalk[tone](text));
|
|
32
|
-
}
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
11
|
const result = runStackScan(featureName);
|
|
38
12
|
if (options.json) {
|
|
39
13
|
const { markdown, ...rest } = result;
|
|
40
14
|
console.log(JSON.stringify(rest, null, 2));
|
|
41
|
-
process.exit(result.exit);
|
|
42
|
-
}
|
|
43
|
-
if (result.error === 'feature-not-found') {
|
|
44
|
-
console.error(chalk.red(`stack-scan: a feature "${featureName}" não existe — rode morph-spec create ${featureName} antes`));
|
|
45
|
-
process.exit(2);
|
|
46
|
-
}
|
|
47
|
-
if (result.written) {
|
|
48
|
-
const high = result.deviations.filter((d) => d.severity === 'Bloqueante' || d.severity === 'Alto').length;
|
|
49
|
-
console.log(chalk.green(`✓ stack-scan: ${join(getFeatureDir(featureName), '2-plan', STACK_SCAN_FILE)} gravado`));
|
|
50
|
-
const summary = result.greenfield ? 'greenfield'
|
|
51
|
-
: !result.audited ? notAuditedReason(result)
|
|
52
|
-
: `${result.deviations.length} desvio(s), ${high} Bloqueante/Alto`;
|
|
53
|
-
console.log(chalk.gray(` ${summary}`));
|
|
54
15
|
} else {
|
|
55
16
|
process.stdout.write(result.markdown);
|
|
56
17
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { setProjectStack } from '../../lib/installers/stack-sync.js';
|
|
3
|
+
import { installStackAssets } from '../../lib/installers/stack-assets.js';
|
|
4
|
+
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
5
|
+
|
|
6
|
+
export async function stackSetCommand(stack, options = {}) {
|
|
7
|
+
const integrations = String(options.with ?? '').split(',').map((t) => t.trim()).filter(Boolean);
|
|
8
|
+
const result = setProjectStack(process.cwd(), stack, {
|
|
9
|
+
integrations,
|
|
10
|
+
log: options.json ? () => {} : (line) => console.log(chalk.gray(line)),
|
|
11
|
+
});
|
|
12
|
+
if (result.error) {
|
|
13
|
+
if (options.json) console.log(JSON.stringify({ error: result.error }, null, 2));
|
|
14
|
+
else console.error(chalk.red(`✗ ${result.error}`));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const { stack: declared, integrations: declaredIntegrations, sync } = result;
|
|
18
|
+
await installStackAssets(process.cwd(), getProjectTags({ project: { stack: declared, integrations: declaredIntegrations } }));
|
|
19
|
+
if (options.json) {
|
|
20
|
+
console.log(JSON.stringify({ stack: declared, integrations: declaredIntegrations, rulesAdded: sync.added, rulesUpdated: sync.updated }, null, 2));
|
|
21
|
+
} else {
|
|
22
|
+
console.log(chalk.green(`✓ stack ${declared}`) + chalk.gray(` — rules, commands e skills da stack instalados (${sync.added.length + sync.updated.length} rule(s) de domínio nova(s))`));
|
|
23
|
+
console.log(chalk.yellow(' As rules carregam no início de uma sessão do Claude Code: implemente numa sessão nova.'));
|
|
24
|
+
}
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { loadState, getFeature, derivePhase, deriveOutputs } from '../../core/state/state-manager.js';
|
|
3
|
-
import {
|
|
3
|
+
import { implementProgress } from '../../../framework/hooks/shared/report-progress.js';
|
|
4
4
|
import { formatNext } from '../../../framework/hooks/shared/next-step.js';
|
|
5
5
|
import { nextStepFor } from '../../lib/next-step.js';
|
|
6
6
|
|
|
@@ -20,8 +20,8 @@ const PHASE_ORDER = [
|
|
|
20
20
|
|
|
21
21
|
const PHASE_OUTPUTS = {
|
|
22
22
|
plan: ['plan', 'checks'],
|
|
23
|
-
uiux: ['
|
|
24
|
-
implement: [],
|
|
23
|
+
uiux: ['uiMockups'],
|
|
24
|
+
implement: ['implementReport'],
|
|
25
25
|
review: ['verifierReport', 'reviewerReport']
|
|
26
26
|
};
|
|
27
27
|
|
|
@@ -118,7 +118,7 @@ export async function statusCommand(featureName, options = {}) {
|
|
|
118
118
|
if (!feature.outputs) {
|
|
119
119
|
feature.outputs = deriveOutputs(featureName, process.cwd());
|
|
120
120
|
}
|
|
121
|
-
const
|
|
121
|
+
const scenarios = implementProgress(featurePath);
|
|
122
122
|
|
|
123
123
|
if (options.json) {
|
|
124
124
|
const status = {
|
|
@@ -137,7 +137,7 @@ export async function statusCommand(featureName, options = {}) {
|
|
|
137
137
|
created: output.created,
|
|
138
138
|
path: output.path
|
|
139
139
|
})),
|
|
140
|
-
|
|
140
|
+
scenarios,
|
|
141
141
|
approvals: feature.approvalGates || {},
|
|
142
142
|
next: nextStepFor(featureName, process.cwd())
|
|
143
143
|
};
|
|
@@ -177,14 +177,13 @@ export async function statusCommand(featureName, options = {}) {
|
|
|
177
177
|
}
|
|
178
178
|
console.log('');
|
|
179
179
|
|
|
180
|
-
if (
|
|
181
|
-
console.log(chalk.white.bold('
|
|
182
|
-
const percentage = Math.round((
|
|
183
|
-
console.log(` Total: ${
|
|
184
|
-
console.log(chalk.green(` ✓
|
|
185
|
-
if (
|
|
186
|
-
if (
|
|
187
|
-
if (slices.pending > 0) console.log(chalk.gray(` ⬜ Pendentes: ${slices.pending}`));
|
|
180
|
+
if (scenarios && scenarios.total > 0) {
|
|
181
|
+
console.log(chalk.white.bold('Cenários (3-implement/report.md):'));
|
|
182
|
+
const percentage = Math.round((scenarios.done / scenarios.total) * 100);
|
|
183
|
+
console.log(` Total: ${scenarios.total}`);
|
|
184
|
+
console.log(chalk.green(` ✓ Feitos: ${scenarios.done} (${percentage}%)`));
|
|
185
|
+
if (scenarios.blocked > 0) console.log(chalk.yellow(` ⏸ Bloqueados (esperam decisão): ${scenarios.blocked}`));
|
|
186
|
+
if (scenarios.pending > 0) console.log(chalk.gray(` ⬜ Pendentes: ${scenarios.pending}`));
|
|
188
187
|
console.log('');
|
|
189
188
|
}
|
|
190
189
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join, dirname, relative, isAbsolute } from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { spawnSync } from 'child_process';
|
|
4
|
-
import { existsSync, readdirSync } from 'fs';
|
|
4
|
+
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -27,9 +27,10 @@ import {
|
|
|
27
27
|
} from '../../utils/version-checker.js';
|
|
28
28
|
import { installClaudeHooks, installGlobalStatusline } from '../../utils/claude-settings-manager.js';
|
|
29
29
|
import { installSkills } from '../../utils/skills-installer.js';
|
|
30
|
-
import { installAgents
|
|
30
|
+
import { installAgents } from '../../utils/agents-installer.js';
|
|
31
31
|
import { injectMorphImport } from '../../utils/claude-md-injector.js';
|
|
32
32
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
33
|
+
import { detectStackTags, mergeDetectedTags } from '../../lib/installers/stack-sync.js';
|
|
33
34
|
import { updateClaudeMdAgentsBlock, installRulesForStack, installCommandsForStack } from '../../scripts/setup-infra.js';
|
|
34
35
|
import { reconcileLegacyTemplatesCopy } from '../../lib/installers/templates-reconcile.js';
|
|
35
36
|
import { createEffects } from '../../lib/installers/effects.js';
|
|
@@ -145,6 +146,26 @@ export function stateJsonIsTracked(targetPath) {
|
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
const SLICES_HEADING = /^## Fatias\s*$/m;
|
|
150
|
+
|
|
151
|
+
/** Active features still planned in 9.x slices: a plan with `## Fatias` and no `3-implement/report.md` yet. */
|
|
152
|
+
export function legacySliceFeatures(targetPath) {
|
|
153
|
+
const featuresDir = join(targetPath, '.morph', 'features');
|
|
154
|
+
let names;
|
|
155
|
+
try { names = readdirSync(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name); } catch { return []; }
|
|
156
|
+
return names.filter((name) => {
|
|
157
|
+
const dir = join(featuresDir, name);
|
|
158
|
+
if (existsSync(join(dir, '3-implement', 'report.md'))) return false;
|
|
159
|
+
try { return SLICES_HEADING.test(readFileSync(join(dir, '2-plan', 'plan.md'), 'utf8')); } catch { return false; }
|
|
160
|
+
}).sort();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function legacySliceNotice(names) {
|
|
164
|
+
if (names.length === 0) return null;
|
|
165
|
+
return `Feature(s) planejada(s) em fatias (9.x): ${names.join(', ')}. Plano não aprovado: tire \`## Fatias\` e rode \`morph-spec plan-check <f>\`. `
|
|
166
|
+
+ 'Já em implement: segue pelos `reports/F*.md`; para o formato novo, crie `3-implement/report.md` com uma linha por `Cn` (CHANGELOG 10.0.0).';
|
|
167
|
+
}
|
|
168
|
+
|
|
148
169
|
export function untrackCommittedStateJson(targetPath, fx = createEffects({ dryRun: false })) {
|
|
149
170
|
if (!stateJsonIsTracked(targetPath)) return false;
|
|
150
171
|
return fx.gitIndex('rm --cached --quiet', '.morph/state.json', { cwd: targetPath });
|
|
@@ -487,6 +508,19 @@ export async function updateCommand(options = {}) {
|
|
|
487
508
|
try {
|
|
488
509
|
if (configBackup) projectTags = getProjectTags(configBackup);
|
|
489
510
|
} catch {}
|
|
511
|
+
// The same detection as `syncStackRules`, folded in before the installs: `restoreUserConfig`
|
|
512
|
+
// writes `configBackup`, and a dry run sees `.claude/rules/` before the clean, not after.
|
|
513
|
+
if (configBackup) {
|
|
514
|
+
try {
|
|
515
|
+
const detected = detectStackTags(targetPath, { config: configBackup });
|
|
516
|
+
const merged = mergeDetectedTags(configBackup, detected.persist);
|
|
517
|
+
if (merged.changed) {
|
|
518
|
+
configBackup.project = merged.config.project;
|
|
519
|
+
logger.dim(` ✓ config.json: + ${merged.added.join(', ')} (detectado nos arquivos)`);
|
|
520
|
+
}
|
|
521
|
+
projectTags = [...new Set([...getProjectTags(configBackup), ...detected.tags])];
|
|
522
|
+
} catch {}
|
|
523
|
+
}
|
|
490
524
|
|
|
491
525
|
let commandsCopied = false;
|
|
492
526
|
const commandsSrc = join(frameworkDir, 'commands');
|
|
@@ -504,7 +538,6 @@ export async function updateCommand(options = {}) {
|
|
|
504
538
|
|
|
505
539
|
updateSpinner.text = 'Syncing agents to .claude/agents/...';
|
|
506
540
|
const agentsResult = await installAgents(targetPath, frameworkDir, { projectTags, fx });
|
|
507
|
-
await installDomainAgents(targetPath, frameworkDir, { projectTags, fx });
|
|
508
541
|
|
|
509
542
|
updateSpinner.text = 'Syncing rules to .claude/rules/...';
|
|
510
543
|
const rulesSrc = join(frameworkDir, 'rules');
|
|
@@ -566,6 +599,9 @@ export async function updateCommand(options = {}) {
|
|
|
566
599
|
}
|
|
567
600
|
}
|
|
568
601
|
|
|
602
|
+
const sliceNotice = legacySliceNotice(legacySliceFeatures(targetPath));
|
|
603
|
+
if (sliceNotice) notices.push(sliceNotice);
|
|
604
|
+
|
|
569
605
|
const untracked = untrackCommittedStateJson(targetPath, fx);
|
|
570
606
|
|
|
571
607
|
updateSpinner.text = 'Saving version info...';
|
|
@@ -320,15 +320,6 @@ export async function runWorktreeSetup(feature, options = {}) {
|
|
|
320
320
|
|
|
321
321
|
const graph = seedWorktreeGraph(worktreePath, cwd);
|
|
322
322
|
|
|
323
|
-
// Fail-open; a no-op under `create --worktree`, which regenerates it after its copy.
|
|
324
|
-
try {
|
|
325
|
-
if (existsSync(join(worktreePath, '.morph', 'features', feature, 'feature.json'))) {
|
|
326
|
-
const { writeHandoff } = await import('../../lib/generators/handoff-generator.js');
|
|
327
|
-
// `worktree` is root-owned and absent from this branch copy: pass it so the doc says "Local: worktree".
|
|
328
|
-
writeHandoff(feature, worktreePath, { worktree: { path: worktreePath, branch } });
|
|
329
|
-
}
|
|
330
|
-
} catch {}
|
|
331
|
-
|
|
332
323
|
try {
|
|
333
324
|
const { loadState, saveState } = await import('../../core/state/state-manager.js');
|
|
334
325
|
const state = loadState(false);
|
|
@@ -834,9 +825,9 @@ export function pickRemovalFailure(stderrs) {
|
|
|
834
825
|
|
|
835
826
|
/**
|
|
836
827
|
* PROVE-or-DECLINE. `refused` (proved unsafe) restores the junctions; a git FAILURE leaves them down
|
|
837
|
-
* (`infraLeftUnlinked`)
|
|
828
|
+
* (`infraLeftUnlinked`).
|
|
838
829
|
*/
|
|
839
|
-
export async function teardownWorktree(worktreePath, { root, feature, branch, options = {}, deleteBranch = true, addressByTarget = false,
|
|
830
|
+
export async function teardownWorktree(worktreePath, { root, feature, branch, options = {}, deleteBranch = true, addressByTarget = false, stamp }) {
|
|
840
831
|
// ORDER IS LOAD-BEARING: unlink the junctions BEFORE git touches the dir. `git worktree remove`
|
|
841
832
|
// follows them and deletes the root's installation; a declined path is repaired by the rollback below.
|
|
842
833
|
const unlinked = unlinkWorktreeInfraLinks(worktreePath, root);
|
|
@@ -881,8 +872,7 @@ export async function teardownWorktree(worktreePath, { root, feature, branch, op
|
|
|
881
872
|
// would make the next retry (or a raw `--force`) follow the junctions out.
|
|
882
873
|
const refused = !canRemove;
|
|
883
874
|
let relinked = false;
|
|
884
|
-
|
|
885
|
-
if ((refused || relinkAfterFailure) && unlinked.length && existsSync(worktreePath)) {
|
|
875
|
+
if (refused && unlinked.length && existsSync(worktreePath)) {
|
|
886
876
|
try {
|
|
887
877
|
setupInfraLinks(worktreePath, root);
|
|
888
878
|
relinked = true;
|
|
@@ -1055,7 +1045,6 @@ export async function runWorktreeRemove(featureArg, options = {}) {
|
|
|
1055
1045
|
options: { discardIgnored: options.discardIgnored === true },
|
|
1056
1046
|
// The branch is the work: deleting it would discard unintegrated commits. Opt-in only.
|
|
1057
1047
|
deleteBranch: options.deleteBranch === true,
|
|
1058
|
-
relinkOnFailure: options.relinkOnFailure === true,
|
|
1059
1048
|
// Re-run advice must address the worktree the way it was addressable here:
|
|
1060
1049
|
// a `--target` worktree has no feature name that would resolve.
|
|
1061
1050
|
addressByTarget: !featureAddressable,
|