@polymorphism-tech/morph-spec 9.0.0 → 9.1.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 +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +4 -62
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +12 -4
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +6 -17
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +11 -67
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +1 -42
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +2 -2
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +2 -2
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +34 -12
- package/framework/standards/STANDARDS.meta.json +16 -2
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +5 -57
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +14 -96
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +2 -5
- package/src/commands/validation/validate-feature.js +5 -94
- package/src/commands/validation/verify.js +15 -98
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +1 -0
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +3 -15
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +5 -31
- package/src/lib/plan/plan-parser.js +1 -6
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +5 -23
- package/src/lib/risk-detector.js +39 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +52 -358
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +13 -95
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -147
- package/src/utils/agents-installer.js +28 -275
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,22 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scaffold Generator - Generates VSA feature slice (.NET) and feature page (Next.js) files.
|
|
3
|
-
*
|
|
4
|
-
* Uses existing Handlebars templates from framework/templates/ to generate
|
|
5
|
-
* complete feature structures with one command.
|
|
6
|
-
*
|
|
7
|
-
* @module scaffold-generator
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
|
|
11
2
|
import { join, dirname, basename } from 'path';
|
|
12
3
|
import { renderTemplate } from '../../core/templates/template-renderer.js';
|
|
13
4
|
import { resolveFrameworkTemplatePath } from '../../core/templates/template-registry.js';
|
|
14
5
|
|
|
15
|
-
/**
|
|
16
|
-
* Convert kebab-case to PascalCase.
|
|
17
|
-
* @param {string} str
|
|
18
|
-
* @returns {string}
|
|
19
|
-
*/
|
|
20
6
|
function toPascalCase(str) {
|
|
21
7
|
return str
|
|
22
8
|
.split('-')
|
|
@@ -24,11 +10,6 @@ function toPascalCase(str) {
|
|
|
24
10
|
.join('');
|
|
25
11
|
}
|
|
26
12
|
|
|
27
|
-
/**
|
|
28
|
-
* Convert kebab-case to camelCase.
|
|
29
|
-
* @param {string} str
|
|
30
|
-
* @returns {string}
|
|
31
|
-
*/
|
|
32
13
|
function toCamelCase(str) {
|
|
33
14
|
return str
|
|
34
15
|
.split('-')
|
|
@@ -36,16 +17,8 @@ function toCamelCase(str) {
|
|
|
36
17
|
.join('');
|
|
37
18
|
}
|
|
38
19
|
|
|
39
|
-
/**
|
|
40
|
-
* Normalize a namespace to kebab-case so Handlebars {{pascalCase}} reconstructs it correctly.
|
|
41
|
-
* Handles: PascalCase ("MyBookStore" → "my-book-store"), dot-separated ("My.App" → "my-app"),
|
|
42
|
-
* and already kebab-case pass-through.
|
|
43
|
-
* @param {string} ns
|
|
44
|
-
* @returns {string}
|
|
45
|
-
*/
|
|
20
|
+
/** Kebab-case so Handlebars {{pascalCase}} reconstructs it: "MyBookStore" → "my-book-store", "My.App" → "my-app". */
|
|
46
21
|
export function normalizeNamespaceToKebab(ns) {
|
|
47
|
-
// Split on dots first (My.BookStore → ["My", "BookStore"])
|
|
48
|
-
// Then split PascalCase on uppercase boundaries (BookStore → Book-Store)
|
|
49
22
|
return ns
|
|
50
23
|
.split('.')
|
|
51
24
|
.map(segment =>
|
|
@@ -57,15 +30,8 @@ export function normalizeNamespaceToKebab(ns) {
|
|
|
57
30
|
.join('-');
|
|
58
31
|
}
|
|
59
32
|
|
|
60
|
-
//
|
|
61
|
-
// .NET VSA Scaffold
|
|
62
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
63
|
-
|
|
64
|
-
// Each entry pairs a REGISTRY.json template `id` with the generator-specific
|
|
65
|
-
// output shaping (class-name suffix, target directory). REGISTRY owns the
|
|
66
|
-
// template path; the generator owns where the rendered file lands.
|
|
33
|
+
// REGISTRY.json owns the template path; the generator owns where the rendered file lands.
|
|
67
34
|
|
|
68
|
-
/** .NET slice-level files (endpoint/handler/request/response/validator). */
|
|
69
35
|
const DOTNET_SLICE_TEMPLATES = [
|
|
70
36
|
{ templateId: 'dotnet-feature-endpoint', suffix: 'Endpoint' },
|
|
71
37
|
{ templateId: 'dotnet-feature-handler', suffix: 'Handler' },
|
|
@@ -74,23 +40,11 @@ const DOTNET_SLICE_TEMPLATES = [
|
|
|
74
40
|
{ templateId: 'dotnet-feature-validator', suffix: 'Validator' },
|
|
75
41
|
];
|
|
76
42
|
|
|
77
|
-
/** .NET feature-level files (entity, errors). */
|
|
78
43
|
const DOTNET_FEATURE_TEMPLATES = [
|
|
79
44
|
{ templateId: 'dotnet-feature-entity', suffix: '', dir: 'entities' },
|
|
80
45
|
{ templateId: 'dotnet-feature-errors', suffix: 'Errors', dir: 'feature' },
|
|
81
46
|
];
|
|
82
47
|
|
|
83
|
-
/**
|
|
84
|
-
* Scaffold a .NET VSA feature slice.
|
|
85
|
-
*
|
|
86
|
-
* @param {Object} options
|
|
87
|
-
* @param {string} options.feature - Entity/feature name in kebab-case (e.g., "book")
|
|
88
|
-
* @param {string} options.sliceName - Slice name in kebab-case (e.g., "create-book")
|
|
89
|
-
* @param {string} options.namespace - Root namespace (e.g., "MyApp")
|
|
90
|
-
* @param {string} [options.outputDir] - Base output directory (default: cwd)
|
|
91
|
-
* @param {boolean} [options.dryRun] - Preview without writing
|
|
92
|
-
* @returns {{ files: Array<{path: string, content: string, skipped: boolean}> }}
|
|
93
|
-
*/
|
|
94
48
|
export function scaffoldDotnetSlice(options) {
|
|
95
49
|
const { feature, sliceName, namespace, outputDir = process.cwd(), dryRun = false } = options;
|
|
96
50
|
|
|
@@ -107,7 +61,6 @@ export function scaffoldDotnetSlice(options) {
|
|
|
107
61
|
|
|
108
62
|
const files = [];
|
|
109
63
|
|
|
110
|
-
// 1. Render slice-level templates
|
|
111
64
|
for (const tmpl of DOTNET_SLICE_TEMPLATES) {
|
|
112
65
|
const templatePath = resolveFrameworkTemplatePath(tmpl.templateId);
|
|
113
66
|
const outputPath = join(
|
|
@@ -128,7 +81,6 @@ export function scaffoldDotnetSlice(options) {
|
|
|
128
81
|
files.push({ path: outputPath, content, skipped: false });
|
|
129
82
|
}
|
|
130
83
|
|
|
131
|
-
// 2. Render feature-level templates (skip if already exist)
|
|
132
84
|
for (const tmpl of DOTNET_FEATURE_TEMPLATES) {
|
|
133
85
|
const templatePath = resolveFrameworkTemplatePath(tmpl.templateId);
|
|
134
86
|
let outputPath;
|
|
@@ -157,12 +109,6 @@ export function scaffoldDotnetSlice(options) {
|
|
|
157
109
|
return { files };
|
|
158
110
|
}
|
|
159
111
|
|
|
160
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
161
|
-
// Next.js Scaffold
|
|
162
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
163
|
-
|
|
164
|
-
// Each entry pairs a REGISTRY.json template `id` with where the rendered file
|
|
165
|
-
// lands. REGISTRY owns the template path; the generator owns the output path.
|
|
166
112
|
const NEXTJS_TEMPLATES = [
|
|
167
113
|
{
|
|
168
114
|
templateId: 'nextjs-page',
|
|
@@ -182,15 +128,6 @@ const NEXTJS_TEMPLATES = [
|
|
|
182
128
|
},
|
|
183
129
|
];
|
|
184
130
|
|
|
185
|
-
/**
|
|
186
|
-
* Scaffold a Next.js feature page with components and hooks.
|
|
187
|
-
*
|
|
188
|
-
* @param {Object} options
|
|
189
|
-
* @param {string} options.featureName - Feature name in kebab-case (e.g., "book")
|
|
190
|
-
* @param {string} [options.outputDir] - Base output directory (default: cwd)
|
|
191
|
-
* @param {boolean} [options.dryRun] - Preview without writing
|
|
192
|
-
* @returns {{ files: Array<{path: string, content: string, skipped: boolean}> }}
|
|
193
|
-
*/
|
|
194
131
|
export function scaffoldNextjsFeature(options) {
|
|
195
132
|
const { featureName, outputDir = process.cwd(), dryRun = false } = options;
|
|
196
133
|
|
|
@@ -226,33 +163,15 @@ export function scaffoldNextjsFeature(options) {
|
|
|
226
163
|
return { files };
|
|
227
164
|
}
|
|
228
165
|
|
|
229
|
-
|
|
230
|
-
// Namespace Detection
|
|
231
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Detect the .NET root namespace from .csproj or config.json.
|
|
235
|
-
*
|
|
236
|
-
* Search order:
|
|
237
|
-
* 1. *.csproj in dir (extract <RootNamespace>)
|
|
238
|
-
* 2. *.csproj in dir (use filename without extension)
|
|
239
|
-
* 3. *.csproj one level deep
|
|
240
|
-
* 4. .morph/config.json → project.name
|
|
241
|
-
*
|
|
242
|
-
* @param {string} dir - Directory to search in
|
|
243
|
-
* @returns {string|null} Detected namespace or null
|
|
244
|
-
*/
|
|
166
|
+
/** <RootNamespace> or the name of a .csproj here or one level deep, else config.json `project.name`. */
|
|
245
167
|
export function detectNamespace(dir) {
|
|
246
|
-
// 1. Search for .csproj in current dir
|
|
247
168
|
const csprojInDir = findCsproj(dir);
|
|
248
169
|
if (csprojInDir) {
|
|
249
170
|
const ns = extractNamespaceFromCsproj(csprojInDir);
|
|
250
171
|
if (ns) return ns;
|
|
251
|
-
// Fallback: use filename
|
|
252
172
|
return basename(csprojInDir, '.csproj');
|
|
253
173
|
}
|
|
254
174
|
|
|
255
|
-
// 2. Search one level deep
|
|
256
175
|
try {
|
|
257
176
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
258
177
|
for (const entry of entries) {
|
|
@@ -265,29 +184,19 @@ export function detectNamespace(dir) {
|
|
|
265
184
|
}
|
|
266
185
|
}
|
|
267
186
|
}
|
|
268
|
-
} catch {
|
|
269
|
-
// ignore read errors
|
|
270
|
-
}
|
|
187
|
+
} catch {}
|
|
271
188
|
|
|
272
|
-
// 3. Fall back to config.json
|
|
273
189
|
const configPath = join(dir, '.morph', 'config.json');
|
|
274
190
|
if (existsSync(configPath)) {
|
|
275
191
|
try {
|
|
276
192
|
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
277
193
|
if (config.project?.name) return config.project.name;
|
|
278
|
-
} catch {
|
|
279
|
-
// ignore parse errors
|
|
280
|
-
}
|
|
194
|
+
} catch {}
|
|
281
195
|
}
|
|
282
196
|
|
|
283
197
|
return null;
|
|
284
198
|
}
|
|
285
199
|
|
|
286
|
-
/**
|
|
287
|
-
* Find the first .csproj file in a directory.
|
|
288
|
-
* @param {string} dir
|
|
289
|
-
* @returns {string|null}
|
|
290
|
-
*/
|
|
291
200
|
function findCsproj(dir) {
|
|
292
201
|
try {
|
|
293
202
|
const entries = readdirSync(dir);
|
|
@@ -298,11 +207,6 @@ function findCsproj(dir) {
|
|
|
298
207
|
}
|
|
299
208
|
}
|
|
300
209
|
|
|
301
|
-
/**
|
|
302
|
-
* Extract <RootNamespace> from a .csproj file.
|
|
303
|
-
* @param {string} csprojPath
|
|
304
|
-
* @returns {string|null}
|
|
305
|
-
*/
|
|
306
210
|
function extractNamespaceFromCsproj(csprojPath) {
|
|
307
211
|
try {
|
|
308
212
|
const content = readFileSync(csprojPath, 'utf-8');
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Settings Generator
|
|
3
|
-
*
|
|
4
|
-
* Generates .claude/settings.local.json for projects based on detected
|
|
5
|
-
* stack, MCPs, and morph-spec configuration. Uses _morph_managed marker
|
|
6
|
-
* to track managed entries for safe reset.
|
|
7
|
-
*/
|
|
1
|
+
// `_morph_managed` marks the managed entries, so a reset never touches the user's.
|
|
8
2
|
|
|
9
3
|
import { join } from 'path';
|
|
10
4
|
import { existsSync } from 'fs';
|
|
@@ -12,13 +6,6 @@ import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
|
12
6
|
|
|
13
7
|
const MORPH_MANAGED_KEY = '_morph_managed';
|
|
14
8
|
|
|
15
|
-
/**
|
|
16
|
-
* Generate and merge settings for a project
|
|
17
|
-
* @param {string} targetPath - Project root directory
|
|
18
|
-
* @param {Object} detectedConfig - Output from detectClaudeConfig()
|
|
19
|
-
* @param {Object} morphConfig - morph-spec config (from .morph/config/config.json)
|
|
20
|
-
* @returns {Promise<Object>} Generated settings summary
|
|
21
|
-
*/
|
|
22
9
|
export async function generateSettings(targetPath, detectedConfig, morphConfig = {}) {
|
|
23
10
|
const claudeDir = join(targetPath, '.claude');
|
|
24
11
|
const settingsPath = join(claudeDir, 'settings.local.json');
|
|
@@ -32,11 +19,9 @@ export async function generateSettings(targetPath, detectedConfig, morphConfig =
|
|
|
32
19
|
generated_at: new Date().toISOString()
|
|
33
20
|
};
|
|
34
21
|
|
|
35
|
-
// Merge without overwriting user config
|
|
36
22
|
const merged = mergeSettings(existing, generated);
|
|
37
23
|
merged[MORPH_MANAGED_KEY] = managed;
|
|
38
24
|
|
|
39
|
-
// Ensure directory exists
|
|
40
25
|
if (!existsSync(claudeDir)) {
|
|
41
26
|
await mkdir(claudeDir, { recursive: true });
|
|
42
27
|
}
|
|
@@ -49,12 +34,7 @@ export async function generateSettings(targetPath, detectedConfig, morphConfig =
|
|
|
49
34
|
};
|
|
50
35
|
}
|
|
51
36
|
|
|
52
|
-
/**
|
|
53
|
-
* Deep merge settings without overwriting user entries
|
|
54
|
-
* @param {Object} existing - Current settings.local.json
|
|
55
|
-
* @param {Object} generated - Generated settings to merge
|
|
56
|
-
* @returns {Object} Merged settings
|
|
57
|
-
*/
|
|
37
|
+
/** Never overwrites user entries: arrays are unioned, objects merged, only new keys added. */
|
|
58
38
|
export function mergeSettings(existing, generated) {
|
|
59
39
|
const result = { ...existing };
|
|
60
40
|
|
|
@@ -62,17 +42,14 @@ export function mergeSettings(existing, generated) {
|
|
|
62
42
|
if (key === MORPH_MANAGED_KEY) continue;
|
|
63
43
|
|
|
64
44
|
if (Array.isArray(value) && Array.isArray(result[key])) {
|
|
65
|
-
// Merge arrays without duplicates
|
|
66
45
|
const set = new Set(result[key]);
|
|
67
46
|
for (const item of value) {
|
|
68
47
|
set.add(item);
|
|
69
48
|
}
|
|
70
49
|
result[key] = [...set];
|
|
71
50
|
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
72
|
-
// Recursively merge objects
|
|
73
51
|
result[key] = mergeSettings(result[key] || {}, value);
|
|
74
52
|
} else if (!(key in result)) {
|
|
75
|
-
// Only add new keys, never overwrite existing
|
|
76
53
|
result[key] = value;
|
|
77
54
|
}
|
|
78
55
|
}
|
|
@@ -80,11 +57,6 @@ export function mergeSettings(existing, generated) {
|
|
|
80
57
|
return result;
|
|
81
58
|
}
|
|
82
59
|
|
|
83
|
-
/**
|
|
84
|
-
* Save integrations.json with detected config
|
|
85
|
-
* @param {string} targetPath - Project root directory
|
|
86
|
-
* @param {Object} detectedConfig - Output from detectClaudeConfig()
|
|
87
|
-
*/
|
|
88
60
|
export async function saveIntegrations(targetPath, detectedConfig) {
|
|
89
61
|
const integrationsPath = join(targetPath, '.morph', 'config', 'integrations.json');
|
|
90
62
|
const integrationsDir = join(targetPath, '.morph', 'config');
|
|
@@ -107,13 +79,8 @@ export async function saveIntegrations(targetPath, detectedConfig) {
|
|
|
107
79
|
return integrationsPath;
|
|
108
80
|
}
|
|
109
81
|
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// ============================================================================
|
|
113
|
-
|
|
114
|
-
// Claude Code reads only `permissions: { allow, deny }`; a bare array (older generator) is ignored.
|
|
115
|
-
// Versões antigas gravavam regras curinga (node:*, npm:*, npx:*…) como array, forma que o
|
|
116
|
-
// Claude Code ignora: nunca valeram. Converter para { allow } as ligaria em silêncio.
|
|
82
|
+
// Claude Code reads only `permissions: { allow, deny }`. Old generators wrote wildcard rules as a bare
|
|
83
|
+
// array that never applied; converting it to { allow } would silently enable them.
|
|
117
84
|
function withoutLegacyPermissions(settings) {
|
|
118
85
|
if (!Array.isArray(settings.permissions)) return settings;
|
|
119
86
|
const { permissions, ...rest } = settings;
|
|
@@ -1,36 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* WHY THIS EXISTS: a squash-merge puts the WORK in `origin/master` while
|
|
6
|
-
* leaving none of the branch's own commits reachable from it. So
|
|
7
|
-
* `git branch --merged origin/master` does not list the branch, and
|
|
8
|
-
* `git branch -d` refuses it as unmerged. The only remaining command is
|
|
9
|
-
* `git branch -D`, which is byte-identical to the command that discards real,
|
|
10
|
-
* never-integrated work — the tool gives the user no way to tell the two apart,
|
|
11
|
-
* and after enough of these the habit becomes "just -D it", which is how work
|
|
12
|
-
* gets lost.
|
|
13
|
-
*
|
|
14
|
-
* Every branch lands in exactly one class, and only the two MERGED ones are
|
|
15
|
-
* ever deletable:
|
|
16
|
-
*
|
|
17
|
-
* merged-ancestral — git itself proves it: the branch tip is an ancestor of
|
|
18
|
-
* the default. A plain merge, or a fast-forward.
|
|
19
|
-
* merged-squash — git cannot see it, but the evidence is positive: either
|
|
20
|
-
* GitHub reports the PR as MERGED, or the branch's tree is
|
|
21
|
-
* IDENTICAL to the default's (every change it carries is
|
|
22
|
-
* already there under a different commit).
|
|
23
|
-
* open — a PR is OPEN for it. Never deleted: the PR is the whole
|
|
24
|
-
* reason the branch must survive.
|
|
25
|
-
* in-worktree — checked out by a live worktree. Never deleted.
|
|
26
|
-
* unmerged — none of the above could be PROVEN. This is the fail-safe
|
|
27
|
-
* default and covers "no `gh`", "no origin", and "genuinely
|
|
28
|
-
* unintegrated work" alike, because from here they are the
|
|
29
|
-
* same thing: not proven integrated.
|
|
30
|
-
*
|
|
31
|
-
* Nothing here mutates. `pruneBranches` in commands/project/branch.js does the
|
|
32
|
-
* deleting, and it deletes ONLY what this file classified as merged.
|
|
33
|
-
*/
|
|
1
|
+
// A squash-merge leaves the work in the default branch but none of the branch's commits,
|
|
2
|
+
// so `branch -d` refuses it and `-D` looks like discarding real work. Only the two
|
|
3
|
+
// `merged-*` classes are deletable; anything not PROVEN integrated is `unmerged`.
|
|
34
4
|
|
|
35
5
|
import { execFileSync } from 'child_process';
|
|
36
6
|
|
|
@@ -38,11 +8,7 @@ function git(args, cwd) {
|
|
|
38
8
|
return execFileSync('git', args, { cwd, stdio: 'pipe' }).toString();
|
|
39
9
|
}
|
|
40
10
|
|
|
41
|
-
/**
|
|
42
|
-
* Local branches with their tips. Excludes nothing — the caller decides.
|
|
43
|
-
*
|
|
44
|
-
* @returns {Array<{ name: string, sha: string, current: boolean, worktreePath: string|null }>}
|
|
45
|
-
*/
|
|
11
|
+
/** @returns {Array<{ name: string, sha: string, current: boolean, worktreePath: string|null }>} */
|
|
46
12
|
export function listLocalBranches(cwd) {
|
|
47
13
|
try {
|
|
48
14
|
const out = git(['for-each-ref', '--format=%(refname:short)%09%(objectname)%09%(HEAD)%09%(worktreepath)', 'refs/heads/'], cwd);
|
|
@@ -55,7 +21,6 @@ export function listLocalBranches(cwd) {
|
|
|
55
21
|
}
|
|
56
22
|
}
|
|
57
23
|
|
|
58
|
-
/** Is `ancestor` reachable from `descendant`? False on any git error. */
|
|
59
24
|
function isAncestor(ancestor, descendant, cwd) {
|
|
60
25
|
try {
|
|
61
26
|
git(['merge-base', '--is-ancestor', ancestor, descendant], cwd);
|
|
@@ -65,19 +30,8 @@ function isAncestor(ancestor, descendant, cwd) {
|
|
|
65
30
|
}
|
|
66
31
|
}
|
|
67
32
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*
|
|
71
|
-
* `git diff --quiet <base> <branch>` compares the two TREES: an empty diff
|
|
72
|
-
* means every file the branch touched already reads exactly as the branch left
|
|
73
|
-
* it in the base. That is precisely the state a squash-merge produces, and it
|
|
74
|
-
* is a positive proof rather than an inference from history shape.
|
|
75
|
-
*
|
|
76
|
-
* Deliberately NOT `base...branch` (the three-dot form diffs against the merge
|
|
77
|
-
* base, which for a squash-merged branch still shows the branch's own changes).
|
|
78
|
-
*
|
|
79
|
-
* Fail-CLOSED: any git error reads as "introduces something", never as merged.
|
|
80
|
-
*/
|
|
33
|
+
// Two-dot tree diff, never `base...branch`: the three-dot form still shows a squash-merged
|
|
34
|
+
// branch's own changes. Fail-closed: a git error never reads as merged.
|
|
81
35
|
function treeIsIdentical(base, branch, cwd) {
|
|
82
36
|
try {
|
|
83
37
|
execFileSync('git', ['diff', '--quiet', base, branch], { cwd, stdio: 'pipe' });
|
|
@@ -87,13 +41,7 @@ function treeIsIdentical(base, branch, cwd) {
|
|
|
87
41
|
}
|
|
88
42
|
}
|
|
89
43
|
|
|
90
|
-
/**
|
|
91
|
-
* How many commits the branch carries that the base does not. Used only for
|
|
92
|
-
* REPORTING — the classification never turns on it, because a squash-merged
|
|
93
|
-
* branch legitimately has commits ahead while carrying no unintegrated work.
|
|
94
|
-
*
|
|
95
|
-
* @returns {number|null} null when it cannot be computed
|
|
96
|
-
*/
|
|
44
|
+
/** Reporting only: a squash-merged branch legitimately has commits ahead. @returns {number|null} */
|
|
97
45
|
export function commitsAhead(base, branch, cwd) {
|
|
98
46
|
try {
|
|
99
47
|
const out = git(['rev-list', '--count', `${base}..${branch}`], cwd).trim();
|
|
@@ -105,12 +53,8 @@ export function commitsAhead(base, branch, cwd) {
|
|
|
105
53
|
}
|
|
106
54
|
|
|
107
55
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* @returns {string|null} null when neither resolves — the caller must then
|
|
112
|
-
* classify everything as `unmerged`, because there is no baseline to prove
|
|
113
|
-
* integration against.
|
|
56
|
+
* `origin/<default>` when it exists, else the local branch.
|
|
57
|
+
* @returns {string|null} null = no baseline, so everything is `unmerged`.
|
|
114
58
|
*/
|
|
115
59
|
export function resolveBaseRef(defaultBranch, cwd) {
|
|
116
60
|
if (!defaultBranch) return null;
|
|
@@ -123,19 +67,8 @@ export function resolveBaseRef(defaultBranch, cwd) {
|
|
|
123
67
|
return null;
|
|
124
68
|
}
|
|
125
69
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
* table above is testable without a GitHub account.
|
|
129
|
-
*
|
|
130
|
-
* ORDER IS THE POLICY, and the two protective classes come FIRST: a branch that
|
|
131
|
-
* is checked out, or that carries an OPEN PR, is never deleted even if its tree
|
|
132
|
-
* happens to match the base — a PR being open means a human still has business
|
|
133
|
-
* with it.
|
|
134
|
-
*
|
|
135
|
-
* @param {object} b - from listLocalBranches
|
|
136
|
-
* @param {{ baseRef: string|null, cwd: string, pr: {number:number,state:string}|null|undefined }} ctx
|
|
137
|
-
* @returns {{ name: string, kind: string, reason: string, pr: number|null, ahead: number|null, deletable: boolean }}
|
|
138
|
-
*/
|
|
70
|
+
// Order is the policy: checked-out and open-PR branches are protected before any
|
|
71
|
+
// merged check, even when the tree matches the base.
|
|
139
72
|
export function classifyBranch(b, { baseRef, cwd, pr }) {
|
|
140
73
|
const prNum = pr?.number ?? null;
|
|
141
74
|
const mk = (kind, reason, deletable) => ({
|
|
@@ -163,12 +96,6 @@ export function classifyBranch(b, { baseRef, cwd, pr }) {
|
|
|
163
96
|
return mk('unmerged', `carrega mudanças que não estão em ${baseRef} e nenhum PR MERGED comprova o contrário`, false);
|
|
164
97
|
}
|
|
165
98
|
|
|
166
|
-
/**
|
|
167
|
-
* Classify every local branch except the default itself.
|
|
168
|
-
*
|
|
169
|
-
* @param {{ cwd: string, defaultBranch: string|null, prIndex: Map|null }} ctx
|
|
170
|
-
* @returns {Array} one entry per branch, in `for-each-ref` order
|
|
171
|
-
*/
|
|
172
99
|
export function classifyBranches({ cwd, defaultBranch, prIndex }) {
|
|
173
100
|
const baseRef = resolveBaseRef(defaultBranch, cwd);
|
|
174
101
|
return listLocalBranches(cwd)
|