@polymorphism-tech/morph-spec 8.47.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 +51 -43
- package/README.md +39 -38
- package/bin/morph-spec.js +41 -113
- package/bin/validate.js +1 -147
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +86 -21
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +1 -1
- package/framework/commands/morph-preflight.md +2 -3
- package/framework/commands/morph-status.md +4 -4
- package/framework/docs/CLI.md +574 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- 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 +3 -17
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
- package/framework/hooks/claude-code/core/gate-guard.js +9 -78
- 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 +36 -2
- package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
- package/framework/hooks/claude-code/core/state-sync.js +15 -85
- package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
- package/framework/hooks/claude-code/core/validate-completion.js +37 -147
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
- package/framework/hooks/claude-code/core/worktree-context.js +15 -114
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- 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 +11 -76
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +168 -44
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +11 -43
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +59 -0
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +4 -0
- package/framework/hooks/shared/gate-question.js +12 -0
- 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 +14 -72
- 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 +99 -0
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +51 -247
- package/framework/hooks/shared/state-writer.js +9 -54
- 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/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- 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/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -50
- package/framework/skills/README.md +3 -4
- 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 +149 -222
- package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +137 -299
- package/framework/skills/core/morph-review/SKILL.md +111 -136
- package/framework/skills/core/morph-standards/SKILL.md +27 -39
- package/framework/skills/core/morph-uiux/SKILL.md +94 -152
- package/framework/standards/STANDARDS.json +109 -1219
- package/framework/standards/STANDARDS.meta.json +1319 -0
- 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/evals-with-cache.md +1 -2
- 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 +3 -3
- 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 +7 -7
- 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/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +15 -179
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- 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/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +18 -105
- 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 +17 -111
- 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-procs.js +1 -1
- package/src/commands/project/doctor.js +53 -307
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +6 -35
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +8 -68
- package/src/commands/project/install-plugin-cmd.js +0 -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 +65 -72
- package/src/commands/project/status.js +28 -121
- package/src/commands/project/update.js +31 -223
- package/src/commands/project/worktree.js +109 -883
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +65 -120
- package/src/commands/state/approve.js +87 -229
- package/src/commands/state/archive.js +23 -139
- package/src/commands/state/auto-approval.js +65 -0
- package/src/commands/state/create.js +49 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +105 -494
- package/src/commands/state/gate-check.js +74 -183
- package/src/commands/state/gate-decision.js +8 -42
- package/src/commands/state/handoff.js +4 -31
- package/src/commands/state/list.js +21 -91
- package/src/commands/state/report-check.js +134 -338
- package/src/commands/state/retype.js +12 -28
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +79 -19
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +52 -216
- package/src/commands/validation/verify.js +45 -160
- package/src/core/paths/output-schema.js +9 -155
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +2 -30
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +32 -395
- 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 +21 -0
- package/src/core/work-types.js +10 -45
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
- package/src/dashboard/app/components/CommandPalette.jsx +4 -11
- 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 +3 -9
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
- package/src/dashboard/app/pages/Features.jsx +9 -16
- package/src/dashboard/app/pages/Guide.jsx +2 -3
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
- 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 +14 -83
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +1 -7
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +92 -113
- 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 +4 -7
- 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 +14 -13
- 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 +8 -59
- 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 +37 -112
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +11 -122
- 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 +2 -1
- 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 +17 -17
- 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 +2 -6
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +8 -33
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/mutation/dotnet-introspection.js +281 -0
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +47 -13
- package/src/lib/plan/artifact-vocabulary.js +40 -0
- package/src/lib/plan/checks.js +208 -0
- package/src/lib/plan/plan-parser.js +204 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +204 -0
- package/src/lib/risk-detector.js +131 -274
- package/src/lib/specs/adr-index.js +38 -85
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +31 -0
- package/src/lib/specs/gfm-table.js +11 -0
- package/src/lib/specs/revocations.js +31 -102
- package/src/lib/specs/screen-checklist.js +56 -132
- package/src/lib/stack/dotnet-build-ignores.js +43 -0
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +260 -0
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +37 -0
- package/src/lib/standards/sections.js +47 -0
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +23 -179
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -313
- package/src/lib/tasks/test-runner.js +107 -447
- package/src/lib/telemetry/metrics.js +13 -103
- package/src/lib/telemetry/telemetry-reader.js +13 -238
- package/src/lib/telemetry/verify-event.js +4 -34
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
- 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 +24 -137
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +54 -156
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +152 -575
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -137
- 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 +31 -126
- 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 -138
- package/src/utils/agents-installer.js +92 -272
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -87
- package/src/utils/file-copier.js +32 -90
- package/src/utils/hooks-installer.js +26 -296
- package/src/utils/skills-installer.js +4 -102
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -57
- 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/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/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/commands/utils/index.js +0 -3
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/evaluator-report.js +0 -104
- package/src/lib/generators/design-system-generator.js +0 -508
- 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/dotnet-test-introspection.js +0 -632
- 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/utils/agent-id-mapper.js +0 -11
|
@@ -1,41 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Telemetry Hook: harness execution-path capture
|
|
5
|
-
*
|
|
6
|
-
* Events: PostToolUse + PostToolUseFailure (all tools), PostToolBatch,
|
|
7
|
-
* SubagentStop, UserPromptSubmit
|
|
8
|
-
*
|
|
9
|
-
* morph-spec is a Natural Language Harness: there is no central runtime to
|
|
10
|
-
* instrument — Claude Code executes the skills and sub-agents. The only
|
|
11
|
-
* vantage points are hooks. This one is the generic capture point: every tool
|
|
12
|
-
* call and every user prompt is appended (one JSON line) to
|
|
13
|
-
* .morph/logs/events.jsonl, from which `morph-spec telemetry` and
|
|
14
|
-
* `morph-spec cost` reconstruct the path and measure it.
|
|
15
|
-
*
|
|
16
|
-
* Correlation is by an EXPLICIT key (feature + phase) written into each event,
|
|
17
|
-
* not by ambient span context — hooks fire as separate node processes with no
|
|
18
|
-
* shared trace context. `sessionId` and `agentId` (present only inside a
|
|
19
|
-
* sub-agent) separate the orchestrator from the sub-agents it dispatched.
|
|
20
|
-
*
|
|
21
|
-
* PostToolUse fires only when a tool SUCCEEDS; a Bash command that exits
|
|
22
|
-
* non-zero fires PostToolUseFailure instead. Only the exit code parsed from
|
|
23
|
-
* its `error` first line is kept — the rest of `error` is the command's
|
|
24
|
-
* output, where a secret value can appear with no name beside it.
|
|
25
|
-
*
|
|
26
|
-
* SINK RESOLUTION: the event is appended to the `.morph/` tree that OWNS the
|
|
27
|
-
* tool's target file — the tree of its `.morph/features/` segment
|
|
28
|
-
* (`resolveMorphRoot`), else the nearest morph tree above the file; tools with
|
|
29
|
-
* no file path sink on the nearest morph tree above the payload's `cwd`, so a
|
|
30
|
-
* sub-agent working in a worktree logs its Bash calls next to its Reads and
|
|
31
|
-
* Writes. The feature is the tree's BINDING (`morph/<f>` branch, then
|
|
32
|
-
* `MORPH_FEATURE` in `.morph/worktree.env`) before its most recent active
|
|
33
|
-
* feature; a bound feature with no `feature.json` in that tree keeps phase null.
|
|
34
|
-
*
|
|
35
|
-
* SILENT: never blocks, never injects context — only appends. Fail-open on any
|
|
36
|
-
* error (exit 0). Only active inside a morph project (stateExists gate).
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
3
|
import { statSync, readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
40
4
|
import { dirname, join } from 'path';
|
|
41
5
|
import { stateExists, getActiveFeature, derivePhaseForFeature } from '../../shared/state-reader.js';
|
|
@@ -51,10 +15,7 @@ function normalize(p) {
|
|
|
51
15
|
return (p || '').replace(/\\/g, '/');
|
|
52
16
|
}
|
|
53
17
|
|
|
54
|
-
//
|
|
55
|
-
// per-line cost. The ORIGINAL length travels in a sibling `<field>Chars` key,
|
|
56
|
-
// which `morph-spec cost` prefers over the truncated string's own length —
|
|
57
|
-
// a contract with the telemetry reader: do not rename either half.
|
|
18
|
+
// The original length travels in a sibling `<field>Chars` key that `morph-spec cost` prefers: do not rename either half.
|
|
58
19
|
const MAX_FIELD_CHARS = 2000;
|
|
59
20
|
const TRUNCATE_SUFFIX = '…[truncado]';
|
|
60
21
|
|
|
@@ -64,13 +25,7 @@ const DEFAULT_READ_LINES = 2000;
|
|
|
64
25
|
const MAX_SLICE_BYTES = 10 * 1024 * 1024;
|
|
65
26
|
const BINARY_READ = /\.(png|jpe?g|gif|webp|bmp|ico|pdf)$/i;
|
|
66
27
|
|
|
67
|
-
|
|
68
|
-
* Redact, then cap a free-text field, reporting its original length. Redacting
|
|
69
|
-
* first keeps a token cut at the cap from surviving as a recognizable prefix.
|
|
70
|
-
*
|
|
71
|
-
* @param {string} value
|
|
72
|
-
* @returns {{ value: string, chars: number }}
|
|
73
|
-
*/
|
|
28
|
+
// Redact before capping: a token cut at the cap would survive as a recognizable prefix.
|
|
74
29
|
function capField(value) {
|
|
75
30
|
const str = typeof value === 'string' ? value : '';
|
|
76
31
|
const clean = redactSecrets(str);
|
|
@@ -78,15 +33,11 @@ function capField(value) {
|
|
|
78
33
|
return { value: clean.slice(0, MAX_FIELD_CHARS) + TRUNCATE_SUFFIX, chars: str.length };
|
|
79
34
|
}
|
|
80
35
|
|
|
81
|
-
|
|
82
|
-
* Classify a Read target into the telemetry `kind` taxonomy — a contract with
|
|
83
|
-
* src/lib/telemetry/telemetry-reader.js, so these values are pinned. Specific
|
|
84
|
-
* categories first, 'code' last.
|
|
85
|
-
*/
|
|
36
|
+
// The `kind` values are a contract with src/lib/telemetry/telemetry-reader.js.
|
|
86
37
|
function classifyReadKind(target) {
|
|
87
38
|
const t = (target || '').toLowerCase();
|
|
88
39
|
|
|
89
|
-
if (t.includes('evals/review-checklist')) return '
|
|
40
|
+
if (t.includes('evals/review-checklist')) return 'checklist';
|
|
90
41
|
if (t.includes('standards/')) return 'standard';
|
|
91
42
|
if (t.endsWith('skill.md') || t.includes('/skills/')) return 'skill';
|
|
92
43
|
if (t.split('/').pop() === 'agents.json') return 'agents';
|
|
@@ -95,11 +46,11 @@ function classifyReadKind(target) {
|
|
|
95
46
|
if (inFeature) {
|
|
96
47
|
const rel = inFeature[1];
|
|
97
48
|
const PLAN_ARTIFACT_BASENAMES = new Set([
|
|
98
|
-
'
|
|
99
|
-
'
|
|
49
|
+
'plan.md', 'checks.json',
|
|
50
|
+
'handoff.md',
|
|
100
51
|
]);
|
|
101
52
|
const base = rel.split('/').pop();
|
|
102
|
-
if (PLAN_ARTIFACT_BASENAMES.has(base) || rel.includes('
|
|
53
|
+
if (PLAN_ARTIFACT_BASENAMES.has(base) || rel.includes('reports/')) {
|
|
103
54
|
return 'plan-artifact';
|
|
104
55
|
}
|
|
105
56
|
}
|
|
@@ -107,7 +58,6 @@ function classifyReadKind(target) {
|
|
|
107
58
|
return 'code';
|
|
108
59
|
}
|
|
109
60
|
|
|
110
|
-
/** Nearest directory at or above `dir` that is a morph project; `fallback` when none. */
|
|
111
61
|
function morphRootAbove(dir, fallback) {
|
|
112
62
|
if (!dir) return fallback;
|
|
113
63
|
let current = normalize(dir);
|
|
@@ -122,12 +72,7 @@ function morphRootAbove(dir, fallback) {
|
|
|
122
72
|
|
|
123
73
|
const FEATURE_NAME = /^[A-Za-z0-9._-]+$/;
|
|
124
74
|
|
|
125
|
-
|
|
126
|
-
* The feature the TREE is bound to, before any guess from its state: the
|
|
127
|
-
* branch `morph/<f>` checked out there, then `MORPH_FEATURE` in its
|
|
128
|
-
* `.morph/worktree.env` (a task worktree sits on `morph-task/<n>`). Without
|
|
129
|
-
* it, a sub-agent's worktree reported the root's most recent active feature.
|
|
130
|
-
*/
|
|
75
|
+
// Branch binding before state: otherwise a sub-agent's worktree reports the root's most recent active feature.
|
|
131
76
|
async function boundFeature(root) {
|
|
132
77
|
const fromBranch = morphBranchFeature(root);
|
|
133
78
|
if (fromBranch) return fromBranch;
|
|
@@ -143,10 +88,6 @@ function featureFromPath(target) {
|
|
|
143
88
|
return m ? m[1] : null;
|
|
144
89
|
}
|
|
145
90
|
|
|
146
|
-
/**
|
|
147
|
-
* What a Read actually put in context: the requested line window, not the
|
|
148
|
-
* whole file. Binary reads (images, PDFs) go in whole.
|
|
149
|
-
*/
|
|
150
91
|
function readSliceBytes(absPath, input, size) {
|
|
151
92
|
if (BINARY_READ.test(absPath)) return size;
|
|
152
93
|
if (size > MAX_SLICE_BYTES) return undefined;
|
|
@@ -157,7 +98,7 @@ function readSliceBytes(absPath, input, size) {
|
|
|
157
98
|
return Buffer.byteLength(lines.slice(offset - 1, offset - 1 + limit).join('\n'), 'utf-8');
|
|
158
99
|
}
|
|
159
100
|
|
|
160
|
-
|
|
101
|
+
// Only the exit code is kept: the rest of `error` is command output, where a secret can appear with no name beside it.
|
|
161
102
|
function outcomeOf(payload, input) {
|
|
162
103
|
if (payload.hook_event_name === 'PostToolUseFailure') {
|
|
163
104
|
const firstLine = String(payload.error ?? '').split('\n', 1)[0];
|
|
@@ -181,11 +122,7 @@ function countByName(calls) {
|
|
|
181
122
|
return out;
|
|
182
123
|
}
|
|
183
124
|
|
|
184
|
-
|
|
185
|
-
* Sub-agent tokens live in their own transcript; each stop emits the delta.
|
|
186
|
-
* One file per agent: two sub-agents stopping together never share a
|
|
187
|
-
* read-modify-write, and one agent never stops twice at once.
|
|
188
|
-
*/
|
|
125
|
+
// One store file per agent, so two sub-agents stopping together never share a read-modify-write.
|
|
189
126
|
function agentUsageEvent(payload, cwd) {
|
|
190
127
|
const usage = readTranscriptUsage(payload.agent_transcript_path);
|
|
191
128
|
if (!usage || !payload.agent_id) return null;
|
|
@@ -204,11 +141,6 @@ function agentUsageEvent(payload, cwd) {
|
|
|
204
141
|
return { event: 'AgentUsage', ...usage, delta };
|
|
205
142
|
}
|
|
206
143
|
|
|
207
|
-
/**
|
|
208
|
-
* @param {object} payload - parsed hook payload (PostToolUse, PostToolUseFailure,
|
|
209
|
-
* PostToolBatch, SubagentStop or UserPromptSubmit)
|
|
210
|
-
* @returns {Promise<null>} always null — pure side effect (append + claim heartbeat)
|
|
211
|
-
*/
|
|
212
144
|
export async function run(payload) {
|
|
213
145
|
try {
|
|
214
146
|
if (!payload) return null;
|
|
@@ -222,8 +154,6 @@ export async function run(payload) {
|
|
|
222
154
|
|
|
223
155
|
if (!stateExists(cwd)) return null;
|
|
224
156
|
|
|
225
|
-
// getActiveFeature → loadState hydrates every feature.json: O(active
|
|
226
|
-
// features) per call, fine while archive keeps that set small.
|
|
227
157
|
let feature = null;
|
|
228
158
|
let phase = null;
|
|
229
159
|
try {
|
|
@@ -237,14 +167,9 @@ export async function run(payload) {
|
|
|
237
167
|
feature = active?.name ?? featureFromPath(targetFilePath);
|
|
238
168
|
if (feature) phase = active?.feature?.phase || derivePhaseForFeature(feature, cwd);
|
|
239
169
|
}
|
|
240
|
-
} catch {
|
|
241
|
-
// enrichment is best-effort — a missing feature never blocks capture
|
|
242
|
-
}
|
|
170
|
+
} catch {}
|
|
243
171
|
|
|
244
|
-
// Claim heartbeat:
|
|
245
|
-
// of life for the advisory feature claim. shouldHeartbeat() is a local,
|
|
246
|
-
// git-free pre-check that says no on ~99% of calls; only then is the git
|
|
247
|
-
// common dir resolved (pure filesystem, no subprocess).
|
|
172
|
+
// Claim heartbeat: shouldHeartbeat is a git-free pre-check that says no on ~99% of calls.
|
|
248
173
|
if (feature && payload.session_id) {
|
|
249
174
|
try {
|
|
250
175
|
const { shouldHeartbeat, heartbeatClaim } = await import('../../shared/claim-registry.js');
|
|
@@ -253,7 +178,7 @@ export async function run(payload) {
|
|
|
253
178
|
const common = resolveGitCommonDir(cwd);
|
|
254
179
|
if (common) heartbeatClaim(feature, { gitCommonDir: common, sessionId: payload.session_id });
|
|
255
180
|
}
|
|
256
|
-
} catch {
|
|
181
|
+
} catch {}
|
|
257
182
|
}
|
|
258
183
|
|
|
259
184
|
const hookEvent = payload.hook_event_name;
|
|
@@ -290,17 +215,14 @@ export async function run(payload) {
|
|
|
290
215
|
event.kind = classifyReadKind(event.target);
|
|
291
216
|
if (Number.isInteger(input.offset)) event.offset = input.offset;
|
|
292
217
|
if (Number.isInteger(input.limit)) event.limit = input.limit;
|
|
293
|
-
// A failed Read ingested nothing
|
|
294
|
-
// between the Read and this hook omits the numbers rather than fail.
|
|
218
|
+
// A failed Read ingested nothing; a file deleted before this hook just omits the numbers.
|
|
295
219
|
if (hookEvent !== 'PostToolUseFailure') {
|
|
296
220
|
try {
|
|
297
221
|
const abs = targetAbs || resolveTargetPath(event.target, projectDir);
|
|
298
222
|
event.bytes = statSync(abs).size;
|
|
299
223
|
const read = readSliceBytes(abs, input, event.bytes);
|
|
300
224
|
if (read !== undefined) event.bytesRead = read;
|
|
301
|
-
} catch {
|
|
302
|
-
// omit bytes
|
|
303
|
-
}
|
|
225
|
+
} catch {}
|
|
304
226
|
}
|
|
305
227
|
} else if (tool === 'Bash' || tool === 'PowerShell') {
|
|
306
228
|
const command = capField(input.command || '');
|
|
@@ -311,8 +233,6 @@ export async function run(payload) {
|
|
|
311
233
|
} else if (tool === 'Write' || tool === 'Edit') {
|
|
312
234
|
event.target = normalize(input.file_path || input.path);
|
|
313
235
|
} else {
|
|
314
|
-
// Every other tool still counts toward "the whole path" — a compact
|
|
315
|
-
// target keeps Grep/Glob/WebFetch bursts distinguishable.
|
|
316
236
|
const target = input.file_path || input.path || input.pattern || input.query || input.url || '';
|
|
317
237
|
if (target) event.target = normalize(String(target));
|
|
318
238
|
}
|
|
@@ -333,7 +253,6 @@ export async function run(payload) {
|
|
|
333
253
|
appendTelemetryEvent(event, cwd);
|
|
334
254
|
return null;
|
|
335
255
|
} catch {
|
|
336
|
-
// Fail-open
|
|
337
256
|
return null;
|
|
338
257
|
}
|
|
339
258
|
}
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Stop Hook: phase-transition alert, recap/tasks audit, session usage.
|
|
5
|
-
*
|
|
6
|
-
* Phase transition tracking: stores last-seen phase per feature in
|
|
7
|
-
* .morph/memory/phase-watch.json so the alert fires exactly once.
|
|
8
|
-
*
|
|
9
|
-
* Fail-open: exits 0 on any error.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
3
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
13
4
|
import { join } from 'path';
|
|
14
5
|
import { createHash } from 'crypto';
|
|
@@ -19,121 +10,40 @@ import { contextResult, runStandalone } from '../../shared/hook-response.js';
|
|
|
19
10
|
import { logHookActivity, logEvent } from '../../shared/activity-logger.js';
|
|
20
11
|
import { appendTelemetryEvent } from '../../shared/telemetry-logger.js';
|
|
21
12
|
import { readTranscriptUsage, usageDelta } from '../../shared/transcript-usage.js';
|
|
13
|
+
import { sliceProgress, sliceReportPath, inspectReport } from '../../shared/slice-progress.js';
|
|
22
14
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*
|
|
28
|
-
* Two checks:
|
|
29
|
-
* 1. Task IDs that appear as a "## T<n>" heading in recap.md but still
|
|
30
|
-
* carry status != 'done' in tasks.json — claim/state divergence.
|
|
31
|
-
* 2. Task headings without a "**Standard:**" line nearby (within 10 lines
|
|
32
|
-
* after the heading) — no standard citation = unauditable decision.
|
|
33
|
-
*/
|
|
34
|
-
function auditRecapAndTasks(featurePath) {
|
|
15
|
+
// Advisory: the same rule `report-check` applies when the orchestrator resumes control.
|
|
16
|
+
function auditSliceReports(featurePath) {
|
|
17
|
+
const progress = sliceProgress(featurePath);
|
|
18
|
+
if (!progress) return [];
|
|
35
19
|
const warnings = [];
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
recap = readFileSync(recapPath, 'utf-8');
|
|
43
|
-
tasksJson = JSON.parse(readFileSync(tasksJsonPath, 'utf-8'));
|
|
44
|
-
} catch {
|
|
45
|
-
return warnings;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const tasks = Array.isArray(tasksJson.tasks) ? tasksJson.tasks : [];
|
|
49
|
-
if (tasks.length === 0) return warnings;
|
|
50
|
-
|
|
51
|
-
// Index recap by task ID — collect line numbers where each task is mentioned
|
|
52
|
-
// as completed. Supports two formats agents commonly use:
|
|
53
|
-
// (a) "## T<n>" — heading style (per the SKILL.md template)
|
|
54
|
-
// (b) "| T<n> | ... done" — markdown table row with done in same row
|
|
55
|
-
const lines = recap.split('\n');
|
|
56
|
-
const recapMentions = new Map(); // id → starting line index
|
|
57
|
-
const headingRe = /^##\s+(T\d+)\b/;
|
|
58
|
-
const tableRowRe = /^\|\s*(T\d+)\s*\|.*\bdone\b/i;
|
|
59
|
-
lines.forEach((ln, i) => {
|
|
60
|
-
const heading = ln.match(headingRe);
|
|
61
|
-
if (heading && !recapMentions.has(heading[1])) {
|
|
62
|
-
recapMentions.set(heading[1], i);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const tableRow = ln.match(tableRowRe);
|
|
66
|
-
if (tableRow && !recapMentions.has(tableRow[1])) {
|
|
67
|
-
recapMentions.set(tableRow[1], i);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Check 1: status drift — task mentioned as done in recap but tasks.json
|
|
72
|
-
// still shows it as pending/in_progress.
|
|
73
|
-
for (const t of tasks) {
|
|
74
|
-
if (recapMentions.has(t.id) && t.status !== 'done') {
|
|
75
|
-
warnings.push(
|
|
76
|
-
`tasks.json drift: ${t.id} appears as done in recap.md but tasks.json shows status="${t.status}". Run writeTasksJson to sync.`
|
|
77
|
-
);
|
|
20
|
+
for (const slice of progress.slices) {
|
|
21
|
+
if (slice.status !== 'feito') continue;
|
|
22
|
+
const { missing } = inspectReport(sliceReportPath(featurePath, slice.id), { covers: slice.covers });
|
|
23
|
+
if (missing.length) {
|
|
24
|
+
const hints = missing.map((id) => MISSING_HINT[id] ?? `cite ${id} pelo id`);
|
|
25
|
+
warnings.push(`relatório de ${slice.id} sem: ${missing.join(', ')} — ${hints.join('; ')} (formato na skill morph-apply; morph-spec report-check)`);
|
|
78
26
|
}
|
|
79
27
|
}
|
|
80
|
-
|
|
81
|
-
// Check 2: missing Standard citation per task mention. Skip pure
|
|
82
|
-
// table-row mentions (they're a summary, the heading-style block
|
|
83
|
-
// is where the standard belongs) — only flag when the agent did
|
|
84
|
-
// not produce a heading block at all for a task that is also marked
|
|
85
|
-
// done in tasks.json.
|
|
86
|
-
const standardRe = /\*\*Standard:\*\*|\bStandard\s*consulted\b|\bstandards?\s+used\b/i;
|
|
87
|
-
const headingMentions = new Map();
|
|
88
|
-
lines.forEach((ln, i) => {
|
|
89
|
-
const m = ln.match(headingRe);
|
|
90
|
-
if (m && !headingMentions.has(m[1])) headingMentions.set(m[1], i);
|
|
91
|
-
});
|
|
92
|
-
for (const t of tasks) {
|
|
93
|
-
if (t.status !== 'done') continue;
|
|
94
|
-
const lineIdx = headingMentions.get(t.id);
|
|
95
|
-
if (lineIdx === undefined) {
|
|
96
|
-
// Task is done but has no heading block at all — recap is summary-only.
|
|
97
|
-
warnings.push(
|
|
98
|
-
`recap.md task ${t.id} (status=done in tasks.json) has no detailed "## ${t.id}" block. Per morph-implement SKILL.md, each done task needs a heading block with a "**Standard:** ..." citation so the reviewer can audit the decision.`
|
|
99
|
-
);
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
const window = lines.slice(lineIdx, lineIdx + 12).join('\n');
|
|
103
|
-
if (!standardRe.test(window)) {
|
|
104
|
-
warnings.push(
|
|
105
|
-
`recap.md task ${t.id} is missing a "**Standard:** ..." citation within 12 lines of its heading. Cite which standard from STANDARDS.json justified the implementation (or "ad-hoc (see decisions.md ADR-N)" if intentional).`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
28
|
return warnings;
|
|
111
29
|
}
|
|
112
30
|
|
|
113
|
-
|
|
31
|
+
const MISSING_HINT = {
|
|
32
|
+
what: '2-3 frases do que foi feito e como foi provado',
|
|
33
|
+
files: 'linha **Arquivos:** com todo arquivo criado ou modificado',
|
|
34
|
+
partial: '1ª linha PARCIAL: a fatia não terminou',
|
|
35
|
+
};
|
|
36
|
+
|
|
114
37
|
const COMPACT_TRIGGER_PHASES = new Set(['implement']);
|
|
115
38
|
|
|
116
|
-
// Phases where compaction is suggested when arriving (heavy spec phases)
|
|
117
39
|
const HEAVY_SOURCE_PHASES = new Set(['plan', 'uiux']);
|
|
118
40
|
|
|
119
|
-
/**
|
|
120
|
-
* @param {object} payload - parsed Stop stdin payload. Most of this hook's
|
|
121
|
-
* logic reads its own state from disk rather than the payload, EXCEPT the
|
|
122
|
-
* session-usage capture below, which needs `payload.transcript_path` (and
|
|
123
|
-
* `payload.session_id` for the throttle key) — the one thing only the
|
|
124
|
-
* payload carries.
|
|
125
|
-
* @returns {Promise<object|null>} a hookSpecificOutput advisory (phase
|
|
126
|
-
* transition / build failure / recap drift), or null (pass/silent).
|
|
127
|
-
*/
|
|
128
41
|
export async function run(payload) {
|
|
129
42
|
try {
|
|
130
|
-
// Prevent infinite loop
|
|
131
43
|
if (process.env.MORPH_STOP_HOOK_ACTIVE === '1') return null;
|
|
132
44
|
|
|
133
45
|
if (!stateExists()) return null;
|
|
134
46
|
|
|
135
|
-
// getActiveFeature() only returns in_progress/draft features.
|
|
136
|
-
// Fall back to the most recently updated feature when all features are 'done'.
|
|
137
47
|
let active = getActiveFeature() || getMostRecentFeature();
|
|
138
48
|
if (!active) return null;
|
|
139
49
|
|
|
@@ -142,9 +52,8 @@ export async function run(payload) {
|
|
|
142
52
|
const warnings = [];
|
|
143
53
|
|
|
144
54
|
const memDir = join(cwd, '.morph', 'memory');
|
|
145
|
-
try { mkdirSync(memDir, { recursive: true }); } catch {
|
|
55
|
+
try { mkdirSync(memDir, { recursive: true }); } catch {}
|
|
146
56
|
|
|
147
|
-
// ── Phase transition detection ────────────────────────────────────────────
|
|
148
57
|
const currentPhase = feature.phase || derivePhaseForFeature(name, cwd);
|
|
149
58
|
|
|
150
59
|
const watchPath = join(memDir, 'phase-watch.json');
|
|
@@ -155,13 +64,12 @@ export async function run(payload) {
|
|
|
155
64
|
|
|
156
65
|
const lastPhase = phaseWatch[name];
|
|
157
66
|
|
|
158
|
-
//
|
|
67
|
+
// Always stored, so the alert fires once per transition.
|
|
159
68
|
try {
|
|
160
69
|
phaseWatch[name] = currentPhase;
|
|
161
70
|
writeFileSync(watchPath, JSON.stringify(phaseWatch, null, 2), 'utf-8');
|
|
162
|
-
} catch {
|
|
71
|
+
} catch {}
|
|
163
72
|
|
|
164
|
-
// Alert when entering implement from a heavy spec phase
|
|
165
73
|
const isPhaseTransition = lastPhase && lastPhase !== currentPhase;
|
|
166
74
|
const enteringImplement = COMPACT_TRIGGER_PHASES.has(currentPhase);
|
|
167
75
|
const comingFromHeavyPhase = HEAVY_SOURCE_PHASES.has(lastPhase);
|
|
@@ -171,7 +79,7 @@ export async function run(payload) {
|
|
|
171
79
|
`⚡ Phase advanced: ${lastPhase} → implement`,
|
|
172
80
|
``,
|
|
173
81
|
`💡 Recommended before starting implementation:`,
|
|
174
|
-
` Run /compact to free up context —
|
|
82
|
+
` Run /compact to free up context — plan.md and the design`,
|
|
175
83
|
` loaded during planning take significant space. A compact now gives`,
|
|
176
84
|
` Claude a clean slate focused on writing code.`,
|
|
177
85
|
``,
|
|
@@ -179,18 +87,10 @@ export async function run(payload) {
|
|
|
179
87
|
);
|
|
180
88
|
}
|
|
181
89
|
|
|
182
|
-
//
|
|
183
|
-
// Surface drift between recap.md and tasks.json, plus missing standard
|
|
184
|
-
// citations. Advisory warnings only — Stop hook never blocks.
|
|
185
|
-
//
|
|
186
|
-
// The same audit re-runs every Stop; without dedup it re-injects the same
|
|
187
|
-
// ~500-token block turn after turn even when nothing changed. Hash-gate it:
|
|
188
|
-
// only inject when the warning SET differs from the last one actually
|
|
189
|
-
// surfaced for this feature. Resolved (no more warnings) clears the stored
|
|
190
|
-
// hash, so a later recurrence — even an identical one — is reported fresh.
|
|
90
|
+
// Hash-gated: the audit re-runs every Stop, so only a changed warning set is injected; resolving clears the hash.
|
|
191
91
|
try {
|
|
192
92
|
const featurePath = join(cwd, '.morph', 'features', name);
|
|
193
|
-
const
|
|
93
|
+
const reportWarnings = auditSliceReports(featurePath);
|
|
194
94
|
|
|
195
95
|
const warnedHashesPath = join(memDir, 'warned-hashes.json');
|
|
196
96
|
let warnedHashes = {};
|
|
@@ -198,28 +98,21 @@ export async function run(payload) {
|
|
|
198
98
|
if (existsSync(warnedHashesPath)) warnedHashes = JSON.parse(readFileSync(warnedHashesPath, 'utf-8'));
|
|
199
99
|
} catch { /* corrupt file — start fresh */ }
|
|
200
100
|
|
|
201
|
-
if (
|
|
202
|
-
const hash = createHash('sha1').update(
|
|
203
|
-
if (warnedHashes[name]?.
|
|
204
|
-
warnings.push('📋
|
|
205
|
-
for (const w of
|
|
206
|
-
warnedHashes[name] = { ...warnedHashes[name],
|
|
207
|
-
try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch {
|
|
101
|
+
if (reportWarnings.length > 0) {
|
|
102
|
+
const hash = createHash('sha1').update(reportWarnings.join('\n')).digest('hex');
|
|
103
|
+
if (warnedHashes[name]?.reports !== hash) {
|
|
104
|
+
warnings.push('📋 Relatórios de fatia:');
|
|
105
|
+
for (const w of reportWarnings) warnings.push(` • ${w}`);
|
|
106
|
+
warnedHashes[name] = { ...warnedHashes[name], reports: hash };
|
|
107
|
+
try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch {}
|
|
208
108
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch { /* non-blocking */ }
|
|
109
|
+
} else if (warnedHashes[name]?.reports) {
|
|
110
|
+
delete warnedHashes[name].reports;
|
|
111
|
+
try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch {}
|
|
213
112
|
}
|
|
214
|
-
} catch {
|
|
215
|
-
// Fail-open — audit must never block Stop
|
|
216
|
-
}
|
|
217
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
113
|
+
} catch {}
|
|
218
114
|
|
|
219
|
-
//
|
|
220
|
-
// Silent side effect. The transcript total is cumulative, so each event
|
|
221
|
-
// also carries the `delta` since the previous sample of the same session:
|
|
222
|
-
// summing deltas per sessionId is the only sum that does not double-count.
|
|
115
|
+
// The transcript total is cumulative: summing the per-session `delta` is the only sum that does not double-count.
|
|
223
116
|
try {
|
|
224
117
|
const usage = readTranscriptUsage(payload?.transcript_path);
|
|
225
118
|
if (usage) {
|
|
@@ -241,13 +134,10 @@ export async function run(payload) {
|
|
|
241
134
|
delta,
|
|
242
135
|
}, cwd);
|
|
243
136
|
stored[sessionKey] = usage;
|
|
244
|
-
try { writeFileSync(usagePath, JSON.stringify(stored, null, 2), 'utf-8'); } catch {
|
|
137
|
+
try { writeFileSync(usagePath, JSON.stringify(stored, null, 2), 'utf-8'); } catch {}
|
|
245
138
|
}
|
|
246
139
|
}
|
|
247
|
-
} catch {
|
|
248
|
-
// Fail-open — usage capture must never block Stop
|
|
249
|
-
}
|
|
250
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
140
|
+
} catch {}
|
|
251
141
|
|
|
252
142
|
if (warnings.length === 0) return null;
|
|
253
143
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Warn only: a source file written by the shell skips every Write/Edit guard. The command still runs.
|
|
3
|
+
import { contextResult, runStandalone } from '../../shared/hook-response.js';
|
|
4
|
+
import { stateExists } from '../../shared/state-reader.js';
|
|
5
|
+
import { writeTargets } from '../../shared/command-tokens.js';
|
|
6
|
+
import { isProjectSource } from '../../shared/source-files.js';
|
|
7
|
+
|
|
8
|
+
export async function run(payload) {
|
|
9
|
+
try {
|
|
10
|
+
if (!stateExists()) return null;
|
|
11
|
+
const sources = writeTargets(payload?.tool_input?.command || '').filter(isProjectSource);
|
|
12
|
+
if (!sources.length) return null;
|
|
13
|
+
return contextResult(
|
|
14
|
+
`⚠️ Código do projeto escrito pelo Bash (${sources.join(', ')}): os guards de escrita (fase, arquivo protegido, comment-guard) ` +
|
|
15
|
+
'só enxergam Write/Edit. Reescreva esse arquivo por Write/Edit.',
|
|
16
|
+
'PreToolUse',
|
|
17
|
+
);
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await runStandalone(run);
|