@polymorphism-tech/morph-spec 9.0.0 → 9.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/CLAUDE.md +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +11 -63
- 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 +2 -1
- package/framework/MORPH.md +3 -3
- package/framework/agents.json +19 -11
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +22 -31
- package/framework/evals/review-checklist.md +12 -2
- package/framework/hooks/README.md +8 -44
- 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 +12 -68
- 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/claude-code/statusline.sh +11 -11
- 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/rules/nextjs-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 +4 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- 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 +9 -7
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- 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 +36 -14
- 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/testing.md +10 -4
- 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/frontend/nextjs/testing.md +10 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +3 -1
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- 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 +15 -97
- 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 +11 -11
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +10 -97
- package/src/commands/validation/verify.js +30 -113
- 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/main.jsx +13 -13
- 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/dashboard/vite.config.js +24 -24
- 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 +14 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/applicability.js +37 -0
- 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/adapters.js +6 -17
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +34 -33
- package/src/lib/plan/plan-parser.js +7 -8
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +11 -26
- package/src/lib/risk-detector.js +41 -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/tests-audit/audit.js +96 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- 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 +21 -158
- package/src/lib/validators/verify-runner.js +85 -376
- 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 +29 -276
- 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/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
- 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,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Template Registry - Discovery and metadata API for all framework templates.
|
|
3
|
-
*
|
|
4
|
-
* Provides programmatic access to REGISTRY.json for:
|
|
5
|
-
* - Template discovery by category, phase, stack
|
|
6
|
-
* - Template metadata lookup
|
|
7
|
-
* - Path resolution with fallback logic
|
|
8
|
-
* - Validation of template existence
|
|
9
|
-
*
|
|
10
|
-
* @module template-registry
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
1
|
import { readFileSync, existsSync } from 'fs';
|
|
14
2
|
import { join, dirname } from 'path';
|
|
15
3
|
import { fileURLToPath } from 'url';
|
|
@@ -17,21 +5,10 @@ import { fileURLToPath } from 'url';
|
|
|
17
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
6
|
const __dirname = dirname(__filename);
|
|
19
7
|
|
|
20
|
-
//
|
|
21
|
-
// NOT a single global slot — so a process that loads the framework registry
|
|
22
|
-
// and a project's registry gets each one correctly, instead of whichever
|
|
23
|
-
// loaded first.
|
|
8
|
+
// Keyed by REGISTRY.json path, so the framework and a project registry never shadow each other.
|
|
24
9
|
const _registryCache = new Map();
|
|
25
10
|
|
|
26
|
-
/**
|
|
27
|
-
* Load the template registry from REGISTRY.json.
|
|
28
|
-
*
|
|
29
|
-
* @param {string} [projectPath] - Optional project path (defaults to framework root)
|
|
30
|
-
* @returns {Object} Parsed registry object
|
|
31
|
-
* @throws {Error} If registry file not found or invalid JSON
|
|
32
|
-
*/
|
|
33
11
|
export function loadTemplateRegistry(projectPath = null) {
|
|
34
|
-
// Resolve the path first — it is the cache key.
|
|
35
12
|
const registryPath = projectPath
|
|
36
13
|
? join(projectPath, 'framework/templates/REGISTRY.json')
|
|
37
14
|
: join(__dirname, '../../../framework/templates/REGISTRY.json');
|
|
@@ -55,92 +32,39 @@ export function loadTemplateRegistry(projectPath = null) {
|
|
|
55
32
|
}
|
|
56
33
|
}
|
|
57
34
|
|
|
58
|
-
/**
|
|
59
|
-
* Clear all cached registries (useful for testing).
|
|
60
|
-
*/
|
|
61
35
|
export function clearRegistryCache() {
|
|
62
36
|
_registryCache.clear();
|
|
63
37
|
}
|
|
64
38
|
|
|
65
|
-
/**
|
|
66
|
-
* Get all templates from registry.
|
|
67
|
-
*
|
|
68
|
-
* @param {string} [projectPath] - Optional project path
|
|
69
|
-
* @returns {Array} Array of template objects
|
|
70
|
-
*/
|
|
71
39
|
export function getAllTemplates(projectPath = null) {
|
|
72
40
|
const registry = loadTemplateRegistry(projectPath);
|
|
73
41
|
return registry.templates || [];
|
|
74
42
|
}
|
|
75
43
|
|
|
76
|
-
/**
|
|
77
|
-
* Get template by ID.
|
|
78
|
-
*
|
|
79
|
-
* @param {string} templateId - Template identifier (e.g., 'plan')
|
|
80
|
-
* @param {string} [projectPath] - Optional project path
|
|
81
|
-
* @returns {Object|null} Template object or null if not found
|
|
82
|
-
*/
|
|
83
44
|
export function getTemplateById(templateId, projectPath = null) {
|
|
84
45
|
const templates = getAllTemplates(projectPath);
|
|
85
46
|
return templates.find(t => t.id === templateId) || null;
|
|
86
47
|
}
|
|
87
48
|
|
|
88
|
-
/**
|
|
89
|
-
* Get templates by category.
|
|
90
|
-
*
|
|
91
|
-
* @param {string} category - Category ('documentation', 'code', 'infrastructure', 'context', 'examples')
|
|
92
|
-
* @param {string} [projectPath] - Optional project path
|
|
93
|
-
* @returns {Array} Filtered template array
|
|
94
|
-
*/
|
|
95
49
|
export function getTemplatesByCategory(category, projectPath = null) {
|
|
96
50
|
const templates = getAllTemplates(projectPath);
|
|
97
51
|
return templates.filter(t => t.category === category);
|
|
98
52
|
}
|
|
99
53
|
|
|
100
|
-
/**
|
|
101
|
-
* Get templates by phase.
|
|
102
|
-
*
|
|
103
|
-
* @param {string} phase - Phase name (e.g., 'plan', 'uiux', 'implement')
|
|
104
|
-
* @param {string} [projectPath] - Optional project path
|
|
105
|
-
* @returns {Array} Filtered template array
|
|
106
|
-
*/
|
|
107
|
-
export function getTemplatesByPhase(phase, projectPath = null) {
|
|
108
|
-
const templates = getAllTemplates(projectPath);
|
|
109
|
-
return templates.filter(t => t.phase === phase);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Get templates by stack.
|
|
114
|
-
*
|
|
115
|
-
* @param {string} stackId - Stack identifier ('blazor-azure', 'nextjs-neon')
|
|
116
|
-
* @param {string} [projectPath] - Optional project path
|
|
117
|
-
* @returns {Array} Filtered template array (includes universal + stack-specific)
|
|
118
|
-
*/
|
|
119
54
|
export function getTemplatesByStack(stackId, projectPath = null) {
|
|
120
55
|
const templates = getAllTemplates(projectPath);
|
|
121
56
|
return templates.filter(t => {
|
|
122
|
-
// Include universal templates (stackSpecific: false)
|
|
123
57
|
if (!t.stackSpecific) return true;
|
|
124
58
|
|
|
125
|
-
// Include stack-specific if matches
|
|
126
59
|
return t.stacks && t.stacks.includes(stackId);
|
|
127
60
|
});
|
|
128
61
|
}
|
|
129
62
|
|
|
130
|
-
/**
|
|
131
|
-
* Get required templates only.
|
|
132
|
-
*
|
|
133
|
-
* @param {string} [stackId] - Optional stack filter
|
|
134
|
-
* @param {string} [projectPath] - Optional project path
|
|
135
|
-
* @returns {Array} Filtered template array
|
|
136
|
-
*/
|
|
137
63
|
export function getRequiredTemplates(stackId = null, projectPath = null) {
|
|
138
64
|
let templates = getAllTemplates(projectPath);
|
|
139
65
|
|
|
140
|
-
// Filter by required flag
|
|
141
66
|
templates = templates.filter(t => t.required === true);
|
|
142
67
|
|
|
143
|
-
// Optional: filter by stack
|
|
144
68
|
if (stackId) {
|
|
145
69
|
templates = templates.filter(t => {
|
|
146
70
|
if (!t.stackSpecific) return true;
|
|
@@ -151,13 +75,6 @@ export function getRequiredTemplates(stackId = null, projectPath = null) {
|
|
|
151
75
|
return templates;
|
|
152
76
|
}
|
|
153
77
|
|
|
154
|
-
/**
|
|
155
|
-
* Search templates by keyword (searches id, name, description).
|
|
156
|
-
*
|
|
157
|
-
* @param {string} keyword - Search term
|
|
158
|
-
* @param {string} [projectPath] - Optional project path
|
|
159
|
-
* @returns {Array} Matching templates
|
|
160
|
-
*/
|
|
161
78
|
export function searchTemplates(keyword, projectPath = null) {
|
|
162
79
|
const templates = getAllTemplates(projectPath);
|
|
163
80
|
const lowerKeyword = keyword.toLowerCase();
|
|
@@ -171,21 +88,9 @@ export function searchTemplates(keyword, projectPath = null) {
|
|
|
171
88
|
});
|
|
172
89
|
}
|
|
173
90
|
|
|
174
|
-
/** Absolute path of this repo's own framework/templates/ directory. */
|
|
175
91
|
const FRAMEWORK_TEMPLATES_DIR = join(__dirname, '../../../framework/templates');
|
|
176
92
|
|
|
177
|
-
/**
|
|
178
|
-
* Resolve a template id to its absolute path inside THIS framework repo's
|
|
179
|
-
* `framework/templates/` directory — for generators running against the
|
|
180
|
-
* framework itself (scaffold-generator, context-generator), not against an
|
|
181
|
-
* installed `.morph/`. Use architecture-scanner.js's `resolveTemplatePath()`
|
|
182
|
-
* when a project root and its project-local overrides are in play.
|
|
183
|
-
*
|
|
184
|
-
* @param {string} templateId - Template identifier (REGISTRY.json `id`)
|
|
185
|
-
* @returns {string} Absolute path to the template file
|
|
186
|
-
* @throws {Error} If the id is not in REGISTRY.json or the file is missing —
|
|
187
|
-
* a missing template is a framework bug, not a recoverable condition.
|
|
188
|
-
*/
|
|
93
|
+
/** Path inside this package's `framework/templates/`, not a project's `.morph/`; throws when missing. */
|
|
189
94
|
export function resolveFrameworkTemplatePath(templateId) {
|
|
190
95
|
const template = getTemplateById(templateId);
|
|
191
96
|
if (!template) {
|
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Template Renderer - Shared Handlebars-based template engine.
|
|
3
|
-
*
|
|
4
|
-
* Provides unified template rendering with custom helpers for:
|
|
5
|
-
* - Case transformations (pascalCase, camelCase, snakeCase, etc.)
|
|
6
|
-
* - Conditional logic (eq, ne, and, or, not)
|
|
7
|
-
* - Loops (each, unless)
|
|
8
|
-
*
|
|
9
|
-
* @module template-renderer
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
1
|
import Handlebars from 'handlebars';
|
|
13
2
|
import { readFileSync, existsSync } from 'fs';
|
|
14
3
|
|
|
15
|
-
/**
|
|
16
|
-
* Register all custom Handlebars helpers
|
|
17
|
-
*/
|
|
18
4
|
function registerHelpers() {
|
|
19
|
-
// Case transformation helpers
|
|
20
5
|
Handlebars.registerHelper('pascalCase', function (str) {
|
|
21
6
|
if (typeof str !== 'string') return '';
|
|
22
7
|
return str
|
|
@@ -60,7 +45,6 @@ function registerHelpers() {
|
|
|
60
45
|
return str.toLowerCase().replace(/_/g, '-');
|
|
61
46
|
});
|
|
62
47
|
|
|
63
|
-
// Comparison helpers
|
|
64
48
|
Handlebars.registerHelper('eq', function (a, b) {
|
|
65
49
|
return a === b;
|
|
66
50
|
});
|
|
@@ -85,14 +69,14 @@ function registerHelpers() {
|
|
|
85
69
|
return a >= b;
|
|
86
70
|
});
|
|
87
71
|
|
|
88
|
-
// Logical helpers
|
|
89
72
|
Handlebars.registerHelper('and', function (...args) {
|
|
90
|
-
//
|
|
73
|
+
// Handlebars passes its options object as the last argument.
|
|
91
74
|
const values = args.slice(0, -1);
|
|
92
75
|
return values.every(Boolean);
|
|
93
76
|
});
|
|
94
77
|
|
|
95
78
|
Handlebars.registerHelper('or', function (...args) {
|
|
79
|
+
// Handlebars passes its options object as the last argument.
|
|
96
80
|
const values = args.slice(0, -1);
|
|
97
81
|
return values.some(Boolean);
|
|
98
82
|
});
|
|
@@ -101,7 +85,6 @@ function registerHelpers() {
|
|
|
101
85
|
return !value;
|
|
102
86
|
});
|
|
103
87
|
|
|
104
|
-
// String helpers
|
|
105
88
|
Handlebars.registerHelper('uppercase', function (str) {
|
|
106
89
|
return typeof str === 'string' ? str.toUpperCase() : '';
|
|
107
90
|
});
|
|
@@ -115,7 +98,6 @@ function registerHelpers() {
|
|
|
115
98
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
116
99
|
});
|
|
117
100
|
|
|
118
|
-
// Utility helpers
|
|
119
101
|
Handlebars.registerHelper('default', function (value, defaultValue) {
|
|
120
102
|
return value != null && value !== '' ? value : defaultValue;
|
|
121
103
|
});
|
|
@@ -133,31 +115,30 @@ function registerHelpers() {
|
|
|
133
115
|
} else if (format === 'time') {
|
|
134
116
|
return date.toTimeString().split(' ')[0];
|
|
135
117
|
}
|
|
136
|
-
return date.toISOString().split('T')[0];
|
|
118
|
+
return date.toISOString().split('T')[0];
|
|
137
119
|
});
|
|
138
120
|
|
|
139
121
|
Handlebars.registerHelper('year', function () {
|
|
140
122
|
return new Date().getFullYear();
|
|
141
123
|
});
|
|
142
124
|
|
|
143
|
-
// Date formatting helpers
|
|
144
125
|
Handlebars.registerHelper('formatDate', function (date, format) {
|
|
145
126
|
if (!date) return '';
|
|
146
127
|
|
|
147
128
|
const d = new Date(date);
|
|
148
|
-
if (isNaN(d.getTime())) return date;
|
|
129
|
+
if (isNaN(d.getTime())) return date;
|
|
149
130
|
|
|
150
131
|
switch (format) {
|
|
151
132
|
case 'iso':
|
|
152
133
|
return d.toISOString();
|
|
153
134
|
case 'date':
|
|
154
|
-
return d.toISOString().split('T')[0];
|
|
135
|
+
return d.toISOString().split('T')[0];
|
|
155
136
|
case 'time':
|
|
156
|
-
return d.toTimeString().split(' ')[0];
|
|
137
|
+
return d.toTimeString().split(' ')[0];
|
|
157
138
|
case 'datetime':
|
|
158
|
-
return d.toISOString().replace('T', ' ').split('.')[0];
|
|
139
|
+
return d.toISOString().replace('T', ' ').split('.')[0];
|
|
159
140
|
case 'short':
|
|
160
|
-
return d.toLocaleDateString();
|
|
141
|
+
return d.toLocaleDateString();
|
|
161
142
|
case 'long':
|
|
162
143
|
return d.toLocaleDateString('en-US', {
|
|
163
144
|
weekday: 'long',
|
|
@@ -166,15 +147,13 @@ function registerHelpers() {
|
|
|
166
147
|
day: 'numeric'
|
|
167
148
|
});
|
|
168
149
|
default:
|
|
169
|
-
return d.toISOString().split('T')[0];
|
|
150
|
+
return d.toISOString().split('T')[0];
|
|
170
151
|
}
|
|
171
152
|
});
|
|
172
153
|
|
|
173
|
-
// Pluralization helpers (basic English rules)
|
|
174
154
|
Handlebars.registerHelper('pluralize', function (str) {
|
|
175
155
|
if (typeof str !== 'string') return '';
|
|
176
156
|
|
|
177
|
-
// Common irregular plurals
|
|
178
157
|
const irregulars = {
|
|
179
158
|
'person': 'people',
|
|
180
159
|
'child': 'children',
|
|
@@ -191,7 +170,6 @@ function registerHelpers() {
|
|
|
191
170
|
: irregulars[lower];
|
|
192
171
|
}
|
|
193
172
|
|
|
194
|
-
// Regular pluralization rules
|
|
195
173
|
if (str.endsWith('s') || str.endsWith('x') || str.endsWith('z') ||
|
|
196
174
|
str.endsWith('ch') || str.endsWith('sh')) {
|
|
197
175
|
return str + 'es';
|
|
@@ -209,7 +187,6 @@ function registerHelpers() {
|
|
|
209
187
|
Handlebars.registerHelper('singularize', function (str) {
|
|
210
188
|
if (typeof str !== 'string') return '';
|
|
211
189
|
|
|
212
|
-
// Common irregular singulars
|
|
213
190
|
const irregulars = {
|
|
214
191
|
'people': 'person',
|
|
215
192
|
'children': 'child',
|
|
@@ -226,7 +203,6 @@ function registerHelpers() {
|
|
|
226
203
|
: irregulars[lower];
|
|
227
204
|
}
|
|
228
205
|
|
|
229
|
-
// Regular singularization rules
|
|
230
206
|
if (str.endsWith('ies')) {
|
|
231
207
|
return str.slice(0, -3) + 'y';
|
|
232
208
|
} else if (str.endsWith('ves')) {
|
|
@@ -240,7 +216,6 @@ function registerHelpers() {
|
|
|
240
216
|
}
|
|
241
217
|
});
|
|
242
218
|
|
|
243
|
-
// String manipulation helpers
|
|
244
219
|
Handlebars.registerHelper('trim', function (str) {
|
|
245
220
|
return typeof str === 'string' ? str.trim() : '';
|
|
246
221
|
});
|
|
@@ -251,7 +226,7 @@ function registerHelpers() {
|
|
|
251
226
|
});
|
|
252
227
|
|
|
253
228
|
Handlebars.registerHelper('concat', function (...args) {
|
|
254
|
-
//
|
|
229
|
+
// Handlebars passes its options object as the last argument.
|
|
255
230
|
const strings = args.slice(0, -1);
|
|
256
231
|
return strings.join('');
|
|
257
232
|
});
|
|
@@ -283,7 +258,6 @@ function registerHelpers() {
|
|
|
283
258
|
return 0;
|
|
284
259
|
});
|
|
285
260
|
|
|
286
|
-
// Math helpers
|
|
287
261
|
Handlebars.registerHelper('add', function (a, b) {
|
|
288
262
|
return Number(a) + Number(b);
|
|
289
263
|
});
|
|
@@ -309,7 +283,6 @@ function registerHelpers() {
|
|
|
309
283
|
return Math.round(Number(num) * multiplier) / multiplier;
|
|
310
284
|
});
|
|
311
285
|
|
|
312
|
-
// Array helpers
|
|
313
286
|
Handlebars.registerHelper('join', function (array, separator = ', ') {
|
|
314
287
|
if (!Array.isArray(array)) return '';
|
|
315
288
|
return array.join(separator);
|
|
@@ -331,19 +304,8 @@ function registerHelpers() {
|
|
|
331
304
|
});
|
|
332
305
|
}
|
|
333
306
|
|
|
334
|
-
// Auto-register helpers on module load
|
|
335
307
|
registerHelpers();
|
|
336
308
|
|
|
337
|
-
/**
|
|
338
|
-
* Render a template file with Handlebars.
|
|
339
|
-
*
|
|
340
|
-
* @param {string} templatePath - Absolute path to template file
|
|
341
|
-
* @param {Object} variables - Variables to pass to template context
|
|
342
|
-
* @param {Object} [options] - Rendering options
|
|
343
|
-
* @param {boolean} [options.noEscape] - Disable HTML escaping (default: true for markdown)
|
|
344
|
-
* @returns {string} Rendered template content
|
|
345
|
-
* @throws {Error} If template file doesn't exist or compilation fails
|
|
346
|
-
*/
|
|
347
309
|
export function renderTemplate(templatePath, variables, options = {}) {
|
|
348
310
|
if (!existsSync(templatePath)) {
|
|
349
311
|
throw new Error(`Template file not found: ${templatePath}`);
|
|
@@ -352,28 +314,18 @@ export function renderTemplate(templatePath, variables, options = {}) {
|
|
|
352
314
|
try {
|
|
353
315
|
const templateContent = readFileSync(templatePath, 'utf-8');
|
|
354
316
|
|
|
355
|
-
// Compile template
|
|
356
317
|
const template = Handlebars.compile(templateContent, {
|
|
357
|
-
noEscape: options.noEscape !== undefined ? options.noEscape : true,
|
|
358
|
-
strict: false,
|
|
359
|
-
preventIndent: true,
|
|
318
|
+
noEscape: options.noEscape !== undefined ? options.noEscape : true,
|
|
319
|
+
strict: false,
|
|
320
|
+
preventIndent: true,
|
|
360
321
|
});
|
|
361
322
|
|
|
362
|
-
// Render with variables
|
|
363
323
|
return template(variables);
|
|
364
324
|
} catch (error) {
|
|
365
325
|
throw new Error(`Failed to render template ${templatePath}: ${error.message}`);
|
|
366
326
|
}
|
|
367
327
|
}
|
|
368
328
|
|
|
369
|
-
/**
|
|
370
|
-
* Render a template string (not from file) with Handlebars.
|
|
371
|
-
*
|
|
372
|
-
* @param {string} templateString - Template content as string
|
|
373
|
-
* @param {Object} variables - Variables to pass to template context
|
|
374
|
-
* @param {Object} [options] - Rendering options
|
|
375
|
-
* @returns {string} Rendered content
|
|
376
|
-
*/
|
|
377
329
|
export function renderTemplateString(templateString, variables, options = {}) {
|
|
378
330
|
try {
|
|
379
331
|
const template = Handlebars.compile(templateString, {
|
|
@@ -388,12 +340,6 @@ export function renderTemplateString(templateString, variables, options = {}) {
|
|
|
388
340
|
}
|
|
389
341
|
}
|
|
390
342
|
|
|
391
|
-
/**
|
|
392
|
-
* Get list of placeholders used in a template file.
|
|
393
|
-
*
|
|
394
|
-
* @param {string} templatePath - Absolute path to template file
|
|
395
|
-
* @returns {string[]} Array of placeholder names found
|
|
396
|
-
*/
|
|
397
343
|
export function getTemplatePlaceholders(templatePath) {
|
|
398
344
|
if (!existsSync(templatePath)) {
|
|
399
345
|
throw new Error(`Template file not found: ${templatePath}`);
|
|
@@ -403,44 +349,26 @@ export function getTemplatePlaceholders(templatePath) {
|
|
|
403
349
|
return getTemplatePlaceholdersFromString(templateContent);
|
|
404
350
|
}
|
|
405
351
|
|
|
406
|
-
/**
|
|
407
|
-
* Get list of placeholders from a template string.
|
|
408
|
-
*
|
|
409
|
-
* @param {string} templateString - Template content
|
|
410
|
-
* @returns {string[]} Array of placeholder names found
|
|
411
|
-
*/
|
|
412
352
|
export function getTemplatePlaceholdersFromString(templateString) {
|
|
413
353
|
const placeholders = new Set();
|
|
414
354
|
|
|
415
|
-
// Match {{PLACEHOLDER}} and {{helper PLACEHOLDER}}
|
|
416
355
|
const regex = /\{\{(?:#|\/)?\s*([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
417
356
|
let match;
|
|
418
357
|
|
|
419
358
|
while ((match = regex.exec(templateString)) !== null) {
|
|
420
359
|
const placeholder = match[1];
|
|
421
360
|
|
|
422
|
-
// Skip Handlebars built-in helpers and custom helpers
|
|
423
361
|
const skipHelpers = [
|
|
424
|
-
// Built-in Handlebars
|
|
425
362
|
'if', 'unless', 'each', 'with', 'lookup', 'log', 'else',
|
|
426
|
-
// Comparison
|
|
427
363
|
'eq', 'ne', 'lt', 'gt', 'lte', 'gte',
|
|
428
|
-
// Logical
|
|
429
364
|
'and', 'or', 'not',
|
|
430
|
-
// Case transformations
|
|
431
365
|
'pascalCase', 'camelCase', 'titleCase', 'upperSnakeCase', 'snakeCase', 'kebabCase',
|
|
432
|
-
// String manipulation
|
|
433
366
|
'uppercase', 'lowercase', 'capitalize', 'trim', 'replace', 'concat', 'substr',
|
|
434
367
|
'startsWith', 'endsWith', 'contains', 'length',
|
|
435
|
-
// Date/Time
|
|
436
368
|
'formatDate', 'now', 'year',
|
|
437
|
-
// Pluralization
|
|
438
369
|
'pluralize', 'singularize',
|
|
439
|
-
// Math
|
|
440
370
|
'add', 'subtract', 'multiply', 'divide', 'mod', 'round',
|
|
441
|
-
// Array
|
|
442
371
|
'join', 'first', 'last', 'slice',
|
|
443
|
-
// Utility
|
|
444
372
|
'default', 'json'
|
|
445
373
|
];
|
|
446
374
|
|
|
@@ -452,14 +380,6 @@ export function getTemplatePlaceholdersFromString(templateString) {
|
|
|
452
380
|
return Array.from(placeholders).sort();
|
|
453
381
|
}
|
|
454
382
|
|
|
455
|
-
/**
|
|
456
|
-
* Validate that all required placeholders are provided in variables.
|
|
457
|
-
*
|
|
458
|
-
* @param {string} templatePath - Absolute path to template file
|
|
459
|
-
* @param {Object} variables - Variables object
|
|
460
|
-
* @param {string[]} [optionalPlaceholders] - Placeholders that are optional
|
|
461
|
-
* @returns {{ valid: boolean, missing: string[] }} Validation result
|
|
462
|
-
*/
|
|
463
383
|
export function validateTemplatePlaceholders(templatePath, variables, optionalPlaceholders = []) {
|
|
464
384
|
const placeholders = getTemplatePlaceholders(templatePath);
|
|
465
385
|
const providedKeys = Object.keys(variables);
|
|
@@ -9,7 +9,6 @@ export function templateSourcePath(templateId, cwd = process.cwd()) {
|
|
|
9
9
|
return local && existsSync(local) ? local : resolveFrameworkTemplatePath(templateId);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
/** @returns {{ path: string, written: boolean }} `written: false` when the file already existed. */
|
|
13
12
|
export function writeIfAbsent(path, content) {
|
|
14
13
|
mkdirSync(dirname(path), { recursive: true });
|
|
15
14
|
try {
|
package/src/core/work-types.js
CHANGED
|
@@ -1,26 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Work Types — Single Source of Truth (v8)
|
|
3
|
-
*
|
|
4
|
-
* Routes work by TYPE (feature/bug/chore/hotfix), each with a dimensioned
|
|
5
|
-
* pipeline. `derivePhase` is already skip-tolerant via approved gates
|
|
6
|
-
* (`gateImpliedPhase`), so "skipping phases" means stamping gates by policy
|
|
7
|
-
* at birth (`approvedBy: "policy:{type}"` — see POLICY_APPROVER_PREFIX).
|
|
8
|
-
*
|
|
9
|
-
* Edit ONLY this file to add, rename, or retune a work type. Consumed by
|
|
10
|
-
* `create`/`retype`/`risk-detector` in later tasks — this module is pure
|
|
11
|
-
* and has no filesystem/CLI side effects.
|
|
12
|
-
*
|
|
13
|
-
* Fields per type:
|
|
14
|
-
* label - Human-readable name
|
|
15
|
-
* scaffold - Phase folders created at feature birth (subset of
|
|
16
|
-
* 2-plan/1-design/3-implement/4-review)
|
|
17
|
-
* skippedGates - Gates stamped `policy:{type}`-approved at birth
|
|
18
|
-
* instead of waiting for human approval
|
|
19
|
-
* forcedPauseGates - Gates that ALWAYS pause for a human, regardless of
|
|
20
|
-
* trust mode or skippedGates (e.g. hotfix.review)
|
|
21
|
-
* entrySkill - The skill that kicks off work of this type
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
1
|
export const DEFAULT_WORK_TYPE = 'feature';
|
|
25
2
|
export const POLICY_APPROVER_PREFIX = 'policy:';
|
|
26
3
|
|
|
@@ -55,24 +32,12 @@ export const WORK_TYPES = {
|
|
|
55
32
|
},
|
|
56
33
|
};
|
|
57
34
|
|
|
58
|
-
/**
|
|
59
|
-
* @param {string} t - candidate work type key
|
|
60
|
-
* @returns {boolean} true iff `t` is a known key of WORK_TYPES
|
|
61
|
-
*/
|
|
62
35
|
export function isValidWorkType(t) {
|
|
63
36
|
return Object.prototype.hasOwnProperty.call(WORK_TYPES, t);
|
|
64
37
|
}
|
|
65
38
|
|
|
66
|
-
/**
|
|
67
|
-
* Resolve a feature's work-type config, defaulting to `feature` when the
|
|
68
|
-
* feature has no `workType` or an unrecognized one.
|
|
69
|
-
*
|
|
70
|
-
* @param {object} feature - feature.json contents (uses `workType`)
|
|
71
|
-
* @returns {object} the matching WORK_TYPES entry
|
|
72
|
-
*/
|
|
73
39
|
export function getWorkType(feature) {
|
|
74
|
-
//
|
|
75
|
-
// WORK_TYPES[key] lookup would resolve those to Object.prototype members.
|
|
40
|
+
// A bare WORK_TYPES[key] would resolve inherited names like "toString".
|
|
76
41
|
return isValidWorkType(feature?.workType)
|
|
77
42
|
? WORK_TYPES[feature.workType]
|
|
78
43
|
: WORK_TYPES[DEFAULT_WORK_TYPE];
|
|
@@ -17,11 +17,6 @@ import Guide from './pages/Guide.jsx';
|
|
|
17
17
|
import { useNow } from './hooks/usePref.js';
|
|
18
18
|
import { useProjects, useCurrentProject, projectHref } from './lib/project.js';
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* What the sidebar and the tab title say at a glance, from the portfolio:
|
|
22
|
-
* per project, how many features are active, waiting on you and being worked
|
|
23
|
-
* on right now.
|
|
24
|
-
*/
|
|
25
20
|
function usePulse(portfolio) {
|
|
26
21
|
const now = useNow();
|
|
27
22
|
const byProject = {};
|
|
@@ -49,7 +44,6 @@ export default function App() {
|
|
|
49
44
|
const current = useCurrentProject(portfolio);
|
|
50
45
|
const pulse = usePulse(portfolio);
|
|
51
46
|
const [palette, setPalette] = useState(false);
|
|
52
|
-
// Ctrl+K / Cmd+K opens the search from anywhere.
|
|
53
47
|
useEffect(() => {
|
|
54
48
|
const onKey = (e) => {
|
|
55
49
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); setPalette((o) => !o); }
|
|
@@ -65,8 +59,7 @@ export default function App() {
|
|
|
65
59
|
<CommandPalette open={palette} onClose={() => setPalette(false)} portfolio={portfolio} project={current.id} />
|
|
66
60
|
<main className="relative z-10 flex-1 overflow-auto">
|
|
67
61
|
<MobileNav {...nav} />
|
|
68
|
-
{/*
|
|
69
|
-
sidebar alive, and navigating away remounts a clean boundary. */}
|
|
62
|
+
{/* Keyed by route: a boundary that caught an error stays in fallback until it remounts. */}
|
|
70
63
|
<ErrorBoundary key={pathname} page={pathname}>
|
|
71
64
|
<Routes>
|
|
72
65
|
<Route path="/" element={<Portfolio />} />
|
|
@@ -65,7 +65,6 @@ function Line({ item, showFeature, project }) {
|
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
/** Group consecutive items by calendar day, newest day first. */
|
|
69
68
|
function byDay(items) {
|
|
70
69
|
const groups = [];
|
|
71
70
|
for (const it of items) {
|
|
@@ -128,10 +127,6 @@ export function FeedView({ items, kinds, onToggleKind, showFeature, onMore, hasM
|
|
|
128
127
|
);
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
/**
|
|
132
|
-
* Live activity feed. The first page follows the server's `morph:changed`
|
|
133
|
-
* pushes; older pages are fetched on demand and appended.
|
|
134
|
-
*/
|
|
135
130
|
export default function ActivityFeed({ project, feature, showFeature = false }) {
|
|
136
131
|
const [kinds, setKinds] = useState(DEFAULT);
|
|
137
132
|
const [older, setOlder] = useState([]);
|
|
@@ -17,7 +17,6 @@ const PAGES = [
|
|
|
17
17
|
['Personas', '/agents'], ['Standards', '/standards'], ['Rules', '/rules'], ['Hooks', '/hooks'],
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
-
/** Everything the framework knows that has a page — built once. */
|
|
21
20
|
function knowledgeIndex() {
|
|
22
21
|
const out = PAGES.map(([title, to]) => ({ kind: 'page', title, to, hay: title }));
|
|
23
22
|
for (const s of morphData.skills || []) out.push({ kind: 'skill', title: s.id, hint: s.description, to: entityHref('skill', s.id), hay: `${s.id} ${s.description}` });
|
|
@@ -42,10 +41,6 @@ function projectIndex(portfolio) {
|
|
|
42
41
|
return (portfolio?.projects || []).map((p) => ({ kind: 'project', title: p.name, hint: p.dir, to: projectHref(p.id), hay: `${p.name} ${p.dir}` }));
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
/**
|
|
46
|
-
* Features and their slices. `projectName` is set for another project's
|
|
47
|
-
* features, so a hit says where it lives.
|
|
48
|
-
*/
|
|
49
44
|
function featureIndex(project, features, projectName = null) {
|
|
50
45
|
const out = [];
|
|
51
46
|
const where = (text) => (projectName ? (text ? `${projectName}: ${text}` : projectName) : text);
|
|
@@ -58,14 +53,12 @@ function featureIndex(project, features, projectName = null) {
|
|
|
58
53
|
return out;
|
|
59
54
|
}
|
|
60
55
|
|
|
61
|
-
/** The active features of the other projects, from the portfolio's summaries. */
|
|
62
56
|
function otherProjectsIndex(portfolio, project) {
|
|
63
57
|
return (portfolio?.projects || [])
|
|
64
58
|
.filter((p) => p.id !== project && p.summary)
|
|
65
59
|
.flatMap((p) => featureIndex(p.id, p.summary.activeFeatures, p.name));
|
|
66
60
|
}
|
|
67
61
|
|
|
68
|
-
/** Every query word must appear; a title that starts with the query ranks first. */
|
|
69
62
|
export function searchIndex(index, query, limit = 40) {
|
|
70
63
|
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
71
64
|
if (!words.length) return [];
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { X } from '@phosphor-icons/react';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* The reading drawer of the knowledge pages. Wide enough for rendered markdown,
|
|
6
|
-
* closes on Escape, and sits over the graph without a backdrop so the graph
|
|
7
|
-
* stays visible beside it.
|
|
8
|
-
*/
|
|
4
|
+
/** No backdrop: the graph stays visible beside the drawer. */
|
|
9
5
|
export default function DetailPanel({ open, title, subtitle, onClose, children }) {
|
|
10
6
|
useEffect(() => {
|
|
11
7
|
if (!open) return undefined;
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { Link, useSearchParams } from 'react-router-dom';
|
|
2
2
|
import { useCallback } from 'react';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Where each kind of thing lives in the app. The ONE map of entity → URL, so a
|
|
6
|
-
* persona, standard or skill reads as a link everywhere it is mentioned.
|
|
7
|
-
* A feature belongs to a project: its links come from `featureHref` (lib/project.js).
|
|
8
|
-
*/
|
|
9
4
|
export function entityHref(kind, id) {
|
|
10
5
|
const q = (page) => `/${page}?sel=${encodeURIComponent(id)}`;
|
|
11
6
|
switch (kind) {
|
|
@@ -20,7 +15,6 @@ export function entityHref(kind, id) {
|
|
|
20
15
|
}
|
|
21
16
|
}
|
|
22
17
|
|
|
23
|
-
// `standards/backend/dotnet/testing.md` (as a plan may cite it) → the registry `path`.
|
|
24
18
|
export function standardKey(ref) {
|
|
25
19
|
return String(ref).replace(/^\.?\/?(\.morph\/framework\/)?standards\//, '');
|
|
26
20
|
}
|
|
@@ -34,12 +28,6 @@ export default function EntityLink({ kind, id, children, className = '', chip =
|
|
|
34
28
|
return <Link to={entityHref(kind, id)} className={cls} title={title}>{children ?? id}</Link>;
|
|
35
29
|
}
|
|
36
30
|
|
|
37
|
-
/**
|
|
38
|
-
* The selected item of a knowledge page, kept in `?sel=` so a panel can be
|
|
39
|
-
* linked, bookmarked and reached from anywhere with EntityLink.
|
|
40
|
-
*
|
|
41
|
-
* @returns {[string|null, (id: string|null) => void]}
|
|
42
|
-
*/
|
|
43
31
|
export function useSelection() {
|
|
44
32
|
const [params, setParams] = useSearchParams();
|
|
45
33
|
const sel = params.get('sel');
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { Component } from 'react';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Catches a page that throws while rendering and shows the error in place of
|
|
5
|
-
* the page, so the rest of the layout (the sidebar) keeps working.
|
|
6
|
-
*
|
|
7
|
-
* Without it one broken page unmounts the whole tree: 8.38.0 opened on
|
|
8
|
-
* /agents, which threw a ReferenceError, and the dashboard was a black screen.
|
|
9
|
-
*
|
|
10
4
|
* Mount it with `key={location.pathname}`: a boundary that already caught an
|
|
11
|
-
* error stays in fallback until it remounts
|
|
12
|
-
* sidebar navigates to another page.
|
|
5
|
+
* error stays in fallback until it remounts.
|
|
13
6
|
*/
|
|
14
7
|
export default class ErrorBoundary extends Component {
|
|
15
8
|
constructor(props) {
|