@polymorphism-tech/morph-spec 9.0.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +4 -62
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +12 -4
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +6 -17
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +11 -67
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +1 -42
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +2 -2
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +2 -2
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +34 -12
- package/framework/standards/STANDARDS.meta.json +16 -2
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +5 -57
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +14 -96
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +2 -5
- package/src/commands/validation/validate-feature.js +5 -94
- package/src/commands/validation/verify.js +15 -98
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +1 -0
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +3 -15
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +5 -31
- package/src/lib/plan/plan-parser.js +1 -6
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +5 -23
- package/src/lib/risk-detector.js +39 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +52 -358
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +13 -95
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -147
- package/src/utils/agents-installer.js +28 -275
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -11,35 +11,17 @@ import { readMorphConfig } from '../../lib/tasks/test-runner.js';
|
|
|
11
11
|
import { readPlanChecks } from '../../lib/plan/checks.js';
|
|
12
12
|
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* — everything else here is read straight off disk (workType, sliceCount, sha,
|
|
17
|
-
* diffstat, verification), and letting a caller override those would let a
|
|
18
|
-
* careless/hostile `--signals` silently defeat the policy this command exists
|
|
19
|
-
* to make deterministic (e.g. overriding `workType` away from "hotfix" would
|
|
20
|
-
* suppress risk-detector's forced Gate-3 pause). Only the signals that are
|
|
21
|
-
* genuinely NOT derivable from disk today are accepted here:
|
|
22
|
-
* - standardsViolations / designSystemDivergence: still an LLM's
|
|
23
|
-
* natural-language read (see the R4 report — no deterministic detector
|
|
24
|
-
* exists in JS yet).
|
|
25
|
-
*/
|
|
14
|
+
// Only signals NOT derivable from disk may be overridden: overriding a disk fact (e.g. `workType`
|
|
15
|
+
// away from hotfix) would silently defeat the policy this command makes deterministic.
|
|
26
16
|
const OVERRIDABLE_SIGNAL_KEYS = ['standardsViolations', 'designSystemDivergence'];
|
|
27
17
|
|
|
28
|
-
/**
|
|
29
|
-
* Os standards que fazem uma feature ser "de mídia", como os ids de
|
|
30
|
-
* STANDARDS.json. Fato do plano congelado: ou uma fatia pinou o standard em
|
|
31
|
-
* `standards:`, ou não pinou.
|
|
32
|
-
*/
|
|
33
18
|
export const MEDIA_STANDARD_IDS = [
|
|
34
19
|
'ai-agents-modalities-image-gen',
|
|
35
20
|
'ai-agents-media-video',
|
|
36
21
|
'ai-agents-media-pipeline',
|
|
37
22
|
];
|
|
38
23
|
|
|
39
|
-
|
|
40
|
-
* O plano escreve o standard pelo id ou pelo caminho (`ai-agents/media-video.md`,
|
|
41
|
-
* com ou sem `framework/` ou `standards/` na frente): as duas grafias viram o id.
|
|
42
|
-
*/
|
|
24
|
+
// O plano escreve o standard pelo id ou pelo caminho (com ou sem `framework/`/`standards/`).
|
|
43
25
|
export function standardIdOf(value) {
|
|
44
26
|
return String(value ?? '').trim().replace(/\\/g, '/')
|
|
45
27
|
.replace(/^(\.morph\/)?(framework\/)?(standards\/)?/, '')
|
|
@@ -47,41 +29,17 @@ export function standardIdOf(value) {
|
|
|
47
29
|
.replace(/\//g, '-');
|
|
48
30
|
}
|
|
49
31
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* standards de mídia. Predicado exportado porque decide se um guardrail arma:
|
|
53
|
-
* um que sempre devolve `false` desarma a pausa de mídia da review e a rota opus em
|
|
54
|
-
* silêncio.
|
|
55
|
-
*
|
|
56
|
-
* @param {{ standards?: string[] }[]|null|undefined} slices
|
|
57
|
-
*/
|
|
32
|
+
// Decide se um guardrail arma: um predicado que sempre devolve `false` desarma em silêncio a
|
|
33
|
+
// pausa de mídia da review e a rota opus.
|
|
58
34
|
export function featureGeneratesMedia(slices) {
|
|
59
35
|
if (!Array.isArray(slices)) return false;
|
|
60
36
|
return slices.some((s) => Array.isArray(s?.standards)
|
|
61
37
|
&& s.standards.some((id) => MEDIA_STANDARD_IDS.includes(standardIdOf(id))));
|
|
62
38
|
}
|
|
63
39
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*
|
|
68
|
-
* `declared: false` é "o projeto não declara caminho algum de prompt/modelo" —
|
|
69
|
-
* a regra é inerte por construção e a razão registrada diz isso. `declared:
|
|
70
|
-
* true` com `touched: []` é "declara, e nada foi tocado" — a regra rodou e não
|
|
71
|
-
* viu nada. Colapsar os dois num array vazio faria um projeto que nunca
|
|
72
|
-
* configurou `watch[]` parecer um projeto que foi verificado.
|
|
73
|
-
*
|
|
74
|
-
* O predicado NÃO é invenção deste framework: o repositório de referência já
|
|
75
|
-
* roda exatamente ele em produção, disparando publicação por `paths:` sobre os
|
|
76
|
-
* mesmos globs e decidindo o escopo com `git diff --name-only`.
|
|
77
|
-
*
|
|
78
|
-
* COLETA, NUNCA DECIDE. Nada aqui julga se o que foi tocado deveria pausar um
|
|
79
|
-
* gate — política escrita no coletor cria duas fontes para o mesmo fato.
|
|
80
|
-
*
|
|
81
|
-
* @param {string} cwd
|
|
82
|
-
* @param {string|null} defaultBranch
|
|
83
|
-
* @returns {{declared: boolean, watch: string[], touched: string[]}}
|
|
84
|
-
*/
|
|
40
|
+
// `declared: false` (nenhum caminho declarado: regra inerte) e `touched: []` (declarou, nada
|
|
41
|
+
// tocado) não podem colapsar num array vazio. COLETA, NUNCA DECIDE: política no coletor cria
|
|
42
|
+
// duas fontes para o mesmo fato.
|
|
85
43
|
function collectPromptModel(cwd, defaultBranch) {
|
|
86
44
|
const watch = readMorphConfig(cwd)?.project?.evals?.watch;
|
|
87
45
|
const globs = Array.isArray(watch) ? watch.filter((g) => typeof g === 'string' && g.trim()) : [];
|
|
@@ -96,12 +54,7 @@ function collectPromptModel(cwd, defaultBranch) {
|
|
|
96
54
|
};
|
|
97
55
|
}
|
|
98
56
|
|
|
99
|
-
/**
|
|
100
|
-
* Quais relatórios o ship exige: a regra única que gate-check, approve e
|
|
101
|
-
* `report-check --before-eval` leem. Coleta do git, nunca decide.
|
|
102
|
-
*
|
|
103
|
-
* @returns {{ required: string[], security: { required: boolean, reasons: string[] } }}
|
|
104
|
-
*/
|
|
57
|
+
/** A regra única de quais relatórios o ship exige, lida por gate-check, approve e report-check. */
|
|
105
58
|
export function collectReviewRequirement(cwd, feature) {
|
|
106
59
|
let changed = [];
|
|
107
60
|
try { changed = changedPathsAgainst(detectDefaultBranch(cwd), cwd); } catch { changed = []; }
|
|
@@ -109,48 +62,21 @@ export function collectReviewRequirement(cwd, feature) {
|
|
|
109
62
|
return { required: requiredReviewRoles({ workType: feature?.workType, securityRequired: security.required }), security };
|
|
110
63
|
}
|
|
111
64
|
|
|
112
|
-
/** O sinal `reviewReports` do ship, lido do disco. */
|
|
113
65
|
export function collectReviewSignal(cwd, featureName, feature, planChecks = readPlanChecks(cwd, featureName)) {
|
|
114
66
|
const { required } = collectReviewRequirement(cwd, feature);
|
|
115
67
|
const checkIds = Array.isArray(planChecks.checks?.checks) ? planChecks.checks.checks.map((c) => c.id) : null;
|
|
116
68
|
return reviewSignal(reviewReports(cwd, featureName), { required, checkIds });
|
|
117
69
|
}
|
|
118
70
|
|
|
119
|
-
/**
|
|
120
|
-
* Why an automatic signature may not close the plan gate — the same rule that
|
|
121
|
-
* makes gate-check pause it, read here by approve and advance.
|
|
122
|
-
*
|
|
123
|
-
* @returns {string|null}
|
|
124
|
-
*/
|
|
71
|
+
/** Why an automatic signature may not close the plan gate: the rule that makes gate-check pause it. */
|
|
125
72
|
export function planAutoApprovalBlock(cwd, featureName) {
|
|
126
73
|
const reason = planChecksPause(readPlanChecks(cwd, featureName).status);
|
|
127
74
|
return reason ? reason.replace('{feature}', featureName) : null;
|
|
128
75
|
}
|
|
129
76
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
* never had (it shipped fully tested with zero callers; see the R4 report).
|
|
134
|
-
*
|
|
135
|
-
* `verificationStatus` feeds a real pause rule (verify vermelho pausa —
|
|
136
|
-
* política adicionada em risk-detector.js no fecho do R4, qualquer gate).
|
|
137
|
-
* `verificationStale` também alimenta uma regra própria, mas só no ship
|
|
138
|
-
* (`gate === 'review'`) — sha do stamp divergindo do HEAD atual. `gate` em si
|
|
139
|
-
* é o que permite risk-detector.js diferenciar as duas. `e2eStatus`/`e2eReason`
|
|
140
|
-
* alimentam a regra da dispensa de e2e (também só no ship): um nó e2e que
|
|
141
|
-
* PULOU não reprova o rollup, logo `verificationStatus` sai 'pass' e a razão
|
|
142
|
-
* literal é o único fato que distingue dispensa legítima de ausência de prova.
|
|
143
|
-
* `planChecks` (só no gate do plano) é o estado dos checks extraídos contra o
|
|
144
|
-
* plano; `sliceCount` e as `standards` das fatias saem da mesma extração.
|
|
145
|
-
* `verification` completo, `e2eSkipClass`, diffstat e sha seguem como contexto
|
|
146
|
-
* de auditoria persistido, sem regra própria.
|
|
147
|
-
*
|
|
148
|
-
* @param {string} featureName
|
|
149
|
-
* @param {object} feature
|
|
150
|
-
* @param {string} cwd
|
|
151
|
-
* @param {string} gateName
|
|
152
|
-
* @returns {object}
|
|
153
|
-
*/
|
|
77
|
+
// Collects only; risk-detector.js decides. `e2eStatus`/`e2eReason` exist because a skipped e2e
|
|
78
|
+
// node does not fail the rollup: the literal reason is the only fact separating a legitimate
|
|
79
|
+
// waiver from missing proof.
|
|
154
80
|
export function collectSignals(featureName, feature, cwd, gateName) {
|
|
155
81
|
const planChecks = readPlanChecks(cwd, featureName);
|
|
156
82
|
const slices = planChecks.checks?.slices ?? null;
|
|
@@ -159,9 +85,7 @@ export function collectSignals(featureName, feature, cwd, gateName) {
|
|
|
159
85
|
const verification = feature.verification || null;
|
|
160
86
|
|
|
161
87
|
const signals = {
|
|
162
|
-
//
|
|
163
|
-
// cannot spoof which gate risk-detector.js thinks it is deciding, same
|
|
164
|
-
// anti-spoof rationale as workType (hotfix's forced pause).
|
|
88
|
+
// Disk-derived, never overridable: a caller cannot spoof which gate is being decided.
|
|
165
89
|
gate: gateName,
|
|
166
90
|
workType: feature.workType || 'feature',
|
|
167
91
|
sliceCount: slices ? slices.length : null,
|
|
@@ -171,8 +95,7 @@ export function collectSignals(featureName, feature, cwd, gateName) {
|
|
|
171
95
|
|
|
172
96
|
if (gateName === 'plan') signals.planChecks = planChecks.status;
|
|
173
97
|
|
|
174
|
-
// Só onde o checklist decide algo: no fecho do design e no ship.
|
|
175
|
-
// disco, fora de OVERRIDABLE_SIGNAL_KEYS.
|
|
98
|
+
// Só onde o checklist decide algo: no fecho do design e no ship.
|
|
176
99
|
if (gateName === 'uiux' || gateName === 'review') {
|
|
177
100
|
signals.screenChecklist = collectScreenChecklist(cwd, featureName, gateName);
|
|
178
101
|
}
|
|
@@ -185,37 +108,19 @@ export function collectSignals(featureName, feature, cwd, gateName) {
|
|
|
185
108
|
};
|
|
186
109
|
}
|
|
187
110
|
|
|
188
|
-
//
|
|
189
|
-
// stamp é auditoria. verificationStale é fail-open na direção "not stale":
|
|
190
|
-
// stamp com sha null (checkout sem git) ou stamp ausente lê como NOT stale.
|
|
111
|
+
// verificationStale é fail-open: stamp sem sha (checkout sem git) ou ausente lê como NOT stale.
|
|
191
112
|
signals.verification = verification;
|
|
192
113
|
if (verification?.status) signals.verificationStatus = verification.status;
|
|
193
114
|
signals.verificationStale = treeMovedSince(cwd, verification?.sha, sha);
|
|
194
115
|
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
// quando o e2e rodou e passou quanto quando ele nunca rodou. `null` aqui é
|
|
198
|
-
// DESCONHECIDO (stamp ausente, ou anterior ao nó e2e), nunca 'skip'.
|
|
199
|
-
// `e2eSkipClass` é DERIVADO pela fonte única (classifyE2eSkip, em
|
|
200
|
-
// risk-detector.js) e persistido só como auditoria — quem decide continua
|
|
201
|
-
// sendo shouldPauseGate, aqui não há política.
|
|
202
|
-
//
|
|
203
|
-
// Nenhuma das três entra em OVERRIDABLE_SIGNAL_KEYS: descrevem O QUE
|
|
204
|
-
// ACONTECEU no disco, como `gate`/`workType`, não uma opinião que o chamador
|
|
205
|
-
// possa afirmar. Um `--signals '{"e2eStatus":"pass"}'` compraria o Gate 3.
|
|
116
|
+
// Separado do rollup: um `skip` NÃO reprova `verification.status`. `null` é DESCONHECIDO, nunca
|
|
117
|
+
// 'skip'. Fora de OVERRIDABLE_SIGNAL_KEYS: um `--signals '{"e2eStatus":"pass"}'` compraria o ship.
|
|
206
118
|
signals.e2eStatus = verification?.nodes?.e2e ?? null;
|
|
207
119
|
signals.e2eReason = verification?.e2eReason ?? null;
|
|
208
120
|
signals.e2eSkipClass = signals.e2eStatus === 'skip' ? classifyE2eSkip(signals.e2eReason) : null;
|
|
209
121
|
|
|
210
|
-
//
|
|
211
|
-
// PULOU não avermelha `verification.status`, então o gate
|
|
212
|
-
// leria 'pass' tanto para "o eval rodou e passou" quanto para "o eval nunca
|
|
213
|
-
// rodou". `null` aqui é "este projeto não declara nó de eval" — DISTINTO de
|
|
214
|
-
// 'skip', que é "declara, e esta corrida não o executou".
|
|
122
|
+
// Mesma razão do e2e. `null` = o projeto não declara nó de eval, DISTINTO de 'skip'.
|
|
215
123
|
signals.evalsStatus = verification?.nodes?.evals ?? null;
|
|
216
|
-
// O que a feature TOCOU, contra os globs que o projeto declarou como
|
|
217
|
-
// prompt/modelo. Fato de disco: `declared` vem do config commitado, `touched`
|
|
218
|
-
// do git. Nenhuma política mora aqui — quem decide é shouldPauseGate.
|
|
219
124
|
signals.promptModel = collectPromptModel(cwd, defaultBranch);
|
|
220
125
|
|
|
221
126
|
signals.diffstat = getDiffStat(defaultBranch, cwd);
|
|
@@ -224,20 +129,8 @@ export function collectSignals(featureName, feature, cwd, gateName) {
|
|
|
224
129
|
return signals;
|
|
225
130
|
}
|
|
226
131
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
* [--json]` — the deterministic, disk-derived counterpart to `gate-decision`:
|
|
230
|
-
* collects the signals `shouldPauseGate()` (risk-detector.js) already knows
|
|
231
|
-
* how to score, calls it, and persists the result through the SAME write path
|
|
232
|
-
* as `gate-decision` ({@link persistGateDecision}) so the two commands can
|
|
233
|
-
* never produce a differently-shaped `gateDecisions.{gate}` entry.
|
|
234
|
-
*
|
|
235
|
-
* Golden rule: this command does NOT change gate policy. It is a faithful
|
|
236
|
-
* caller of risk-detector.js's existing, already-tested rules — nothing here
|
|
237
|
-
* adds a new pause condition. Where the skills' natural-language risk tables
|
|
238
|
-
* (morph-plan/morph-review/morph-apply) diverge from risk-detector.js,
|
|
239
|
-
* risk-detector.js wins.
|
|
240
|
-
*/
|
|
132
|
+
// Never changes gate policy: a faithful caller of risk-detector.js, which wins wherever the
|
|
133
|
+
// skills' risk tables diverge. Persists through the same path as `gate-decision`.
|
|
241
134
|
export async function gateCheckCommand(featureName, gateName, options = {}) {
|
|
242
135
|
try {
|
|
243
136
|
if (!VALID_GATES.includes(gateName)) {
|
|
@@ -252,10 +145,7 @@ export async function gateCheckCommand(featureName, gateName, options = {}) {
|
|
|
252
145
|
process.exit(1);
|
|
253
146
|
}
|
|
254
147
|
|
|
255
|
-
//
|
|
256
|
-
// AND writes the resulting decision through persistGateDecision — both
|
|
257
|
-
// halves are wrong from a tree that isn't this feature's own, so refuse
|
|
258
|
-
// rather than compute a decision from stale reads and then fail to save it.
|
|
148
|
+
// Both the reads and the persisted decision are wrong from a tree that doesn't own the feature.
|
|
259
149
|
const owner = assertOwnerTree(featureName, process.cwd());
|
|
260
150
|
if (owner.blocked) {
|
|
261
151
|
for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec gate-check ${featureName} ${gateName}`)) {
|
|
@@ -15,11 +15,7 @@ function parsePauseValue(raw) {
|
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* trust from the committed config (BOM-tolerant), defaulting to "auto".
|
|
20
|
-
* Shared by `gate-decision` (recorded as metadata) and `gate-check`
|
|
21
|
-
* (drives shouldPauseGate's decision directly).
|
|
22
|
-
*/
|
|
18
|
+
/** `trust` from the committed config (BOM-tolerant), defaulting to "auto". */
|
|
23
19
|
export function readTrustFromConfig() {
|
|
24
20
|
try {
|
|
25
21
|
const path = join(process.cwd(), '.morph', 'config', 'config.json');
|
|
@@ -32,22 +28,8 @@ export function readTrustFromConfig() {
|
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* caller-supplied pause/reason/signals, e.g. an LLM's natural-language risk
|
|
38
|
-
* read, or a manual override) and `gate-check` (a `shouldPauseGate()`-derived
|
|
39
|
-
* pause/reason/signals) feed into, so the two commands can never drift into
|
|
40
|
-
* two different persisted shapes for the same field. Does NOT validate
|
|
41
|
-
* `gateName` — callers that need a friendly CLI error check `VALID_GATES`
|
|
42
|
-
* themselves before calling this.
|
|
43
|
-
*
|
|
44
|
-
* @param {string} featureName
|
|
45
|
-
* @param {string} gateName
|
|
46
|
-
* @param {{ pause: boolean, reason?: string, signals?: object, trust?: string, cwd?: string, source: 'manual'|'gate-check' }} decision
|
|
47
|
-
* @returns {Promise<{ pause: boolean, reason: string, signals: object, trust: string, decidedAt: string, mockupsHash?: string|null }>}
|
|
48
|
-
* the exact entry written (decidedAt is server-side, generated here). A uiux
|
|
49
|
-
* decision also carries the hash of the mockup it saw: it answers that
|
|
50
|
-
* mockup's pause, not the next one's.
|
|
31
|
+
* The ONE write path of `gateDecisions.{gate}`, shared by `gate-decision` and `gate-check`.
|
|
32
|
+
* Does not validate `gateName`. A uiux decision records the `mockupsHash` it saw.
|
|
51
33
|
*/
|
|
52
34
|
export async function persistGateDecision(featureName, gateName, { pause, reason, signals = {}, trust, cwd = process.cwd(), source } = {}) {
|
|
53
35
|
const entry = {
|
|
@@ -64,24 +46,6 @@ export async function persistGateDecision(featureName, gateName, { pause, reason
|
|
|
64
46
|
return entry;
|
|
65
47
|
}
|
|
66
48
|
|
|
67
|
-
/**
|
|
68
|
-
* `morph-spec gate-decision <feature> <gate> --pause <bool> [--reason <txt>]
|
|
69
|
-
* [--signals <json>] [--trust <mode>]` — record the trust decision for a gate
|
|
70
|
-
* under `gateDecisions` in the authoritative feature.json.
|
|
71
|
-
*
|
|
72
|
-
* The timestamp (`decidedAt`) is
|
|
73
|
-
* stamped server-side and the write is atomic (state-manager). This replaces
|
|
74
|
-
* the manual Edit of feature.json from the skills — which collided with the
|
|
75
|
-
* state-sync hook rewriting the file between the agent's Read and Edit
|
|
76
|
-
* ("file modified since read"). The canonical shape is documented in
|
|
77
|
-
* framework/schemas/feature.schema.json and consumed by gate-guard.js.
|
|
78
|
-
*
|
|
79
|
-
* The manual escape hatch: `gate-check` (src/commands/state/gate-check.js) is
|
|
80
|
-
* the default, disk-derived path via `shouldPauseGate()`; this command stays
|
|
81
|
-
* for a caller that already knows the decision (an LLM's natural-language
|
|
82
|
-
* risk read a script can't see, or a deliberate override) — both write
|
|
83
|
-
* through {@link persistGateDecision}, so the persisted shape never diverges.
|
|
84
|
-
*/
|
|
85
49
|
export async function gateDecisionCommand(featureName, gateName, options = {}) {
|
|
86
50
|
try {
|
|
87
51
|
if (!VALID_GATES.includes(gateName)) {
|
|
@@ -1,34 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `morph-spec handoff <feature>` — generate the compressed session-handoff
|
|
3
|
-
* document (`.morph/features/{f}/handoff.md`).
|
|
4
|
-
*
|
|
5
|
-
* The fleet workflow hands a feature between Claude Code instances (a new
|
|
6
|
-
* worktree session, a resumed session after a phase transition). The handoff
|
|
7
|
-
* is the sanctioned way to carry context across: derived, regenerable, and
|
|
8
|
-
* pointer-based (see handoff-generator.js). This command is also called
|
|
9
|
-
* in-process by `worktree setup`, `create --worktree`, `advance` and
|
|
10
|
-
* `approve` — the CLI surface exists for the LLM/user to refresh the
|
|
11
|
-
* document on demand.
|
|
12
|
-
*
|
|
13
|
-
* WORKTREE-AWARE: `create --worktree` writes a COMPLETE feature.json into the
|
|
14
|
-
* primary root and the branch-committed copy (`morph/{f}`) becomes the truth
|
|
15
|
-
* from then on. Building the handoff from `process.cwd()` unconditionally
|
|
16
|
-
* used to mean: run this from the root while the worktree is alive, and you
|
|
17
|
-
* either got "Feature not found" (root's copy was already sheded) or a
|
|
18
|
-
* document silently built from the stale birth skeleton, written INTO the
|
|
19
|
-
* root (a second, always-defasada copy of a document that's supposed to be
|
|
20
|
-
* regenerated, not duplicated). This resolves the owning tree first
|
|
21
|
-
* (`resolveFeatureTree`) and writes THERE, unless `--here` forces the cwd.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
1
|
import chalk from 'chalk';
|
|
25
2
|
import { buildHandoff, writeHandoff } from '../../lib/generators/handoff-generator.js';
|
|
26
3
|
import { resolveFeatureTree } from '../../lib/worktree/feature-truth.js';
|
|
27
4
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* @param {{ json?: boolean, here?: boolean }} [options]
|
|
31
|
-
*/
|
|
5
|
+
// With a live worktree the root copy is a stale birth skeleton: build and write in the
|
|
6
|
+
// owning tree (`resolveFeatureTree`) unless `--here` forces the cwd.
|
|
32
7
|
export function handoffCommand(featureName, options = {}) {
|
|
33
8
|
const cwd = process.cwd();
|
|
34
9
|
|
|
@@ -44,9 +19,7 @@ export function handoffCommand(featureName, options = {}) {
|
|
|
44
19
|
}
|
|
45
20
|
|
|
46
21
|
if (owner.source === 'branch-only') {
|
|
47
|
-
// The
|
|
48
|
-
// into ANY tree right now. A handoff points at files on disk — pointing
|
|
49
|
-
// at files that exist nowhere is worse than recusing outright.
|
|
22
|
+
// The handoff points at files on disk; a truth materialized in no tree is refused.
|
|
50
23
|
const hint = `morph-spec worktree setup ${featureName} (ou: git checkout ${owner.branch})`;
|
|
51
24
|
if (options.json) {
|
|
52
25
|
console.log(JSON.stringify({ error: 'feature-not-in-tree', feature: featureName, branch: owner.branch, hint }));
|
|
@@ -10,32 +10,8 @@ import { morphBranches, currentBranch, branchView } from '../../lib/worktree/fea
|
|
|
10
10
|
import { buildPrIndex } from '../../lib/git/pr-index.js';
|
|
11
11
|
import { pendingPrFeatures, prForFeature } from '../../lib/git/pending-prs.js';
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
* One row per feature: slug × workType × status × phase × gates (G1/Gd/G3) ×
|
|
17
|
-
* description, plus a flags column for anomalies (missing description, gate
|
|
18
|
-
* order, dangling worktree pointer). Complements `delete`: see what's dead
|
|
19
|
-
* before removing it. Replaces the ad-hoc "loop over every feature.json in
|
|
20
|
-
* python" audit.
|
|
21
|
-
*
|
|
22
|
-
* Read-only by construction — reads feature.json straight off disk (never
|
|
23
|
-
* `loadState`, which mutates), so listing can never resurrect or prune state.
|
|
24
|
-
* Always exits 0 (fail-open — an empty board is an answer).
|
|
25
|
-
*
|
|
26
|
-
* WORKTREE TRUTH: `create --worktree` writes a COMPLETE feature.json into the
|
|
27
|
-
* primary root, then the branch-committed copy (`morph/{f}`) becomes the
|
|
28
|
-
* truth and the root's copy never changes again. By default this command
|
|
29
|
-
* reads that branch when it disagrees with the disk — same mechanism fleet
|
|
30
|
-
* already uses (`branchToReadFrom`/`branchView`, moved to feature-truth.js so
|
|
31
|
-
* both consumers share the one place this condition lives). `--no-branch`
|
|
32
|
-
* turns that off for a faster, disk-only read.
|
|
33
|
-
*
|
|
34
|
-
* NOT AN OBJECTIVE: this command only enumerates what exists IN THIS TREE.
|
|
35
|
-
* A feature that lives solely on a branch with no local `.morph/features/`
|
|
36
|
-
* copy in any tree never appears here — aggregating across trees is
|
|
37
|
-
* `morph-spec fleet`'s job, not this one's.
|
|
38
|
-
*/
|
|
13
|
+
// Read-only: feature.json straight off disk, never `loadState` (which mutates). By default the
|
|
14
|
+
// branch `morph/{f}` wins over a stale root copy; `--no-branch` reads only the disk.
|
|
39
15
|
|
|
40
16
|
const G = ['plan', 'uiux', 'review'];
|
|
41
17
|
|
|
@@ -59,34 +35,23 @@ function readFeaturesIn(baseDir, subdir, location) {
|
|
|
59
35
|
return rows;
|
|
60
36
|
}
|
|
61
37
|
|
|
62
|
-
/** The feature's `worktree` pointer, normalized with a computed `exists`. */
|
|
63
38
|
function readWorktreePointer(fj) {
|
|
64
39
|
const w = fj?.worktree;
|
|
65
40
|
if (!w?.path) return null;
|
|
66
41
|
return { path: w.path, branch: w.branch || null, exists: existsSync(w.path) };
|
|
67
42
|
}
|
|
68
43
|
|
|
69
|
-
|
|
70
|
-
* Build a row's flags/gates/identity shape from a `feature.json` object, given
|
|
71
|
-
* an already-resolved phase and provenance. Shared by the disk read
|
|
72
|
-
* (`buildRow`) and the branch read (`resolveBranchTruth`) so the two never
|
|
73
|
-
* drift on what counts as an anomaly.
|
|
74
|
-
*/
|
|
44
|
+
// Shared by the disk and branch reads so the two never drift on what counts as an anomaly.
|
|
75
45
|
function shapeRow(name, fj, location, phase, { corruptDisk = false, worktree = null, source = 'disk', branch = null } = {}) {
|
|
76
46
|
const gates = {};
|
|
77
47
|
for (const g of G) gates[g] = fj.approvalGates?.[g]?.approved === true;
|
|
78
48
|
|
|
79
|
-
//
|
|
80
|
-
// A recorded degradation is only worth recording if it is visible — this is
|
|
81
|
-
// the surface that makes it so (D2).
|
|
49
|
+
// Review approved via `--self-assessed`: a recorded degradation is only worth it if visible.
|
|
82
50
|
const selfAssessed = fj.approvalGates?.review?.selfAssessed === true;
|
|
83
51
|
|
|
84
52
|
const flags = [];
|
|
85
53
|
if (corruptDisk) flags.push('corrupt');
|
|
86
54
|
if (selfAssessed) flags.push('self-assessed-g3');
|
|
87
|
-
// Identity + gate-order anomalies come from the shared rules
|
|
88
|
-
// (core/state/feature-identity.js, core/state/gate-order.js) — same source
|
|
89
|
-
// doctor reports from.
|
|
90
55
|
for (const field of missingIdentity(fj)) flags.push(`missing-${field}`);
|
|
91
56
|
for (const v of gateOrderViolations(fj.approvalGates)) flags.push(`gate-order:${v.gate}`);
|
|
92
57
|
if (worktree && !worktree.exists) flags.push('worktree-missing');
|
|
@@ -104,10 +69,7 @@ function shapeRow(name, fj, location, phase, { corruptDisk = false, worktree = n
|
|
|
104
69
|
location,
|
|
105
70
|
createdAt: fj.createdAt || null,
|
|
106
71
|
updatedAt: fj.updatedAt || null,
|
|
107
|
-
//
|
|
108
|
-
// read from the LOCAL disk copy (root-owned bookkeeping, stripped from
|
|
109
|
-
// branch-committed copies at birth) regardless of where `source` reads
|
|
110
|
-
// status/phase/gates from.
|
|
72
|
+
// `worktree` always comes from the local disk copy, whatever `source` reads the rest from.
|
|
111
73
|
source,
|
|
112
74
|
branch,
|
|
113
75
|
worktree,
|
|
@@ -124,14 +86,7 @@ function buildRow(name, fj, fdir, location) {
|
|
|
124
86
|
});
|
|
125
87
|
}
|
|
126
88
|
|
|
127
|
-
|
|
128
|
-
* Refresh active rows from the branch that owns their truth, when the disk
|
|
129
|
-
* copy in THIS tree is not that owner. Never throws — a git hiccup or a
|
|
130
|
-
* corrupt blob just leaves the disk-read row in place.
|
|
131
|
-
*
|
|
132
|
-
* @param {string} cwd
|
|
133
|
-
* @param {object[]} rows - mutated in place (active rows only)
|
|
134
|
-
*/
|
|
89
|
+
// Never throws: a git hiccup or a corrupt blob leaves the disk-read row in place.
|
|
135
90
|
function resolveBranchTruth(cwd, rows) {
|
|
136
91
|
if (!isGitRepo(cwd)) return;
|
|
137
92
|
let head;
|
|
@@ -160,15 +115,8 @@ function resolveBranchTruth(cwd, rows) {
|
|
|
160
115
|
}
|
|
161
116
|
}
|
|
162
117
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
* the signature left behind when a feature is merged and archived on its
|
|
166
|
-
* branch (or via GitHub) without `morph-spec finish` ever running here:
|
|
167
|
-
* the untracked root skeleton from `create` survives `git pull` (it was
|
|
168
|
-
* never tracked, so pull has nothing to remove) while `.morph/archive/{f}`
|
|
169
|
-
* arrives for real. Flagging beats suppressing the row — a read-only board
|
|
170
|
-
* that resurrects or prunes nothing must not silently hide the anomaly either.
|
|
171
|
-
*/
|
|
118
|
+
// The untracked root skeleton survives a `git pull` that brings `.morph/archive/{f}`. Flagged,
|
|
119
|
+
// never hidden: a read-only board must not silently suppress the anomaly.
|
|
172
120
|
function flagArchivedElsewhere(cwd, rows) {
|
|
173
121
|
for (const row of rows) {
|
|
174
122
|
if (row.location !== 'active') continue;
|
|
@@ -178,13 +126,8 @@ function flagArchivedElsewhere(cwd, rows) {
|
|
|
178
126
|
}
|
|
179
127
|
}
|
|
180
128
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
* arquivada nesta árvore. É o estado que o `finish --pr` deixa: o scaffold do
|
|
184
|
-
* root foi apagado pelo shed e o archive viaja no diff do PR, chegando na
|
|
185
|
-
* default apenas no merge. Sem esta linha o trabalho fica sem dono no quadro,
|
|
186
|
-
* e uma branch sem dono é candidata a poda.
|
|
187
|
-
*/
|
|
129
|
+
// O estado que o `finish --pr` deixa: sem esta linha o trabalho fica sem dono no quadro, e
|
|
130
|
+
// uma branch sem dono é candidata a poda.
|
|
188
131
|
function pendingRow(p) {
|
|
189
132
|
return {
|
|
190
133
|
feature: p.feature,
|
|
@@ -232,15 +175,8 @@ export function listCommand(options = {}) {
|
|
|
232
175
|
// Commander's `--no-branch` sets options.branch = false; anything else (undefined/true) reads the branch.
|
|
233
176
|
if (options.branch !== false) resolveBranchTruth(cwd, rows);
|
|
234
177
|
|
|
235
|
-
//
|
|
236
|
-
// coluna e
|
|
237
|
-
//
|
|
238
|
-
// `prIndex` continua `null` quando a pergunta não pôde ser feita (sem `gh`,
|
|
239
|
-
// sem auth, remoto não-GitHub, ou `--no-branch`). É o mesmo sinal que
|
|
240
|
-
// `--json` já expõe como `pr: null` por linha — a tabela texto usa o mesmo
|
|
241
|
-
// fato para decidir se a coluna PR sequer aparece (achado #2): índice `null`
|
|
242
|
-
// degrada para o comportamento de antes, sem coluna e sem `-` que afirme
|
|
243
|
-
// ausência.
|
|
178
|
+
// Uma consulta ao `gh` por invocação. `prIndex` `null` = a pergunta não pôde ser feita: sem
|
|
179
|
+
// coluna PR, e sem `-` que afirme "sem PR" para o que é "não sabemos".
|
|
244
180
|
let prIndex = null;
|
|
245
181
|
if (options.branch !== false) {
|
|
246
182
|
prIndex = buildPrIndex(cwd);
|
|
@@ -249,8 +185,7 @@ export function listCommand(options = {}) {
|
|
|
249
185
|
row.pr = pr ? { number: pr.number, state: pr.state, url: pr.url, ageDays: null } : null;
|
|
250
186
|
}
|
|
251
187
|
const known = new Set(rows.map((r) => r.feature));
|
|
252
|
-
// `null` propaga desconhecido: nenhuma linha sintetizada
|
|
253
|
-
// afirmação de que não há pendência.
|
|
188
|
+
// `null` propaga desconhecido: nenhuma linha sintetizada.
|
|
254
189
|
for (const p of pendingPrFeatures(cwd, { index: prIndex }) || []) {
|
|
255
190
|
if (!known.has(p.feature)) rows.push(pendingRow(p));
|
|
256
191
|
}
|
|
@@ -271,13 +206,8 @@ export function listCommand(options = {}) {
|
|
|
271
206
|
}
|
|
272
207
|
|
|
273
208
|
const col = (s, w) => String(s ?? '-').padEnd(w);
|
|
274
|
-
// A coluna PR só aparece quando o índice foi construído com sucesso (achado
|
|
275
|
-
// #2) — um índice `null` não vira uma coluna cheia de `-`, que afirmaria
|
|
276
|
-
// "sem PR" para algo que na verdade é "não sabemos".
|
|
277
209
|
const showPr = Boolean(prIndex);
|
|
278
|
-
// `pr-pending` é
|
|
279
|
-
// não deve marcar `⚠` nem contar no rodapé (achado #6). Continua em
|
|
280
|
-
// `flags` no `--json`: é metadado real, só não é anomalia.
|
|
210
|
+
// `pr-pending` é trabalho em voo, não anomalia: segue em `flags` no `--json`, sem `⚠`.
|
|
281
211
|
const warnFlags = (r) => r.flags.filter((f) => f !== 'pr-pending');
|
|
282
212
|
console.log(chalk.white.bold(
|
|
283
213
|
col('FEATURE', 22) + col('TYPE', 9) + col('STATUS', 13) + col('PHASE', 17) + col('WHERE', 22) + (showPr ? col('PR', 12) : '')
|