@polymorphism-tech/morph-spec 8.46.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +53 -44
- package/README.md +3 -4
- package/bin/morph-spec.js +38 -55
- package/bin/validate.js +5 -134
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +2 -2
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +75 -18
- package/framework/commands/create-umbraco-lp.md +2 -0
- package/framework/commands/migrate-lp-to-umbraco.md +2 -0
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +2 -2
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +17 -203
- package/framework/commands/morph-media.md +3 -0
- package/framework/commands/morph-preflight.md +7 -31
- package/framework/commands/morph-proposal.md +2 -50
- package/framework/commands/morph-status.md +3 -3
- package/framework/commands/morph-troubleshoot.md +2 -14
- package/framework/docs/CLI.md +585 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/comment-guard.js +1 -1
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
- package/framework/hooks/claude-code/core/gate-guard.js +2 -11
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
- package/framework/hooks/claude-code/core/state-sync.js +17 -19
- package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
- package/framework/hooks/claude-code/core/validate-completion.js +33 -97
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
- package/framework/hooks/claude-code/core/worktree-context.js +5 -4
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +1 -1
- package/framework/hooks/shared/build-check.js +10 -6
- package/framework/hooks/shared/command-tokens.js +169 -0
- package/framework/hooks/shared/comment-rules.js +1 -1
- package/framework/hooks/shared/comment-scanner.js +10 -23
- package/framework/hooks/shared/comment-stripper.js +1 -1
- package/framework/hooks/shared/e2e-check.js +67 -31
- package/framework/hooks/shared/errata.js +72 -0
- package/framework/hooks/shared/feature-locator.js +1 -1
- package/framework/hooks/shared/frontend-locations.js +6 -0
- package/framework/hooks/shared/gate-question.js +18 -0
- package/framework/hooks/shared/phase-utils.js +15 -32
- package/framework/hooks/shared/slice-progress.js +118 -0
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +61 -87
- package/framework/hooks/shared/state-writer.js +3 -3
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/ai-agents-standards.md +18 -16
- package/framework/rules/asaas-standards.md +3 -1
- package/framework/rules/csharp-standards.md +10 -8
- package/framework/rules/data-standards.md +5 -5
- package/framework/rules/dotnet-templates.md +1 -1
- package/framework/rules/frontend-standards.md +4 -2
- package/framework/rules/gsap-standards.md +10 -5
- package/framework/rules/hangfire-standards.md +3 -1
- package/framework/rules/infrastructure-standards.md +4 -2
- package/framework/rules/neon-auth-standards.md +3 -1
- package/framework/rules/neon-standards.md +6 -4
- package/framework/rules/nextjs-standards.md +13 -11
- package/framework/rules/nextjs-templates.md +1 -1
- package/framework/rules/resend-standards.md +3 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -52
- package/framework/skills/README.md +4 -5
- package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
- package/framework/skills/core/morph-apply/SKILL.md +150 -223
- package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
- package/framework/skills/core/morph-machine/SKILL.md +4 -4
- package/framework/skills/core/morph-media/SKILL.md +3 -3
- package/framework/skills/core/morph-plan/SKILL.md +138 -300
- package/framework/skills/core/morph-review/SKILL.md +112 -137
- package/framework/skills/core/morph-standards/SKILL.md +29 -41
- package/framework/skills/core/morph-uiux/SKILL.md +78 -153
- package/framework/standards/STANDARDS.json +90 -1219
- package/framework/standards/STANDARDS.meta.json +1305 -0
- package/framework/standards/ai-agents/evals-with-cache.md +2 -3
- package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
- package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
- package/framework/standards/ai-agents/prompt-sources.md +6 -4
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/structured-output.md +1 -1
- package/framework/standards/ai-agents/sweet-spot.md +1 -1
- package/framework/standards/architecture/code-comments.md +8 -8
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
- package/framework/standards/backend/api/validation.md +135 -47
- package/framework/standards/backend/database/postgresql/database.md +11 -7
- package/framework/standards/backend/database/vector-search-rag.md +207 -324
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
- package/framework/standards/backend/dotnet/result-pattern.md +40 -6
- package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
- package/framework/standards/backend/dotnet/scrutor.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
- package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
- package/framework/standards/data/nosql/cache/redis.md +120 -44
- package/framework/standards/data/nosql/cosmos-db.md +114 -71
- package/framework/standards/frontend/design-system/animations.md +1 -1
- package/framework/standards/frontend/design-system/naming.md +1 -1
- package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
- package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
- package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
- package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +6 -170
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
- package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
- package/framework/templates/infrastructure/github/README.md +2 -1
- package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +2 -2
- package/src/commands/observability/cost.js +18 -60
- package/src/commands/observability/telemetry.js +3 -5
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +9 -9
- package/src/commands/project/fleet.js +4 -4
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +6 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +79 -25
- package/src/commands/project/status.js +29 -69
- package/src/commands/project/update.js +33 -19
- package/src/commands/project/worktree.js +1 -1
- package/src/commands/state/advance.js +62 -46
- package/src/commands/state/approve.js +95 -138
- package/src/commands/state/archive.js +20 -84
- package/src/commands/state/auto-approval.js +68 -0
- package/src/commands/state/create.js +28 -1
- package/src/commands/state/delete.js +1 -1
- package/src/commands/state/finish.js +11 -23
- package/src/commands/state/gate-check.js +86 -95
- package/src/commands/state/gate-decision.js +6 -4
- package/src/commands/state/handoff.js +1 -1
- package/src/commands/state/list.js +9 -9
- package/src/commands/state/report-check.js +157 -311
- package/src/commands/state/retype.js +9 -3
- package/src/commands/validation/mutate.js +77 -14
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +60 -135
- package/src/commands/validation/verify.js +41 -82
- package/src/core/paths/output-schema.js +20 -67
- package/src/core/state/gate-order.js +5 -6
- package/src/core/state/lifecycle-audit.js +2 -2
- package/src/core/state/state-manager.js +17 -98
- package/src/core/templates/template-registry.js +2 -2
- package/src/core/templates/template-seed.js +22 -0
- package/src/core/work-types.js +10 -10
- package/src/dashboard/app/App.jsx +0 -2
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
- package/src/dashboard/app/components/CommandPalette.jsx +7 -8
- package/src/dashboard/app/components/EntityLink.jsx +1 -2
- package/src/dashboard/app/components/Markdown.jsx +1 -1
- package/src/dashboard/app/components/Sidebar.jsx +1 -2
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
- package/src/dashboard/app/lib/phases.js +2 -4
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
- package/src/dashboard/app/pages/Features.jsx +9 -9
- package/src/dashboard/app/pages/Guide.jsx +2 -2
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
- package/src/dashboard/app/pages/RulesMap.jsx +3 -3
- package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
- package/src/dashboard/app/pages/Standards.jsx +1 -1
- package/src/dashboard/scanner/activity-scanner.js +6 -16
- package/src/dashboard/scanner/agents-scanner.js +1 -1
- package/src/dashboard/scanner/docs-scanner.js +5 -23
- package/src/dashboard/scanner/features-scanner.js +95 -60
- package/src/dashboard/scanner/index.js +0 -2
- package/src/dashboard/scanner/project-api.js +1 -1
- package/src/dashboard/scanner/rules-scanner.js +15 -10
- package/src/dashboard/scanner/skills-scanner.js +2 -3
- package/src/dashboard/scanner/standards-scanner.js +2 -1
- package/src/lib/ci/ci-verify.js +17 -1
- package/src/lib/ci/github-ci.js +1 -1
- package/src/lib/detectors/index.js +3 -8
- package/src/lib/generators/handoff-generator.js +34 -65
- package/src/lib/generators/settings-generator.js +11 -89
- package/src/lib/git/evidence-staleness.js +1 -1
- package/src/lib/git/tree-fingerprint.js +19 -7
- package/src/lib/git/tree-state.js +2 -2
- package/src/lib/heavy/cleanup.js +1 -5
- package/src/lib/installers/memory-installer.js +6 -8
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +46 -9
- package/src/lib/plan/artifact-vocabulary.js +60 -0
- package/src/lib/plan/checks.js +234 -0
- package/src/lib/plan/plan-parser.js +209 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/review-reports.js +222 -0
- package/src/lib/risk-detector.js +134 -135
- package/src/lib/specs/adr-index.js +50 -69
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +44 -0
- package/src/lib/specs/gfm-table.js +17 -0
- package/src/lib/specs/old-format.js +68 -0
- package/src/lib/specs/revocations.js +37 -95
- package/src/lib/specs/screen-checklist.js +18 -17
- package/src/lib/stack/dotnet-build-ignores.js +47 -0
- package/src/lib/stack/stack-profile.js +1 -1
- package/src/lib/stack/stack-scan.js +267 -0
- package/src/lib/standards/registry.js +43 -0
- package/src/lib/standards/sections.js +58 -0
- package/src/lib/tasks/eval-node.js +4 -12
- package/src/lib/tasks/test-nodes.js +1 -74
- package/src/lib/tasks/test-runner.js +75 -117
- package/src/lib/telemetry/metrics.js +8 -69
- package/src/lib/telemetry/telemetry-reader.js +5 -118
- package/src/lib/telemetry/verify-event.js +5 -7
- package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
- package/src/lib/validators/reuse/reuse-validator.js +24 -48
- package/src/lib/validators/reuse/symbol-scanner.js +2 -4
- package/src/lib/validators/traceability-check.js +85 -129
- package/src/lib/validators/validation-runner.js +8 -88
- package/src/lib/validators/verify-runner.js +134 -301
- package/src/lib/worktree/docker-audit.js +1 -2
- package/src/lib/worktree/fleet-scanner.js +25 -38
- package/src/scripts/setup-infra.js +42 -22
- package/src/utils/agents-installer.js +92 -13
- package/src/utils/claude-settings-manager.js +0 -35
- package/src/utils/file-copier.js +34 -6
- package/src/utils/hooks-installer.js +16 -9
- package/src/utils/skills-installer.js +1 -2
- package/framework/commands/commit.md +0 -166
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rubrics/backend.json +0 -41
- package/framework/rubrics/default.json +0 -36
- package/framework/rubrics/frontend.json +0 -36
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/project/validate.js +0 -97
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/dashboard/app/pages/Rubrics.jsx +0 -97
- package/src/dashboard/scanner/rubrics-scanner.js +0 -12
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/evaluator-report.js +0 -91
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- package/src/lib/validators/architecture/architecture-validator.js +0 -270
- package/src/lib/validators/architecture/index.js +0 -7
- package/src/lib/validators/css/css-validator.js +0 -340
- package/src/lib/validators/css/index.js +0 -7
- package/src/lib/validators/design-system/design-system-validator.js +0 -237
- package/src/lib/validators/design-system/index.js +0 -7
- package/src/lib/validators/score-aggregator.js +0 -244
- package/src/lib/validators/ui/index.js +0 -7
- package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
|
@@ -74,8 +74,7 @@ function resolveGitCommonDir(cwd) {
|
|
|
74
74
|
* treating those as different paths would make every stack look foreign.
|
|
75
75
|
*
|
|
76
76
|
* Exported as the ONE copy of that rule: `doctor.js` compares a declared test
|
|
77
|
-
* node `cwd` with a discovered `package.json` folder through it
|
|
78
|
-
* `test-scope.js` takes only the CASE of a canonical path through it (#87 N3).
|
|
77
|
+
* node `cwd` with a discovered `package.json` folder through it.
|
|
79
78
|
*/
|
|
80
79
|
export const samePathCase = process.platform === 'win32' ? (s) => s.toLowerCase() : (s) => s;
|
|
81
80
|
const norm = (p) => samePathCase(resolve(String(p)).replace(/\\/g, '/'));
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* operator loses track of "what is each session doing, what is waiting on me".
|
|
7
7
|
* This scanner answers that from the primary root's vantage point: one entry
|
|
8
8
|
* per active feature, worktree or root-resident, with the operational facts
|
|
9
|
-
* (phase, pending gates,
|
|
9
|
+
* (phase, pending gates, slice counts, port block, handoff freshness, last
|
|
10
10
|
* commit, dirtiness) resolved per entry.
|
|
11
11
|
*
|
|
12
12
|
* Read-only and fail-open per FIELD: a corrupt feature.json or a git hiccup
|
|
@@ -24,52 +24,39 @@ import {
|
|
|
24
24
|
getFeature,
|
|
25
25
|
derivePhaseForFeature,
|
|
26
26
|
derivePhaseFrom,
|
|
27
|
+
fileSetArtifactProbe,
|
|
27
28
|
getPendingGates,
|
|
28
29
|
pendingGatesFrom,
|
|
29
30
|
} from '../../../framework/hooks/shared/state-reader.js';
|
|
30
31
|
import { listClaims } from '../../../framework/hooks/shared/claim-registry.js';
|
|
31
32
|
import { buildPrIndex } from '../git/pr-index.js';
|
|
32
33
|
import { pendingPrFeatures } from '../git/pending-prs.js';
|
|
34
|
+
import { sliceProgress, sliceProgressFrom } from '../../../framework/hooks/shared/slice-progress.js';
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
const data = JSON.parse(raw);
|
|
36
|
-
if (!Array.isArray(data.tasks)) return null;
|
|
37
|
-
return { done: data.tasks.filter((t) => t.status === 'done').length, total: data.tasks.length };
|
|
38
|
-
}
|
|
36
|
+
const counts = (progress) => (progress ? { done: progress.done, total: progress.total } : null);
|
|
39
37
|
|
|
40
38
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* directly. The real mechanism is which COPY of that file is on disk. Since
|
|
46
|
-
* v8.4 every feature is born on `morph/{f}` and its tasks.json is COMMITTED
|
|
47
|
-
* there; the primary root's working tree only ever holds the copy of the
|
|
48
|
-
* CHECKED-OUT branch. An operator sitting on the default therefore read the
|
|
49
|
-
* pre-work copy — the reported `2/2` for a feature whose branch already had
|
|
50
|
-
* T1..T3 all done.
|
|
39
|
+
* Slice progress for one feature, read from the copy that holds the work: a
|
|
40
|
+
* root-resident feature's plan checks and reports are committed on its own
|
|
41
|
+
* `morph/{f}` branch, while the root working tree only holds the checked-out
|
|
42
|
+
* branch's (pre-work) copy.
|
|
51
43
|
*
|
|
52
44
|
* @param {string} featureDir - the feature dir inside `path`
|
|
53
45
|
* @param {{ root: string, feature: string, featureBranch: string|null }} ctx
|
|
54
46
|
*/
|
|
55
|
-
function
|
|
47
|
+
function sliceCounts(featureDir, { root, feature, featureBranch } = {}) {
|
|
56
48
|
if (featureBranch && root && feature) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'git',
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
return countFrom(readFileSync(join(featureDir, '2-plan', 'tasks.json'), 'utf8'));
|
|
70
|
-
} catch {
|
|
71
|
-
return null;
|
|
49
|
+
const show = (rel) => {
|
|
50
|
+
try {
|
|
51
|
+
return execFileSync('git', ['show', `${featureBranch}:.morph/features/${feature}/${rel}`], { cwd: root, stdio: 'pipe' }).toString();
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const onBranch = sliceProgressFrom(show('2-plan/checks.json'), (id) => show(`3-implement/reports/${id}.md`));
|
|
57
|
+
if (onBranch) return counts(onBranch);
|
|
72
58
|
}
|
|
59
|
+
return counts(sliceProgress(featureDir));
|
|
73
60
|
}
|
|
74
61
|
|
|
75
62
|
function handoffInfo(featureDir) {
|
|
@@ -164,15 +151,15 @@ function buildRemote(feature, branch, heads, prs) {
|
|
|
164
151
|
function buildEntry(feature, { location, path, branch, root, allocations, blockSize, heads, prs, claims }) {
|
|
165
152
|
let workType = null;
|
|
166
153
|
let status = 'unknown';
|
|
167
|
-
let phase = '
|
|
154
|
+
let phase = 'plan';
|
|
168
155
|
let pendingGates = [];
|
|
169
156
|
|
|
170
|
-
// Resolved once and shared with
|
|
157
|
+
// Resolved once and shared with sliceCounts — see branchToReadFrom.
|
|
171
158
|
const featureBranch = branchToReadFrom(root, feature, location, branch);
|
|
172
159
|
|
|
173
160
|
try {
|
|
174
161
|
// B3b: phase and gates suffered the same branch-staleness B3 fixed for
|
|
175
|
-
//
|
|
162
|
+
// progress. They read `.morph/features/{f}/` through the working tree, so a
|
|
176
163
|
// root parked on the default reported the PRE-WORK phase for a feature
|
|
177
164
|
// whose branch was already at implement — and re-asked for a gate the
|
|
178
165
|
// feature branch had already approved.
|
|
@@ -180,7 +167,7 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
|
|
|
180
167
|
if (view) {
|
|
181
168
|
workType = view.fj?.workType || null;
|
|
182
169
|
status = view.fj?.status || 'unknown';
|
|
183
|
-
phase = derivePhaseFrom(view.fj, (
|
|
170
|
+
phase = derivePhaseFrom(view.fj, fileSetArtifactProbe(view.files));
|
|
184
171
|
pendingGates = pendingGatesFrom(view.fj);
|
|
185
172
|
} else {
|
|
186
173
|
const fj = getFeature(feature, path);
|
|
@@ -202,7 +189,7 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
|
|
|
202
189
|
location,
|
|
203
190
|
path,
|
|
204
191
|
branch,
|
|
205
|
-
|
|
192
|
+
slices: sliceCounts(featureDir, { root, feature, featureBranch }),
|
|
206
193
|
env: alloc && Number.isInteger(alloc.base)
|
|
207
194
|
? { base: alloc.base, range: [alloc.base, alloc.base + blockSize - 1] }
|
|
208
195
|
: null,
|
|
@@ -316,7 +303,7 @@ export function scanFleet(cwd = process.cwd()) {
|
|
|
316
303
|
location: 'pr-pending',
|
|
317
304
|
path: null,
|
|
318
305
|
branch: p.branch,
|
|
319
|
-
|
|
306
|
+
slices: null,
|
|
320
307
|
env: null,
|
|
321
308
|
handoff: { exists: false, generatedAt: null },
|
|
322
309
|
lastCommit: null,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { join, dirname, resolve } from 'path';
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
15
|
import { execSync } from 'child_process';
|
|
16
|
-
import { readFileSync, readdirSync } from 'fs';
|
|
16
|
+
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
17
17
|
import {
|
|
18
18
|
copyDirectory,
|
|
19
19
|
copyFile,
|
|
@@ -32,6 +32,7 @@ import { installClaudeHooks, installGlobalStatusline } from '../utils/claude-set
|
|
|
32
32
|
import { installSkills } from '../utils/skills-installer.js';
|
|
33
33
|
import { installAgents, installDomainAgents } from '../utils/agents-installer.js';
|
|
34
34
|
import { resolvePrimaryRoot } from '../commands/project/worktree.js';
|
|
35
|
+
import { findOldFormatFeatures, oldFormatGuardLines } from '../lib/specs/old-format.js';
|
|
35
36
|
import { reconcileLegacyTemplatesCopy } from '../lib/installers/templates-reconcile.js';
|
|
36
37
|
import { realEffects } from '../lib/installers/effects.js';
|
|
37
38
|
|
|
@@ -89,7 +90,27 @@ export function installRulesForStack(rulesSrc, rulesDest, projectTags, fx = real
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
/**
|
|
92
|
-
*
|
|
93
|
+
* Slash commands filtered by `stacks:` frontmatter. Clean-slate like the
|
|
94
|
+
* skills: removes only a framework command that the stack filtered out, never
|
|
95
|
+
* a command the user wrote into `.claude/commands/`.
|
|
96
|
+
*/
|
|
97
|
+
export function installCommandsForStack(commandsSrc, commandsDest, projectTags, fx = realEffects()) {
|
|
98
|
+
const sourceFiles = readdirSync(commandsSrc).filter(f => f.endsWith('.md'));
|
|
99
|
+
let installed = 0;
|
|
100
|
+
for (const file of sourceFiles) {
|
|
101
|
+
const dest = join(commandsDest, file);
|
|
102
|
+
if (shouldInstall(parseStacks(readFileSync(join(commandsSrc, file), 'utf-8')), projectTags)) {
|
|
103
|
+
fx.copyFile(join(commandsSrc, file), dest);
|
|
104
|
+
installed++;
|
|
105
|
+
} else if (!fx.dryRun && existsSync(dest)) {
|
|
106
|
+
fx.remove(dest);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { installed };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Re-install only stack-aware assets (commands, rules, skills, agents) using current config.json.
|
|
93
114
|
* Skips infrastructure steps (dirs, framework copy, hooks).
|
|
94
115
|
*
|
|
95
116
|
* @param {string} targetPath - Absolute path to the target project directory
|
|
@@ -109,8 +130,16 @@ async function installStackAwareAssets(targetPath, log) {
|
|
|
109
130
|
} catch { /* ignore malformed config */ }
|
|
110
131
|
}
|
|
111
132
|
|
|
112
|
-
log('Installing stack-filtered rules...');
|
|
113
133
|
const claudeDest = join(targetPath, '.claude');
|
|
134
|
+
log('Installing stack-filtered commands...');
|
|
135
|
+
const commandsSrc = join(FRAMEWORK_DIR, 'commands');
|
|
136
|
+
const commandsDest = join(claudeDest, 'commands');
|
|
137
|
+
if (await pathExists(commandsSrc)) {
|
|
138
|
+
await ensureDir(commandsDest);
|
|
139
|
+
installCommandsForStack(commandsSrc, commandsDest, projectTags);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
log('Installing stack-filtered rules...');
|
|
114
143
|
const rulesSrc = join(FRAMEWORK_DIR, 'rules');
|
|
115
144
|
const rulesDest = join(claudeDest, 'rules');
|
|
116
145
|
if (await pathExists(rulesSrc)) {
|
|
@@ -213,6 +242,14 @@ export async function setupInfra(targetPath, {
|
|
|
213
242
|
}
|
|
214
243
|
}
|
|
215
244
|
|
|
245
|
+
// Same refusal as `update`, and neither `force` nor `reinstall` waives it:
|
|
246
|
+
// `init --force` and `setup-infra` overwrite `.morph/` without passing through it.
|
|
247
|
+
const oldFormat = findOldFormatFeatures(targetPath);
|
|
248
|
+
if (oldFormat.length) {
|
|
249
|
+
for (const line of oldFormatGuardLines(oldFormat, targetPath)) log(line);
|
|
250
|
+
return { refused: true, reason: 'old-format-features', features: oldFormat };
|
|
251
|
+
}
|
|
252
|
+
|
|
216
253
|
// --- Reinstall mode: only re-install stack-aware assets ---
|
|
217
254
|
if (reinstall) {
|
|
218
255
|
log('setup-infra (reinstall): re-installing stack-aware assets...');
|
|
@@ -336,18 +373,8 @@ export async function setupInfra(targetPath, {
|
|
|
336
373
|
await copyDirectory(hooksSrc, hooksDest);
|
|
337
374
|
}
|
|
338
375
|
|
|
339
|
-
// --- 7b. Copy framework/rubrics → .morph/framework/rubrics ---
|
|
340
|
-
log('Step 7b: Copying rubrics...');
|
|
341
|
-
const rubricsSrc = join(FRAMEWORK_DIR, 'rubrics');
|
|
342
|
-
const rubricsDest = join(frameworkDestDir, 'rubrics');
|
|
343
|
-
if (await pathExists(rubricsSrc)) {
|
|
344
|
-
await copyDirectory(rubricsSrc, rubricsDest);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
376
|
// --- 7c. Copy framework/evals → .morph/framework/evals ---
|
|
348
|
-
|
|
349
|
-
// morph-review/agents.json via .morph/framework/evals/.
|
|
350
|
-
log('Step 7c: Copying evals (rubrics .md + trace schema)...');
|
|
377
|
+
log('Step 7c: Copying evals (review checklist + trace schema)...');
|
|
351
378
|
const evalsSrc = join(FRAMEWORK_DIR, 'evals');
|
|
352
379
|
const evalsDest = join(frameworkDestDir, 'evals');
|
|
353
380
|
if (await pathExists(evalsSrc)) {
|
|
@@ -396,16 +423,9 @@ export async function setupInfra(targetPath, {
|
|
|
396
423
|
await copyDirectory(schemasSrc, schemasDest);
|
|
397
424
|
}
|
|
398
425
|
|
|
399
|
-
// --- 9. Copy framework/commands → .claude/commands ---
|
|
400
|
-
log('Step 9: Setting up Claude Code integration...');
|
|
401
426
|
const claudeDest = join(targetPath, '.claude');
|
|
402
|
-
const commandsSrc = join(FRAMEWORK_DIR, 'commands');
|
|
403
|
-
const commandsDest = join(claudeDest, 'commands');
|
|
404
|
-
if (await pathExists(commandsSrc)) {
|
|
405
|
-
await copyDirectory(commandsSrc, commandsDest);
|
|
406
|
-
}
|
|
407
427
|
|
|
408
|
-
// ---
|
|
428
|
+
// --- 9-13. Install stack-aware assets (commands, rules, skills, agents) ---
|
|
409
429
|
log('Steps 10-13: Installing stack-aware assets...');
|
|
410
430
|
const { agentCounts, domainCounts, projectTags } = await installStackAwareAssets(targetPath, log);
|
|
411
431
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* In v5 the harness has 8 personas (no tiers) across two axes — domain
|
|
17
17
|
* (dotnet-senior, ef-modeler, maf-expert, nextjs-expert, ui-designer,
|
|
18
|
-
* infra-engineer,
|
|
18
|
+
* infra-engineer, verifier, reviewer, security) and stage (scout, read-only diagnosis before
|
|
19
19
|
* implementation). Each persona is a subagent the Specialist (Claude in
|
|
20
20
|
* implementer role) can dispatch. The 3 roles (Orchestrator/Specialist/
|
|
21
21
|
* Evaluator) are assumed by Claude itself per MORPH.md, not as separate agents.
|
|
@@ -294,7 +294,7 @@ export function loadKnownMcpIds(frameworkDir) {
|
|
|
294
294
|
* with NO known stack tags (greenfield / install before config.json exists)
|
|
295
295
|
* EVERY persona is generated — a project that dispatches `morph-{id}` must
|
|
296
296
|
* find the subagent registered, and an install that only lands
|
|
297
|
-
* morph-
|
|
297
|
+
* morph-verifier forces every domain dispatch into the general-purpose
|
|
298
298
|
* fallback. Stack filtering only applies when the project's stack is KNOWN
|
|
299
299
|
* (tags non-empty): the fallback stays reserved for stacks the project
|
|
300
300
|
* genuinely does not have. Note this deliberately differs from rules/skills
|
|
@@ -431,10 +431,8 @@ function buildFrontmatter(agent, description, knownMcpIds, maxTurns) {
|
|
|
431
431
|
// Canonical 1:1 naming: frontmatter name === filename === subagent_type.
|
|
432
432
|
`name: morph-${agent.id}`,
|
|
433
433
|
`description: ${description}`,
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
// scout → sonnet (measured: same cost as haiku, fewer rejected maps). Falls back to `inherit` only for
|
|
437
|
-
// personas that omit the field.
|
|
434
|
+
// The verifier is sonnet here; the dispatch routes it to opus when the risk
|
|
435
|
+
// detector flags the diff. A persona without the field inherits the session model.
|
|
438
436
|
`model: ${agent.model ?? 'inherit'}`,
|
|
439
437
|
`tools: ${buildToolsLine(agent, knownMcpIds)}`,
|
|
440
438
|
// Per persona in agents.json (ADR-004); absent → DEFAULT_MAX_TURNS. The
|
|
@@ -455,6 +453,63 @@ function buildDescription(agent) {
|
|
|
455
453
|
return desc.length > 200 ? desc.slice(0, 197) + '...' : desc;
|
|
456
454
|
}
|
|
457
455
|
|
|
456
|
+
/**
|
|
457
|
+
* The fixed half of every implementer dispatch. The orchestrator paraphrases
|
|
458
|
+
* a prompt template in the field; the generated body is the only text a
|
|
459
|
+
* dispatch is guaranteed to carry, so the contract lives here and the
|
|
460
|
+
* dispatch carries only what changes per slice.
|
|
461
|
+
*/
|
|
462
|
+
const NEON_CAUTION = 'NEON: o MCP `neon` inspeciona schema e ensaia migração numa branch descartável ' +
|
|
463
|
+
'(prepare_database_migration → complete_database_migration); ele também APAGA branch e projeto — ' +
|
|
464
|
+
'nunca chame tool destrutiva sem confirmação humana explícita.';
|
|
465
|
+
|
|
466
|
+
const STANDARDS_HINT = 'Digest de cada um: `morph-spec standards --find <id>`; só a seção que importa: ' +
|
|
467
|
+
'`morph-spec standards --show <id>#<âncora>` (`--show <id>` lista as âncoras).';
|
|
468
|
+
|
|
469
|
+
export const DISPATCH_CONTRACT = [
|
|
470
|
+
'## Contrato de dispatch',
|
|
471
|
+
'',
|
|
472
|
+
'O dispatch traz só o que muda por fatia: id e título, `cobre` com o texto de cada cenário, `arquivos`, `standards`, ' +
|
|
473
|
+
'o caminho do relatório e, se houver, o worktree. O resto é este contrato.',
|
|
474
|
+
'',
|
|
475
|
+
'1. RELATÓRIO: GRAVE o arquivo de relatório que o dispatch nomeia (`.morph/features/{feature}/3-implement/reports/F{n}.md`) ' +
|
|
476
|
+
'ANTES de encerrar — é o retorno autoritativo, não a sua mensagem final. `SendMessage` não é substituto do relatório e não deve ser chamado.',
|
|
477
|
+
'2. ESTADO: NÃO toque em `plan.md`, `checks.json`, `feature.json`, `state.json` nem no board de sessão — todo estado é gravado exclusivamente pelo orquestrador.',
|
|
478
|
+
'3. TESTE: a prova é do orquestrador (`morph-spec verify {feature}`, depois do seu retorno), não sua. Escreva os testes pelo orçamento: ' +
|
|
479
|
+
'um por pós-condição, um de regressão por bug, variantes numa tabela (`[Theory]`/`it.each`). NÃO rode teste a cada marco nem faça ciclo red→green: ' +
|
|
480
|
+
'escreva código e testes, compile, e rode SÓ o filtro dos seus testes UMA vez, em primeiro plano, antes do relatório; se falhar, corrija e rode só os que falharam. ' +
|
|
481
|
+
'Grave a saída em arquivo (`… > .morph/logs/test-last.log 2>&1`, sem nada depois) e leia o arquivo — um pipe troca o exit do teste pelo do último comando. ' +
|
|
482
|
+
'Em batch paralelo SEM isolamento, só compile. Com `isolation: "worktree"`, o mesmo vale no seu worktree, e o último ato é o commit na branch dele.',
|
|
483
|
+
'4. ESCOPO: uma fatia, ~4 arquivos (nunca mais de 8). Escopo maior falha e volta — fatie antes, não depois.',
|
|
484
|
+
'5. SOBREVIVÊNCIA: o limite de turnos corta sem aviso: commite `wip(F{n})` a cada marco (um arquivo ou uma fatia coerente escrita), não só ao fim — ' +
|
|
485
|
+
'e só os seus arquivos (`git add <paths>`, nunca `-A` nem diretório). Sem commit, a retomada recomeça do zero.',
|
|
486
|
+
'6. PROCESSOS: NUNCA `taskkill /IM node.exe` (derruba os MCPs de todas as sessões). Encerre dev server pelo PID que você mesmo subiu. ' +
|
|
487
|
+
'Build e suíte pesados rodam por `morph-spec heavy run -- <comando>`, em primeiro plano. Exit 3 é fila cheia depois da espera da própria fila: ' +
|
|
488
|
+
'não repita nem espere em laço — diga no relatório o que não rodou.',
|
|
489
|
+
'7. REUSO: antes de criar tipo, componente, hook ou serviço NOVO, verifique se já existe: `morph-spec graph explain <nome>` e ' +
|
|
490
|
+
'`graph affected <conceito> --uses-only`; sem grafo (`no-graph`), caia para `grep -rn "<nome>"` e siga — a ausência de grafo nunca bloqueia. ' +
|
|
491
|
+
'Se criar mesmo assim, diga por que em uma linha no relatório.',
|
|
492
|
+
'8. MUTAÇÃO: nunca altere, reverta, mova ou apague código de produção para ver um teste falhar (`sed`, `mv`, `git show HEAD:… >`, `git stash`, ' +
|
|
493
|
+
'`git checkout --`). A prova de que o teste morde é do verificador (Gate 3).',
|
|
494
|
+
'9. ESCRITA: código e teste do projeto só por Write/Edit, nunca pelo Bash — heredoc, redirecionamento, `sed -i` ou script python/node que grava (`cat > X.cs <<EOF`, `sed -i … X.cs`): ' +
|
|
495
|
+
'os guards de escrita só enxergam Write/Edit.',
|
|
496
|
+
'',
|
|
497
|
+
'Formato do relatório (o `report-check` lê estas marcas):',
|
|
498
|
+
'',
|
|
499
|
+
'```',
|
|
500
|
+
'# F1 — <título>',
|
|
501
|
+
'O que foi feito, em 2-3 frases: o comportamento entregue e onde ele mora, sem repetir a lista de arquivos.',
|
|
502
|
+
'**Arquivos:** `src/a.cs`, `src/a.test.cs`',
|
|
503
|
+
'**Checks:**',
|
|
504
|
+
'- C1 — provado: `a.test.cs` › nome do teste (ou `arquivo:linha`)',
|
|
505
|
+
'- C2 — não provado: por quê',
|
|
506
|
+
'```',
|
|
507
|
+
'',
|
|
508
|
+
'`**Arquivos:**` lista TODO arquivo criado ou modificado, inclusive artefato auxiliar fora do plano (factory, seed, config gerada): ' +
|
|
509
|
+
'arquivo em produção sem dono é falha de protocolo que o Gate 3 aponta. Todo cenário que a fatia `cobre` aparece em `**Checks:**`. ' +
|
|
510
|
+
'Problemas e como foram resolvidos vão em prosa depois.',
|
|
511
|
+
];
|
|
512
|
+
|
|
458
513
|
function buildBody(agent, maxTurns) {
|
|
459
514
|
const lines = [
|
|
460
515
|
`# ${agent.title ?? agent.id}`,
|
|
@@ -463,12 +518,20 @@ function buildBody(agent, maxTurns) {
|
|
|
463
518
|
'',
|
|
464
519
|
];
|
|
465
520
|
|
|
466
|
-
|
|
521
|
+
// The 5.3.0 shape (no effort, no surfaces, implementer) renders byte-for-byte as before.
|
|
522
|
+
const legacyShape = !agent.effort && !agent.surfaces && isImplementer(agent);
|
|
523
|
+
|
|
524
|
+
if (legacyShape && agent.domains?.length) {
|
|
467
525
|
lines.push('## Domains', ...agent.domains.map(d => `- ${d}`), '');
|
|
468
526
|
}
|
|
469
527
|
|
|
528
|
+
if (!legacyShape && agent.mcp?.includes('neon')) {
|
|
529
|
+
lines.push(NEON_CAUTION, '');
|
|
530
|
+
}
|
|
531
|
+
|
|
470
532
|
if (agent.standards?.length) {
|
|
471
533
|
lines.push('## Standards to consult on demand');
|
|
534
|
+
if (!legacyShape) lines.push(STANDARDS_HINT);
|
|
472
535
|
for (const s of agent.standards) lines.push(`- ${s}`);
|
|
473
536
|
lines.push('');
|
|
474
537
|
}
|
|
@@ -487,13 +550,29 @@ function buildBody(agent, maxTurns) {
|
|
|
487
550
|
}
|
|
488
551
|
}
|
|
489
552
|
|
|
553
|
+
if (agent.effort && isImplementer(agent)) {
|
|
554
|
+
lines.push(...DISPATCH_CONTRACT, '');
|
|
555
|
+
} else if (isImplementer(agent)) {
|
|
556
|
+
lines.push(
|
|
557
|
+
'## How you operate',
|
|
558
|
+
'',
|
|
559
|
+
'You are a domain persona dispatched per task. Read the spec/tasks/standards passed to you, do the work, and report each doneCriteria with its evidence. Refer to MORPH.md section 6 for the dispatch contract.',
|
|
560
|
+
'',
|
|
561
|
+
);
|
|
562
|
+
} else {
|
|
563
|
+
// A read-only persona has no slice to implement nor scenario to report.
|
|
564
|
+
lines.push(
|
|
565
|
+
'## Como você opera',
|
|
566
|
+
'',
|
|
567
|
+
'Você é read-only: nunca escreva, edite nem reverta código, testes, o plano ou `feature.json` (nada de `git checkout --`). ' +
|
|
568
|
+
'O entregável é o arquivo de relatório que o dispatch nomeia; sem arquivo nomeado, é a sua mensagem final (no scout, o mapa).',
|
|
569
|
+
'',
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
490
573
|
lines.push(
|
|
491
|
-
'## How you operate',
|
|
492
|
-
'',
|
|
493
|
-
'You are a domain persona dispatched per task. Read the spec/tasks/standards passed to you, do the work, and report each doneCriteria with its evidence. Refer to MORPH.md section 6 for the dispatch contract.',
|
|
494
|
-
'',
|
|
495
574
|
// `PARCIAL` on line 1 is what report-check reads; "no verdict" keeps an
|
|
496
|
-
//
|
|
575
|
+
// review partial from carrying a Verdict the gate would read as final.
|
|
497
576
|
'## Turn budget',
|
|
498
577
|
'',
|
|
499
578
|
// Pasted into EVERY dispatch of every persona — kept to the minimum that
|
|
@@ -517,7 +596,7 @@ function buildBody(agent, maxTurns) {
|
|
|
517
596
|
'Use Read/Grep/Glob, never grep/sed/cat/find via Bash. Check a library API in the `context7` MCP before guessing ' +
|
|
518
597
|
'(load a deferred `mcp__` tool with ToolSearch). Write the tests the budget asks for, then run only your own ' +
|
|
519
598
|
'filter ONCE before the report — no red→green cycles, no `morph-spec verify` (the orchestrator proves it). ' +
|
|
520
|
-
'Never alter, revert or move production code to watch a test fail: mutation proof belongs to the Gate 3
|
|
599
|
+
'Never alter, revert or move production code to watch a test fail: mutation proof belongs to the Gate 3 verifier.',
|
|
521
600
|
);
|
|
522
601
|
}
|
|
523
602
|
|
|
@@ -24,41 +24,6 @@ export { installAgents } from './agents-installer.js';
|
|
|
24
24
|
// Extended settings management
|
|
25
25
|
// ============================================================================
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
* Install permission rules into .claude/settings.local.json
|
|
29
|
-
* @param {string} targetPath - Project root directory
|
|
30
|
-
* @param {Array<string>} permissions - Permission patterns to add
|
|
31
|
-
* @returns {Promise<Object>} Summary of changes
|
|
32
|
-
*/
|
|
33
|
-
export async function installPermissions(targetPath, permissions) {
|
|
34
|
-
const settings = await loadSettings(targetPath);
|
|
35
|
-
|
|
36
|
-
settings.permissions = settings.permissions || [];
|
|
37
|
-
|
|
38
|
-
const added = [];
|
|
39
|
-
for (const perm of permissions) {
|
|
40
|
-
if (!settings.permissions.includes(perm)) {
|
|
41
|
-
settings.permissions.push(perm);
|
|
42
|
-
added.push(perm);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Track in _morph_managed
|
|
47
|
-
settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
|
|
48
|
-
settings[MORPH_MANAGED_KEY].permissions_added =
|
|
49
|
-
settings[MORPH_MANAGED_KEY].permissions_added || [];
|
|
50
|
-
for (const perm of added) {
|
|
51
|
-
if (!settings[MORPH_MANAGED_KEY].permissions_added.includes(perm)) {
|
|
52
|
-
settings[MORPH_MANAGED_KEY].permissions_added.push(perm);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
settings[MORPH_MANAGED_KEY].generated_at = new Date().toISOString();
|
|
56
|
-
|
|
57
|
-
await saveSettings(targetPath, settings);
|
|
58
|
-
|
|
59
|
-
return { added, total: settings.permissions.length };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
27
|
/**
|
|
63
28
|
* Install environment variables into .claude/settings.local.json
|
|
64
29
|
* @param {string} targetPath - Project root directory
|
package/src/utils/file-copier.js
CHANGED
|
@@ -156,7 +156,6 @@ export const MORPH_GITIGNORE_ENTRIES = [
|
|
|
156
156
|
'.morph/memory/',
|
|
157
157
|
'.morph/plans/',
|
|
158
158
|
'.morph/analytics/',
|
|
159
|
-
'.morph/traces/',
|
|
160
159
|
// Per-instance activity/session logs (activity-logger.js). Each worktree
|
|
161
160
|
// writes its own — committing this guarantees a merge conflict between
|
|
162
161
|
// feature branches.
|
|
@@ -199,10 +198,12 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
|
199
198
|
content = await readFile(gitignorePath);
|
|
200
199
|
hasMorphSection = content.includes('# MORPH-SPEC');
|
|
201
200
|
}
|
|
201
|
+
const original = content;
|
|
202
|
+
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
202
203
|
|
|
203
204
|
// If MORPH section already exists, check if rules are up to date
|
|
204
205
|
if (hasMorphSection) {
|
|
205
|
-
const lines = content.split(
|
|
206
|
+
const lines = content.split(/\r?\n/);
|
|
206
207
|
const morphStartIndex = lines.findIndex(line => line.trim() === '# MORPH-SPEC');
|
|
207
208
|
|
|
208
209
|
// Find the end of MORPH section (next empty line or section header)
|
|
@@ -225,17 +226,44 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
|
225
226
|
if (missingRules.length > 0) {
|
|
226
227
|
// Update the section with all rules
|
|
227
228
|
lines.splice(morphStartIndex, morphEndIndex - morphStartIndex, ...morphRules.slice(1, -1));
|
|
228
|
-
content = lines.join(
|
|
229
|
+
content = lines.join(eol);
|
|
229
230
|
}
|
|
230
231
|
} else {
|
|
231
232
|
// Add MORPH section
|
|
232
233
|
if (content && !content.endsWith('\n')) {
|
|
233
|
-
content +=
|
|
234
|
+
content += eol;
|
|
234
235
|
}
|
|
235
|
-
content += morphRules.join(
|
|
236
|
+
content += morphRules.join(eol);
|
|
236
237
|
}
|
|
237
238
|
|
|
238
|
-
fx.writeFile(gitignorePath, content);
|
|
239
|
+
if (content !== original) fx.writeFile(gitignorePath, content);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const DOTNET_BUILD_DIRS = ['bin', 'obj'];
|
|
243
|
+
|
|
244
|
+
const ignoredDir = (line) => line.trim()
|
|
245
|
+
.replace(/^\/|^\*\*\//, '')
|
|
246
|
+
.replace(/\/(\*\*)?$/, '')
|
|
247
|
+
.replace(/^\[(\w)(\w)\]/, (_, a, b) => (a.toLowerCase() === b.toLowerCase() ? a : `[${a}${b}]`))
|
|
248
|
+
.toLowerCase();
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Appends `bin/` and `obj/` to the project `.gitignore` when no line already
|
|
252
|
+
* ignores them. Never rewrites an existing line.
|
|
253
|
+
*
|
|
254
|
+
* @returns {string[]} the entries added
|
|
255
|
+
*/
|
|
256
|
+
export function ensureDotnetBuildIgnores(projectPath) {
|
|
257
|
+
const gitignorePath = join(projectPath, '.gitignore');
|
|
258
|
+
const content = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
|
259
|
+
const present = new Set(content.split(/\r?\n/).filter((l) => !l.trim().startsWith('#')).map(ignoredDir));
|
|
260
|
+
const missing = DOTNET_BUILD_DIRS.filter((dir) => !present.has(dir)).map((dir) => `${dir}/`);
|
|
261
|
+
if (!missing.length) return [];
|
|
262
|
+
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
263
|
+
const lead = content && !content.endsWith('\n') ? eol : '';
|
|
264
|
+
const block = [...(content ? [''] : []), '# .NET build output', ...missing, ''].join(eol);
|
|
265
|
+
fs.writeFileSync(gitignorePath, content + lead + block);
|
|
266
|
+
return missing;
|
|
239
267
|
}
|
|
240
268
|
|
|
241
269
|
/**
|
|
@@ -55,8 +55,11 @@ import { realEffects } from '../lib/installers/effects.js';
|
|
|
55
55
|
*
|
|
56
56
|
* 3.12.0 adds heavy-gate to the Bash PreToolUse chain and heavy-release to the
|
|
57
57
|
* PostToolUse and PostToolUseFailure chains: the opt-in `heavy.gateBash`.
|
|
58
|
+
*
|
|
59
|
+
* 3.13.0 adds warn-bash-source-write to the Bash PreToolUse chain: a warning,
|
|
60
|
+
* never a block, when a command writes project source through a redirect.
|
|
58
61
|
*/
|
|
59
|
-
const HOOKS_VERSION = '3.
|
|
62
|
+
const HOOKS_VERSION = '3.13.0';
|
|
60
63
|
|
|
61
64
|
/** Marker for old dispatch.js (v1) */
|
|
62
65
|
const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
@@ -93,21 +96,24 @@ const MORPH_DENY = [
|
|
|
93
96
|
*
|
|
94
97
|
* Pre-authorizes operations the agent legitimately performs during the
|
|
95
98
|
* morph-spec flow, so the user does not get permission prompts on every
|
|
96
|
-
*
|
|
99
|
+
* plan.md / slice report write.
|
|
97
100
|
*
|
|
98
101
|
* `Edit(path)` ONLY, for the same reason as MORPH_DENY: the `Edit` rule already
|
|
99
102
|
* covers Write, and a `Write(path)` rule is inert noise.
|
|
100
103
|
*/
|
|
101
104
|
const MORPH_ALLOW = [
|
|
102
|
-
// Feature artifacts — the agent's working directory during
|
|
105
|
+
// Feature artifacts — the agent's working directory during plan/uiux/implement/review
|
|
103
106
|
'Edit(.morph/features/**)',
|
|
104
107
|
// state.json — fallback path when state-sync hook can't cover a case
|
|
105
108
|
'Edit(.morph/state.json)',
|
|
106
|
-
// Long-lived architectural context — agent may reconcile during
|
|
109
|
+
// Long-lived architectural context — agent may reconcile during planning
|
|
107
110
|
'Edit(.morph/context/**)',
|
|
108
111
|
// Folder creation under .morph
|
|
109
112
|
'Bash(mkdir:.morph/**)',
|
|
110
113
|
'Bash(ls:.morph/**)',
|
|
114
|
+
// Gate 3 mutation round: restores bytes itself and has --restore; an auto-mode deny pauses the gate
|
|
115
|
+
'Bash(morph-spec mutate:*)',
|
|
116
|
+
'Bash(npx morph-spec mutate:*)',
|
|
111
117
|
];
|
|
112
118
|
|
|
113
119
|
// Backwards-compat alias used by older tests
|
|
@@ -168,6 +174,8 @@ const MORPH_HOOKS = [
|
|
|
168
174
|
// on someone having read MORPH.md. block-raw-worktree-add is its way-IN
|
|
169
175
|
// counterpart (a raw `git worktree add` builds a tree with no junctions, no
|
|
170
176
|
// ports, no identity) — static, so it costs nothing after the first two.
|
|
177
|
+
// warn-bash-source-write only adds context (source written by redirect skips
|
|
178
|
+
// the Write/Edit guards); after the guards, so a blocked command is not warned.
|
|
171
179
|
// heavy-gate comes after every guard — it takes a slot, and a command a guard
|
|
172
180
|
// blocks must not hold one — and before the rewriter.
|
|
173
181
|
// worktree-env-inject is LAST: it rewrites launcher commands (updatedInput)
|
|
@@ -179,7 +187,7 @@ const MORPH_HOOKS = [
|
|
|
179
187
|
hooks: [{
|
|
180
188
|
type: 'command',
|
|
181
189
|
command: 'node framework/hooks/claude-code/core/chain.js',
|
|
182
|
-
args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'heavy-gate', 'worktree-env-inject']
|
|
190
|
+
args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'warn-bash-source-write', 'heavy-gate', 'worktree-env-inject']
|
|
183
191
|
}]
|
|
184
192
|
},
|
|
185
193
|
|
|
@@ -214,15 +222,14 @@ const MORPH_HOOKS = [
|
|
|
214
222
|
}]
|
|
215
223
|
},
|
|
216
224
|
|
|
217
|
-
// === PostToolUse: Write|Edit —
|
|
218
|
-
//
|
|
225
|
+
// === PostToolUse: Write|Edit — comment-guard, solo: it blocks the edit that
|
|
226
|
+
// adds a forbidden comment. ===
|
|
219
227
|
{
|
|
220
228
|
event: 'PostToolUse',
|
|
221
229
|
matcher: 'Write|Edit',
|
|
222
230
|
hooks: [{
|
|
223
231
|
type: 'command',
|
|
224
|
-
command: 'node framework/hooks/claude-code/core/
|
|
225
|
-
args: ['tasks-json-guard', 'trace-autogen', 'comment-guard']
|
|
232
|
+
command: 'node framework/hooks/claude-code/core/comment-guard.js'
|
|
226
233
|
}]
|
|
227
234
|
},
|
|
228
235
|
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Copies framework skills to .claude/skills/ during morph-spec init.
|
|
5
5
|
* Installs only the `core/` skill set — the canonical NLH skills referenced by
|
|
6
|
-
* MORPH.md (morph-
|
|
7
|
-
* morph-review, morph-standards). The 3-tier system was collapsed
|
|
6
|
+
* MORPH.md (morph-plan, morph-uiux, morph-apply, morph-review, morph-standards…). The 3-tier system was collapsed
|
|
8
7
|
* into a single source of truth in framework/skills/core/.
|
|
9
8
|
*
|
|
10
9
|
* Source skills are directory-based: framework/skills/core/{skill-name}/SKILL.md
|