@polymorphism-tech/morph-spec 9.0.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +4 -62
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +12 -4
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +6 -17
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +11 -67
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +1 -42
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +2 -2
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +2 -2
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +34 -12
- package/framework/standards/STANDARDS.meta.json +16 -2
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +5 -57
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +14 -96
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +2 -5
- package/src/commands/validation/validate-feature.js +5 -94
- package/src/commands/validation/verify.js +15 -98
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +1 -0
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +3 -15
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +5 -31
- package/src/lib/plan/plan-parser.js +1 -6
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +5 -23
- package/src/lib/risk-detector.js +39 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +52 -358
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +13 -95
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -147
- package/src/utils/agents-installer.js +28 -275
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* module lists the projects, routes `/p/:id/…` to the right one, and answers
|
|
5
|
-
* which project a changed file belongs to.
|
|
6
|
-
*
|
|
7
|
-
* The list is the registry (src/lib/projects/registry.js) plus the project the
|
|
8
|
-
* dashboard was launched from, which is always shown even if it cannot be
|
|
9
|
-
* registered. Projects found next to the registered ones are offered, never
|
|
10
|
-
* added on their own.
|
|
11
|
-
*
|
|
12
|
-
* A cold project can take seconds to read (a 100k-event log). The portfolio
|
|
13
|
-
* never waits for one: `summary` is served only for projects already in
|
|
14
|
-
* memory, and the rest are read in the background (`warmNext`).
|
|
2
|
+
* The portfolio never waits for a cold project (a 100k-event log takes seconds):
|
|
3
|
+
* `summary` is served only from memory, the rest is read by `warmNext`.
|
|
15
4
|
*/
|
|
16
5
|
|
|
17
6
|
import { dirname, basename } from 'path';
|
|
@@ -25,18 +14,12 @@ function slug(text) {
|
|
|
25
14
|
|
|
26
15
|
const lower = (p) => (process.platform === 'win32' ? p.toLowerCase() : p);
|
|
27
16
|
|
|
28
|
-
/**
|
|
29
|
-
* @param {{ launchDir: string, registry?: object, createApi?: Function }} opts
|
|
30
|
-
* `registry` and `createApi` are test seams.
|
|
31
|
-
*/
|
|
32
17
|
export function createPortfolio({ launchDir, registry = registryModule, createApi = createProjectApi }) {
|
|
33
18
|
const launchKey = registry.projectKey(launchDir);
|
|
34
|
-
const apis = new Map();
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
const lastSummary = new Map(); // dir key -> summary
|
|
19
|
+
const apis = new Map();
|
|
20
|
+
// While a changed project is read again, its last summary keeps showing.
|
|
21
|
+
const lastSummary = new Map();
|
|
38
22
|
|
|
39
|
-
/** The projects shown, with stable ids derived from the folder name. */
|
|
40
23
|
function list() {
|
|
41
24
|
const dirs = registry.readRegistry().projects.map((p) => ({ dir: p.dir, name: p.name, source: p.source }));
|
|
42
25
|
if (!dirs.some((p) => lower(p.dir) === lower(launchKey))) {
|
|
@@ -61,7 +44,6 @@ export function createPortfolio({ launchDir, registry = registryModule, createAp
|
|
|
61
44
|
return list().find((p) => p.id === id) || null;
|
|
62
45
|
}
|
|
63
46
|
|
|
64
|
-
/** The project a changed path belongs to — the deepest registered folder containing it. */
|
|
65
47
|
function ownerOf(path) {
|
|
66
48
|
const p = lower(String(path).replace(/\\/g, '/'));
|
|
67
49
|
let best = null;
|
|
@@ -78,7 +60,6 @@ export function createPortfolio({ launchDir, registry = registryModule, createAp
|
|
|
78
60
|
return registry.discoverProjects(roots).filter((s) => !projects.some((p) => lower(p.dir) === lower(s.dir)));
|
|
79
61
|
}
|
|
80
62
|
|
|
81
|
-
/** Read the next project that is not in memory yet. @returns {object|null} the project warmed */
|
|
82
63
|
function warmNext() {
|
|
83
64
|
const next = list().find((p) => !apiFor(p.dir).ready());
|
|
84
65
|
if (!next) return null;
|
|
@@ -106,11 +87,6 @@ export function createPortfolio({ launchDir, registry = registryModule, createAp
|
|
|
106
87
|
|
|
107
88
|
const json = (status, body) => ({ status, body });
|
|
108
89
|
|
|
109
|
-
/**
|
|
110
|
-
* @param {string} method
|
|
111
|
-
* @param {string} url - relative to `/api/morph`
|
|
112
|
-
* @param {object|null} [body] - parsed JSON of a POST
|
|
113
|
-
*/
|
|
114
90
|
function handle(method, url, body = null) {
|
|
115
91
|
const u = new URL(url, 'http://local');
|
|
116
92
|
const parts = u.pathname.split('/').filter(Boolean);
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* changes on disk. The framework knowledge stays in the static virtual module.
|
|
5
|
-
*
|
|
6
|
-
* Two caches with two lifetimes. A running agent appends to `events.jsonl` on
|
|
7
|
-
* every tool call, so a log change drops only the event cache — rescanning the
|
|
8
|
-
* features (git calls for the active ones) on each tool call would be waste.
|
|
2
|
+
* Two caches with two lifetimes: an agent appends to `events.jsonl` on every tool
|
|
3
|
+
* call, so a log change drops only the event cache, never the feature scan.
|
|
9
4
|
*/
|
|
10
5
|
|
|
11
6
|
import { join } from 'path';
|
|
@@ -16,17 +11,9 @@ import { containerDirs } from '../../../framework/hooks/shared/worktree-root.js'
|
|
|
16
11
|
import { isFinished } from './feature-status.js';
|
|
17
12
|
import { resolveFeatureTree } from '../../lib/worktree/feature-truth.js';
|
|
18
13
|
|
|
19
|
-
/** How long a remembered "which tree owns this feature" answer is trusted without a structural change. */
|
|
20
14
|
const TREE_TTL_MS = 60_000;
|
|
21
15
|
|
|
22
|
-
/**
|
|
23
|
-
* Which cache a changed path invalidates, or null when it is none of ours.
|
|
24
|
-
* `.morph/framework/` is the installed framework copy (often a junction), and
|
|
25
|
-
* it never changes what this API returns.
|
|
26
|
-
*
|
|
27
|
-
* @param {string} path
|
|
28
|
-
* @returns {'activity'|'features'|null}
|
|
29
|
-
*/
|
|
16
|
+
/** `.morph/framework/` is the installed framework copy and never changes what this API returns. */
|
|
30
17
|
export function classifyChange(path) {
|
|
31
18
|
const p = String(path).replace(/\\/g, '/');
|
|
32
19
|
if (!p.includes('/.morph/') && !p.endsWith('/.morph')) return null;
|
|
@@ -36,19 +23,12 @@ export function classifyChange(path) {
|
|
|
36
23
|
return null;
|
|
37
24
|
}
|
|
38
25
|
|
|
39
|
-
/**
|
|
40
|
-
* A full-page reload Vite would send for a file under a project's `.morph/`
|
|
41
|
-
* (an agent writing a design .html). The dashboard's own code never lives
|
|
42
|
-
* there, so such a reload is never needed.
|
|
43
|
-
*/
|
|
26
|
+
/** Vite would full-reload for a `.html` under `.morph/`; the dashboard's code never lives there. */
|
|
44
27
|
export function isMorphDataReload(path) {
|
|
45
28
|
return typeof path === 'string' && path.replace(/\\/g, '/').includes('/.morph/');
|
|
46
29
|
}
|
|
47
30
|
|
|
48
|
-
/**
|
|
49
|
-
* The directories worth watching: the root's and each worktree's feature,
|
|
50
|
-
* archive, log and config folders — never a whole worktree (its node_modules).
|
|
51
|
-
*/
|
|
31
|
+
/** Never a whole worktree: its node_modules. */
|
|
52
32
|
export function watchTargets(projectDir) {
|
|
53
33
|
const morphDirs = [join(projectDir, '.morph')];
|
|
54
34
|
for (const container of containerDirs(projectDir)) {
|
|
@@ -72,12 +52,6 @@ function json(status, body) {
|
|
|
72
52
|
return { status, body };
|
|
73
53
|
}
|
|
74
54
|
|
|
75
|
-
/**
|
|
76
|
-
* @param {string} projectDir
|
|
77
|
-
* @param {{ scanFeatures?: Function, readFeatureDetail?: Function,
|
|
78
|
-
* readFeatureFile?: Function, loadEvents?: Function, resolveTree?: Function,
|
|
79
|
-
* now?: () => number }} [deps] - test seams
|
|
80
|
-
*/
|
|
81
55
|
export function createProjectApi(projectDir, deps = {}) {
|
|
82
56
|
const scan = deps.scanFeatures || scanFeatures;
|
|
83
57
|
const detail = deps.readFeatureDetail || readFeatureDetail;
|
|
@@ -87,10 +61,8 @@ export function createProjectApi(projectDir, deps = {}) {
|
|
|
87
61
|
: {});
|
|
88
62
|
const cache = { features: null };
|
|
89
63
|
|
|
90
|
-
// Which tree owns a feature costs
|
|
91
|
-
//
|
|
92
|
-
// an agent rewrites feature.json or a slice report, which is what happens all
|
|
93
|
-
// the time. Remembered per feature; a structural change or TTL_MS forgets it.
|
|
64
|
+
// Which tree owns a feature costs git calls (~0.3 s each on Windows) and only
|
|
65
|
+
// changes on add/remove, never on a rewrite: remembered until a structural event or TTL_MS.
|
|
94
66
|
const resolveTree = deps.resolveTree || resolveFeatureTree;
|
|
95
67
|
const now = deps.now || Date.now;
|
|
96
68
|
const trees = new Map();
|
|
@@ -104,17 +76,9 @@ export function createProjectApi(projectDir, deps = {}) {
|
|
|
104
76
|
|
|
105
77
|
const features = () => (cache.features ||= scan(projectDir, { resolveTree: rememberedTree }));
|
|
106
78
|
const events = () => store.events();
|
|
107
|
-
// Where the list scan found each feature: opening one reuses it instead of
|
|
108
|
-
// asking git again.
|
|
109
79
|
const known = (name) => features().features?.find((f) => f.name === name) || undefined;
|
|
110
80
|
|
|
111
|
-
/**
|
|
112
|
-
* @param {'features'|'activity'|'all'} scope
|
|
113
|
-
* @param {string} [path] - the file that changed; a live log is then tailed
|
|
114
|
-
* instead of reloading every log
|
|
115
|
-
* @param {string} [event] - the watcher event; anything but 'change' (a file
|
|
116
|
-
* or folder added or removed) may move a feature to another tree
|
|
117
|
-
*/
|
|
81
|
+
/** Any `event` but 'change' may move a feature to another tree. */
|
|
118
82
|
function invalidate(scope, path, event) {
|
|
119
83
|
if (scope === 'features' || scope === 'all') cache.features = null;
|
|
120
84
|
if (scope === 'all' || (scope === 'features' && event !== 'change')) trees.clear();
|
|
@@ -122,10 +86,6 @@ export function createProjectApi(projectDir, deps = {}) {
|
|
|
122
86
|
else if (scope === 'activity') store.changed(path);
|
|
123
87
|
}
|
|
124
88
|
|
|
125
|
-
/**
|
|
126
|
-
* @param {string} url - path + query, relative to `/api/morph`
|
|
127
|
-
* @returns {{ status: number, body: object }}
|
|
128
|
-
*/
|
|
129
89
|
function handle(url) {
|
|
130
90
|
const u = new URL(url, 'http://local');
|
|
131
91
|
const parts = u.pathname.split('/').filter(Boolean).map(decodeURIComponent);
|
|
@@ -159,20 +119,14 @@ export function createProjectApi(projectDir, deps = {}) {
|
|
|
159
119
|
}
|
|
160
120
|
}
|
|
161
121
|
|
|
162
|
-
/** Load everything once, so the first page a person opens does not pay for it. */
|
|
163
122
|
function warm() {
|
|
164
123
|
try { features(); events(); } catch { /* the first request will report it */ }
|
|
165
124
|
}
|
|
166
125
|
|
|
167
|
-
/** Cheap to answer now: the list and the events are both in memory. */
|
|
168
126
|
function ready() {
|
|
169
127
|
return cache.features !== null && store.ready();
|
|
170
128
|
}
|
|
171
129
|
|
|
172
|
-
/**
|
|
173
|
-
* What the portfolio shows of this project: counts, the active features with
|
|
174
|
-
* their last activity, and when anything last happened.
|
|
175
|
-
*/
|
|
176
130
|
function summary() {
|
|
177
131
|
const scan = features();
|
|
178
132
|
const last = lastActivityByFeature(events());
|
|
@@ -20,8 +20,7 @@ function parseFrontmatter(content) {
|
|
|
20
20
|
|
|
21
21
|
const ROUTE_LINE = /^- \*\*([a-z0-9-]+)\*\* \(`([^`]+\.md)`\)/;
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
// import inside .claude/rules resolves relative to the rule file and never loads.
|
|
23
|
+
// An `@path` import inside .claude/rules resolves relative to the rule file and never loads.
|
|
25
24
|
export function parseStandardRoutes(content) {
|
|
26
25
|
const routes = [];
|
|
27
26
|
for (const line of content.split(/\r?\n/)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/** CRLF-safe: a checkout with autocrlf is still read. */
|
|
5
5
|
export function parseFrontmatter(content) {
|
|
6
6
|
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
|
|
7
7
|
if (!match) return {};
|
|
@@ -21,19 +21,9 @@ export function parseFrontmatter(content) {
|
|
|
21
21
|
const FENCE = /^(\s*)```/;
|
|
22
22
|
const DISPATCH_HINT = /dispatch|despach|subagent_type|Template do prompt/i;
|
|
23
23
|
const PERSONA_REF = /morph-(\{id\}|\{task\.agent\}|[a-z][a-z-]*[a-z])/g;
|
|
24
|
-
/** Words after `morph-` that are skills or CLI verbs, never personas. */
|
|
25
|
-
/** Claude Code's own sub-agents: dispatched by skills, but not personas of agents.json. */
|
|
26
24
|
const BUILTIN_AGENT = /`(Explore|general-purpose|Plan)`/g;
|
|
27
25
|
const NOT_PERSONA = /^(spec|proposal|brainstorming|stack-scan|uiux|plan|implement|apply|review|eval|hotfix|standards|media|task)$/;
|
|
28
26
|
|
|
29
|
-
/**
|
|
30
|
-
* The prompts a skill tells the orchestrator to send to a sub-agent: fenced
|
|
31
|
-
* blocks that are either written as a dispatch (`dispatch Agent (…)`) or sit
|
|
32
|
-
* right under a line about dispatching. Each carries the personas it names —
|
|
33
|
-
* `generic` when the template is `morph-{id}` (any implementer).
|
|
34
|
-
*
|
|
35
|
-
* @returns {Array<{ index: number, personas: string[], generic: boolean, builtin: string[], context: string, text: string }>}
|
|
36
|
-
*/
|
|
37
27
|
export function extractDispatchPrompts(content) {
|
|
38
28
|
const lines = content.split(/\r?\n/);
|
|
39
29
|
const out = [];
|
|
@@ -45,7 +35,7 @@ export function extractDispatchPrompts(content) {
|
|
|
45
35
|
const body = lines.slice(i + 1, j);
|
|
46
36
|
const text = body.map((l) => l.slice(Math.min(open[1].length, l.length - l.trimStart().length))).join('\n');
|
|
47
37
|
// Only the sentence that introduces the block counts: a paragraph further up
|
|
48
|
-
// that mentions a dispatch
|
|
38
|
+
// that mentions a dispatch is not this block's.
|
|
49
39
|
let k = i - 1;
|
|
50
40
|
while (k >= 0 && lines[k].trim() === '') k--;
|
|
51
41
|
const before = k >= 0 && i - k <= 2 ? lines[k] : '';
|
|
@@ -10,12 +10,8 @@ export function scanStandards(frameworkDir, agents) {
|
|
|
10
10
|
const refMap = {};
|
|
11
11
|
for (const agent of agents) {
|
|
12
12
|
for (const std of personaStandardIds(agent)) {
|
|
13
|
-
// agents.json stores standards as
|
|
14
|
-
//
|
|
15
|
-
// [undefined] and the lookup below missed all 60 — the Standards graph
|
|
16
|
-
// drew standards with no agent edge at all. The object form is the v4
|
|
17
|
-
// shape (`{ id, scope, priority }`); still accepted so a mixed or
|
|
18
|
-
// restored agents.json does not silently lose edges again.
|
|
13
|
+
// agents.json stores standards as id strings; the `{ id }` object form is
|
|
14
|
+
// still accepted so a mixed agents.json does not lose edges.
|
|
19
15
|
const id = typeof std === 'string' ? std : std?.id;
|
|
20
16
|
if (!id) continue;
|
|
21
17
|
if (!refMap[id]) refMap[id] = [];
|
|
@@ -21,14 +21,6 @@ function readBody(req) {
|
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/**
|
|
25
|
-
* Two data paths:
|
|
26
|
-
* - `virtual:morph-data` — the framework's knowledge (agents, phases, skills,
|
|
27
|
-
* standards, rules, hooks, docs). Static for the life of the server.
|
|
28
|
-
* - `/api/morph/*` — the portfolio (`/projects`) and each project's features
|
|
29
|
-
* and activity (`/p/:id/…`), read on demand and pushed as `morph:changed`
|
|
30
|
-
* over the HMR socket when a project's `.morph/` changes.
|
|
31
|
-
*/
|
|
32
24
|
export default function morphDataPlugin(frameworkDir, projectDir) {
|
|
33
25
|
return {
|
|
34
26
|
name: 'vite-plugin-morph-data',
|
|
@@ -43,11 +35,8 @@ export default function morphDataPlugin(frameworkDir, projectDir) {
|
|
|
43
35
|
const portfolio = createPortfolio({ launchDir: projectDir });
|
|
44
36
|
const push = (data) => server.ws.send({ type: 'custom', event: 'morph:changed', data });
|
|
45
37
|
|
|
46
|
-
// Vite reloads
|
|
47
|
-
//
|
|
48
|
-
// reload). A project's .morph/ carries .html design sources, and an agent
|
|
49
|
-
// writing one reloaded the dashboard under the person reading it. The
|
|
50
|
-
// dashboard's own code never lives under .morph/.
|
|
38
|
+
// Vite 6 full-reloads any changed .html it watches even when handleHotUpdate
|
|
39
|
+
// returns [], and a project's .morph/ carries .html design sources.
|
|
51
40
|
const clientHot = server.environments?.client?.hot;
|
|
52
41
|
if (clientHot?.send) {
|
|
53
42
|
const send = clientHot.send.bind(clientHot);
|
|
@@ -64,10 +53,8 @@ export default function morphDataPlugin(frameworkDir, projectDir) {
|
|
|
64
53
|
if (fresh.length) server.watcher.add(fresh);
|
|
65
54
|
}
|
|
66
55
|
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// event loop for seconds, so each step waits WARM_GAP_MS first: the reads
|
|
70
|
-
// a push (or an add) just caused are served before the next block.
|
|
56
|
+
// One project at a time, WARM_GAP_MS apart: a read blocks the event loop for
|
|
57
|
+
// seconds, and the reads a push just caused are served first.
|
|
71
58
|
let warming = false;
|
|
72
59
|
function warmSoon() {
|
|
73
60
|
if (warming) return;
|
|
@@ -88,7 +75,6 @@ export default function morphDataPlugin(frameworkDir, projectDir) {
|
|
|
88
75
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
89
76
|
res.setHeader('Cache-Control', 'no-store');
|
|
90
77
|
res.end(JSON.stringify(out));
|
|
91
|
-
// The portfolio's list changed: watch and read any project just added.
|
|
92
78
|
if (req.method !== 'GET' && status === 200) { watchNew(); push({ scope: 'projects' }); warmSoon(); }
|
|
93
79
|
});
|
|
94
80
|
|
|
@@ -98,7 +84,7 @@ export default function morphDataPlugin(frameworkDir, projectDir) {
|
|
|
98
84
|
rescan.unref?.();
|
|
99
85
|
|
|
100
86
|
const timers = new Map();
|
|
101
|
-
const changed = new Map();
|
|
87
|
+
const changed = new Map();
|
|
102
88
|
const onFsEvent = (event, path) => {
|
|
103
89
|
const scope = classifyChange(path);
|
|
104
90
|
if (!scope) return;
|
package/src/lib/ai/ai-checks.js
CHANGED
|
@@ -1,26 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Contrato de uma checagem:
|
|
5
|
-
*
|
|
6
|
-
* { id, name, status: 'ok'|'warn'|'error'|'skip', reason?, findings: [], unverified?: [] }
|
|
7
|
-
*
|
|
8
|
-
* Duas regras que este módulo existe para tornar mecânicas:
|
|
9
|
-
*
|
|
10
|
-
* 1. **`status: 'skip'` sai com `reason` obrigatória.** "Não verificado" sem
|
|
11
|
-
* motivo é ruído — quem lê não consegue decidir se falta insumo ou se falta
|
|
12
|
-
* checagem. Um `skip` sem razão é bug deste arquivo, e há canário.
|
|
13
|
-
* 2. **`null` de uma função de checagem é DESCONHECIDO, nunca "não achei
|
|
14
|
-
* nada".** `[]` é "perguntei e não achei" e vira `ok`. É a doutrina de
|
|
15
|
-
* `pr-index.js`, já viva no `doctor`.
|
|
16
|
-
*
|
|
17
|
-
* E uma proibição: **nada alcançável daqui escreve.** Os candidatos óbvios ao
|
|
18
|
-
* erro — `scripts/generate-ai-pin-props.js` e
|
|
19
|
-
* `scripts/generate-standards-registry.js` — escrevem por padrão e NÃO entram
|
|
20
|
-
* no grafo de imports deste módulo. É a lição do T10 da #90, onde a auditoria
|
|
21
|
-
* ia ALOCAR um bloco de porta ao auditar, inventando o estado que reportava.
|
|
22
|
-
* O canário assere a lista de chamadas feitas, não só o retorno.
|
|
23
|
-
*/
|
|
1
|
+
// `skip` sai com `reason` obrigatória, e `null` de uma checagem é DESCONHECIDO, nunca "não achei".
|
|
2
|
+
// Nada alcançável daqui escreve: os geradores de `scripts/` ficam fora do grafo de imports.
|
|
24
3
|
|
|
25
4
|
import { scanAiPackageRefs } from './ai-project-scan.js';
|
|
26
5
|
import { resolveAiPin, auditPinDrift } from './pin-drift.js';
|
|
@@ -30,7 +9,6 @@ import { auditRenamedApi, auditPromptSource, renamedApiGate } from './code-audit
|
|
|
30
9
|
/** Os cinco ids, na ordem em que o relatório os imprime. */
|
|
31
10
|
export const AI_CHECK_IDS = ['pin-drift', 'removed-api', 'pricing-missing', 'api-per-alias', 'prompt-source'];
|
|
32
11
|
|
|
33
|
-
/** Nome legível de cada checagem. */
|
|
34
12
|
export const AI_CHECK_NAMES = {
|
|
35
13
|
'pin-drift': 'pin × projeto (versões de pacote de IA)',
|
|
36
14
|
'removed-api': 'API renomeada antes do 1.0 GA',
|
|
@@ -42,7 +20,6 @@ export const AI_CHECK_NAMES = {
|
|
|
42
20
|
/** O pacote cuja versão gateia a checagem de API renomeada. */
|
|
43
21
|
export const MAF_PACKAGE = 'Microsoft.Agents.AI';
|
|
44
22
|
|
|
45
|
-
/** As dependências reais. Substituíveis inteiras ou em parte, por canário. */
|
|
46
23
|
export function defaultAiDeps() {
|
|
47
24
|
return {
|
|
48
25
|
scanAiPackageRefs,
|
|
@@ -75,19 +52,8 @@ function skipAll(reason) {
|
|
|
75
52
|
return AI_CHECK_IDS.map((id) => skip(id, reason));
|
|
76
53
|
}
|
|
77
54
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
*
|
|
81
|
-
* As três condições de silêncio total acontecem ANTES de qualquer leitura do
|
|
82
|
-
* pin — e é essa ordem, não o retorno, que o canário assere: sem `.csproj`
|
|
83
|
-
* alcançável, `resolveAiPin` não é sequer chamada.
|
|
84
|
-
*
|
|
85
|
-
* @param {string} targetPath - raiz do projeto
|
|
86
|
-
* @param {object} [options]
|
|
87
|
-
* @param {object|null} [options.config] - `.morph/config/config.json` já lido
|
|
88
|
-
* @param {object} [options.deps] - substituição parcial de `defaultAiDeps()`
|
|
89
|
-
* @returns {Array<{id: string, name: string, status: string, reason?: string, findings: Array}>}
|
|
90
|
-
*/
|
|
55
|
+
// As condições de silêncio total acontecem ANTES de ler o pin: sem `.csproj` alcançável,
|
|
56
|
+
// `resolveAiPin` nem é chamada.
|
|
91
57
|
export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
|
|
92
58
|
const d = { ...defaultAiDeps(), ...deps };
|
|
93
59
|
|
|
@@ -114,8 +80,7 @@ export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
|
|
|
114
80
|
const unverified = scan.unreadable.slice();
|
|
115
81
|
const checks = [];
|
|
116
82
|
|
|
117
|
-
//
|
|
118
|
-
// Condição 2: sem pin resolvível, a checagem que depende dele sai skip.
|
|
83
|
+
// Sem pin resolvível, a checagem que depende dele sai skip.
|
|
119
84
|
const pin = d.resolveAiPin(targetPath);
|
|
120
85
|
if (!pin) {
|
|
121
86
|
checks.push(skip('pin-drift', 'nenhum `ai-pin.json` resolvível (nem em `.morph/framework/`, nem '
|
|
@@ -127,7 +92,6 @@ export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
|
|
|
127
92
|
: resolved('pin-drift', findings, { unverified }));
|
|
128
93
|
}
|
|
129
94
|
|
|
130
|
-
// ── removed-api ──────────────────────────────────────────────────────────
|
|
131
95
|
const maf = scan.packages.find((p) => p.name === MAF_PACKAGE) ?? null;
|
|
132
96
|
const gate = d.renamedApiGate(maf);
|
|
133
97
|
if (gate !== null) {
|
|
@@ -139,7 +103,6 @@ export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
|
|
|
139
103
|
: resolved('removed-api', findings));
|
|
140
104
|
}
|
|
141
105
|
|
|
142
|
-
// ── pricing-missing e api-per-alias ──────────────────────────────────────
|
|
143
106
|
const registry = d.locateAiRegistry(targetPath, config);
|
|
144
107
|
const noRegistry = 'nenhum registry de modelos encontrado — nem `project.ai.modelRegistry` no '
|
|
145
108
|
+ 'config, nem um `.json` com `defaultAlias` + mapa de aliases, nem uma seção `Pricing` num '
|
|
@@ -155,7 +118,6 @@ export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
|
|
|
155
118
|
: noRegistry)
|
|
156
119
|
: resolved('api-per-alias', perAlias));
|
|
157
120
|
|
|
158
|
-
// ── prompt-source ────────────────────────────────────────────────────────
|
|
159
121
|
const prompts = d.auditPromptSource(targetPath);
|
|
160
122
|
checks.push(prompts === null
|
|
161
123
|
? skip('prompt-source', 'nenhum `.cs` alcançável a partir desta raiz')
|
|
@@ -164,19 +126,12 @@ export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
|
|
|
164
126
|
return checks;
|
|
165
127
|
}
|
|
166
128
|
|
|
167
|
-
/**
|
|
168
|
-
* Código de saída do `doctor --ai`: 1 quando há ao menos um `error`, 0 caso
|
|
169
|
-
* contrário. `warn` e `skip` NUNCA mudam o código de saída — só `pin-drift`
|
|
170
|
-
* produz `error`, e só na faixa flutuante, a única classe com prejuízo medido.
|
|
171
|
-
*
|
|
172
|
-
* @param {Array<{status: string}>} checks
|
|
173
|
-
* @returns {0|1}
|
|
174
|
-
*/
|
|
129
|
+
/** `warn` e `skip` nunca mudam o código de saída: só `pin-drift` produz `error`, na faixa flutuante. */
|
|
175
130
|
export function aiExitCode(checks) {
|
|
176
131
|
return checks.some((c) => c.status === 'error') ? 1 : 0;
|
|
177
132
|
}
|
|
178
133
|
|
|
179
|
-
/** true quando
|
|
134
|
+
/** true quando nenhuma checagem teve insumo: o relatório sai em uma linha. */
|
|
180
135
|
export function allSkipped(checks) {
|
|
181
136
|
return checks.length > 0 && checks.every((c) => c.status === 'skip');
|
|
182
137
|
}
|