@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
|
@@ -8,9 +8,6 @@ import SearchFilter from '../components/SearchFilter.jsx';
|
|
|
8
8
|
import Markdown from '../components/Markdown.jsx';
|
|
9
9
|
import EntityLink, { useSelection, standardKey } from '../components/EntityLink.jsx';
|
|
10
10
|
|
|
11
|
-
// ─── Colors ───────────────────────────────────────────────────────────────────
|
|
12
|
-
|
|
13
|
-
/** Token palette the categories draw from, in order; the categories come from the data. */
|
|
14
11
|
const PALETTE = [
|
|
15
12
|
'var(--color-phase-implement)',
|
|
16
13
|
'var(--color-phase-uiux)',
|
|
@@ -24,20 +21,17 @@ const PALETTE = [
|
|
|
24
21
|
|
|
25
22
|
const tint = (color, pct = 14) => `color-mix(in srgb, ${color} ${pct}%, var(--color-core))`;
|
|
26
23
|
|
|
27
|
-
/**
|
|
24
|
+
/** Stable by category name, so a color never jumps between renders. */
|
|
28
25
|
function categoryColors(standards) {
|
|
29
26
|
const cats = [...new Set(standards.map((s) => s.category || 'outros'))].sort();
|
|
30
27
|
return Object.fromEntries(cats.map((c, i) => [c, PALETTE[i % PALETTE.length]]));
|
|
31
28
|
}
|
|
32
29
|
|
|
33
|
-
// Resolve `?sel=` to a standard: by id first, then by the path a plan cites.
|
|
34
30
|
export function findStandard(list, sel) {
|
|
35
31
|
if (!sel) return null;
|
|
36
32
|
return list.find((s) => s.id === sel) || list.find((s) => s.path === standardKey(sel)) || null;
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
// ─── Nodes ────────────────────────────────────────────────────────────────────
|
|
40
|
-
|
|
41
35
|
const HANDLE = { width: 7, height: 7, border: 0 };
|
|
42
36
|
const NODE_W = 230;
|
|
43
37
|
const NODE_H = 52;
|
|
@@ -97,8 +91,6 @@ function CategoryNode({ data }) {
|
|
|
97
91
|
|
|
98
92
|
const nodeTypes = { standardNode: StandardNode, agentNode: AgentNode, categoryNode: CategoryNode };
|
|
99
93
|
|
|
100
|
-
// ─── Layout ───────────────────────────────────────────────────────────────────
|
|
101
|
-
|
|
102
94
|
const CELL_W = NODE_W + 14;
|
|
103
95
|
const CELL_H = NODE_H + 12;
|
|
104
96
|
const PAD = 16;
|
|
@@ -106,7 +98,6 @@ const HEADER = 40;
|
|
|
106
98
|
const BLOCK_GAP = 36;
|
|
107
99
|
const MAX_ROW_W = 1700;
|
|
108
100
|
|
|
109
|
-
/** How many columns a category block gets: tall categories spread sideways. */
|
|
110
101
|
function colsFor(n) {
|
|
111
102
|
if (n > 12) return 3;
|
|
112
103
|
if (n > 5) return 2;
|
|
@@ -127,7 +118,6 @@ function buildGraph({ standards, agents, colors, isMatch, hasFilter, persona, se
|
|
|
127
118
|
.map(([cat, list]) => [cat, [...list].sort((a, b) => (a.subcategory || '').localeCompare(b.subcategory || '') || a.name.localeCompare(b.name))])
|
|
128
119
|
.sort((a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0]));
|
|
129
120
|
|
|
130
|
-
// Pack the category blocks into rows no wider than MAX_ROW_W.
|
|
131
121
|
const blocks = cats.map(([cat, list]) => {
|
|
132
122
|
const cols = colsFor(list.length);
|
|
133
123
|
const rows = Math.ceil(list.length / cols);
|
|
@@ -141,7 +131,7 @@ function buildGraph({ standards, agents, colors, isMatch, hasFilter, persona, se
|
|
|
141
131
|
}
|
|
142
132
|
const totalW = Math.max(...rows.map((r) => r.w), 0);
|
|
143
133
|
|
|
144
|
-
const std = new Set();
|
|
134
|
+
const std = new Set();
|
|
145
135
|
let y = 180;
|
|
146
136
|
for (const row of rows) {
|
|
147
137
|
let x = (totalW - row.w) / 2;
|
|
@@ -170,7 +160,6 @@ function buildGraph({ standards, agents, colors, isMatch, hasFilter, persona, se
|
|
|
170
160
|
y += row.h + BLOCK_GAP;
|
|
171
161
|
}
|
|
172
162
|
|
|
173
|
-
// The personas on top, each with an edge to every standard it consults.
|
|
174
163
|
const withStandards = agents.filter((a) => (a.standards || []).some((id) => std.has(id)));
|
|
175
164
|
const agentX0 = (totalW - withStandards.length * (NODE_W + 24)) / 2;
|
|
176
165
|
const byId = new Map(standards.map((s) => [s.id, s]));
|
|
@@ -194,8 +183,6 @@ function buildGraph({ standards, agents, colors, isMatch, hasFilter, persona, se
|
|
|
194
183
|
return { nodes, edges, categories: cats.map(([c, l]) => [c, l.length]) };
|
|
195
184
|
}
|
|
196
185
|
|
|
197
|
-
// ─── Detail ───────────────────────────────────────────────────────────────────
|
|
198
|
-
|
|
199
186
|
function Section({ title, children }) {
|
|
200
187
|
return (
|
|
201
188
|
<section className="space-y-2">
|
|
@@ -307,8 +294,6 @@ export function StandardDetail({ std, color = 'var(--color-dim)' }) {
|
|
|
307
294
|
);
|
|
308
295
|
}
|
|
309
296
|
|
|
310
|
-
// ─── Page ─────────────────────────────────────────────────────────────────────
|
|
311
|
-
|
|
312
297
|
export default function Standards() {
|
|
313
298
|
const standards = morphData.standards?.list || [];
|
|
314
299
|
const version = morphData.standards?.version;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Reading and summing are NOT done here: `loadTelemetryAllTrees` gathers every
|
|
6
|
-
* tree's log plus the finish backups, and `aggregateCost` is the one place
|
|
7
|
-
* tokens are added up (only `delta` can be summed; a v1 stream yields `null`,
|
|
8
|
-
* which the UI shows as unknown — never 0). This module only decides what each
|
|
9
|
-
* event means in words, and which ones are worth a line.
|
|
2
|
+
* `loadTelemetryAllTrees` reads and `aggregateCost` is the one place tokens are
|
|
3
|
+
* summed: a v1 stream yields `null`, shown as unknown, never 0.
|
|
10
4
|
*/
|
|
11
5
|
|
|
12
6
|
import { statSync, openSync, readSync, closeSync } from 'fs';
|
|
@@ -14,7 +8,6 @@ import { join, resolve } from 'path';
|
|
|
14
8
|
import { loadTelemetryAllTrees, aggregateCost, dedupeEvents } from '../../lib/telemetry/telemetry-reader.js';
|
|
15
9
|
import { isFailedToolCall } from '../../../framework/hooks/shared/telemetry-logger.js';
|
|
16
10
|
|
|
17
|
-
/** Kinds a person follows by default; the rest are one click away. */
|
|
18
11
|
export const DEFAULT_KINDS = ['prompt', 'question', 'skill', 'dispatch', 'handback', 'verify', 'gate', 'failure'];
|
|
19
12
|
export const ALL_KINDS = [...DEFAULT_KINDS, 'write', 'read', 'command', 'tool'];
|
|
20
13
|
|
|
@@ -44,16 +37,6 @@ function nodesSummary(nodes) {
|
|
|
44
37
|
return Object.entries(nodes).map(([name, n]) => ({ name, status: typeof n === 'string' ? n : n?.status ?? null }));
|
|
45
38
|
}
|
|
46
39
|
|
|
47
|
-
/**
|
|
48
|
-
* One event → one feed line, or null when the event is bookkeeping.
|
|
49
|
-
*
|
|
50
|
-
* @returns {null | {
|
|
51
|
-
* id: string, ts: string, feature: string|null, phase: string|null,
|
|
52
|
-
* kind: string, title: string, detail: string|null, status: 'ok'|'fail'|null,
|
|
53
|
-
* refs: { persona?: string, skill?: string, path?: string },
|
|
54
|
-
* nodes?: Array<{name: string, status: string|null}>
|
|
55
|
-
* }}
|
|
56
|
-
*/
|
|
57
40
|
export function describeEvent(e) {
|
|
58
41
|
if (!e || typeof e !== 'object' || SILENT_EVENTS.has(e.event)) return null;
|
|
59
42
|
const base = {
|
|
@@ -69,9 +52,8 @@ export function describeEvent(e) {
|
|
|
69
52
|
const line = (kind, title, extra = {}) => ({ ...base, kind, title, ...extra, refs: { ...base.refs, ...(extra.refs || {}) } });
|
|
70
53
|
|
|
71
54
|
if (e.event === 'UserPromptSubmit') {
|
|
72
|
-
// Hand-backs,
|
|
73
|
-
//
|
|
74
|
-
// prompt a person typed.
|
|
55
|
+
// Hand-backs, notifications and reminders arrive as a prompt wrapped in a tag;
|
|
56
|
+
// a slash command is the one tagged prompt a person typed.
|
|
75
57
|
const text = typeof e.prompt === 'string' ? e.prompt.trim() : '';
|
|
76
58
|
const command = /^<command-name>\/?([^<]+)<\/command-name>/.exec(text);
|
|
77
59
|
if (command) return line('prompt', `Você rodou /${command[1].trim()}`);
|
|
@@ -119,8 +101,7 @@ export function describeEvent(e) {
|
|
|
119
101
|
return line('tool', tool || e.event);
|
|
120
102
|
}
|
|
121
103
|
|
|
122
|
-
//
|
|
123
|
-
// describing ~100k events once, not per page, is the difference that matters.
|
|
104
|
+
// Memoized per events array: describing ~100k events once, not per page.
|
|
124
105
|
const described = new WeakMap();
|
|
125
106
|
|
|
126
107
|
function describeAll(events) {
|
|
@@ -137,12 +118,6 @@ function describeAll(events) {
|
|
|
137
118
|
return described.get(events);
|
|
138
119
|
}
|
|
139
120
|
|
|
140
|
-
/**
|
|
141
|
-
* The feed, newest first, filtered and paginated by a `before` timestamp cursor.
|
|
142
|
-
*
|
|
143
|
-
* @param {Object[]} events - raw events, any order
|
|
144
|
-
* @param {{ feature?: string, kinds?: string[], before?: string, limit?: number }} [opts]
|
|
145
|
-
*/
|
|
146
121
|
export function buildFeed(events, { feature, kinds = DEFAULT_KINDS, before, limit = 100 } = {}) {
|
|
147
122
|
const wanted = new Set(kinds);
|
|
148
123
|
const lines = describeAll(events).filter((l) => wanted.has(l.kind) && (!feature || l.feature === feature));
|
|
@@ -152,15 +127,6 @@ export function buildFeed(events, { feature, kinds = DEFAULT_KINDS, before, limi
|
|
|
152
127
|
return { items: page, next: page.length === limit && last ? last.ts : null, total: lines.length };
|
|
153
128
|
}
|
|
154
129
|
|
|
155
|
-
/**
|
|
156
|
-
* What the feature list shows as "the last thing that happened" in each
|
|
157
|
-
* feature: the newest line a person follows by default (a dispatch, a verify, a
|
|
158
|
-
* task — not a Read or a shell command), plus `ts`, the newest event of ANY kind,
|
|
159
|
-
* which is what says an agent is working right now.
|
|
160
|
-
*
|
|
161
|
-
* @param {Object[]} events
|
|
162
|
-
* @returns {Record<string, {ts: string, title: string|null, kind: string|null, status: string|null, titleTs: string|null}>}
|
|
163
|
-
*/
|
|
164
130
|
const lastByLines = new WeakMap();
|
|
165
131
|
|
|
166
132
|
export function lastActivityByFeature(events) {
|
|
@@ -179,7 +145,6 @@ export function lastActivityByFeature(events) {
|
|
|
179
145
|
return out;
|
|
180
146
|
}
|
|
181
147
|
|
|
182
|
-
/** Every event of the project across its trees and finish backups. */
|
|
183
148
|
export function loadProjectEvents(projectDir) {
|
|
184
149
|
return dedupeEvents(loadTelemetryAllTrees(projectDir).flatMap((t) => t.events));
|
|
185
150
|
}
|
|
@@ -195,27 +160,14 @@ function fileSize(path) {
|
|
|
195
160
|
}
|
|
196
161
|
|
|
197
162
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* Re-reading every log (≈100k events, seconds) on each append would block the
|
|
202
|
-
* dev server for the whole run, so the store remembers how far it read each
|
|
203
|
-
* live log and parses only the bytes after that. Anything it cannot follow
|
|
204
|
-
* that way — a log that shrank (SessionStart rotated it), a log it has never
|
|
205
|
-
* seen (a new worktree), an archive or backup — falls back to a full reload
|
|
206
|
-
* through `loadTelemetryAllTrees`, the same reader as always. Copies of one
|
|
207
|
-
* event are still dropped by `eventId`.
|
|
208
|
-
*
|
|
209
|
-
* Every update yields a NEW events array, so everything memoized per array
|
|
210
|
-
* (described lines, per-feature cost) follows the change.
|
|
211
|
-
*
|
|
212
|
-
* @param {string} projectDir
|
|
213
|
-
* @param {{ loadTrees?: Function }} [deps] - test seam, defaults to loadTelemetryAllTrees
|
|
163
|
+
* Tails each live log from the last byte read; a shrunk (rotated) or unseen log,
|
|
164
|
+
* an archive or a backup falls back to a full reload. Every update yields a NEW
|
|
165
|
+
* events array, so everything memoized per array follows the change.
|
|
214
166
|
*/
|
|
215
167
|
export function createEventStore(projectDir, { loadTrees = loadTelemetryAllTrees } = {}) {
|
|
216
168
|
let events = null;
|
|
217
169
|
let seen = new Set();
|
|
218
|
-
let offsets = new Map();
|
|
170
|
+
let offsets = new Map();
|
|
219
171
|
let pending = new Set();
|
|
220
172
|
let fullReload = true;
|
|
221
173
|
const costs = new Map();
|
|
@@ -238,7 +190,6 @@ export function createEventStore(projectDir, { loadTrees = loadTelemetryAllTrees
|
|
|
238
190
|
costs.clear();
|
|
239
191
|
}
|
|
240
192
|
|
|
241
|
-
/** Parse the bytes appended to `path` since the last read; false when it cannot follow. */
|
|
242
193
|
function tail(path) {
|
|
243
194
|
const from = offsets.get(path);
|
|
244
195
|
const size = fileSize(path);
|
|
@@ -273,7 +224,6 @@ export function createEventStore(projectDir, { loadTrees = loadTelemetryAllTrees
|
|
|
273
224
|
const prevLines = described.get(events);
|
|
274
225
|
const merged = events.concat(fresh);
|
|
275
226
|
events = fresh.every((e) => (e.ts || '') >= last) ? merged : dedupeEvents(merged);
|
|
276
|
-
// Carry the described lines over instead of describing 100k events again.
|
|
277
227
|
if (prevLines) {
|
|
278
228
|
const byTsDesc = (a, b) => (b.ts || '').localeCompare(a.ts || '');
|
|
279
229
|
const added = fresh.map(describeEvent).filter(Boolean).sort(byTsDesc);
|
|
@@ -302,19 +252,13 @@ export function createEventStore(projectDir, { loadTrees = loadTelemetryAllTrees
|
|
|
302
252
|
|
|
303
253
|
return {
|
|
304
254
|
events: current,
|
|
305
|
-
/**
|
|
306
|
-
* Something under `.morph/logs` changed. A known live log is tailed on the
|
|
307
|
-
* next read; anything else (rotation archive, new tree, backup) reloads.
|
|
308
|
-
*/
|
|
309
255
|
changed(path) {
|
|
310
256
|
const known = path && [...offsets.keys()].find((p) => resolve(p) === resolve(path));
|
|
311
257
|
if (known) pending.add(known);
|
|
312
258
|
else fullReload = true;
|
|
313
259
|
},
|
|
314
260
|
reset() { fullReload = true; },
|
|
315
|
-
/** Loaded and not waiting for a full reload: reading it now is cheap. */
|
|
316
261
|
ready() { return events !== null && !fullReload; },
|
|
317
|
-
/** `featureCost`, memoized until the events change. */
|
|
318
262
|
cost(feature) {
|
|
319
263
|
const all = current();
|
|
320
264
|
if (!costs.has(feature)) costs.set(feature, featureCost(all, feature));
|
|
@@ -323,10 +267,7 @@ export function createEventStore(projectDir, { loadTrees = loadTelemetryAllTrees
|
|
|
323
267
|
};
|
|
324
268
|
}
|
|
325
269
|
|
|
326
|
-
/**
|
|
327
|
-
* What the feature cost, straight from `aggregateCost`. `sessionUsage` and
|
|
328
|
-
* `agentUsage` stay `null` when the stream has no summable sample.
|
|
329
|
-
*/
|
|
270
|
+
/** `sessionUsage` and `agentUsage` stay `null` when the stream has no summable sample. */
|
|
330
271
|
export function featureCost(events, feature) {
|
|
331
272
|
const c = aggregateCost(events, { feature });
|
|
332
273
|
return {
|
|
@@ -2,27 +2,13 @@ import { readFileSync, existsSync } from 'fs';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { personaStandardIds, isImplementer, renderAgentMd, loadKnownMcpIds } from '../../utils/agents-installer.js';
|
|
4
4
|
|
|
5
|
-
/** A line of a phase skill that dispatches a sub-agent. */
|
|
6
5
|
const DISPATCH_LINE = /despach|dispatch|subagent_type/i;
|
|
7
6
|
|
|
8
|
-
/** The generic dispatch token: "the implementer of the task's domain". */
|
|
9
7
|
const GENERIC_DISPATCH = 'morph-{id}';
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
|
-
* Which phases each persona works in, read from the phase skills themselves.
|
|
13
|
-
*
|
|
14
|
-
* v5 has no phase list on the persona: a phase dispatches a persona when its
|
|
15
|
-
* skill (`phases.json → skill`) names `morph-{personaId}` on a dispatch line
|
|
16
|
-
* (morph-uiux → ui-designer, morph-plan → scout, morph-review → verifier, reviewer, security).
|
|
17
|
-
* A skill that dispatches the generic `morph-{id}` runs every implementer —
|
|
18
|
-
* the same `isImplementer` the validator and the installer use.
|
|
19
|
-
*
|
|
20
10
|
* Only dispatch lines count: morph-plan cites `morph-dotnet-senior` as a
|
|
21
11
|
* spelling example of the `agent` field, which dispatches nobody.
|
|
22
|
-
*
|
|
23
|
-
* @param {string} frameworkDir
|
|
24
|
-
* @param {Record<string, object>} agents - agents.json `agents` map
|
|
25
|
-
* @returns {Map<string, string[]>} personaId → phase ids, in phase order
|
|
26
12
|
*/
|
|
27
13
|
export function personaPhases(frameworkDir, agents) {
|
|
28
14
|
const result = new Map(Object.keys(agents).map((id) => [id, []]));
|
|
@@ -46,10 +32,6 @@ export function personaPhases(frameworkDir, agents) {
|
|
|
46
32
|
return result;
|
|
47
33
|
}
|
|
48
34
|
|
|
49
|
-
/**
|
|
50
|
-
* Scan agents.json (v5) — flat domain personas, no tiers.
|
|
51
|
-
* Returns the list shaped for the dashboard inspector.
|
|
52
|
-
*/
|
|
53
35
|
export function scanAgents(frameworkDir) {
|
|
54
36
|
const agentsJsonPath = join(frameworkDir, 'agents.json');
|
|
55
37
|
if (!existsSync(agentsJsonPath)) {
|
|
@@ -58,9 +40,7 @@ export function scanAgents(frameworkDir) {
|
|
|
58
40
|
const agentsJson = JSON.parse(readFileSync(agentsJsonPath, 'utf8'));
|
|
59
41
|
const phasesOf = personaPhases(frameworkDir, agentsJson.agents);
|
|
60
42
|
const knownMcpIds = loadKnownMcpIds(frameworkDir);
|
|
61
|
-
//
|
|
62
|
-
// prompt the sub-agent actually runs with. A persona whose data the renderer
|
|
63
|
-
// refuses shows the refusal instead of hiding the persona.
|
|
43
|
+
// A persona whose data the renderer refuses shows the refusal instead of hiding the persona.
|
|
64
44
|
const promptOf = (id, agent) => {
|
|
65
45
|
try { return renderAgentMd({ id, ...agent }, knownMcpIds); } catch (err) { return `<!-- não renderizou: ${err.message} -->`; }
|
|
66
46
|
};
|
|
@@ -71,8 +51,6 @@ export function scanAgents(frameworkDir) {
|
|
|
71
51
|
color: agent.color || null,
|
|
72
52
|
domains: agent.domains || [],
|
|
73
53
|
keywords: agent.keywords || [],
|
|
74
|
-
// Every standard the persona can cite, surfaces included — the same
|
|
75
|
-
// reading the installer and the validator use.
|
|
76
54
|
standards: personaStandardIds(agent),
|
|
77
55
|
surfaces: agent.surfaces || null,
|
|
78
56
|
stack: agent.stack || ['*'],
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The reading material of the framework — the charter, the runtime
|
|
3
|
-
* instructions, the CLI reference and the Gate 3 review checklist — for the
|
|
4
|
-
* dashboard's Guide page.
|
|
5
|
-
*/
|
|
6
1
|
import { readFileSync } from 'fs';
|
|
7
2
|
import { join } from 'path';
|
|
8
3
|
|
|
@@ -10,7 +5,6 @@ function read(path) {
|
|
|
10
5
|
try { return readFileSync(path, 'utf8'); } catch { return null; }
|
|
11
6
|
}
|
|
12
7
|
|
|
13
|
-
/** `## Heading` lines, the table of contents a long document is read by. */
|
|
14
8
|
export function headingsOf(content) {
|
|
15
9
|
const out = [];
|
|
16
10
|
let inFence = false;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* A feature in `.morph/archive/` is closed whatever its status says: legacy
|
|
6
|
-
* archives keep `draft` or `in_progress` from before `finish` set the status.
|
|
2
|
+
* Pure, no node imports: the client bundles it. An archived feature is closed
|
|
3
|
+
* whatever its legacy status says.
|
|
7
4
|
*/
|
|
8
5
|
export function isFinished(f) {
|
|
9
6
|
return f?.status === 'completed' || f?.status === 'abandoned' || f?.location === 'archive';
|
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Every rule here is borrowed, never re-encoded:
|
|
6
|
-
* - which tree owns a feature → `resolveFeatureTree` (feature-truth.js)
|
|
7
|
-
* - the current phase → `derivePhaseFrom` (state-reader.js)
|
|
8
|
-
* - gate order → `GATE_PREREQ` (gate-order.js)
|
|
9
|
-
* - the review verdict → `reviewReportsFromDir` (review-reports.js)
|
|
10
|
-
* - path confinement → `canonicalPath`/`isInside` (test-runner.js)
|
|
11
|
-
* - slice progress → `sliceProgress` (slice-progress.js)
|
|
12
|
-
*
|
|
13
|
-
* Three places a feature can live: the root `.morph/features/{f}`, a worktree
|
|
14
|
-
* `{worktrees}/{wt}/.morph/features/{f}`, and `.morph/archive/{f}` once
|
|
15
|
-
* `finish` ran. The root copy of a feature built in a worktree is its
|
|
16
|
-
* birth-instant skeleton and never changes again, so for an active feature the
|
|
17
|
-
* owner tree wins. Only active features pay for that question — it spawns git.
|
|
18
|
-
*
|
|
19
|
-
* Read-only and fail-open: a corrupt file degrades one feature, never the scan.
|
|
2
|
+
* The root copy of a feature built in a worktree is its birth skeleton, so for an
|
|
3
|
+
* active feature the owner tree wins. Only active features ask: it spawns git.
|
|
20
4
|
*/
|
|
21
5
|
|
|
22
6
|
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
@@ -48,10 +32,7 @@ function subdirs(dir) {
|
|
|
48
32
|
} catch { return []; }
|
|
49
33
|
}
|
|
50
34
|
|
|
51
|
-
/**
|
|
52
|
-
* Old feature.json files stamped a gate as a bare `true` (MORPH_OS). Lifting it
|
|
53
|
-
* to `{approved: true}` is what lets the shared rules read them unchanged.
|
|
54
|
-
*/
|
|
35
|
+
/** Old feature.json files stamped a gate as a bare `true`. */
|
|
55
36
|
export function normalizeGates(gates) {
|
|
56
37
|
const out = {};
|
|
57
38
|
for (const [name, value] of Object.entries(gates || {})) {
|
|
@@ -65,10 +46,6 @@ function normalizeFeature(fj) {
|
|
|
65
46
|
return { ...f, approvalGates: normalizeGates(f.approvalGates || f.approvals) };
|
|
66
47
|
}
|
|
67
48
|
|
|
68
|
-
/**
|
|
69
|
-
* Every place a feature lives, before deciding which one tells the truth.
|
|
70
|
-
* @returns {Map<string, {root?: string, worktrees: string[], archive?: string}>}
|
|
71
|
-
*/
|
|
72
49
|
function collectLocations(projectDir) {
|
|
73
50
|
const found = new Map();
|
|
74
51
|
const slot = (name) => {
|
|
@@ -93,17 +70,10 @@ function collectLocations(projectDir) {
|
|
|
93
70
|
return found;
|
|
94
71
|
}
|
|
95
72
|
|
|
96
|
-
/**
|
|
97
|
-
* Decide which directory is this feature's truth.
|
|
98
|
-
*
|
|
99
|
-
* @returns {{ location: 'root'|'worktree'|'archive', dir: string, feature: object,
|
|
100
|
-
* ownerTree: string|null, rootStale: boolean }}
|
|
101
|
-
*/
|
|
102
73
|
function resolveLocation(projectDir, name, loc, resolveTree) {
|
|
103
74
|
const rootFj = loc.root ? readJson(join(loc.root, 'feature.json')) : null;
|
|
104
75
|
|
|
105
|
-
//
|
|
106
|
-
// keeps a feature.json per finished feature) — the archive holds the files.
|
|
76
|
+
// An old root copy may linger; the archive holds the files.
|
|
107
77
|
const rootFinished = rootFj && (FINISHED.has(rootFj.status) || rootFj.archivedAt);
|
|
108
78
|
if (loc.archive && (!rootFj || rootFinished)) {
|
|
109
79
|
const archFj = readJson(join(loc.archive, 'feature.json'));
|
|
@@ -123,8 +93,7 @@ function resolveLocation(projectDir, name, loc, resolveTree) {
|
|
|
123
93
|
const wtDir = join(tree, '.morph', 'features', name);
|
|
124
94
|
const wtFj = readJson(join(wtDir, 'feature.json'));
|
|
125
95
|
if (wtFj) return { location: 'worktree', dir: wtDir, feature: wtFj, ownerTree: tree, rootStale: false };
|
|
126
|
-
// The owner tree
|
|
127
|
-
// the root skeleton, and the UI has to say so.
|
|
96
|
+
// The owner tree has no copy of the feature: only the root skeleton is left.
|
|
128
97
|
return { location: 'root', dir: loc.root, feature: rootFj, ownerTree: tree, rootStale: true };
|
|
129
98
|
}
|
|
130
99
|
}
|
|
@@ -141,10 +110,6 @@ function resolveLocation(projectDir, name, loc, resolveTree) {
|
|
|
141
110
|
};
|
|
142
111
|
}
|
|
143
112
|
|
|
144
|
-
/**
|
|
145
|
-
* The plan's slices with the status their reports give, plus the fields the
|
|
146
|
-
* extraction carries for the card. `null` when the plan has not been checked yet.
|
|
147
|
-
*/
|
|
148
113
|
export function readSlices(dir) {
|
|
149
114
|
const progress = sliceProgress(dir);
|
|
150
115
|
if (!progress) return null;
|
|
@@ -189,10 +154,6 @@ function reviewVerdict(dir, workType, reports = reviewReportsFromDir(dir)) {
|
|
|
189
154
|
return { verdict: combinedVerdict(reports, required), blockers: allBlockers(reports) };
|
|
190
155
|
}
|
|
191
156
|
|
|
192
|
-
/**
|
|
193
|
-
* One row per review report the feature asks for or already has: status,
|
|
194
|
-
* verdict, blockers and the SHA it reviewed.
|
|
195
|
-
*/
|
|
196
157
|
export function reviewRows(reports, workType) {
|
|
197
158
|
const required = requiredReviewRoles({ workType, securityRequired: reports.security.status !== 'missing' });
|
|
198
159
|
return Object.keys(REVIEW_ROLES)
|
|
@@ -210,10 +171,7 @@ export function reviewRows(reports, workType) {
|
|
|
210
171
|
});
|
|
211
172
|
}
|
|
212
173
|
|
|
213
|
-
/**
|
|
214
|
-
* The plan's scenarios, each with the verifier's row for it. `coverage` is
|
|
215
|
-
* `null` until a verifier report exists; only PROVADO covers.
|
|
216
|
-
*/
|
|
174
|
+
/** `coverage` is `null` until a verifier report exists; only PROVADO covers. */
|
|
217
175
|
export function readChecks(dir, verifier) {
|
|
218
176
|
const checks = readJson(checksPath(dir))?.checks;
|
|
219
177
|
if (!Array.isArray(checks)) return null;
|
|
@@ -258,8 +216,7 @@ function summarize(name, resolved) {
|
|
|
258
216
|
gates,
|
|
259
217
|
awaiting: !finished && gates.some((g) => g.paused),
|
|
260
218
|
slices: slices?.counts ?? NO_SLICES,
|
|
261
|
-
//
|
|
262
|
-
// features leave them out: 170 archived plans would bloat every list reply.
|
|
219
|
+
// Finished features leave slice titles out: archived plans would bloat every list reply.
|
|
263
220
|
sliceIndex: finished || !slices ? [] : slices.list.map((sl) => ({ id: sl.id, title: sl.title, status: sl.status })),
|
|
264
221
|
verification: feature.verification
|
|
265
222
|
? { status: feature.verification.status ?? null, ts: feature.verification.ts ?? null }
|
|
@@ -272,10 +229,6 @@ function summarize(name, resolved) {
|
|
|
272
229
|
};
|
|
273
230
|
}
|
|
274
231
|
|
|
275
|
-
/**
|
|
276
|
-
* Files of a feature, grouped by the phase their top folder belongs to.
|
|
277
|
-
* @returns {Array<{path: string, phase: string|null, size: number, mtime: string}>}
|
|
278
|
-
*/
|
|
279
232
|
export function listDocuments(dir) {
|
|
280
233
|
const out = [];
|
|
281
234
|
const walk = (abs, rel, depth) => {
|
|
@@ -305,9 +258,8 @@ export function listDocuments(dir) {
|
|
|
305
258
|
}
|
|
306
259
|
|
|
307
260
|
/**
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
* must not pay again. It is only trusted while its feature.json still reads.
|
|
261
|
+
* `known` is where a previous scan found the feature, so opening it does not pay
|
|
262
|
+
* for git again. It is only trusted while its feature.json still reads.
|
|
311
263
|
*/
|
|
312
264
|
function resolveOne(projectDir, name, resolveTree, known) {
|
|
313
265
|
if (known?.dir) {
|
|
@@ -319,13 +271,6 @@ function resolveOne(projectDir, name, resolveTree, known) {
|
|
|
319
271
|
return resolveLocation(projectDir, name, loc, resolveTree);
|
|
320
272
|
}
|
|
321
273
|
|
|
322
|
-
/**
|
|
323
|
-
* The feature list of a project.
|
|
324
|
-
*
|
|
325
|
-
* @param {string} projectDir
|
|
326
|
-
* @param {{ resolveTree?: Function }} [opts] - seam for tests; defaults to
|
|
327
|
-
* `resolveFeatureTree`
|
|
328
|
-
*/
|
|
329
274
|
export function scanFeatures(projectDir, { resolveTree = resolveFeatureTree } = {}) {
|
|
330
275
|
const config = readMorphConfig(projectDir);
|
|
331
276
|
const features = [];
|
|
@@ -344,8 +289,7 @@ export function scanFeatures(projectDir, { resolveTree = resolveFeatureTree } =
|
|
|
344
289
|
trust: config?.trust || null,
|
|
345
290
|
dir: projectDir,
|
|
346
291
|
initialized: existsSync(join(projectDir, '.morph')),
|
|
347
|
-
//
|
|
348
|
-
// per-feature state model — the dashboard says so instead of showing "empty".
|
|
292
|
+
// state.json with no feature.json anywhere predates the per-feature state model.
|
|
349
293
|
legacyStateOnly: hasState && features.length === 0,
|
|
350
294
|
},
|
|
351
295
|
features,
|
|
@@ -26,13 +26,7 @@ function extractImports(source) {
|
|
|
26
26
|
return imports;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
* Claude Code event name → the kebab-case key the Hook Inspector groups by.
|
|
31
|
-
* `pre-compact` and `teammate-idle` have no MORPH hooks — those nodes render
|
|
32
|
-
* empty on purpose. `worktree-create`/`worktree-remove` are the worktree
|
|
33
|
-
* lifecycle events (v3.8.0) — outside the tool-call flow, drawn as their own
|
|
34
|
-
* pair in HookInspector.jsx.
|
|
35
|
-
*/
|
|
29
|
+
/** `pre-compact` and `teammate-idle` have no MORPH hooks: those nodes render empty on purpose. */
|
|
36
30
|
const EVENT_KEYS = {
|
|
37
31
|
SessionStart: 'session-start',
|
|
38
32
|
UserPromptSubmit: 'user-prompt',
|
|
@@ -47,20 +41,7 @@ const EVENT_KEYS = {
|
|
|
47
41
|
WorktreeRemove: 'worktree-remove',
|
|
48
42
|
};
|
|
49
43
|
|
|
50
|
-
/**
|
|
51
|
-
* Build filename → [{ event, matcher }] from the installer's MORPH_HOOKS.
|
|
52
|
-
*
|
|
53
|
-
* The event is NOT derivable from the path. Before the v4→v5 migration the
|
|
54
|
-
* hooks sat in per-event folders, so `basename(dirname(file))` produced the
|
|
55
|
-
* event key; the migration flattened everything into `core/`, and from then on
|
|
56
|
-
* every hook reported `event: 'core'`. Nothing crashed — the Inspector groups
|
|
57
|
-
* by event, found no key it knew, and rendered every node empty. Twelve hooks,
|
|
58
|
-
* zero shown.
|
|
59
|
-
*
|
|
60
|
-
* `getHookDefinitions()` is the single source of truth (the same array the
|
|
61
|
-
* installer writes into settings.local.json), so the registration and the
|
|
62
|
-
* drawing can no longer disagree.
|
|
63
|
-
*/
|
|
44
|
+
/** The event is not derivable from the path: every hook sits in `core/`. */
|
|
64
45
|
export function hookRegistrations(definitions = getHookDefinitions()) {
|
|
65
46
|
const byFile = new Map();
|
|
66
47
|
for (const def of definitions) {
|
|
@@ -69,10 +50,8 @@ export function hookRegistrations(definitions = getHookDefinitions()) {
|
|
|
69
50
|
for (const h of def.hooks || []) {
|
|
70
51
|
const file = basename(String(h.command || '').trim());
|
|
71
52
|
if (!file.endsWith('.js')) continue;
|
|
72
|
-
// Grupos consolidados apontam para chain.js com os
|
|
73
|
-
//
|
|
74
|
-
// runner: é o elo que o Inspector precisa mostrar. chain.js em si não é
|
|
75
|
-
// um hook de domínio e ficaria como nó fantasma em todo evento.
|
|
53
|
+
// Grupos consolidados apontam para chain.js com os elos em `args`: registre
|
|
54
|
+
// cada elo, senão chain.js vira nó fantasma em todo evento.
|
|
76
55
|
const files = file === 'chain.js' && Array.isArray(h.args) && h.args.length
|
|
77
56
|
? h.args.map(a => `${a}.js`)
|
|
78
57
|
: [file];
|
|
@@ -80,8 +59,7 @@ export function hookRegistrations(definitions = getHookDefinitions()) {
|
|
|
80
59
|
if (!byFile.has(f)) byFile.set(f, []);
|
|
81
60
|
const entries = byFile.get(f);
|
|
82
61
|
const matcher = def.matcher || null;
|
|
83
|
-
//
|
|
84
|
-
// Failure/Batch siblings): one node per column, not one per event.
|
|
62
|
+
// PostToolUse and its Failure/Batch siblings share one Inspector column.
|
|
85
63
|
if (entries.some((e) => e.event === key && e.matcher === matcher)) continue;
|
|
86
64
|
entries.push({ event: key, rawEvent: def.event, matcher });
|
|
87
65
|
}
|
|
@@ -111,7 +89,6 @@ export function scanHooks(frameworkDir) {
|
|
|
111
89
|
// PostToolUse and UserPromptSubmit, and must show under both.
|
|
112
90
|
const regs = registrations.get(filename);
|
|
113
91
|
if (!regs || regs.length === 0) {
|
|
114
|
-
// On disk but never registered — real dead weight, and worth seeing.
|
|
115
92
|
return [{ ...base, event: 'unregistered', matcher: null }];
|
|
116
93
|
}
|
|
117
94
|
return regs.map(r => ({ ...base, event: r.event, rawEvent: r.rawEvent, matcher: r.matcher }));
|
|
@@ -6,10 +6,6 @@ import { scanStandards } from './standards-scanner.js';
|
|
|
6
6
|
import { scanRules } from './rules-scanner.js';
|
|
7
7
|
import { scanDocs } from './docs-scanner.js';
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* The framework's knowledge, for the static virtual module. The project's
|
|
11
|
-
* state (features, activity) is served live by project-api.js instead.
|
|
12
|
-
*/
|
|
13
9
|
export function scanAll(frameworkDir) {
|
|
14
10
|
const agents = scanAgents(frameworkDir);
|
|
15
11
|
const phases = scanPhases(frameworkDir);
|