@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
|
@@ -15,10 +15,10 @@ import { loadTelemetryAllTrees, aggregateCost, dedupeEvents } from '../../lib/te
|
|
|
15
15
|
import { isFailedToolCall } from '../../../framework/hooks/shared/telemetry-logger.js';
|
|
16
16
|
|
|
17
17
|
/** Kinds a person follows by default; the rest are one click away. */
|
|
18
|
-
export const DEFAULT_KINDS = ['prompt', 'question', 'skill', 'dispatch', 'handback', '
|
|
18
|
+
export const DEFAULT_KINDS = ['prompt', 'question', 'skill', 'dispatch', 'handback', 'verify', 'gate', 'failure'];
|
|
19
19
|
export const ALL_KINDS = [...DEFAULT_KINDS, 'write', 'read', 'command', 'tool'];
|
|
20
20
|
|
|
21
|
-
const GATE_COMMAND = /\bmorph-spec\s+(approve|gate-check|gate-decision|advance|
|
|
21
|
+
const GATE_COMMAND = /\bmorph-spec\s+(approve|gate-check|gate-decision|advance|finish)\b/;
|
|
22
22
|
const MORPH_PERSONA = /^morph-(.+)$/;
|
|
23
23
|
const SHELL_TOOLS = new Set(['Bash', 'PowerShell']);
|
|
24
24
|
const SILENT_EVENTS = new Set(['ToolBatch', 'SessionUsage', 'AgentUsage']);
|
|
@@ -50,14 +50,14 @@ function nodesSummary(nodes) {
|
|
|
50
50
|
* @returns {null | {
|
|
51
51
|
* id: string, ts: string, feature: string|null, phase: string|null,
|
|
52
52
|
* kind: string, title: string, detail: string|null, status: 'ok'|'fail'|null,
|
|
53
|
-
* refs: { persona?: string, skill?: string,
|
|
53
|
+
* refs: { persona?: string, skill?: string, path?: string },
|
|
54
54
|
* nodes?: Array<{name: string, status: string|null}>
|
|
55
55
|
* }}
|
|
56
56
|
*/
|
|
57
57
|
export function describeEvent(e) {
|
|
58
58
|
if (!e || typeof e !== 'object' || SILENT_EVENTS.has(e.event)) return null;
|
|
59
59
|
const base = {
|
|
60
|
-
id: e.eventId || `${e.ts}:${e.event}:${e.tool || ''}
|
|
60
|
+
id: e.eventId || `${e.ts}:${e.event}:${e.tool || ''}`,
|
|
61
61
|
ts: e.ts || null,
|
|
62
62
|
feature: e.feature || null,
|
|
63
63
|
phase: e.phase || null,
|
|
@@ -78,18 +78,10 @@ export function describeEvent(e) {
|
|
|
78
78
|
if (text.startsWith('<')) return null;
|
|
79
79
|
return line('prompt', 'Você escreveu', { detail: clip(text) });
|
|
80
80
|
}
|
|
81
|
-
if (e.event === 'TaskStart') return line('task', `${e.task} começou`, { refs: { task: e.task } });
|
|
82
|
-
if (e.event === 'TaskEnd') {
|
|
83
|
-
const done = e.status === 'done';
|
|
84
|
-
return line('task', done ? `${e.task} concluída` : `${e.task} terminou (${e.status ?? 'sem status'})`, {
|
|
85
|
-
status: done ? 'ok' : null, refs: { task: e.task },
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
81
|
if (e.event === 'Verify') {
|
|
89
82
|
const ok = e.status === 'pass';
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
status: ok ? 'ok' : 'fail', nodes: nodesSummary(e.nodes), refs: e.task ? { task: e.task } : {},
|
|
83
|
+
return line('verify', `verify ${ok ? 'passou' : 'falhou'}`, {
|
|
84
|
+
status: ok ? 'ok' : 'fail', nodes: nodesSummary(e.nodes),
|
|
93
85
|
detail: e.persisted === false && e.reason ? `não carimbou: ${e.reason}` : null,
|
|
94
86
|
});
|
|
95
87
|
}
|
|
@@ -344,8 +336,6 @@ export function featureCost(events, feature) {
|
|
|
344
336
|
dispatches: c.dispatches,
|
|
345
337
|
bytesTotal: c.bytesTotal,
|
|
346
338
|
bytesByKind: c.bytesByKind,
|
|
347
|
-
taskDurationSeconds: c.taskDurationSeconds,
|
|
348
|
-
tasks: c.tasks,
|
|
349
339
|
events: c.total,
|
|
350
340
|
};
|
|
351
341
|
}
|
|
@@ -13,7 +13,7 @@ const GENERIC_DISPATCH = 'morph-{id}';
|
|
|
13
13
|
*
|
|
14
14
|
* v5 has no phase list on the persona: a phase dispatches a persona when its
|
|
15
15
|
* skill (`phases.json → skill`) names `morph-{personaId}` on a dispatch line
|
|
16
|
-
* (morph-uiux → ui-designer, morph-plan → scout, morph-review →
|
|
16
|
+
* (morph-uiux → ui-designer, morph-plan → scout, morph-review → verifier, reviewer, security).
|
|
17
17
|
* A skill that dispatches the generic `morph-{id}` runs every implementer —
|
|
18
18
|
* the same `isImplementer` the validator and the installer use.
|
|
19
19
|
*
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The reading material of the framework — the charter, the runtime
|
|
3
|
-
* instructions, the CLI reference and the
|
|
4
|
-
*
|
|
3
|
+
* instructions, the CLI reference and the Gate 3 review checklist — for the
|
|
4
|
+
* dashboard's Guide page.
|
|
5
5
|
*/
|
|
6
|
-
import { readFileSync
|
|
6
|
+
import { readFileSync } from 'fs';
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
|
|
9
9
|
function read(path) {
|
|
@@ -23,26 +23,11 @@ export function headingsOf(content) {
|
|
|
23
23
|
return out;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function titleOf(content, fallback) {
|
|
27
|
-
const h = headingsOf(content || '').find((x) => x.depth === 1);
|
|
28
|
-
return h?.text || fallback;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function mdDir(dir) {
|
|
32
|
-
if (!existsSync(dir)) return [];
|
|
33
|
-
return readdirSync(dir)
|
|
34
|
-
.filter((f) => f.endsWith('.md'))
|
|
35
|
-
.sort()
|
|
36
|
-
.map((f) => {
|
|
37
|
-
const content = read(join(dir, f)) || '';
|
|
38
|
-
return { id: f.replace(/\.md$/, ''), file: f, title: titleOf(content, f.replace(/\.md$/, '')), content };
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
26
|
const GUIDE = [
|
|
43
27
|
{ id: 'morph', path: 'MORPH.md', title: 'Carta do harness (MORPH.md)', summary: 'Como o Claude trabalha: papéis, fases, gates, sub-agents. É a fonte única do comportamento.' },
|
|
44
28
|
{ id: 'runtime', path: 'CLAUDE.md', title: 'Instruções de runtime (CLAUDE.md)', summary: 'O que é injetado no CLAUDE.md do projeto: como começar cada sessão, regras críticas, comandos.' },
|
|
45
29
|
{ id: 'cli', path: 'docs/CLI.md', title: 'Referência do CLI', summary: 'Todos os comandos morph-spec: gates, verify, worktrees, grafo, telemetria.' },
|
|
30
|
+
{ id: 'review-checklist', path: 'evals/review-checklist.md', title: 'Checklist do Gate 3', summary: 'O que verificador, revisor e segurança procuram antes do ship: veredito SHIP/FIX e bloqueador com evidência.' },
|
|
46
31
|
];
|
|
47
32
|
|
|
48
33
|
export function scanDocs(frameworkDir) {
|
|
@@ -51,8 +36,5 @@ export function scanDocs(frameworkDir) {
|
|
|
51
36
|
return { ...g, content, headings: headingsOf(content).filter((h) => h.depth <= 3) };
|
|
52
37
|
}).filter((g) => g.content);
|
|
53
38
|
|
|
54
|
-
return {
|
|
55
|
-
guide,
|
|
56
|
-
rubrics: mdDir(join(frameworkDir, 'evals')).filter((d) => d.id === 'review-checklist'),
|
|
57
|
-
};
|
|
39
|
+
return { guide };
|
|
58
40
|
}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* - which tree owns a feature → `resolveFeatureTree` (feature-truth.js)
|
|
7
7
|
* - the current phase → `derivePhaseFrom` (state-reader.js)
|
|
8
8
|
* - gate order → `GATE_PREREQ` (gate-order.js)
|
|
9
|
-
* - the
|
|
9
|
+
* - the review verdict → `reviewReportsFromDir` (review-reports.js)
|
|
10
10
|
* - path confinement → `canonicalPath`/`isInside` (test-runner.js)
|
|
11
|
-
* -
|
|
11
|
+
* - slice progress → `sliceProgress` (slice-progress.js)
|
|
12
12
|
*
|
|
13
13
|
* Three places a feature can live: the root `.morph/features/{f}`, a worktree
|
|
14
14
|
* `{worktrees}/{wt}/.morph/features/{f}`, and `.morph/archive/{f}` once
|
|
@@ -21,10 +21,12 @@
|
|
|
21
21
|
|
|
22
22
|
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
23
23
|
import { join, basename, isAbsolute } from 'path';
|
|
24
|
-
import { derivePhaseFrom, PHASE_ARTIFACTS } from '../../../framework/hooks/shared/state-reader.js';
|
|
24
|
+
import { derivePhaseFrom, diskArtifactProbe, PHASE_ARTIFACTS } from '../../../framework/hooks/shared/state-reader.js';
|
|
25
25
|
import { GATE_PREREQ } from '../../core/state/gate-order.js';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
26
|
+
import { sliceProgress, checksPath } from '../../../framework/hooks/shared/slice-progress.js';
|
|
27
|
+
import {
|
|
28
|
+
reviewReportsFromDir, requiredReviewRoles, combinedVerdict, allBlockers, checksCoverage, reviewFileName, REVIEW_ROLES,
|
|
29
|
+
} from '../../lib/review-reports.js';
|
|
28
30
|
import { resolveFeatureTree } from '../../lib/worktree/feature-truth.js';
|
|
29
31
|
import { containerDirs } from '../../../framework/hooks/shared/worktree-root.js';
|
|
30
32
|
import { canonicalPath, isInside, readMorphConfig } from '../../lib/tasks/test-runner.js';
|
|
@@ -33,7 +35,6 @@ import { isFinished } from './feature-status.js';
|
|
|
33
35
|
export const GATES = Object.keys(GATE_PREREQ);
|
|
34
36
|
const FINISHED = new Set(['completed', 'abandoned']);
|
|
35
37
|
const FOLDER_PHASE = Object.fromEntries(PHASE_ARTIFACTS.map(([folder, phase]) => [folder, phase]));
|
|
36
|
-
const EVALUATOR_REPORT = '4-review/evaluator-report.md';
|
|
37
38
|
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
38
39
|
const MAX_DEPTH = 4;
|
|
39
40
|
|
|
@@ -141,31 +142,26 @@ function resolveLocation(projectDir, name, loc, resolveTree) {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
145
|
+
* The plan's slices with the status their reports give, plus the fields the
|
|
146
|
+
* extraction carries for the card. `null` when the plan has not been checked yet.
|
|
146
147
|
*/
|
|
147
|
-
export function
|
|
148
|
-
const
|
|
149
|
-
if (!
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
export function readSlices(dir) {
|
|
149
|
+
const progress = sliceProgress(dir);
|
|
150
|
+
if (!progress) return null;
|
|
151
|
+
const extracted = new Map((readJson(checksPath(dir))?.slices || []).filter((x) => x && x.id).map((x) => [x.id, x]));
|
|
152
|
+
const list = progress.slices.map((sl) => {
|
|
153
|
+
const x = extracted.get(sl.id) || {};
|
|
154
|
+
return {
|
|
155
|
+
...sl,
|
|
156
|
+
persona: x.persona ?? null,
|
|
157
|
+
files: Array.isArray(x.files) ? x.files : [],
|
|
158
|
+
covers: Array.isArray(x.covers) ? x.covers : [],
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
return { counts: { total: progress.total, done: progress.done, partial: progress.partial, pending: progress.pending }, list };
|
|
155
162
|
}
|
|
156
163
|
|
|
157
|
-
|
|
158
|
-
const counts = { total: list.length, done: 0, inProgress: 0, blocked: 0, pending: 0, skipped: 0 };
|
|
159
|
-
for (const t of list) {
|
|
160
|
-
// `completed` is the pre-schema spelling of `done` (MORPH_OS archives carry both).
|
|
161
|
-
if (t.status === 'done' || t.status === 'completed') counts.done += 1;
|
|
162
|
-
else if (t.status === 'in_progress') counts.inProgress += 1;
|
|
163
|
-
else if (t.status === 'blocked') counts.blocked += 1;
|
|
164
|
-
else if (t.status === 'skipped') counts.skipped += 1;
|
|
165
|
-
else counts.pending += 1;
|
|
166
|
-
}
|
|
167
|
-
return counts;
|
|
168
|
-
}
|
|
164
|
+
const NO_SLICES = { total: 0, done: 0, partial: 0, pending: 0 };
|
|
169
165
|
|
|
170
166
|
function gatesOf(feature) {
|
|
171
167
|
const skipped = new Set(feature.skippedPhases || []);
|
|
@@ -185,20 +181,67 @@ function gatesOf(feature) {
|
|
|
185
181
|
});
|
|
186
182
|
}
|
|
187
183
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
184
|
+
// Security counts only when its report exists: deciding it is required takes git.
|
|
185
|
+
function reviewVerdict(dir, workType, reports = reviewReportsFromDir(dir)) {
|
|
186
|
+
const present = Object.values(reports).some((r) => r.status !== 'missing');
|
|
187
|
+
if (!present) return null;
|
|
188
|
+
const required = requiredReviewRoles({ workType, securityRequired: reports.security.status !== 'missing' });
|
|
189
|
+
return { verdict: combinedVerdict(reports, required), blockers: allBlockers(reports) };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* One row per review report the feature asks for or already has: status,
|
|
194
|
+
* verdict, blockers and the SHA it reviewed.
|
|
195
|
+
*/
|
|
196
|
+
export function reviewRows(reports, workType) {
|
|
197
|
+
const required = requiredReviewRoles({ workType, securityRequired: reports.security.status !== 'missing' });
|
|
198
|
+
return Object.keys(REVIEW_ROLES)
|
|
199
|
+
.filter((role) => required.includes(role) || reports[role].status !== 'missing')
|
|
200
|
+
.map((role) => {
|
|
201
|
+
const r = reports[role];
|
|
202
|
+
return {
|
|
203
|
+
role,
|
|
204
|
+
path: `4-review/${reviewFileName(role)}`,
|
|
205
|
+
status: r.status,
|
|
206
|
+
verdict: r.verdict ?? null,
|
|
207
|
+
blockers: r.blockers?.length ?? 0,
|
|
208
|
+
reviewedSha: r.reviewedSha ?? null,
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The plan's scenarios, each with the verifier's row for it. `coverage` is
|
|
215
|
+
* `null` until a verifier report exists; only PROVADO covers.
|
|
216
|
+
*/
|
|
217
|
+
export function readChecks(dir, verifier) {
|
|
218
|
+
const checks = readJson(checksPath(dir))?.checks;
|
|
219
|
+
if (!Array.isArray(checks)) return null;
|
|
220
|
+
const list = checks.filter((c) => c && c.id);
|
|
221
|
+
const rows = verifier?.checks ?? null;
|
|
222
|
+
const byId = new Map((rows ?? []).map((r) => [r.id, r]));
|
|
223
|
+
const coverage = verifier && verifier.status !== 'missing'
|
|
224
|
+
? { total: list.length, ...checksCoverage(list.map((c) => c.id), rows) }
|
|
225
|
+
: null;
|
|
226
|
+
return {
|
|
227
|
+
coverage,
|
|
228
|
+
list: list.map((c) => ({
|
|
229
|
+
id: c.id,
|
|
230
|
+
text: c.text ?? '',
|
|
231
|
+
proof: c.proof ?? null,
|
|
232
|
+
status: byId.get(c.id)?.status ?? null,
|
|
233
|
+
evidence: byId.get(c.id)?.evidence ?? null,
|
|
234
|
+
})),
|
|
235
|
+
};
|
|
192
236
|
}
|
|
193
237
|
|
|
194
238
|
function summarize(name, resolved) {
|
|
195
239
|
const { location, dir, ownerTree, rootStale } = resolved;
|
|
196
240
|
const feature = normalizeFeature(resolved.feature);
|
|
197
|
-
const has = (rel) => existsSync(join(dir, ...rel.split('/')));
|
|
198
241
|
const gates = gatesOf(feature);
|
|
199
242
|
const reviewApproved = gates.find((g) => g.gate === 'review')?.approved === true;
|
|
200
|
-
const
|
|
201
|
-
const verdict =
|
|
243
|
+
const slices = readSlices(dir);
|
|
244
|
+
const verdict = reviewVerdict(dir, feature.workType);
|
|
202
245
|
const status = feature.status || (location === 'archive' ? 'completed' : 'draft');
|
|
203
246
|
const finished = isFinished({ status, location });
|
|
204
247
|
return {
|
|
@@ -210,19 +253,19 @@ function summarize(name, resolved) {
|
|
|
210
253
|
dir,
|
|
211
254
|
ownerTree,
|
|
212
255
|
rootStale,
|
|
213
|
-
phase: derivePhaseFrom(feature,
|
|
214
|
-
inEvaluation:
|
|
256
|
+
phase: derivePhaseFrom(feature, diskArtifactProbe(dir)),
|
|
257
|
+
inEvaluation: verdict !== null && !reviewApproved,
|
|
215
258
|
gates,
|
|
216
259
|
awaiting: !finished && gates.some((g) => g.paused),
|
|
217
|
-
|
|
218
|
-
// Titles of an active feature's
|
|
260
|
+
slices: slices?.counts ?? NO_SLICES,
|
|
261
|
+
// Titles of an active feature's slices, for the global search. Finished
|
|
219
262
|
// features leave them out: 170 archived plans would bloat every list reply.
|
|
220
|
-
|
|
263
|
+
sliceIndex: finished || !slices ? [] : slices.list.map((sl) => ({ id: sl.id, title: sl.title, status: sl.status })),
|
|
221
264
|
verification: feature.verification
|
|
222
|
-
? { status: feature.verification.status ?? null,
|
|
265
|
+
? { status: feature.verification.status ?? null, ts: feature.verification.ts ?? null }
|
|
223
266
|
: null,
|
|
224
267
|
verdict: verdict?.verdict ?? null,
|
|
225
|
-
blockers: verdict
|
|
268
|
+
blockers: verdict ? verdict.blockers.length : null,
|
|
226
269
|
createdAt: feature.createdAt || null,
|
|
227
270
|
updatedAt: feature.updatedAt || feature.archivedAt || feature.createdAt || null,
|
|
228
271
|
archivedAt: feature.archivedAt || null,
|
|
@@ -309,24 +352,21 @@ export function scanFeatures(projectDir, { resolveTree = resolveFeatureTree } =
|
|
|
309
352
|
};
|
|
310
353
|
}
|
|
311
354
|
|
|
312
|
-
/**
|
|
313
|
-
* One feature in full: summary + tasks with their verify/score, the gate
|
|
314
|
-
* decisions, the verify nodes and the document tree.
|
|
315
|
-
*/
|
|
316
355
|
export function readFeatureDetail(projectDir, name, { resolveTree = resolveFeatureTree, known } = {}) {
|
|
317
356
|
const resolved = resolveOne(projectDir, name, resolveTree, known);
|
|
318
357
|
if (!resolved) return null;
|
|
319
358
|
const summary = summarize(name, resolved);
|
|
320
359
|
const feature = normalizeFeature(resolved.feature);
|
|
321
|
-
const
|
|
322
|
-
const
|
|
360
|
+
const slices = readSlices(resolved.dir);
|
|
361
|
+
const reports = reviewReportsFromDir(resolved.dir);
|
|
362
|
+
const verdict = reviewVerdict(resolved.dir, feature.workType, reports);
|
|
363
|
+
const checks = readChecks(resolved.dir, reports.verifier);
|
|
323
364
|
return {
|
|
324
365
|
...summary,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
tasksErrors: tasks.errors,
|
|
366
|
+
slicesList: slices?.list ?? [],
|
|
367
|
+
checksList: checks?.list ?? [],
|
|
368
|
+
checksCoverage: checks?.coverage ?? null,
|
|
369
|
+
reviews: reviewRows(reports, feature.workType),
|
|
330
370
|
gateDecisions: feature.gateDecisions || {},
|
|
331
371
|
verificationNodes: feature.verification?.nodes || null,
|
|
332
372
|
featureVerification: feature.verifications?.feature || null,
|
|
@@ -336,13 +376,8 @@ export function readFeatureDetail(projectDir, name, { resolveTree = resolveFeatu
|
|
|
336
376
|
};
|
|
337
377
|
}
|
|
338
378
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
* Junctions and win32 case are resolved by `canonicalPath` before the check,
|
|
342
|
-
* so `..`, an absolute path or a link that leaves the folder are all refused.
|
|
343
|
-
*
|
|
344
|
-
* @returns {{ path: string, content: string, mtime: string } | { error: string, status: number }}
|
|
345
|
-
*/
|
|
379
|
+
// Junctions and win32 case are resolved by `canonicalPath` before the check,
|
|
380
|
+
// so `..`, an absolute path or a link that leaves the feature folder are refused.
|
|
346
381
|
export function readFeatureFile(projectDir, name, relPath, { resolveTree = resolveFeatureTree, known } = {}) {
|
|
347
382
|
if (typeof relPath !== 'string' || relPath === '') return { error: 'path ausente', status: 400 };
|
|
348
383
|
if (isAbsolute(relPath) || /^[a-zA-Z]:/.test(relPath)) return { error: 'path fora da feature', status: 403 };
|
|
@@ -4,7 +4,6 @@ import { scanHooks } from './hooks-scanner.js';
|
|
|
4
4
|
import { scanSkills } from './skills-scanner.js';
|
|
5
5
|
import { scanStandards } from './standards-scanner.js';
|
|
6
6
|
import { scanRules } from './rules-scanner.js';
|
|
7
|
-
import { scanRubrics } from './rubrics-scanner.js';
|
|
8
7
|
import { scanDocs } from './docs-scanner.js';
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -21,7 +20,6 @@ export function scanAll(frameworkDir) {
|
|
|
21
20
|
skills: scanSkills(frameworkDir, phases.list),
|
|
22
21
|
standards: scanStandards(frameworkDir, agents.list),
|
|
23
22
|
rules: scanRules(frameworkDir),
|
|
24
|
-
rubrics: scanRubrics(frameworkDir),
|
|
25
23
|
docs: scanDocs(frameworkDir),
|
|
26
24
|
};
|
|
27
25
|
}
|
|
@@ -89,7 +89,7 @@ export function createProjectApi(projectDir, deps = {}) {
|
|
|
89
89
|
|
|
90
90
|
// Which tree owns a feature costs a few git calls (~0.3 s each on Windows)
|
|
91
91
|
// and only changes when a file or folder appears or disappears — never when
|
|
92
|
-
// an agent rewrites feature.json or
|
|
92
|
+
// an agent rewrites feature.json or a slice report, which is what happens all
|
|
93
93
|
// the time. Remembered per feature; a structural change or TTL_MS forgets it.
|
|
94
94
|
const resolveTree = deps.resolveTree || resolveFeatureTree;
|
|
95
95
|
const now = deps.now || Date.now;
|
|
@@ -18,17 +18,22 @@ function parseFrontmatter(content) {
|
|
|
18
18
|
return { paths, stacks };
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const ROUTE_LINE = /^- \*\*([a-z0-9-]+)\*\* \(`([^`]+\.md)`\)/;
|
|
22
|
+
|
|
23
|
+
// Bullet form: - **<id>** (`<path under standards/>`) — <digest>. An `@path`
|
|
24
|
+
// import inside .claude/rules resolves relative to the rule file and never loads.
|
|
25
|
+
export function parseStandardRoutes(content) {
|
|
26
|
+
const routes = [];
|
|
27
|
+
for (const line of content.split(/\r?\n/)) {
|
|
28
|
+
const m = ROUTE_LINE.exec(line);
|
|
29
|
+
if (m) routes.push({ id: m[1], path: m[2] });
|
|
30
|
+
}
|
|
31
|
+
return routes;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// RulesMap keys one edge per ref, so a repeated path would collide.
|
|
26
35
|
export function extractStandardRefs(content) {
|
|
27
|
-
|
|
28
|
-
const regex = /@\.morph\/framework\/standards\/(.+\.md)/g;
|
|
29
|
-
let m;
|
|
30
|
-
while ((m = regex.exec(content)) !== null) refs.add(m[1]);
|
|
31
|
-
return [...refs];
|
|
36
|
+
return [...new Set(parseStandardRoutes(content).map((r) => r.path))];
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
export function scanRules(frameworkDir) {
|
|
@@ -84,13 +84,12 @@ export function scanSkills(frameworkDir, phases = []) {
|
|
|
84
84
|
if (!existsSync(skillPath)) continue;
|
|
85
85
|
const content = readFileSync(skillPath, 'utf8');
|
|
86
86
|
const fm = parseFrontmatter(content);
|
|
87
|
-
|
|
88
|
-
const invocable = fm['user-invokable'] ?? fm['user-invocable'];
|
|
87
|
+
const invocable = fm['user-invocable'];
|
|
89
88
|
skills.push({
|
|
90
89
|
id: skillDir.name,
|
|
91
90
|
name: fm.name || skillDir.name,
|
|
92
91
|
description: fm.description || '',
|
|
93
|
-
|
|
92
|
+
whenToUse: fm.when_to_use || '',
|
|
94
93
|
level: levelDir.name,
|
|
95
94
|
userInvocable: invocable === true || invocable === 'true',
|
|
96
95
|
stacks: typeof fm.stacks === 'string' ? fm.stacks.replace(/[[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean) : [],
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { personaStandardIds } from '../../utils/agents-installer.js';
|
|
4
|
+
import { loadStandardsRegistry } from '../../lib/standards/registry.js';
|
|
4
5
|
|
|
5
6
|
export function scanStandards(frameworkDir, agents) {
|
|
6
7
|
const standardsJsonPath = join(frameworkDir, 'standards', 'STANDARDS.json');
|
|
7
8
|
if (!existsSync(standardsJsonPath)) return { version: 'unknown', list: [] };
|
|
8
|
-
const sJson =
|
|
9
|
+
const sJson = loadStandardsRegistry(standardsJsonPath);
|
|
9
10
|
const refMap = {};
|
|
10
11
|
for (const agent of agents) {
|
|
11
12
|
for (const std of personaStandardIds(agent)) {
|
package/src/lib/ci/ci-verify.js
CHANGED
|
@@ -18,7 +18,7 @@ export class CiUsageError extends Error {}
|
|
|
18
18
|
const NODE_KEYS = ['build', 'tests', 'validators', 'e2e'];
|
|
19
19
|
|
|
20
20
|
function isVerifyResult(result) {
|
|
21
|
-
return Boolean(result) && ['pass', 'fail'].includes(result.status)
|
|
21
|
+
return Boolean(result) && ['pass', 'fail'].includes(result.status)
|
|
22
22
|
&& NODE_KEYS.every((k) => typeof result.nodes?.[k]?.status === 'string');
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -47,6 +47,22 @@ function dispatchWorkflow(root, workflow, branch) {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* When CI falls back to local, the last green feature stamp still proves the tree if nothing moved
|
|
52
|
+
* since it by the gate's own staleness rule and nothing is uncommitted (treeMovedSince sees only
|
|
53
|
+
* commits). A null sha or HEAD would read as "not moved", so both are required.
|
|
54
|
+
*
|
|
55
|
+
* @returns {object|null} the stamp to reuse, or null = run local
|
|
56
|
+
*/
|
|
57
|
+
export function reusableLocalStamp(root, stamp) {
|
|
58
|
+
if (stamp?.status !== 'pass' || !stamp.sha) return null;
|
|
59
|
+
const head = getHeadSha(root);
|
|
60
|
+
if (!head) return null;
|
|
61
|
+
const dirty = uncommittedCode(root);
|
|
62
|
+
if (dirty === null || dirty.length > 0) return null;
|
|
63
|
+
return treeMovedSince(root, stamp.sha, head) ? null : stamp;
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
/**
|
|
51
67
|
* `verify {f} --ci`: the feature-scope proof, run by the project's CI over the pushed commit.
|
|
52
68
|
* Every way CI cannot answer is `mode: 'local'` with the reason, and the caller runs the local
|
package/src/lib/ci/github-ci.js
CHANGED
|
@@ -140,7 +140,7 @@ export function listRuns(root, branch, workflow) {
|
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
142
|
* The newest run over the same code as HEAD. `isCurrent` is a tree comparison, not SHA equality:
|
|
143
|
-
* the
|
|
143
|
+
* the review agents and the archive commit only `.morph/`, and the workflow does not re-run for them.
|
|
144
144
|
*/
|
|
145
145
|
export function pickRun(runs, isCurrent) {
|
|
146
146
|
return (runs || []).find((run) => !(run.status === 'completed' && NO_VERDICT.has(run.conclusion))
|
|
@@ -10,14 +10,9 @@
|
|
|
10
10
|
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
11
11
|
import { join } from 'path';
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* Exported so `discoverFrontendPackages()` (src/lib/tasks/test-nodes.js, #87
|
|
17
|
-
* PR B) scans the SAME candidate list this detector already uses — one
|
|
18
|
-
* spelling of "where a frontend package might live", not two that drift.
|
|
19
|
-
*/
|
|
20
|
-
export const PACKAGE_LOCATIONS = ['.', 'src/frontend', 'src/web', 'frontend', 'apps/web', 'packages/app'];
|
|
13
|
+
import { PACKAGE_LOCATIONS } from '../../../framework/hooks/shared/frontend-locations.js';
|
|
14
|
+
|
|
15
|
+
export { PACKAGE_LOCATIONS };
|
|
21
16
|
|
|
22
17
|
/**
|
|
23
18
|
* Detect project structure from filesystem evidence.
|