@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,181 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { existsSync, readFileSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
import { parseTasksJsonLenient, oversizedTasks, MAX_TASK_OUTPUTS } from '../../lib/tasks/tasks-json-parser.js';
|
|
5
|
-
import { planDispatch } from '../../lib/tasks/dispatch-planner.js';
|
|
6
|
-
import { renderMermaidFlowchart } from '../../lib/tasks/mermaid-renderer.js';
|
|
7
|
-
import { checkUseCases } from '../../lib/specs/usecase-check.js';
|
|
8
|
-
import { checkArtifactCriteria } from '../../lib/specs/done-criteria-check.js';
|
|
9
|
-
import { checkLifecycle } from '../../lib/specs/acervo-lifecycle.js';
|
|
10
|
-
|
|
11
|
-
/** Shared by `dag` and `usecase check` — the two surfaces of the same finding. */
|
|
12
|
-
export function printLifecycleWarnings(lifecycle) {
|
|
13
|
-
if (!lifecycle?.warnings?.length) return;
|
|
14
|
-
const n = lifecycle.warnings.length;
|
|
15
|
-
console.error(chalk.yellow(`\n⚠ Ciclo de vida do acervo — ${n} ${n === 1 ? 'aviso' : 'avisos'}`));
|
|
16
|
-
for (const w of lifecycle.warnings) console.error(chalk.yellow(` ${w}`));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function planGateApproved(featureDir) {
|
|
20
|
-
try {
|
|
21
|
-
const feature = JSON.parse(readFileSync(join(featureDir, 'feature.json'), 'utf8'));
|
|
22
|
-
return feature?.approvalGates?.plan?.approved === true;
|
|
23
|
-
} catch {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* `morph-spec dag <feature>` — analyze the tasks.json DAG and print the
|
|
30
|
-
* sequential vs parallel dispatch decision, weighted by effort.
|
|
31
|
-
*
|
|
32
|
-
* This is the command morph-apply §2 runs instead of re-deriving the rules
|
|
33
|
-
* (and instead of the old inline JS snippets the LLM could not execute).
|
|
34
|
-
*
|
|
35
|
-
* `--mermaid` emits the DAG as a Mermaid `flowchart TD` instead (for
|
|
36
|
-
* visualizing the plan at Gate 2), bypassing the dispatch analysis entirely.
|
|
37
|
-
*/
|
|
38
|
-
export async function dagCommand(featureName, options = {}) {
|
|
39
|
-
try {
|
|
40
|
-
const featureDir = join(process.cwd(), '.morph', 'features', featureName);
|
|
41
|
-
const planDir = join(featureDir, '2-plan');
|
|
42
|
-
const tasksPath = join(planDir, 'tasks.json');
|
|
43
|
-
if (!existsSync(tasksPath)) {
|
|
44
|
-
console.error(chalk.red(`\n❌ tasks.json not found for feature "${featureName}"`));
|
|
45
|
-
console.error(chalk.gray(`Expected at: .morph/features/${featureName}/2-plan/tasks.json\n`));
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// parseTasksJsonLenient validates the schema and throws with the error
|
|
50
|
-
// list, but defaults a missing `version` (legacy plans predating the
|
|
51
|
-
// field) instead of failing — the DAG analysis doesn't depend on it.
|
|
52
|
-
const { data, versionDefaulted } = await parseTasksJsonLenient(tasksPath);
|
|
53
|
-
if (versionDefaulted) {
|
|
54
|
-
console.error(chalk.yellow(
|
|
55
|
-
`⚠ tasks.json has no 'version' field — defaulting to "1.0" for this analysis.\n` +
|
|
56
|
-
` Consider adding it back explicitly (safe: protect-spec-files allows backfilling it).`
|
|
57
|
-
));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// --mermaid short-circuits before the dispatch analysis: visualizing the
|
|
61
|
-
// plan's DAG doesn't need a sequential-vs-parallel decision.
|
|
62
|
-
if (options.mermaid) {
|
|
63
|
-
console.log(renderMermaidFlowchart(data));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Use-case cross-check. Runs AFTER the --mermaid short-circuit on purpose:
|
|
68
|
-
// rendering the DAG is pure visualization and must never be blocked by the
|
|
69
|
-
// state of the spec. A broken `usecase` reference IS fatal here — the file
|
|
70
|
-
// either exists or it does not, so there is no false positive to weigh
|
|
71
|
-
// against a plan that points at a use case nobody can read.
|
|
72
|
-
const ucCheck = checkUseCases(data, join(planDir, 'usecases'));
|
|
73
|
-
|
|
74
|
-
// Artifact-postcondition warning (issue #87). Lexical, not wiring — it
|
|
75
|
-
// runs over ALL tasks even when the feature has no use cases at all
|
|
76
|
-
// (checkUseCases short-circuits above in that case), because a task's
|
|
77
|
-
// own doneCriteria can describe an artifact with no use case in sight.
|
|
78
|
-
const artifactCriteria = checkArtifactCriteria(data, join(planDir, 'usecases'));
|
|
79
|
-
|
|
80
|
-
// Acervo lifecycle: a task citing a superseded use case, and the spec.md
|
|
81
|
-
// revocation rows the finish would fail to apply. Warnings only.
|
|
82
|
-
const lifecycle = checkLifecycle({ cwd: process.cwd(), featureDir, tasksData: data });
|
|
83
|
-
|
|
84
|
-
// Task acima do teto não cabe num dispatch. Antes do Gate 2 é erro (o plano
|
|
85
|
-
// ainda fatia); depois é aviso, para não travar feature em voo.
|
|
86
|
-
const oversized = oversizedTasks(data);
|
|
87
|
-
const sizeBlocks = oversized.length > 0 && !planGateApproved(featureDir);
|
|
88
|
-
const taskSize = { max: MAX_TASK_OUTPUTS, oversized, blocking: sizeBlocks };
|
|
89
|
-
|
|
90
|
-
const plan = planDispatch(data);
|
|
91
|
-
|
|
92
|
-
if (options.json) {
|
|
93
|
-
console.log(JSON.stringify({ ...plan, useCases: ucCheck, artifactCriteria, lifecycle, taskSize }, null, 2));
|
|
94
|
-
if (ucCheck.errors.length || sizeBlocks) process.exit(1);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
99
|
-
|
|
100
|
-
if (ucCheck.errors.length) {
|
|
101
|
-
console.error(chalk.red(`\n❌ Use cases — ${plural(ucCheck.errors.length, 'erro', 'erros')}\n`));
|
|
102
|
-
for (const e of ucCheck.errors) console.error(chalk.red(` ${e}`));
|
|
103
|
-
console.error(chalk.gray('\n Corrija a referência no tasks.json ou restaure o use case.\n'));
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
if (oversized.length) {
|
|
107
|
-
const tone = sizeBlocks ? chalk.red : chalk.yellow;
|
|
108
|
-
const head = sizeBlocks ? '❌' : '⚠';
|
|
109
|
-
console.error(tone(`\n${head} Tamanho de task — ${plural(oversized.length, 'task', 'tasks')} acima de ${MAX_TASK_OUTPUTS} outputs`));
|
|
110
|
-
for (const t of oversized) console.error(tone(` ${t.id}: ${t.outputs} outputs`));
|
|
111
|
-
console.error(chalk.gray(' Fatie por pós-condição: uma task cabe num dispatch (morph-plan, "Tamanho da task").\n'));
|
|
112
|
-
if (sizeBlocks) process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
if (ucCheck.warnings.length) {
|
|
115
|
-
console.error(chalk.yellow(`\n⚠ Use cases — ${plural(ucCheck.warnings.length, 'aviso', 'avisos')}`));
|
|
116
|
-
for (const w of ucCheck.warnings) console.error(chalk.yellow(` ${w}`));
|
|
117
|
-
}
|
|
118
|
-
if (artifactCriteria.warnings.length) {
|
|
119
|
-
console.error(chalk.yellow(`\n⚠ Critérios de artefato — ${plural(artifactCriteria.warnings.length, 'aviso', 'avisos')}`));
|
|
120
|
-
for (const w of artifactCriteria.warnings) console.error(chalk.yellow(` ${w}`));
|
|
121
|
-
console.error(chalk.gray(' Teste que lê texto de arquivo não prova comportamento (morph-plan §2, "Por que artefato").'));
|
|
122
|
-
}
|
|
123
|
-
printLifecycleWarnings(lifecycle);
|
|
124
|
-
|
|
125
|
-
console.log(chalk.bold(`\n📊 DAG — ${featureName}`));
|
|
126
|
-
console.log('━'.repeat(60));
|
|
127
|
-
|
|
128
|
-
const { stats } = plan;
|
|
129
|
-
console.log(`\nTasks: ${stats.total} total, ${stats.pending} pendente(s)`);
|
|
130
|
-
const groupLine = Object.keys(stats.groups)
|
|
131
|
-
.map(g => `${g}:${stats.groups[g]} (peso ${stats.weights[g]})`)
|
|
132
|
-
.join(' | ');
|
|
133
|
-
if (groupLine) console.log(chalk.gray(`Grupos pendentes: ${groupLine}`));
|
|
134
|
-
|
|
135
|
-
const modeLabel = {
|
|
136
|
-
'sequential': 'SEQUENCIAL (auto-advance)',
|
|
137
|
-
'parallel-flows': 'PARALELO por fluxo de domínio',
|
|
138
|
-
'parallel-roots': 'PARALELO por raízes independentes',
|
|
139
|
-
}[plan.mode];
|
|
140
|
-
console.log(chalk.cyan(`\nDecisão: ${modeLabel}`));
|
|
141
|
-
console.log(` → ${plan.reason}`);
|
|
142
|
-
|
|
143
|
-
for (const flow of plan.flows) {
|
|
144
|
-
console.log(` ${chalk.green('▶')} Fluxo ${chalk.bold(flow.group)} (peso ${flow.weight}): ${flow.taskIds.join(', ')}`);
|
|
145
|
-
}
|
|
146
|
-
if (plan.batch.length > 0) {
|
|
147
|
-
console.log(` ${chalk.green('▶')} Batch paralelo: ${plan.batch.join(', ')}`);
|
|
148
|
-
}
|
|
149
|
-
if (plan.inline.length > 0) {
|
|
150
|
-
console.log(` ${chalk.yellow('○')} Inline (sem dispatch próprio): ${plan.inline.join(', ')}`);
|
|
151
|
-
}
|
|
152
|
-
if (plan.sequentialRemainder.length > 0) {
|
|
153
|
-
console.log(` ${chalk.gray('→')} Sequencial: ${plan.sequentialRemainder.join(', ')}`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Isolamento — only meaningful when there IS a parallel batch. In a shared
|
|
157
|
-
// working tree two agents writing the same path corrupt each other; the
|
|
158
|
-
// dag recommends `isolation: "worktree"` so each runs in its own worktree
|
|
159
|
-
// (morph-apply §3b-w).
|
|
160
|
-
const iso = plan.isolation;
|
|
161
|
-
if (iso && (iso.recommended === 'worktree' || iso.unknownFootprint.length > 0)) {
|
|
162
|
-
console.log(chalk.bold('\nIsolamento:'));
|
|
163
|
-
if (iso.recommended === 'worktree') {
|
|
164
|
-
console.log(` ${chalk.yellow('⚠')} recomendado: ${chalk.bold('worktree')} — ${iso.reason}`);
|
|
165
|
-
for (const c of iso.collisions) {
|
|
166
|
-
console.log(chalk.gray(` colisão em "${c.path}": ${c.units.join(' ‖ ')} (${c.tasks.join(', ')})`));
|
|
167
|
-
}
|
|
168
|
-
} else {
|
|
169
|
-
console.log(` ${chalk.gray('○')} ${iso.reason}`);
|
|
170
|
-
}
|
|
171
|
-
if (iso.unknownFootprint.length > 0) {
|
|
172
|
-
console.log(chalk.gray(` footprint desconhecido (sem outputs): ${iso.unknownFootprint.join(', ')}`));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
console.log('\n' + '━'.repeat(60) + '\n');
|
|
177
|
-
} catch (error) {
|
|
178
|
-
console.error(chalk.red(`\n❌ Error analyzing DAG: ${error.message}\n`));
|
|
179
|
-
process.exit(1);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import morphData from 'virtual:morph-data';
|
|
2
|
-
import KnowledgeMarkdown from '../components/KnowledgeMarkdown.jsx';
|
|
3
|
-
import { useSelection } from '../components/EntityLink.jsx';
|
|
4
|
-
|
|
5
|
-
const DOCS = morphData.docs || { rubrics: [] };
|
|
6
|
-
const CHECKLIST = DOCS.rubrics.find((r) => r.id === 'review-checklist');
|
|
7
|
-
const VALIDATORS = morphData.rubrics || [];
|
|
8
|
-
|
|
9
|
-
const GROUPS = [
|
|
10
|
-
{ title: 'Avaliador do Gate 3', hint: 'veredito e bloqueadores com evidência', items: CHECKLIST ? [{ key: 'dim:review-checklist', title: CHECKLIST.title, doc: CHECKLIST }] : [] },
|
|
11
|
-
{ title: 'Validadores determinísticos', hint: 'pesos por categoria no verify', items: VALIDATORS.map((v) => ({ key: `val:${v.id}`, title: `Rubrica ${v.id}`, validator: v })) },
|
|
12
|
-
].filter((g) => g.items.length);
|
|
13
|
-
|
|
14
|
-
const ALL = GROUPS.flatMap((g) => g.items);
|
|
15
|
-
|
|
16
|
-
function ValidatorRubric({ v }) {
|
|
17
|
-
return (
|
|
18
|
-
<article className="prose-morph">
|
|
19
|
-
<h1>Rubrica {v.id}</h1>
|
|
20
|
-
{v.description && <p>{v.description}</p>}
|
|
21
|
-
<table>
|
|
22
|
-
<thead><tr><th>Categoria</th><th>Peso</th><th>Validadores</th><th>O que mede</th></tr></thead>
|
|
23
|
-
<tbody>
|
|
24
|
-
{Object.entries(v.categories).map(([name, c]) => (
|
|
25
|
-
<tr key={name}>
|
|
26
|
-
<td>{name}</td>
|
|
27
|
-
<td className="font-mono">{c.weight ?? '—'}</td>
|
|
28
|
-
<td className="font-mono text-xs">{(c.validators || []).join(', ') || '—'}</td>
|
|
29
|
-
<td>{c.description || ''}</td>
|
|
30
|
-
</tr>
|
|
31
|
-
))}
|
|
32
|
-
</tbody>
|
|
33
|
-
</table>
|
|
34
|
-
<p>O score é informativo: o nó de validadores do verify reprova por erro de validador, não pelo número.</p>
|
|
35
|
-
</article>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function RubricsView({ sel, onSel }) {
|
|
40
|
-
const current = ALL.find((i) => i.key === sel) || null;
|
|
41
|
-
return (
|
|
42
|
-
<div className="mx-auto max-w-7xl px-4 pb-24 pt-10 md:px-10 md:pt-14">
|
|
43
|
-
<header className="rise mb-8">
|
|
44
|
-
<h1 className="text-4xl font-semibold tracking-tight md:text-5xl">Rubricas</h1>
|
|
45
|
-
<p className="mt-3 max-w-2xl text-sm leading-relaxed text-[var(--color-dim)]">
|
|
46
|
-
Com o que o trabalho é julgado. O avaliador independente decide SHIP ou FIX, e só bloqueia com evidência de UC, teste, regressão ou segurança.
|
|
47
|
-
Os validadores rodam no <span className="font-mono text-[var(--color-fg)]">morph-spec verify</span>, sem modelo.
|
|
48
|
-
</p>
|
|
49
|
-
</header>
|
|
50
|
-
<div className="rise-2 grid gap-6 lg:grid-cols-[17rem_minmax(0,1fr)]">
|
|
51
|
-
<nav aria-label="Rubricas" className="lg:sticky lg:top-4 lg:self-start">
|
|
52
|
-
<button
|
|
53
|
-
type="button"
|
|
54
|
-
onClick={() => onSel(null)}
|
|
55
|
-
aria-current={!current ? 'page' : undefined}
|
|
56
|
-
className={['fluid mb-4 block w-full rounded-lg px-3 py-2 text-left text-sm transition-colors', !current ? 'bg-white/[0.08] text-[var(--color-fg)]' : 'text-[var(--color-dim)] hover:bg-white/[0.04]'].join(' ')}
|
|
57
|
-
>
|
|
58
|
-
Como o Gate 3 decide
|
|
59
|
-
</button>
|
|
60
|
-
{GROUPS.map((g) => (
|
|
61
|
-
<div key={g.title} className="mb-5">
|
|
62
|
-
<p className="px-3 text-xs text-[var(--color-faint)]">{g.title}</p>
|
|
63
|
-
<p className="mb-1.5 px-3 text-[11px] text-[var(--color-faint)]">{g.hint}</p>
|
|
64
|
-
<ul>
|
|
65
|
-
{g.items.map((i) => (
|
|
66
|
-
<li key={i.key}>
|
|
67
|
-
<button
|
|
68
|
-
type="button"
|
|
69
|
-
onClick={() => onSel(i.key)}
|
|
70
|
-
aria-current={current?.key === i.key ? 'page' : undefined}
|
|
71
|
-
className={['fluid block w-full truncate rounded-lg px-3 py-1.5 text-left text-sm transition-colors', current?.key === i.key ? 'bg-white/[0.08] text-[var(--color-fg)]' : 'text-[var(--color-dim)] hover:bg-white/[0.04] hover:text-[var(--color-fg)]'].join(' ')}
|
|
72
|
-
title={i.title}
|
|
73
|
-
>
|
|
74
|
-
{i.title}
|
|
75
|
-
</button>
|
|
76
|
-
</li>
|
|
77
|
-
))}
|
|
78
|
-
</ul>
|
|
79
|
-
</div>
|
|
80
|
-
))}
|
|
81
|
-
</nav>
|
|
82
|
-
<div className="bezel min-w-0">
|
|
83
|
-
<div className="bezel-core min-w-0 px-6 py-8 md:px-12 md:py-10">
|
|
84
|
-
{!current && (CHECKLIST ? <KnowledgeMarkdown text={CHECKLIST.content} /> : <p className="text-sm text-[var(--color-dim)]">Escolha uma rubrica.</p>)}
|
|
85
|
-
{current?.doc && <KnowledgeMarkdown text={current.doc.content} />}
|
|
86
|
-
{current?.validator && <ValidatorRubric v={current.validator} />}
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export default function Rubrics() {
|
|
95
|
-
const [sel, setSel] = useSelection();
|
|
96
|
-
return <RubricsView sel={sel} onSel={setSel} />;
|
|
97
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
|
|
4
|
-
export function scanRubrics(frameworkDir) {
|
|
5
|
-
const dir = join(frameworkDir, 'rubrics');
|
|
6
|
-
if (!existsSync(dir)) return [];
|
|
7
|
-
return readdirSync(dir).filter(f => f.endsWith('.json')).map(f => {
|
|
8
|
-
const d = JSON.parse(readFileSync(join(dir, f), 'utf8'));
|
|
9
|
-
return { id: d.id, version: d.version || '1.0.0', description: d.description || '',
|
|
10
|
-
categories: d.categories || {}, thresholds: d.thresholds || { approve: 9.0, retry: 6.0 } };
|
|
11
|
-
});
|
|
12
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* output-collector — Collect phase output files for multi-source agent detection
|
|
3
|
-
*
|
|
4
|
-
* Reads all existing phase outputs for a feature and returns them as
|
|
5
|
-
* weighted TextSource[] for use in detectAgents().
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, existsSync } from 'fs';
|
|
9
|
-
import { OUTPUT_SCHEMA, getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Default source weights per output type.
|
|
13
|
-
* Keys match OUTPUT_SCHEMA keys (camelCase).
|
|
14
|
-
*/
|
|
15
|
-
export const SOURCE_WEIGHTS = {
|
|
16
|
-
proposal: 1.0,
|
|
17
|
-
stackScan: 0.8,
|
|
18
|
-
spec: 1.5,
|
|
19
|
-
tasks: 1.3,
|
|
20
|
-
decisions: 1.0,
|
|
21
|
-
uiDesignSystem: 1.0,
|
|
22
|
-
uiMockups: 1.0,
|
|
23
|
-
uiComponents: 1.0,
|
|
24
|
-
uiFlows: 1.0,
|
|
25
|
-
recap: 1.0,
|
|
26
|
-
reviewReport: 1.0,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/** Phase order for sorting collected outputs (v5 NLH model) */
|
|
30
|
-
const PHASE_ORDER = ['proposal', 'uiux', 'plan', 'implement', 'review'];
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Collect all existing phase output files for a feature.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} projectRoot - Absolute path to project root
|
|
36
|
-
* @param {string} featureName - Feature name
|
|
37
|
-
* @returns {Array<{source: string, content: string, weight: number}>}
|
|
38
|
-
*/
|
|
39
|
-
export function collectPhaseOutputs(projectRoot, featureName) {
|
|
40
|
-
const results = [];
|
|
41
|
-
|
|
42
|
-
for (const [key, entry] of Object.entries(OUTPUT_SCHEMA)) {
|
|
43
|
-
// Multi-file types resolve to a DIRECTORY — there is no single document to
|
|
44
|
-
// weigh as one source. Skipped explicitly rather than left to fail as an
|
|
45
|
-
// EISDIR swallowed by the catch below.
|
|
46
|
-
if (entry.multi) continue;
|
|
47
|
-
|
|
48
|
-
const absPath = getAbsoluteOutputPath(projectRoot, featureName, key);
|
|
49
|
-
if (!existsSync(absPath)) continue;
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
const content = readFileSync(absPath, 'utf8');
|
|
53
|
-
if (!content.trim()) continue; // Skip empty files
|
|
54
|
-
|
|
55
|
-
results.push({
|
|
56
|
-
source: entry.filename,
|
|
57
|
-
content,
|
|
58
|
-
weight: SOURCE_WEIGHTS[key] ?? 1.0,
|
|
59
|
-
_phase: entry.phase,
|
|
60
|
-
});
|
|
61
|
-
} catch {
|
|
62
|
-
// Skip unreadable files — fail-open
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Sort by phase order
|
|
68
|
-
results.sort((a, b) => {
|
|
69
|
-
const orderA = PHASE_ORDER.indexOf(a._phase);
|
|
70
|
-
const orderB = PHASE_ORDER.indexOf(b._phase);
|
|
71
|
-
return (orderA === -1 ? 99 : orderA) - (orderB === -1 ? 99 : orderB);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Remove internal _phase field
|
|
75
|
-
return results.map(({ source, content, weight }) => ({ source, content, weight }));
|
|
76
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'fs';
|
|
2
|
-
import { getAbsoluteOutputPath } from '../core/paths/output-schema.js';
|
|
3
|
-
|
|
4
|
-
export const VERDICT_TOKEN = '**Verdict:**';
|
|
5
|
-
export const VERDICTS = ['SHIP', 'FIX'];
|
|
6
|
-
export const BLOCKER_TYPES = ['uc', 'teste', 'regressao', 'seguranca'];
|
|
7
|
-
|
|
8
|
-
// O relatório é append-only: uma reavaliação acrescenta ao fim, e o token vigente é o ÚLTIMO.
|
|
9
|
-
const VERDICT_RE = /\*\*Verdict:\*\*\s*([A-Za-z]+)/g;
|
|
10
|
-
const BLOCKERS_SECTION_RE = /^##\s+Bloqueadores\s*$/gim;
|
|
11
|
-
const MEDIA_SECTION_RE = /^##\s+M[íi]dia\s+gerada\s*$/m;
|
|
12
|
-
const SCREEN_CHECKLIST_SECTION_RE = /^##\s+Checklist\s+de\s+tela\s*$/gim;
|
|
13
|
-
|
|
14
|
-
export function evaluatorReportPath(cwd, featureName) {
|
|
15
|
-
return getAbsoluteOutputPath(cwd, featureName, 'evaluatorReport');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function readReport(cwd, featureName) {
|
|
19
|
-
try {
|
|
20
|
-
const p = evaluatorReportPath(cwd, featureName);
|
|
21
|
-
return existsSync(p) ? readFileSync(p, 'utf8') : null;
|
|
22
|
-
} catch {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function lastSectionBody(text, re) {
|
|
28
|
-
const matches = [...text.matchAll(re)];
|
|
29
|
-
if (matches.length === 0) return null;
|
|
30
|
-
const last = matches[matches.length - 1];
|
|
31
|
-
const rest = text.slice(last.index + last[0].length);
|
|
32
|
-
const next = rest.search(/^#{1,2}\s/m);
|
|
33
|
-
return next === -1 ? rest : rest.slice(0, next);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const normalizeType = (s) => s.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').trim();
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Linhas de tabela da última `## Bloqueadores`: `| id | tipo | onde | evidência |`.
|
|
40
|
-
* Cabeçalho e separador não contam; `untyped` são as linhas cujo tipo não é de {@link BLOCKER_TYPES}.
|
|
41
|
-
*/
|
|
42
|
-
function parseBlockers(body) {
|
|
43
|
-
const blockers = [];
|
|
44
|
-
const untyped = [];
|
|
45
|
-
for (const line of body.split(/\r?\n/)) {
|
|
46
|
-
if (!/^\s*\|/.test(line)) continue;
|
|
47
|
-
const cells = line.trim().replace(/^\||\|$/g, '').split('|').map((c) => c.trim());
|
|
48
|
-
if (cells.every((c) => /^:?-{2,}:?$/.test(c) || c === '')) continue;
|
|
49
|
-
if (/^#$|^id$/i.test(cells[0]) && /^tipo$/i.test(cells[1] ?? '')) continue;
|
|
50
|
-
const row = { id: cells[0], type: normalizeType(cells[1] ?? ''), where: cells[2] ?? '', evidence: cells[3] ?? '' };
|
|
51
|
-
(BLOCKER_TYPES.includes(row.type) ? blockers : untyped).push(row);
|
|
52
|
-
}
|
|
53
|
-
return { blockers, untyped };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function evaluatorVerdictFromText(text) {
|
|
57
|
-
if (typeof text !== 'string') return { verdict: undefined, blockers: [], untyped: [], superseded: [] };
|
|
58
|
-
const verdicts = [...text.matchAll(VERDICT_RE)].map((m) => m[1].toUpperCase());
|
|
59
|
-
const body = lastSectionBody(text, BLOCKERS_SECTION_RE);
|
|
60
|
-
const { blockers, untyped } = body === null ? { blockers: [], untyped: [] } : parseBlockers(body);
|
|
61
|
-
return {
|
|
62
|
-
verdict: verdicts.length ? verdicts[verdicts.length - 1] : undefined,
|
|
63
|
-
blockers,
|
|
64
|
-
untyped,
|
|
65
|
-
superseded: verdicts.slice(0, -1),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* `missing`: sem relatório. `unverdicted`: relatório sem `**Verdict:** SHIP|FIX` válido.
|
|
71
|
-
* `ok`: veredito legível — quem decide se ele libera o ship é o risk-detector.
|
|
72
|
-
*/
|
|
73
|
-
export function evaluatorReportState(cwd, featureName) {
|
|
74
|
-
const path = evaluatorReportPath(cwd, featureName);
|
|
75
|
-
const text = readReport(cwd, featureName);
|
|
76
|
-
if (text === null) return { status: 'missing', path };
|
|
77
|
-
const parsed = evaluatorVerdictFromText(text);
|
|
78
|
-
if (!VERDICTS.includes(parsed.verdict)) return { status: 'unverdicted', path };
|
|
79
|
-
return { status: 'ok', path, ...parsed };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function evaluatorMediaSection(cwd, featureName) {
|
|
83
|
-
const text = readReport(cwd, featureName);
|
|
84
|
-
return text !== null && MEDIA_SECTION_RE.test(text);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function evaluatorScreenChecklist(cwd, featureName) {
|
|
88
|
-
const text = readReport(cwd, featureName);
|
|
89
|
-
const body = text === null ? null : lastSectionBody(text, SCREEN_CHECKLIST_SECTION_RE);
|
|
90
|
-
return body === null ? { section: false, body: '' } : { section: true, body };
|
|
91
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The shapes every reader and writer of the acervo agrees on — one copy each.
|
|
3
|
-
* A second copy of any of these is how two readers start disagreeing about the
|
|
4
|
-
* same file: a use case one of them sees and the other does not, a table row
|
|
5
|
-
* one of them splits differently.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync, readdirSync } from 'fs';
|
|
9
|
-
import { OUTPUT_SCHEMA } from '../../core/paths/output-schema.js';
|
|
10
|
-
|
|
11
|
-
/** A use-case id: `UC-07`. */
|
|
12
|
-
export const UC_ID_RE = /^UC-\d+$/;
|
|
13
|
-
|
|
14
|
-
/** A use case no longer holds as the system's behaviour (or holds only in its `scope`). */
|
|
15
|
-
export const SUPERSEDED = 'superseded';
|
|
16
|
-
|
|
17
|
-
const UC_FILENAME_RE = new RegExp(OUTPUT_SCHEMA.usecases.filenamePattern);
|
|
18
|
-
|
|
19
|
-
/** `UC-07-listar-veterinarios.md` → `UC-07`. Null when the name isn't a use case. */
|
|
20
|
-
export function useCaseId(filename) {
|
|
21
|
-
const m = /^(UC-\d+)-/.exec(filename);
|
|
22
|
-
return m ? m[1] : null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** The name the promotion carries: `UC-{nn}-{slug}.md`, slug in lowercase kebab. */
|
|
26
|
-
export const isCanonicalUseCaseName = (name) => UC_FILENAME_RE.test(name);
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Every use case in `dir`, grouped by id. By default only the canonical names
|
|
30
|
-
* — the ones the promotion ever carries. `canonicalOnly: false` also lists any
|
|
31
|
-
* `UC-{nn}-….md`, for the plan-time check that has always accepted them and
|
|
32
|
-
* now warns instead. An id with more than one file is a broken acervo, and
|
|
33
|
-
* readers must be able to see it rather than silently pick one.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} dir
|
|
36
|
-
* @param {{ canonicalOnly?: boolean }} [options]
|
|
37
|
-
* @returns {Map<string, string[]>} id → filenames, sorted; empty when the folder is missing or unreadable
|
|
38
|
-
*/
|
|
39
|
-
export function useCaseFilesById(dir, { canonicalOnly = true } = {}) {
|
|
40
|
-
const byId = new Map();
|
|
41
|
-
try {
|
|
42
|
-
if (!existsSync(dir)) return byId;
|
|
43
|
-
for (const name of readdirSync(dir).sort()) {
|
|
44
|
-
const id = useCaseId(name);
|
|
45
|
-
if (!id || !name.endsWith('.md')) continue;
|
|
46
|
-
if (canonicalOnly && !isCanonicalUseCaseName(name)) continue;
|
|
47
|
-
if (!byId.has(id)) byId.set(id, []);
|
|
48
|
-
byId.get(id).push(name);
|
|
49
|
-
}
|
|
50
|
-
} catch { /* unreadable — nothing to see */ }
|
|
51
|
-
return byId;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** A markdown table separator row: `|---|:--:|`, with or without the outer pipes. */
|
|
55
|
-
export const SEPARATOR_ROW_RE = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/;
|
|
56
|
-
|
|
57
|
-
/** A line that belongs to a table: it carries an unescaped pipe (GFM does not require the leading one). */
|
|
58
|
-
export const isTableLine = (line) => /(?<!\\)\|/.test(line);
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Split a table row on unescaped pipes, outer pipes optional; `\|` inside a
|
|
62
|
-
* cell is a literal pipe.
|
|
63
|
-
*
|
|
64
|
-
* @param {string} row
|
|
65
|
-
* @returns {string[]} trimmed cells
|
|
66
|
-
*/
|
|
67
|
-
export function tableCells(row) {
|
|
68
|
-
const inner = row.trim().replace(/^\|/, '').replace(/(?<!\\)\|$/, '');
|
|
69
|
-
return inner.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, '|').trim());
|
|
70
|
-
}
|