@polymorphism-tech/morph-spec 8.46.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +53 -44
- package/README.md +3 -4
- package/bin/morph-spec.js +38 -55
- package/bin/validate.js +5 -134
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +2 -2
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +75 -18
- package/framework/commands/create-umbraco-lp.md +2 -0
- package/framework/commands/migrate-lp-to-umbraco.md +2 -0
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +2 -2
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +17 -203
- package/framework/commands/morph-media.md +3 -0
- package/framework/commands/morph-preflight.md +7 -31
- package/framework/commands/morph-proposal.md +2 -50
- package/framework/commands/morph-status.md +3 -3
- package/framework/commands/morph-troubleshoot.md +2 -14
- package/framework/docs/CLI.md +585 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/comment-guard.js +1 -1
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
- package/framework/hooks/claude-code/core/gate-guard.js +2 -11
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
- package/framework/hooks/claude-code/core/state-sync.js +17 -19
- package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
- package/framework/hooks/claude-code/core/validate-completion.js +33 -97
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
- package/framework/hooks/claude-code/core/worktree-context.js +5 -4
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +1 -1
- package/framework/hooks/shared/build-check.js +10 -6
- package/framework/hooks/shared/command-tokens.js +169 -0
- package/framework/hooks/shared/comment-rules.js +1 -1
- package/framework/hooks/shared/comment-scanner.js +10 -23
- package/framework/hooks/shared/comment-stripper.js +1 -1
- package/framework/hooks/shared/e2e-check.js +67 -31
- package/framework/hooks/shared/errata.js +72 -0
- package/framework/hooks/shared/feature-locator.js +1 -1
- package/framework/hooks/shared/frontend-locations.js +6 -0
- package/framework/hooks/shared/gate-question.js +18 -0
- package/framework/hooks/shared/phase-utils.js +15 -32
- package/framework/hooks/shared/slice-progress.js +118 -0
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +61 -87
- package/framework/hooks/shared/state-writer.js +3 -3
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/ai-agents-standards.md +18 -16
- package/framework/rules/asaas-standards.md +3 -1
- package/framework/rules/csharp-standards.md +10 -8
- package/framework/rules/data-standards.md +5 -5
- package/framework/rules/dotnet-templates.md +1 -1
- package/framework/rules/frontend-standards.md +4 -2
- package/framework/rules/gsap-standards.md +10 -5
- package/framework/rules/hangfire-standards.md +3 -1
- package/framework/rules/infrastructure-standards.md +4 -2
- package/framework/rules/neon-auth-standards.md +3 -1
- package/framework/rules/neon-standards.md +6 -4
- package/framework/rules/nextjs-standards.md +13 -11
- package/framework/rules/nextjs-templates.md +1 -1
- package/framework/rules/resend-standards.md +3 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -52
- package/framework/skills/README.md +4 -5
- package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
- package/framework/skills/core/morph-apply/SKILL.md +150 -223
- package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
- package/framework/skills/core/morph-machine/SKILL.md +4 -4
- package/framework/skills/core/morph-media/SKILL.md +3 -3
- package/framework/skills/core/morph-plan/SKILL.md +138 -300
- package/framework/skills/core/morph-review/SKILL.md +112 -137
- package/framework/skills/core/morph-standards/SKILL.md +29 -41
- package/framework/skills/core/morph-uiux/SKILL.md +78 -153
- package/framework/standards/STANDARDS.json +90 -1219
- package/framework/standards/STANDARDS.meta.json +1305 -0
- package/framework/standards/ai-agents/evals-with-cache.md +2 -3
- package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
- package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
- package/framework/standards/ai-agents/prompt-sources.md +6 -4
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/structured-output.md +1 -1
- package/framework/standards/ai-agents/sweet-spot.md +1 -1
- package/framework/standards/architecture/code-comments.md +8 -8
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
- package/framework/standards/backend/api/validation.md +135 -47
- package/framework/standards/backend/database/postgresql/database.md +11 -7
- package/framework/standards/backend/database/vector-search-rag.md +207 -324
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
- package/framework/standards/backend/dotnet/result-pattern.md +40 -6
- package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
- package/framework/standards/backend/dotnet/scrutor.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
- package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
- package/framework/standards/data/nosql/cache/redis.md +120 -44
- package/framework/standards/data/nosql/cosmos-db.md +114 -71
- package/framework/standards/frontend/design-system/animations.md +1 -1
- package/framework/standards/frontend/design-system/naming.md +1 -1
- package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
- package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
- package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
- package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +6 -170
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
- package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
- package/framework/templates/infrastructure/github/README.md +2 -1
- package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +2 -2
- package/src/commands/observability/cost.js +18 -60
- package/src/commands/observability/telemetry.js +3 -5
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +9 -9
- package/src/commands/project/fleet.js +4 -4
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +6 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +79 -25
- package/src/commands/project/status.js +29 -69
- package/src/commands/project/update.js +33 -19
- package/src/commands/project/worktree.js +1 -1
- package/src/commands/state/advance.js +62 -46
- package/src/commands/state/approve.js +95 -138
- package/src/commands/state/archive.js +20 -84
- package/src/commands/state/auto-approval.js +68 -0
- package/src/commands/state/create.js +28 -1
- package/src/commands/state/delete.js +1 -1
- package/src/commands/state/finish.js +11 -23
- package/src/commands/state/gate-check.js +86 -95
- package/src/commands/state/gate-decision.js +6 -4
- package/src/commands/state/handoff.js +1 -1
- package/src/commands/state/list.js +9 -9
- package/src/commands/state/report-check.js +157 -311
- package/src/commands/state/retype.js +9 -3
- package/src/commands/validation/mutate.js +77 -14
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +60 -135
- package/src/commands/validation/verify.js +41 -82
- package/src/core/paths/output-schema.js +20 -67
- package/src/core/state/gate-order.js +5 -6
- package/src/core/state/lifecycle-audit.js +2 -2
- package/src/core/state/state-manager.js +17 -98
- package/src/core/templates/template-registry.js +2 -2
- package/src/core/templates/template-seed.js +22 -0
- package/src/core/work-types.js +10 -10
- package/src/dashboard/app/App.jsx +0 -2
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
- package/src/dashboard/app/components/CommandPalette.jsx +7 -8
- package/src/dashboard/app/components/EntityLink.jsx +1 -2
- package/src/dashboard/app/components/Markdown.jsx +1 -1
- package/src/dashboard/app/components/Sidebar.jsx +1 -2
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
- package/src/dashboard/app/lib/phases.js +2 -4
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
- package/src/dashboard/app/pages/Features.jsx +9 -9
- package/src/dashboard/app/pages/Guide.jsx +2 -2
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
- package/src/dashboard/app/pages/RulesMap.jsx +3 -3
- package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
- package/src/dashboard/app/pages/Standards.jsx +1 -1
- package/src/dashboard/scanner/activity-scanner.js +6 -16
- package/src/dashboard/scanner/agents-scanner.js +1 -1
- package/src/dashboard/scanner/docs-scanner.js +5 -23
- package/src/dashboard/scanner/features-scanner.js +95 -60
- package/src/dashboard/scanner/index.js +0 -2
- package/src/dashboard/scanner/project-api.js +1 -1
- package/src/dashboard/scanner/rules-scanner.js +15 -10
- package/src/dashboard/scanner/skills-scanner.js +2 -3
- package/src/dashboard/scanner/standards-scanner.js +2 -1
- package/src/lib/ci/ci-verify.js +17 -1
- package/src/lib/ci/github-ci.js +1 -1
- package/src/lib/detectors/index.js +3 -8
- package/src/lib/generators/handoff-generator.js +34 -65
- package/src/lib/generators/settings-generator.js +11 -89
- package/src/lib/git/evidence-staleness.js +1 -1
- package/src/lib/git/tree-fingerprint.js +19 -7
- package/src/lib/git/tree-state.js +2 -2
- package/src/lib/heavy/cleanup.js +1 -5
- package/src/lib/installers/memory-installer.js +6 -8
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +46 -9
- package/src/lib/plan/artifact-vocabulary.js +60 -0
- package/src/lib/plan/checks.js +234 -0
- package/src/lib/plan/plan-parser.js +209 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/review-reports.js +222 -0
- package/src/lib/risk-detector.js +134 -135
- package/src/lib/specs/adr-index.js +50 -69
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +44 -0
- package/src/lib/specs/gfm-table.js +17 -0
- package/src/lib/specs/old-format.js +68 -0
- package/src/lib/specs/revocations.js +37 -95
- package/src/lib/specs/screen-checklist.js +18 -17
- package/src/lib/stack/dotnet-build-ignores.js +47 -0
- package/src/lib/stack/stack-profile.js +1 -1
- package/src/lib/stack/stack-scan.js +267 -0
- package/src/lib/standards/registry.js +43 -0
- package/src/lib/standards/sections.js +58 -0
- package/src/lib/tasks/eval-node.js +4 -12
- package/src/lib/tasks/test-nodes.js +1 -74
- package/src/lib/tasks/test-runner.js +75 -117
- package/src/lib/telemetry/metrics.js +8 -69
- package/src/lib/telemetry/telemetry-reader.js +5 -118
- package/src/lib/telemetry/verify-event.js +5 -7
- package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
- package/src/lib/validators/reuse/reuse-validator.js +24 -48
- package/src/lib/validators/reuse/symbol-scanner.js +2 -4
- package/src/lib/validators/traceability-check.js +85 -129
- package/src/lib/validators/validation-runner.js +8 -88
- package/src/lib/validators/verify-runner.js +134 -301
- package/src/lib/worktree/docker-audit.js +1 -2
- package/src/lib/worktree/fleet-scanner.js +25 -38
- package/src/scripts/setup-infra.js +42 -22
- package/src/utils/agents-installer.js +92 -13
- package/src/utils/claude-settings-manager.js +0 -35
- package/src/utils/file-copier.js +34 -6
- package/src/utils/hooks-installer.js +16 -9
- package/src/utils/skills-installer.js +1 -2
- package/framework/commands/commit.md +0 -166
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rubrics/backend.json +0 -41
- package/framework/rubrics/default.json +0 -36
- package/framework/rubrics/frontend.json +0 -36
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/project/validate.js +0 -97
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/dashboard/app/pages/Rubrics.jsx +0 -97
- package/src/dashboard/scanner/rubrics-scanner.js +0 -12
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/evaluator-report.js +0 -91
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- package/src/lib/validators/architecture/architecture-validator.js +0 -270
- package/src/lib/validators/architecture/index.js +0 -7
- package/src/lib/validators/css/css-validator.js +0 -340
- package/src/lib/validators/css/index.js +0 -7
- package/src/lib/validators/design-system/design-system-validator.js +0 -237
- package/src/lib/validators/design-system/index.js +0 -7
- package/src/lib/validators/score-aggregator.js +0 -244
- package/src/lib/validators/ui/index.js +0 -7
- package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `morph-spec cost [feature] [--by
|
|
2
|
+
* `morph-spec cost [feature] [--by phase] [--all-trees] [--metrics [--top n]] [--json]`
|
|
3
3
|
*
|
|
4
4
|
* A cost-facing view over the same harness event stream `telemetry` reads
|
|
5
|
-
* (.morph/logs/events.jsonl): how
|
|
6
|
-
* measured tasks (TaskStart→TaskEnd), how many context bytes were
|
|
7
|
-
* re-ingested (grouped by kind: rubric/standard/skill/agents/plan-artifact/
|
|
5
|
+
* (.morph/logs/events.jsonl): how many context bytes were re-ingested (grouped by kind: rubric/standard/skill/agents/plan-artifact/
|
|
8
6
|
* code), and how many sub-agent dispatches happened (with total prompt
|
|
9
7
|
* chars). When the stream has `SessionUsage` events (real, hook-observed
|
|
10
8
|
* token counts), those are summed and shown too — see the sessionUsage
|
|
@@ -22,7 +20,7 @@
|
|
|
22
20
|
*
|
|
23
21
|
* `--metrics` measures the loop instead: turns, tool calls by orchestrator and
|
|
24
22
|
* sub-agent, time per command class, identical reruns and why, rereads, how
|
|
25
|
-
* many
|
|
23
|
+
* many verifies each feature ran, git calls and whether
|
|
26
24
|
* the reuse validator ran in root and worktree (see lib/telemetry/metrics.js).
|
|
27
25
|
*
|
|
28
26
|
* Read-only, always exits 0 on a normal (even empty) run — fail-open, an
|
|
@@ -37,7 +35,7 @@ import { computeMetrics } from '../../lib/telemetry/metrics.js';
|
|
|
37
35
|
const BYTES_CAVEAT =
|
|
38
36
|
'Bytes re-ingeridos são um PROXY determinístico de custo (o que as ferramentas leram do disco) — NÃO são tokens reais. Hooks não enxergam o uso real de tokens do modelo.';
|
|
39
37
|
|
|
40
|
-
const BY_MODES = ['
|
|
38
|
+
const BY_MODES = ['phase'];
|
|
41
39
|
|
|
42
40
|
function fmtBytes(n) {
|
|
43
41
|
if (!n) return '0 B';
|
|
@@ -65,21 +63,6 @@ function printBytesByKind(bytesByKind) {
|
|
|
65
63
|
}
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
function printTasks(tasks) {
|
|
69
|
-
if (!tasks.length) {
|
|
70
|
-
console.log(chalk.gray('\nNenhuma task com TaskStart/TaskEnd capturada.'));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
for (const t of tasks) {
|
|
74
|
-
const status = t.open ? chalk.yellow('[em aberto]') : (t.status || '-');
|
|
75
|
-
console.log('\n' + chalk.white.bold(`Task ${t.task}`) + chalk.gray(` · ${t.feature ?? '(sem feature)'}`) + ` · ${status}`);
|
|
76
|
-
console.log(` duração: ${fmtDuration(t.durationSeconds)}`);
|
|
77
|
-
console.log(` dispatches: ${t.dispatches.count} (${t.dispatches.promptChars} chars de prompt)`);
|
|
78
|
-
console.log(' bytes re-ingeridos por kind:');
|
|
79
|
-
printBytesByKind(t.bytesByKind);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
66
|
function printPhases(phases) {
|
|
84
67
|
if (!phases.length) {
|
|
85
68
|
console.log(chalk.gray('\nNenhum byte de Read com phase capturado.'));
|
|
@@ -118,8 +101,7 @@ function printSessionUsage(summary) {
|
|
|
118
101
|
}
|
|
119
102
|
|
|
120
103
|
function printSummary(summary) {
|
|
121
|
-
console.log(`\n
|
|
122
|
-
console.log(` dispatches: ${summary.dispatches.count} (${summary.dispatches.promptChars} chars de prompt)`);
|
|
104
|
+
console.log(`\n dispatches: ${summary.dispatches.count} (${summary.dispatches.promptChars} chars de prompt)`);
|
|
123
105
|
|
|
124
106
|
console.log('\n' + chalk.white.bold('Bytes re-ingeridos por kind') + ` (${fmtBytes(summary.bytesTotal)} total)`);
|
|
125
107
|
printBytesByKind(summary.bytesByKind);
|
|
@@ -140,7 +122,7 @@ function split(counts) {
|
|
|
140
122
|
return `${counts.total} (orq ${counts.orchestrator} · sub ${counts.subagent}${counts.unknown ? ` · sem agente ${counts.unknown}` : ''})`;
|
|
141
123
|
}
|
|
142
124
|
|
|
143
|
-
function printMetricsBody(m
|
|
125
|
+
function printMetricsBody(m) {
|
|
144
126
|
const legacyShare = m.window.events ? m.coverage.legacy / m.window.events : 0;
|
|
145
127
|
console.log(chalk.gray(`janela: ${m.window.from ?? '-'} → ${m.window.to ?? '-'} · ${m.window.events} eventos`
|
|
146
128
|
+ ` · ${pct(1 - legacyShare)} no esquema v2`));
|
|
@@ -149,16 +131,8 @@ function printMetricsBody(m, { by }) {
|
|
|
149
131
|
}
|
|
150
132
|
|
|
151
133
|
console.log('\n' + chalk.white.bold('Turnos e chamadas'));
|
|
152
|
-
console.log(` turnos: ${m.turns ? split(m.turns) : 'desconhecido (nenhum PostToolBatch na janela)'}`
|
|
153
|
-
+ (m.turnsPerTaskMedian !== null ? chalk.gray(` · mediana por task ${m.turnsPerTaskMedian}`) : ''));
|
|
134
|
+
console.log(` turnos: ${m.turns ? split(m.turns) : 'desconhecido (nenhum PostToolBatch na janela)'}`);
|
|
154
135
|
console.log(` chamadas de ferramenta: ${split(m.toolCalls)}`);
|
|
155
|
-
const perTask = m.perTaskByFeature;
|
|
156
|
-
if (perTask.toolCalls !== null) {
|
|
157
|
-
console.log(` por task (total da feature ÷ tasks dela, mediana): ${perTask.toolCalls} chamadas`
|
|
158
|
-
+ (perTask.turns !== null ? ` · ${perTask.turns} turnos` : '') + ` · ${perTask.git} git status+diff`
|
|
159
|
-
+ chalk.gray(` · as janelas TaskStart→TaskEnd têm ${pct(m.taskWindowCoverage)} das chamadas`));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
136
|
console.log('\n' + chalk.white.bold('Tempo por classe de comando'));
|
|
163
137
|
for (const [klass, t] of Object.entries(m.timeByClass).sort((a, b) => b[1].measuredMs - a[1].measuredMs)) {
|
|
164
138
|
const unmeasured = t.unmeasured ? chalk.gray(` (${t.unmeasured} sem duração)`) : '';
|
|
@@ -183,9 +157,9 @@ function printMetricsBody(m, { by }) {
|
|
|
183
157
|
}
|
|
184
158
|
|
|
185
159
|
console.log('\n' + chalk.white.bold('verify, git e reuso'));
|
|
186
|
-
const perFeature = Object.entries(m.verify.byFeature).sort((a, b) => b[1]
|
|
187
|
-
console.log(` verify
|
|
188
|
-
+ (perFeature.length ? chalk.gray(` (${perFeature.slice(0, 5).map(([f,
|
|
160
|
+
const perFeature = Object.entries(m.verify.byFeature).sort((a, b) => b[1] - a[1]);
|
|
161
|
+
console.log(` verify por feature: mediana ${m.verify.median ?? '-'}`
|
|
162
|
+
+ (perFeature.length ? chalk.gray(` (${perFeature.slice(0, 5).map(([f, n]) => `${f} ${n}`).join(', ')})`) : ''));
|
|
189
163
|
const repeats = Object.entries(m.verify.unchangedRepeats);
|
|
190
164
|
if (repeats.length) {
|
|
191
165
|
console.log(` verify de feature sobre a mesma árvore do anterior: ${repeats.reduce((n, [, v]) => n + v, 0)}`
|
|
@@ -196,24 +170,12 @@ function printMetricsBody(m, { by }) {
|
|
|
196
170
|
console.log(` suíte .NET inteira que recompila (sem --no-build): ${m.dotnetFullSuite.withBuild}/${m.dotnetFullSuite.runs}`
|
|
197
171
|
+ chalk.gray(` (${phases})`));
|
|
198
172
|
}
|
|
199
|
-
console.log(` git status / diff
|
|
200
|
-
+ chalk.gray(` · total ${m.git.status} / ${m.git.diff}`));
|
|
173
|
+
console.log(` git status / diff: ${m.git.status} / ${m.git.diff}`);
|
|
201
174
|
const trees = Object.entries(m.reuse.byTree);
|
|
202
175
|
console.log(` reuse-validator: ${trees.length
|
|
203
176
|
? trees.map(([tree, s]) => `${tree} rodou ${s.ran}, pulou ${s.skipped}`).join(' · ')
|
|
204
177
|
: 'nenhum evento Verify na janela'}`);
|
|
205
178
|
for (const [reason, n] of Object.entries(m.reuse.skipReasons)) console.log(chalk.gray(` pulou ${n}×: ${reason}`));
|
|
206
|
-
|
|
207
|
-
if (by === 'task') {
|
|
208
|
-
for (const t of m.tasks) {
|
|
209
|
-
const status = t.open ? chalk.yellow('[em aberto]') : (t.status || '-');
|
|
210
|
-
console.log('\n' + chalk.white.bold(`Task ${t.task}`) + chalk.gray(` · ${t.feature ?? '(sem feature)'}`) + ` · ${status}`);
|
|
211
|
-
console.log(` turnos ${t.turns ? split(t.turns) : '-'} · chamadas ${split(t.toolCalls)}`);
|
|
212
|
-
const time = Object.entries(t.timeByClass).map(([k, v]) => `${k} ${fmtDuration(v.measuredMs / 1000)}`).join(' · ');
|
|
213
|
-
if (time) console.log(` tempo: ${time}`);
|
|
214
|
-
console.log(` rerun ${pct(t.reruns.rate)} (${t.reruns.reruns}/${t.reruns.runs}) · releitura ${pct(t.rereads.rate)} · git status/diff ${t.git.status}/${t.git.diff}`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
179
|
}
|
|
218
180
|
|
|
219
181
|
/**
|
|
@@ -237,17 +199,17 @@ function printMetrics(feature, options, cwd) {
|
|
|
237
199
|
}
|
|
238
200
|
const scope = feature ? chalk.cyan(` · feature ${feature}`) : '';
|
|
239
201
|
console.log(chalk.white.bold(`Medições do loop${options.allTrees ? ' — todas as árvores + finish-backup' : ''}`) + scope);
|
|
240
|
-
printMetricsBody(metrics
|
|
202
|
+
printMetricsBody(metrics);
|
|
241
203
|
}
|
|
242
204
|
|
|
243
205
|
/**
|
|
244
206
|
* `--all-trees` rendering: a short line per tree (so a fleet operator can
|
|
245
207
|
* spot which tree is expensive) followed by the COMBINED aggregate — full
|
|
246
|
-
* detail (
|
|
208
|
+
* detail (the phase breakdown honors `--by` here too, same as the
|
|
247
209
|
* single-tree path; a typeable flag combination is never a silent no-op).
|
|
248
210
|
*
|
|
249
211
|
* @param {string} [feature]
|
|
250
|
-
* @param {{ json?: boolean, by?: '
|
|
212
|
+
* @param {{ json?: boolean, by?: 'phase' }} options
|
|
251
213
|
* @param {string} cwd
|
|
252
214
|
*/
|
|
253
215
|
function printAllTrees(feature, options, cwd) {
|
|
@@ -277,7 +239,7 @@ function printAllTrees(feature, options, cwd) {
|
|
|
277
239
|
|
|
278
240
|
for (const t of perTree) {
|
|
279
241
|
console.log('\n' + chalk.cyan.bold(`── Árvore: ${t.tree}`) + chalk.gray(` — ${t.path}`));
|
|
280
|
-
console.log(` ${t.total} eventos ·
|
|
242
|
+
console.log(` ${t.total} eventos · `
|
|
281
243
|
+ `bytes: ${fmtBytes(t.bytesTotal)} · dispatches: ${t.dispatches.count}`);
|
|
282
244
|
}
|
|
283
245
|
|
|
@@ -285,9 +247,7 @@ function printAllTrees(feature, options, cwd) {
|
|
|
285
247
|
if (combined.features.length) {
|
|
286
248
|
console.log(chalk.gray(`Features: ${combined.features.join(', ')}`));
|
|
287
249
|
}
|
|
288
|
-
if (options.by === '
|
|
289
|
-
printTasks(combined.tasks);
|
|
290
|
-
} else if (options.by === 'phase') {
|
|
250
|
+
if (options.by === 'phase') {
|
|
291
251
|
printPhases(combined.phases);
|
|
292
252
|
} else {
|
|
293
253
|
printSummary(combined);
|
|
@@ -296,7 +256,7 @@ function printAllTrees(feature, options, cwd) {
|
|
|
296
256
|
|
|
297
257
|
/**
|
|
298
258
|
* @param {string} [feature] - narrow to a single feature
|
|
299
|
-
* @param {{ json?: boolean, by?: '
|
|
259
|
+
* @param {{ json?: boolean, by?: 'phase', allTrees?: boolean, cwd?: string }} [options]
|
|
300
260
|
*/
|
|
301
261
|
export function costCommand(feature, options = {}) {
|
|
302
262
|
if (options.by && !BY_MODES.includes(options.by)) {
|
|
@@ -345,9 +305,7 @@ export function costCommand(feature, options = {}) {
|
|
|
345
305
|
console.log(chalk.gray(`Features: ${summary.features.join(', ')}`));
|
|
346
306
|
}
|
|
347
307
|
|
|
348
|
-
if (options.by === '
|
|
349
|
-
printTasks(summary.tasks);
|
|
350
|
-
} else if (options.by === 'phase') {
|
|
308
|
+
if (options.by === 'phase') {
|
|
351
309
|
printPhases(summary.phases);
|
|
352
310
|
} else {
|
|
353
311
|
printSummary(summary);
|
|
@@ -12,12 +12,10 @@
|
|
|
12
12
|
import chalk from 'chalk';
|
|
13
13
|
import { loadTelemetry, aggregateTelemetry } from '../../lib/telemetry/telemetry-reader.js';
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// Claude Code's rules system, not via the Read tool, so no hook ever sees them.
|
|
18
|
-
// A low count here means "few on-demand reads", NOT "standards barely used".
|
|
15
|
+
// A rule injects its standards' digests through Claude Code's rules system,
|
|
16
|
+
// not the Read tool, so no hook sees them: a low count means "few full-text reads".
|
|
19
17
|
const STANDARDS_CAVEAT =
|
|
20
|
-
'Só reads explícitos (via Read) são observáveis;
|
|
18
|
+
'Só reads explícitos (via Read) são observáveis; o resumo que uma rule injeta ao tocar arquivo casado por `paths:` não passa por tool call: não observável aqui.';
|
|
21
19
|
|
|
22
20
|
function section(title, rows, fmt) {
|
|
23
21
|
if (!rows.length) return;
|
|
@@ -134,7 +134,7 @@ export async function doctorProcsCommand(targetPath, options = {}) {
|
|
|
134
134
|
const { afterHeavyRun } = await import('../../lib/heavy/cleanup.js');
|
|
135
135
|
report.prune = report.procs.status === 'ok' ? pruneOrphans(report.procs.rows, options.pruneDeps) : [];
|
|
136
136
|
report.buildServer = afterHeavyRun({
|
|
137
|
-
projectPath: targetPath, config: readHeavyConfig(targetPath),
|
|
137
|
+
projectPath: targetPath, config: readHeavyConfig(targetPath), deps: options.cleanupDeps,
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -242,10 +242,12 @@ const FRAMEWORK_STANDARDS = [
|
|
|
242
242
|
'framework/standards/integration/mcp/mcp-tools.md'
|
|
243
243
|
];
|
|
244
244
|
|
|
245
|
-
// required personas expected in agents.json
|
|
246
|
-
//
|
|
245
|
+
// required personas expected in agents.json — the universal, stack-agnostic
|
|
246
|
+
// ones: the three review roles and scout (stage axis)
|
|
247
247
|
const REQUIRED_AGENTS = [
|
|
248
|
-
'
|
|
248
|
+
'verifier',
|
|
249
|
+
'reviewer',
|
|
250
|
+
'security',
|
|
249
251
|
'scout'
|
|
250
252
|
];
|
|
251
253
|
|
|
@@ -1289,7 +1291,7 @@ export async function doctorCommand(options = {}) {
|
|
|
1289
1291
|
// Check for missing agents based on stack tags. Empty tags (no config
|
|
1290
1292
|
// yet) is NOT a skip: personas install-all by default, so with an
|
|
1291
1293
|
// unknown stack ALL personas are expected (agentEligible semantics) —
|
|
1292
|
-
// the exact field failure where only morph-
|
|
1294
|
+
// the exact field failure where only morph-verifier landed and every
|
|
1293
1295
|
// domain dispatch fell back to general-purpose.
|
|
1294
1296
|
try {
|
|
1295
1297
|
let projectTags = [];
|
|
@@ -1509,10 +1511,8 @@ export async function doctorCommand(options = {}) {
|
|
|
1509
1511
|
// Check key templates exist (in framework package, not project copy)
|
|
1510
1512
|
const frameworkTemplatesRoot = join(import.meta.dirname, '..', '..', '..', 'framework', 'templates');
|
|
1511
1513
|
const keyTemplates = [
|
|
1512
|
-
{ name: '
|
|
1513
|
-
{ name: '
|
|
1514
|
-
{ name: 'tasks.template.json', path: join(frameworkTemplatesRoot, 'tasks.template.json') },
|
|
1515
|
-
{ name: 'decisions.md', path: join(frameworkTemplatesRoot, 'feature', 'decisions.md') }
|
|
1514
|
+
{ name: 'plan.md', path: join(frameworkTemplatesRoot, 'docs', 'plan.md') },
|
|
1515
|
+
{ name: 'ui-mockups.md', path: join(frameworkTemplatesRoot, 'docs', 'ui-mockups.md') }
|
|
1516
1516
|
];
|
|
1517
1517
|
const missingTemplates = [];
|
|
1518
1518
|
for (const template of keyTemplates) {
|
|
@@ -1560,7 +1560,7 @@ export async function doctorCommand(options = {}) {
|
|
|
1560
1560
|
}
|
|
1561
1561
|
|
|
1562
1562
|
// Per-feature detail for the lifecycle inconsistencies (item: doctor lists
|
|
1563
|
-
// what's already wrong in the repo, e.g.
|
|
1563
|
+
// what's already wrong in the repo, e.g. review approved with plan pending).
|
|
1564
1564
|
if (lifecycleIssues.length) {
|
|
1565
1565
|
logger.blank();
|
|
1566
1566
|
console.log(chalk.yellow(' Feature lifecycle inconsistencies:'));
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `morph-spec fleet [--json]` — the fleet view.
|
|
3
3
|
*
|
|
4
4
|
* One line per active feature across the primary root and every morph/*
|
|
5
|
-
* worktree: phase, pending gates,
|
|
5
|
+
* worktree: phase, pending gates, slice progress, port block, handoff
|
|
6
6
|
* freshness, last commit and dirtiness. This is the terminal surface the
|
|
7
7
|
* operator scans between sessions ("what is each instance doing, what is
|
|
8
8
|
* waiting on me") — `worktree list` remains the lean per-worktree view.
|
|
@@ -59,12 +59,12 @@ export function fleetCommand(options = {}) {
|
|
|
59
59
|
// fleet-scanner.js) is 16 chars — 1 wider than the col width `list.js`
|
|
60
60
|
// already uses (17) for the identical string. A narrower column here
|
|
61
61
|
// would misalign every column after it just for that one row.
|
|
62
|
-
col('FEATURE', 20) + col('TYPE', 9) + col('PHASE', 18) + col('
|
|
62
|
+
col('FEATURE', 20) + col('TYPE', 9) + col('PHASE', 18) + col('FATIAS', 8)
|
|
63
63
|
+ col('GATES PEND.', 20) + col('WHERE', 24) + col('REMOTO', 16) + col('CLAIM', 16)
|
|
64
64
|
+ col('PORTS', 12) + col('HANDOFF', 9) + 'ATIVIDADE',
|
|
65
65
|
));
|
|
66
66
|
for (const e of fleet.entries) {
|
|
67
|
-
const
|
|
67
|
+
const slices = e.slices ? `${e.slices.done}/${e.slices.total}` : '-';
|
|
68
68
|
const gates = e.pendingGates.length ? e.pendingGates.join(',') : '-';
|
|
69
69
|
// pr-pending has no worktree and isn't root-resident either — its branch
|
|
70
70
|
// is the only place it exists, so show that instead of the misleading
|
|
@@ -80,7 +80,7 @@ export function fleetCommand(options = {}) {
|
|
|
80
80
|
// length and append the padding by hand.
|
|
81
81
|
const claimPlain = e.claim ? `${e.claim.live ? 'sessão' : 'morta'} (${claimAge(e.claim)})` : '-';
|
|
82
82
|
console.log(
|
|
83
|
-
col(e.feature, 20) + col(e.workType || '-', 9) + col(e.phase, 18) + col(
|
|
83
|
+
col(e.feature, 20) + col(e.workType || '-', 9) + col(e.phase, 18) + col(slices, 8)
|
|
84
84
|
+ col(gates, 20) + col(where, 24) + col(remoteCell(e.remote), 16)
|
|
85
85
|
+ claimCell(e.claim) + ' '.repeat(Math.max(1, 16 - claimPlain.length))
|
|
86
86
|
+ col(ports, 12) + col(handoff, 9) + activity,
|
|
@@ -4,9 +4,9 @@ import { logger } from '../../utils/logger.js';
|
|
|
4
4
|
import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
|
|
5
5
|
import { createHeavyGate, HeavyBusyError } from '../../lib/heavy/gate.js';
|
|
6
6
|
import { describeHolder, heavyDir, heavyStatus } from '../../../framework/hooks/shared/heavy-slots.js';
|
|
7
|
+
import { resolveSpawn } from '../../../framework/hooks/shared/spawn-command.js';
|
|
7
8
|
|
|
8
9
|
const EXIT_BUSY = 3;
|
|
9
|
-
const NEEDS_CMD_SHIM = /^(npm|npx|pnpm|yarn)$/;
|
|
10
10
|
|
|
11
11
|
export function heavyStatusCommand(options = {}) {
|
|
12
12
|
const config = readHeavyConfig(process.cwd());
|
|
@@ -49,8 +49,8 @@ export function heavyRunCommand(command, options = {}) {
|
|
|
49
49
|
process.exit(EXIT_BUSY);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
const
|
|
53
|
-
const res = spawnSync(
|
|
52
|
+
const spawn = resolveSpawn(file, args);
|
|
53
|
+
const res = spawnSync(spawn.file, spawn.args, { stdio: 'inherit', shell: spawn.shell });
|
|
54
54
|
gate.release();
|
|
55
55
|
|
|
56
56
|
if (res.error) {
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
setupStitch,
|
|
29
29
|
loadMcpRegistry
|
|
30
30
|
} from '../../lib/installers/mcp-installer.js';
|
|
31
|
-
import { setupInfra, installRulesForStack } from '../../scripts/setup-infra.js';
|
|
31
|
+
import { setupInfra, installRulesForStack, installCommandsForStack } from '../../scripts/setup-infra.js';
|
|
32
32
|
import { installSkills } from '../../utils/skills-installer.js';
|
|
33
33
|
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
34
34
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
@@ -111,6 +111,10 @@ export async function initCommand(options) {
|
|
|
111
111
|
// ARE the primary root, or the caller explicitly passed --force), so it
|
|
112
112
|
// must not be redundantly re-applied inside setupInfra().
|
|
113
113
|
const infraResult = await setupInfra(targetPath, { force: true });
|
|
114
|
+
if (infraResult?.refused) {
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
return infraResult;
|
|
117
|
+
}
|
|
114
118
|
spinner.start('Continuing...');
|
|
115
119
|
|
|
116
120
|
// Derive display variables from what setupInfra installed
|
|
@@ -263,6 +267,7 @@ export async function initCommand(options) {
|
|
|
263
267
|
const rulesSrc = join(fwSrcDir, 'rules');
|
|
264
268
|
const rulesDest = join(targetPath, '.claude', 'rules');
|
|
265
269
|
installRulesForStack(rulesSrc, rulesDest, stackTags);
|
|
270
|
+
installCommandsForStack(join(fwSrcDir, 'commands'), join(targetPath, '.claude', 'commands'), stackTags);
|
|
266
271
|
|
|
267
272
|
// Skills
|
|
268
273
|
await installSkills(targetPath, { projectTags: stackTags });
|
|
@@ -4,7 +4,8 @@ import { logger } from '../../utils/logger.js';
|
|
|
4
4
|
export async function setupInfraCommand(options) {
|
|
5
5
|
const targetPath = options.path || process.cwd();
|
|
6
6
|
try {
|
|
7
|
-
await setupInfra(targetPath, { reinstall: options.reinstall ?? false });
|
|
7
|
+
const result = await setupInfra(targetPath, { reinstall: options.reinstall ?? false });
|
|
8
|
+
if (result?.refused) process.exitCode = 1;
|
|
8
9
|
} catch (err) {
|
|
9
10
|
logger.error(`setup-infra failed: ${err.message}`);
|
|
10
11
|
process.exit(1);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { getFeatureDir } from '../../core/paths/output-schema.js';
|
|
5
|
+
import { readMorphConfig } from '../../lib/tasks/test-runner.js';
|
|
6
|
+
import { notAuditedReason, scanStack, renderStackScan } from '../../lib/stack/stack-scan.js';
|
|
7
|
+
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
8
|
+
|
|
9
|
+
export const STACK_SCAN_FILE = 'stack-scan.md';
|
|
10
|
+
|
|
11
|
+
/** Scan the tree at `cwd`; with a feature, write `2-plan/stack-scan.md`. */
|
|
12
|
+
export function runStackScan(featureName, cwd = process.cwd()) {
|
|
13
|
+
const result = scanStack(cwd, readMorphConfig(cwd));
|
|
14
|
+
const markdown = renderStackScan(result, featureName ?? 'projeto');
|
|
15
|
+
if (!featureName) return { ...result, markdown, written: null, exit: 0 };
|
|
16
|
+
|
|
17
|
+
const featureDir = join(cwd, getFeatureDir(featureName));
|
|
18
|
+
if (!existsSync(featureDir)) return { ...result, markdown, written: null, exit: 2, error: 'feature-not-found' };
|
|
19
|
+
const planDir = join(featureDir, '2-plan');
|
|
20
|
+
mkdirSync(planDir, { recursive: true });
|
|
21
|
+
const written = join(planDir, STACK_SCAN_FILE);
|
|
22
|
+
writeFileSync(written, markdown, 'utf8');
|
|
23
|
+
return { ...result, markdown, written, exit: 0 };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function stackScanCommand(featureName, options = {}) {
|
|
27
|
+
if (featureName) {
|
|
28
|
+
const owner = assertOwnerTree(featureName, process.cwd());
|
|
29
|
+
if (owner.blocked) {
|
|
30
|
+
for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec stack-scan ${featureName}`)) {
|
|
31
|
+
console.error(chalk[tone](text));
|
|
32
|
+
}
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const result = runStackScan(featureName);
|
|
38
|
+
if (options.json) {
|
|
39
|
+
const { markdown, ...rest } = result;
|
|
40
|
+
console.log(JSON.stringify(rest, null, 2));
|
|
41
|
+
process.exit(result.exit);
|
|
42
|
+
}
|
|
43
|
+
if (result.error === 'feature-not-found') {
|
|
44
|
+
console.error(chalk.red(`stack-scan: a feature "${featureName}" não existe — rode morph-spec create ${featureName} antes`));
|
|
45
|
+
process.exit(2);
|
|
46
|
+
}
|
|
47
|
+
if (result.written) {
|
|
48
|
+
const high = result.deviations.filter((d) => d.severity === 'Bloqueante' || d.severity === 'Alto').length;
|
|
49
|
+
console.log(chalk.green(`✓ stack-scan: ${join(getFeatureDir(featureName), '2-plan', STACK_SCAN_FILE)} gravado`));
|
|
50
|
+
const summary = result.greenfield ? 'greenfield'
|
|
51
|
+
: !result.audited ? notAuditedReason(result)
|
|
52
|
+
: `${result.deviations.length} desvio(s), ${high} Bloqueante/Alto`;
|
|
53
|
+
console.log(chalk.gray(` ${summary}`));
|
|
54
|
+
} else {
|
|
55
|
+
process.stdout.write(result.markdown);
|
|
56
|
+
}
|
|
57
|
+
process.exit(result.exit);
|
|
58
|
+
}
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `morph-spec standards --find "<query>" [--category <c>] [--tags <a,b>] [--json]`
|
|
2
|
+
* `morph-spec standards [--find "<query>"] [--show <id>[#<anchor>]] [--category <c>] [--tags <a,b>] [--json]`
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* task. This command searches server-side and returns only the TOP 3 hits —
|
|
9
|
-
* `id · path · digest` — where the digest is the FULL text: it is what
|
|
10
|
-
* substitutes for actually reading the standard's .md file.
|
|
4
|
+
* `--find` scores the registry server-side and returns the TOP 3 hits with
|
|
5
|
+
* digest and anchor keys, so nobody reads the whole registry to pick a
|
|
6
|
+
* standard. `--show <id>#<anchor>` prints only that section of the .md;
|
|
7
|
+
* `--show <id>` prints the digest and the anchors. Bare call: categories + counts.
|
|
11
8
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* A query with no hit exits 0 (this is advisory tooling, not a gate) with a
|
|
16
|
-
* human-readable warning + category suggestions on stderr, and — under
|
|
17
|
-
* `--json` — a stable `{ query, results: [], suggestion }` shape on stdout so
|
|
18
|
-
* a JSON consumer never has to special-case "no results" as a parse error.
|
|
9
|
+
* A query with no hit exits 0 (advisory, not a gate); under `--json` the
|
|
10
|
+
* shape stays `{ query, results: [], suggestion }`.
|
|
19
11
|
*/
|
|
20
12
|
|
|
21
13
|
import { existsSync, readFileSync } from 'fs';
|
|
22
|
-
import { join } from 'path';
|
|
14
|
+
import { dirname, join } from 'path';
|
|
23
15
|
import chalk from 'chalk';
|
|
16
|
+
import { loadStandardsRegistry } from '../../lib/standards/registry.js';
|
|
17
|
+
import { markdownHeadings, sectionBySlug } from '../../lib/standards/sections.js';
|
|
24
18
|
|
|
25
19
|
/**
|
|
26
20
|
* Resolve STANDARDS.json the same way doctor.js resolves the standards root:
|
|
@@ -41,10 +35,67 @@ export function resolveStandardsJsonPath(targetPath) {
|
|
|
41
35
|
return null;
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
/**
|
|
39
|
+
* The one section a `--show` target names. `anchor` is an anchor key of the
|
|
40
|
+
* registry entry (`regra`) or any heading slug of the .md (`#regra-principal`).
|
|
41
|
+
* @returns {{ status: 'ok', section: string, slug: string }
|
|
42
|
+
* | { status: 'unknown-anchor', anchors: Record<string, string>, headings: string[] }
|
|
43
|
+
* | { status: 'unreadable' }}
|
|
44
|
+
*/
|
|
45
|
+
export function resolveStandardSection(standard, mdText, anchor) {
|
|
46
|
+
if (mdText === null) return { status: 'unreadable' };
|
|
47
|
+
const anchors = standard.anchors || {};
|
|
48
|
+
const slug = String(anchors[anchor] ?? anchor).replace(/^#/, '');
|
|
49
|
+
const section = sectionBySlug(mdText, slug);
|
|
50
|
+
if (section) return { status: 'ok', section, slug };
|
|
51
|
+
return { status: 'unknown-anchor', anchors, headings: markdownHeadings(mdText).map((h) => h.slug) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readStandardMd(standardsJsonPath, standard) {
|
|
55
|
+
try {
|
|
56
|
+
return readFileSync(join(dirname(standardsJsonPath), standard.path), 'utf8');
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function showStandard(standards, standardsJsonPath, target, json) {
|
|
63
|
+
const hash = String(target).indexOf('#');
|
|
64
|
+
const id = hash === -1 ? String(target) : String(target).slice(0, hash);
|
|
65
|
+
const anchor = hash === -1 ? '' : String(target).slice(hash + 1).replace(/^#+/, '');
|
|
66
|
+
const standard = standards.find((s) => s.id === id);
|
|
67
|
+
if (!standard) {
|
|
68
|
+
const near = findStandards(standards, id.replace(/-/g, ' ')).map((s) => s.id);
|
|
69
|
+
if (json) console.log(JSON.stringify({ id, error: 'unknown-id', suggestions: near }));
|
|
70
|
+
else console.error(chalk.red(`\n❌ Standard "${id}" não existe.`) + (near.length ? chalk.gray(` Talvez: ${near.join(', ')}`) : '') + '\n');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const anchors = standard.anchors || {};
|
|
74
|
+
|
|
75
|
+
if (!anchor) {
|
|
76
|
+
if (json) {
|
|
77
|
+
console.log(JSON.stringify({ id: standard.id, path: standard.path, digest: standard.digest, anchors }));
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
console.log(chalk.cyan(`\n${standard.id}`) + chalk.gray(` (${standard.path})`));
|
|
81
|
+
console.log(`\n${standard.digest || '(sem digest)'}\n`);
|
|
82
|
+
console.log(chalk.bold('Âncoras') + chalk.gray(` — morph-spec standards --show ${standard.id}#<âncora>`));
|
|
83
|
+
for (const [key, slug] of Object.entries(anchors)) console.log(` ${key.padEnd(16)}${slug}`);
|
|
84
|
+
console.log('');
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = resolveStandardSection(standard, readStandardMd(standardsJsonPath, standard), anchor);
|
|
89
|
+
if (result.status === 'ok') {
|
|
90
|
+
if (json) console.log(JSON.stringify({ id: standard.id, anchor, slug: result.slug, section: result.section }));
|
|
91
|
+
else process.stdout.write(result.section);
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
if (json) console.log(JSON.stringify({ id: standard.id, anchor, error: result.status, anchors }));
|
|
95
|
+
else if (result.status === 'unreadable') console.error(chalk.red(`\n❌ ${standard.path} não pôde ser lido.\n`));
|
|
96
|
+
else console.error(chalk.red(`\n❌ "${anchor}" não é âncora nem heading de ${standard.id}.`)
|
|
97
|
+
+ chalk.gray(` Âncoras: ${Object.keys(anchors).join(', ') || '(nenhuma)'}\n`));
|
|
98
|
+
process.exit(1);
|
|
48
99
|
}
|
|
49
100
|
|
|
50
101
|
/**
|
|
@@ -145,7 +196,7 @@ function parseTagsOption(raw) {
|
|
|
145
196
|
}
|
|
146
197
|
|
|
147
198
|
/**
|
|
148
|
-
* @param {{ find?: string, category?: string, tags?: string, json?: boolean, cwd?: string }} [options]
|
|
199
|
+
* @param {{ find?: string, show?: string, category?: string, tags?: string, json?: boolean, cwd?: string }} [options]
|
|
149
200
|
*/
|
|
150
201
|
export function standardsCommand(options = {}) {
|
|
151
202
|
const cwd = options.cwd || process.cwd();
|
|
@@ -159,13 +210,14 @@ export function standardsCommand(options = {}) {
|
|
|
159
210
|
|
|
160
211
|
let registry;
|
|
161
212
|
try {
|
|
162
|
-
registry =
|
|
213
|
+
registry = loadStandardsRegistry(standardsJsonPath);
|
|
163
214
|
} catch (err) {
|
|
164
215
|
console.error(chalk.red(`\n❌ STANDARDS.json inválido em ${standardsJsonPath}: ${err.message}\n`));
|
|
165
216
|
process.exit(1);
|
|
166
217
|
}
|
|
167
218
|
|
|
168
|
-
const standards =
|
|
219
|
+
const standards = registry.standards;
|
|
220
|
+
if (options.show) showStandard(standards, standardsJsonPath, options.show, options.json);
|
|
169
221
|
const query = (options.find || '').trim();
|
|
170
222
|
|
|
171
223
|
// ── Bare call (no --find): categories + counts, cheap orientation ────────
|
|
@@ -180,7 +232,7 @@ export function standardsCommand(options = {}) {
|
|
|
180
232
|
for (const { category, count } of counts) {
|
|
181
233
|
console.log(` ${col(category, 20)}${count}`);
|
|
182
234
|
}
|
|
183
|
-
console.log(chalk.gray('\nUse --find "<query>" para buscar (top 3, com digest
|
|
235
|
+
console.log(chalk.gray('\nUse --find "<query>" para buscar (top 3, com digest e âncoras) e --show <id>#<âncora> para ler uma seção.\n'));
|
|
184
236
|
process.exit(0);
|
|
185
237
|
}
|
|
186
238
|
|
|
@@ -201,7 +253,8 @@ export function standardsCommand(options = {}) {
|
|
|
201
253
|
}
|
|
202
254
|
|
|
203
255
|
const shaped = results.map((r) => ({
|
|
204
|
-
id: r.id, name: r.name, path: r.path, category: r.category, digest: r.digest,
|
|
256
|
+
id: r.id, name: r.name, path: r.path, category: r.category, digest: r.digest,
|
|
257
|
+
anchors: Object.keys(r.anchors || {}), score: r.score,
|
|
205
258
|
}));
|
|
206
259
|
|
|
207
260
|
if (options.json) {
|
|
@@ -214,6 +267,7 @@ export function standardsCommand(options = {}) {
|
|
|
214
267
|
console.log(chalk.cyan(`${i + 1}. ${r.id}`) + chalk.gray(` (${r.category})`));
|
|
215
268
|
console.log(` ${r.path}`);
|
|
216
269
|
console.log(chalk.gray(` ${r.digest}`));
|
|
270
|
+
if (r.anchors.length) console.log(` âncoras: ${r.anchors.join(', ')}` + chalk.gray(` → morph-spec standards --show ${r.id}#<âncora>`));
|
|
217
271
|
console.log('');
|
|
218
272
|
});
|
|
219
273
|
process.exit(0);
|