@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
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, mkdirSync, rmdirSync, statSync, readdirSync } from 'fs';
|
|
9
9
|
import { join, dirname } from 'path';
|
|
10
10
|
import { getAllOutputPaths, getOutputPath } from '../paths/output-schema.js';
|
|
11
|
+
import { derivePhaseFrom, diskArtifactProbe } from '../../../framework/hooks/shared/state-reader.js';
|
|
11
12
|
|
|
12
13
|
const STATE_FILE_NAME = '.morph/state.json';
|
|
13
14
|
const FEATURES_DIR_NAME = '.morph/features';
|
|
@@ -156,56 +157,19 @@ function scaffoldState() {
|
|
|
156
157
|
};
|
|
157
158
|
}
|
|
158
159
|
/**
|
|
159
|
-
* Derive current phase from filesystem
|
|
160
|
-
*
|
|
161
|
-
* Walks the phase map in descending order and returns the first phase whose
|
|
162
|
-
* CANONICAL ARTIFACT exists. An empty folder is not enough — `mkdir 2-plan/`
|
|
163
|
-
* does not count as "in plan phase" until `spec.md` (the first canonical
|
|
164
|
-
* plan-phase output) lands. If no canonical artifact is found anywhere,
|
|
165
|
-
* returns `'proposal'` (the starting phase).
|
|
166
|
-
*
|
|
167
|
-
* This avoids the chicken-and-egg problem where the agent pre-creates all
|
|
168
|
-
* phase folders, the derived phase jumps to the highest one, and
|
|
169
|
-
* `enforce-phase-writes` then blocks the agent from writing back to the
|
|
170
|
-
* prior phase folder.
|
|
160
|
+
* Derive current phase from filesystem — delegates to the hook-side rule
|
|
161
|
+
* (`derivePhaseFrom`) so the CLI and the hooks never disagree on a feature.
|
|
171
162
|
*
|
|
172
163
|
* @param {string} featurePath - Absolute path to .morph/features/{feature}/
|
|
173
|
-
* @returns {string} Phase name: 'review' | 'implement' | '
|
|
164
|
+
* @returns {string} Phase name: 'review' | 'implement' | 'uiux' | 'plan'
|
|
174
165
|
*/
|
|
175
|
-
const PHASE_INDEX = { proposal: 0, uiux: 1, plan: 2, implement: 3, review: 4 };
|
|
176
|
-
|
|
177
|
-
/** Phase implied purely by approved gates (mirrors hook-side gateImpliedPhase). */
|
|
178
|
-
function gateImpliedPhaseFromGates(gates) {
|
|
179
|
-
const ok = (g) => gates?.[g]?.approved === true;
|
|
180
|
-
if (ok('review')) return 'review';
|
|
181
|
-
if (ok('plan')) return 'implement';
|
|
182
|
-
if (ok('uiux')) return 'plan';
|
|
183
|
-
return 'proposal';
|
|
184
|
-
}
|
|
185
|
-
|
|
186
166
|
export function derivePhase(featurePath) {
|
|
187
|
-
|
|
188
|
-
['4-review', 'review', 'review-report.md'],
|
|
189
|
-
['3-implement', 'implement', 'recap.md'],
|
|
190
|
-
['2-plan', 'plan', 'spec.md'],
|
|
191
|
-
['1-design', 'uiux', 'design-system.md'],
|
|
192
|
-
['0-proposal', 'proposal', 'proposal.md'],
|
|
193
|
-
];
|
|
194
|
-
let artifactPhase = 'proposal';
|
|
195
|
-
for (const [folder, phase, canonical] of phaseMap) {
|
|
196
|
-
if (existsSync(join(featurePath, folder, canonical))) { artifactPhase = phase; break; }
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Gate-aware (same rule as the hook-side derivePhaseForFeature): an approved
|
|
200
|
-
// gate advances the phase before the next phase's canonical artifact exists,
|
|
201
|
-
// so the agent can actually write that first artifact.
|
|
202
|
-
let gates = null;
|
|
167
|
+
let feature = null;
|
|
203
168
|
try {
|
|
204
169
|
const fjPath = join(featurePath, 'feature.json');
|
|
205
|
-
if (existsSync(fjPath))
|
|
206
|
-
} catch {
|
|
207
|
-
|
|
208
|
-
return PHASE_INDEX[implied] > PHASE_INDEX[artifactPhase] ? implied : artifactPhase;
|
|
170
|
+
if (existsSync(fjPath)) feature = JSON.parse(readFileSync(fjPath, 'utf8'));
|
|
171
|
+
} catch { feature = null; }
|
|
172
|
+
return derivePhaseFrom(feature, diskArtifactProbe(featurePath));
|
|
209
173
|
}
|
|
210
174
|
|
|
211
175
|
/**
|
|
@@ -498,7 +462,7 @@ export function getFeature(featureName) {
|
|
|
498
462
|
/**
|
|
499
463
|
* Create or get feature with default structure.
|
|
500
464
|
* v5: every feature follows the fixed phase sequence
|
|
501
|
-
* (
|
|
465
|
+
* (plan → [uiux] → implement → review). No workflow selection.
|
|
502
466
|
* @param {string} featureName - Feature name
|
|
503
467
|
* @returns {Promise<Object>} Feature object
|
|
504
468
|
*/
|
|
@@ -511,17 +475,10 @@ async function ensureFeature(featureName) {
|
|
|
511
475
|
createdAt: new Date().toISOString(),
|
|
512
476
|
updatedAt: new Date().toISOString(),
|
|
513
477
|
approvalGates: {
|
|
514
|
-
proposal: { approved: false, timestamp: null, approvedBy: null },
|
|
515
|
-
uiux: { approved: false, timestamp: null, approvedBy: null },
|
|
516
478
|
plan: { approved: false, timestamp: null, approvedBy: null },
|
|
479
|
+
uiux: { approved: false, timestamp: null, approvedBy: null },
|
|
517
480
|
review: { approved: false, timestamp: null, approvedBy: null }
|
|
518
481
|
},
|
|
519
|
-
tasks: {
|
|
520
|
-
total: 0,
|
|
521
|
-
completed: 0,
|
|
522
|
-
inProgress: 0,
|
|
523
|
-
pending: 0
|
|
524
|
-
},
|
|
525
482
|
checkpoints: [],
|
|
526
483
|
trustConfig: {},
|
|
527
484
|
contextBundles: [],
|
|
@@ -575,18 +532,11 @@ export async function createFeature(featureName, options = {}) {
|
|
|
575
532
|
createdAt: now,
|
|
576
533
|
updatedAt: now,
|
|
577
534
|
approvalGates: {
|
|
578
|
-
proposal: { approved: false, timestamp: null, approvedBy: null },
|
|
579
|
-
uiux: { approved: false, timestamp: null, approvedBy: null },
|
|
580
535
|
plan: { approved: false, timestamp: null, approvedBy: null },
|
|
536
|
+
uiux: { approved: false, timestamp: null, approvedBy: null },
|
|
581
537
|
review: { approved: false, timestamp: null, approvedBy: null }
|
|
582
538
|
},
|
|
583
539
|
gateDecisions: {},
|
|
584
|
-
tasks: {
|
|
585
|
-
total: 0,
|
|
586
|
-
completed: 0,
|
|
587
|
-
inProgress: 0,
|
|
588
|
-
pending: 0
|
|
589
|
-
},
|
|
590
540
|
checkpoints: [],
|
|
591
541
|
trustConfig: {},
|
|
592
542
|
contextBundles: [],
|
|
@@ -603,7 +553,7 @@ export async function createFeature(featureName, options = {}) {
|
|
|
603
553
|
}
|
|
604
554
|
|
|
605
555
|
/**
|
|
606
|
-
* Update feature property (supports nested keys like "
|
|
556
|
+
* Update feature property (supports nested keys like "approvalGates.plan.approved")
|
|
607
557
|
* @param {string} featureName - Feature name
|
|
608
558
|
* @param {string} key - Property key (supports dot notation)
|
|
609
559
|
* @param {any} value - Value to set
|
|
@@ -657,36 +607,6 @@ export async function updateFeatureMultiple(featureName, updates) {
|
|
|
657
607
|
saveState(state);
|
|
658
608
|
}
|
|
659
609
|
|
|
660
|
-
/**
|
|
661
|
-
* Add checkpoint to feature
|
|
662
|
-
* @param {string} featureName - Feature name
|
|
663
|
-
* @param {string} note - Checkpoint note
|
|
664
|
-
* @returns {Object} Checkpoint object
|
|
665
|
-
*/
|
|
666
|
-
export async function addCheckpoint(featureName, note) {
|
|
667
|
-
await ensureFeature(featureName);
|
|
668
|
-
const state = loadState();
|
|
669
|
-
const feature = state.features[featureName];
|
|
670
|
-
|
|
671
|
-
const checkpoint = {
|
|
672
|
-
passed: true,
|
|
673
|
-
checkpointNum: feature.checkpoints.length + 1,
|
|
674
|
-
timestamp: new Date().toISOString(),
|
|
675
|
-
phase: feature.phase || 'proposal',
|
|
676
|
-
results: [],
|
|
677
|
-
summary: {
|
|
678
|
-
note: note,
|
|
679
|
-
completedTasks: feature.tasks.completed
|
|
680
|
-
}
|
|
681
|
-
};
|
|
682
|
-
|
|
683
|
-
feature.checkpoints.push(checkpoint);
|
|
684
|
-
feature.updatedAt = new Date().toISOString();
|
|
685
|
-
|
|
686
|
-
saveState(state);
|
|
687
|
-
return checkpoint;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
610
|
/**
|
|
691
611
|
* Normalize output type from kebab-case to camelCase (BUG-002 fix - Enhanced)
|
|
692
612
|
* Supports both kebab-case and camelCase input formats for all output types
|
|
@@ -781,7 +701,7 @@ function findClosestMatch(inputType, validTypes) {
|
|
|
781
701
|
/**
|
|
782
702
|
* Mark output as created
|
|
783
703
|
* @param {string} featureName - Feature name
|
|
784
|
-
* @param {string} outputType - Output type (
|
|
704
|
+
* @param {string} outputType - Output type (plan, checks, verifierReport, etc.)
|
|
785
705
|
*/
|
|
786
706
|
export async function markOutput(featureName, outputType) {
|
|
787
707
|
await ensureFeature(featureName);
|
|
@@ -860,7 +780,7 @@ export async function trackFileChange(featureName, filePath, action, phase) {
|
|
|
860
780
|
state.features[featureName].fileChanges.push({
|
|
861
781
|
path: filePath,
|
|
862
782
|
action,
|
|
863
|
-
phase: phase || state.features[featureName].phase || '
|
|
783
|
+
phase: phase || state.features[featureName].phase || 'plan',
|
|
864
784
|
timestamp: new Date().toISOString()
|
|
865
785
|
});
|
|
866
786
|
|
|
@@ -968,7 +888,7 @@ export function getSummary() {
|
|
|
968
888
|
/**
|
|
969
889
|
* Set approval gate status
|
|
970
890
|
* @param {string} featureName - Feature name
|
|
971
|
-
* @param {string} gate - Gate name (
|
|
891
|
+
* @param {string} gate - Gate name (plan, uiux, review)
|
|
972
892
|
* @param {boolean} approved - Approval status
|
|
973
893
|
* @param {Object} metadata - Additional metadata (approvedBy, reason, etc.)
|
|
974
894
|
*/
|
|
@@ -978,9 +898,8 @@ export async function setApprovalGate(featureName, gate, approved, metadata = {}
|
|
|
978
898
|
|
|
979
899
|
if (!feature.approvalGates) {
|
|
980
900
|
feature.approvalGates = {
|
|
981
|
-
proposal: { approved: false, timestamp: null, approvedBy: null },
|
|
982
|
-
uiux: { approved: false, timestamp: null, approvedBy: null },
|
|
983
901
|
plan: { approved: false, timestamp: null, approvedBy: null },
|
|
902
|
+
uiux: { approved: false, timestamp: null, approvedBy: null },
|
|
984
903
|
review: { approved: false, timestamp: null, approvedBy: null }
|
|
985
904
|
};
|
|
986
905
|
}
|
|
@@ -1026,7 +945,7 @@ export function isPendingApproval(featureName) {
|
|
|
1026
945
|
return false;
|
|
1027
946
|
}
|
|
1028
947
|
|
|
1029
|
-
const currentPhase = feature.phase || '
|
|
948
|
+
const currentPhase = feature.phase || 'plan';
|
|
1030
949
|
|
|
1031
950
|
// Check if current phase has an approval gate
|
|
1032
951
|
const phaseGateMap = {
|
|
@@ -76,7 +76,7 @@ export function getAllTemplates(projectPath = null) {
|
|
|
76
76
|
/**
|
|
77
77
|
* Get template by ID.
|
|
78
78
|
*
|
|
79
|
-
* @param {string} templateId - Template identifier (e.g., '
|
|
79
|
+
* @param {string} templateId - Template identifier (e.g., 'plan')
|
|
80
80
|
* @param {string} [projectPath] - Optional project path
|
|
81
81
|
* @returns {Object|null} Template object or null if not found
|
|
82
82
|
*/
|
|
@@ -100,7 +100,7 @@ export function getTemplatesByCategory(category, projectPath = null) {
|
|
|
100
100
|
/**
|
|
101
101
|
* Get templates by phase.
|
|
102
102
|
*
|
|
103
|
-
* @param {string} phase - Phase name (e.g., '
|
|
103
|
+
* @param {string} phase - Phase name (e.g., 'plan', 'uiux', 'implement')
|
|
104
104
|
* @param {string} [projectPath] - Optional project path
|
|
105
105
|
* @returns {Array} Filtered template array
|
|
106
106
|
*/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { getTemplateById, resolveFrameworkTemplatePath } from './template-registry.js';
|
|
4
|
+
|
|
5
|
+
/** The project-local override (same relative path as in the package) wins over the package copy. */
|
|
6
|
+
export function templateSourcePath(templateId, cwd = process.cwd()) {
|
|
7
|
+
const template = getTemplateById(templateId);
|
|
8
|
+
const local = template ? join(cwd, '.morph', 'framework', 'templates', template.path) : null;
|
|
9
|
+
return local && existsSync(local) ? local : resolveFrameworkTemplatePath(templateId);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** @returns {{ path: string, written: boolean }} `written: false` when the file already existed. */
|
|
13
|
+
export function writeIfAbsent(path, content) {
|
|
14
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
15
|
+
try {
|
|
16
|
+
writeFileSync(path, content, { encoding: 'utf8', flag: 'wx' });
|
|
17
|
+
return { path, written: true };
|
|
18
|
+
} catch (err) {
|
|
19
|
+
if (err.code === 'EEXIST') return { path, written: false };
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/core/work-types.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Fields per type:
|
|
14
14
|
* label - Human-readable name
|
|
15
15
|
* scaffold - Phase folders created at feature birth (subset of
|
|
16
|
-
*
|
|
16
|
+
* 2-plan/1-design/3-implement/4-review)
|
|
17
17
|
* skippedGates - Gates stamped `policy:{type}`-approved at birth
|
|
18
18
|
* instead of waiting for human approval
|
|
19
19
|
* forcedPauseGates - Gates that ALWAYS pause for a human, regardless of
|
|
@@ -27,29 +27,29 @@ export const POLICY_APPROVER_PREFIX = 'policy:';
|
|
|
27
27
|
export const WORK_TYPES = {
|
|
28
28
|
feature: {
|
|
29
29
|
label: 'Feature',
|
|
30
|
-
scaffold: ['
|
|
30
|
+
scaffold: ['2-plan'],
|
|
31
31
|
skippedGates: [],
|
|
32
32
|
forcedPauseGates: [],
|
|
33
|
-
entrySkill: 'morph-
|
|
33
|
+
entrySkill: 'morph-plan',
|
|
34
34
|
},
|
|
35
35
|
bug: {
|
|
36
36
|
label: 'Bug fix',
|
|
37
|
-
scaffold: ['
|
|
37
|
+
scaffold: ['2-plan'],
|
|
38
38
|
skippedGates: [],
|
|
39
39
|
forcedPauseGates: [],
|
|
40
|
-
entrySkill: 'morph-
|
|
40
|
+
entrySkill: 'morph-plan',
|
|
41
41
|
},
|
|
42
42
|
chore: {
|
|
43
43
|
label: 'Chore',
|
|
44
|
-
scaffold: ['
|
|
45
|
-
skippedGates: ['
|
|
44
|
+
scaffold: ['2-plan', '3-implement'],
|
|
45
|
+
skippedGates: ['plan'],
|
|
46
46
|
forcedPauseGates: [],
|
|
47
|
-
entrySkill: 'morph-
|
|
47
|
+
entrySkill: 'morph-plan',
|
|
48
48
|
},
|
|
49
49
|
hotfix: {
|
|
50
50
|
label: 'Hotfix',
|
|
51
|
-
scaffold: ['
|
|
52
|
-
skippedGates: ['
|
|
51
|
+
scaffold: ['2-plan', '3-implement'],
|
|
52
|
+
skippedGates: ['plan'],
|
|
53
53
|
forcedPauseGates: ['review'],
|
|
54
54
|
entrySkill: 'morph-hotfix',
|
|
55
55
|
},
|
|
@@ -14,7 +14,6 @@ import SkillsLibrary from './pages/SkillsLibrary.jsx';
|
|
|
14
14
|
import RulesMap from './pages/RulesMap.jsx';
|
|
15
15
|
import Standards from './pages/Standards.jsx';
|
|
16
16
|
import Guide from './pages/Guide.jsx';
|
|
17
|
-
import Rubrics from './pages/Rubrics.jsx';
|
|
18
17
|
import { useNow } from './hooks/usePref.js';
|
|
19
18
|
import { useProjects, useCurrentProject, projectHref } from './lib/project.js';
|
|
20
19
|
|
|
@@ -77,7 +76,6 @@ export default function App() {
|
|
|
77
76
|
<Route path="/p/:project/activity" element={<Activity />} />
|
|
78
77
|
<Route path="/guide" element={<Guide />} />
|
|
79
78
|
<Route path="/guide/:doc" element={<Guide />} />
|
|
80
|
-
<Route path="/rubrics" element={<Rubrics />} />
|
|
81
79
|
<Route path="/agents" element={<AgentMap />} />
|
|
82
80
|
<Route path="/phases" element={<PhasePipeline />} />
|
|
83
81
|
<Route path="/hooks" element={<HookInspector />} />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import { Link } from 'react-router-dom';
|
|
3
3
|
import {
|
|
4
|
-
ChatCircleText, Question, BookOpenText, PaperPlaneTilt, ArrowBendDownLeft,
|
|
4
|
+
ChatCircleText, Question, BookOpenText, PaperPlaneTilt, ArrowBendDownLeft, ShieldCheck, Flag, WarningOctagon,
|
|
5
5
|
PencilSimpleLine, Eye, TerminalWindow, Wrench,
|
|
6
6
|
} from '@phosphor-icons/react';
|
|
7
7
|
import { useMorphQuery } from '../hooks/useMorphQuery.js';
|
|
@@ -16,7 +16,6 @@ export const KIND_META = {
|
|
|
16
16
|
skill: { label: 'Skills', icon: BookOpenText },
|
|
17
17
|
dispatch: { label: 'Despachos', icon: PaperPlaneTilt },
|
|
18
18
|
handback: { label: 'Retornos', icon: ArrowBendDownLeft },
|
|
19
|
-
task: { label: 'Tasks', icon: CheckSquare },
|
|
20
19
|
verify: { label: 'Verify', icon: ShieldCheck },
|
|
21
20
|
gate: { label: 'Gates', icon: Flag },
|
|
22
21
|
failure: { label: 'Falhas', icon: WarningOctagon },
|
|
@@ -25,7 +24,7 @@ export const KIND_META = {
|
|
|
25
24
|
command: { label: 'Comandos', icon: TerminalWindow },
|
|
26
25
|
tool: { label: 'Outras tools', icon: Wrench },
|
|
27
26
|
};
|
|
28
|
-
const DEFAULT = ['prompt', 'question', 'skill', 'dispatch', 'handback', '
|
|
27
|
+
const DEFAULT = ['prompt', 'question', 'skill', 'dispatch', 'handback', 'verify', 'gate', 'failure'];
|
|
29
28
|
|
|
30
29
|
const NODE_TONE = { pass: 'text-[var(--color-ok)]', fail: 'text-[var(--color-bad)]', skip: 'text-[var(--color-faint)]' };
|
|
31
30
|
|
|
@@ -8,13 +8,13 @@ import { useMorphQuery } from '../hooks/useMorphQuery.js';
|
|
|
8
8
|
import { apiBase, projectHref, featureHref } from '../lib/project.js';
|
|
9
9
|
|
|
10
10
|
const KIND_LABEL = {
|
|
11
|
-
page: 'Página', project: 'Projeto', feature: 'Feature',
|
|
12
|
-
standard: 'Standard', rule: 'Rule', hook: 'Hook', guide: 'Guia',
|
|
11
|
+
page: 'Página', project: 'Projeto', feature: 'Feature', slice: 'Fatia', skill: 'Skill', persona: 'Persona',
|
|
12
|
+
standard: 'Standard', rule: 'Rule', hook: 'Hook', guide: 'Guia',
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const PAGES = [
|
|
16
16
|
['Portfólio', '/'], ['Guia', '/guide'], ['Fases', '/phases'], ['Skills', '/skills'],
|
|
17
|
-
['Personas', '/agents'], ['Standards', '/standards'], ['Rules', '/rules'], ['Hooks', '/hooks'],
|
|
17
|
+
['Personas', '/agents'], ['Standards', '/standards'], ['Rules', '/rules'], ['Hooks', '/hooks'],
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
/** Everything the framework knows that has a page — built once. */
|
|
@@ -35,7 +35,6 @@ function knowledgeIndex() {
|
|
|
35
35
|
out.push({ kind: 'guide', title: h.text, hint: d.title, to: `/guide/${d.id}#${h.slug}`, hay: `${h.text} ${d.title}` });
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
for (const r of morphData.docs?.rubrics || []) out.push({ kind: 'rubric', title: r.title, to: `/rubrics?sel=${encodeURIComponent(`dim:${r.id}`)}`, hay: r.title });
|
|
39
38
|
return out;
|
|
40
39
|
}
|
|
41
40
|
|
|
@@ -44,7 +43,7 @@ function projectIndex(portfolio) {
|
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
/**
|
|
47
|
-
* Features and their
|
|
46
|
+
* Features and their slices. `projectName` is set for another project's
|
|
48
47
|
* features, so a hit says where it lives.
|
|
49
48
|
*/
|
|
50
49
|
function featureIndex(project, features, projectName = null) {
|
|
@@ -52,8 +51,8 @@ function featureIndex(project, features, projectName = null) {
|
|
|
52
51
|
const where = (text) => (projectName ? (text ? `${projectName}: ${text}` : projectName) : text);
|
|
53
52
|
for (const f of features || []) {
|
|
54
53
|
out.push({ kind: 'feature', title: f.name, hint: where(f.description), to: featureHref(project, f.name), hay: `${f.name} ${f.description || ''} ${projectName || ''}` });
|
|
55
|
-
for (const
|
|
56
|
-
out.push({ kind: '
|
|
54
|
+
for (const sl of f.sliceIndex || []) {
|
|
55
|
+
out.push({ kind: 'slice', title: `${sl.id} ${sl.title}`, hint: where(f.name), to: featureHref(project, f.name, '?tab=slices'), hay: `${sl.id} ${sl.title} ${f.name} ${projectName || ''}` });
|
|
57
56
|
}
|
|
58
57
|
}
|
|
59
58
|
return out;
|
|
@@ -120,7 +119,7 @@ export default function CommandPalette({ open, onClose, portfolio, project }) {
|
|
|
120
119
|
value={query}
|
|
121
120
|
onChange={(e) => setQuery(e.target.value)}
|
|
122
121
|
onKeyDown={onKey}
|
|
123
|
-
placeholder="Buscar features,
|
|
122
|
+
placeholder="Buscar features, fatias, skills, personas, standards, seções do guia…"
|
|
124
123
|
className="w-full bg-transparent text-base text-[var(--color-fg)] outline-none focus-visible:outline-none placeholder:text-[var(--color-faint)]"
|
|
125
124
|
aria-controls="palette-results"
|
|
126
125
|
aria-activedescendant={results[active] ? `palette-${active}` : undefined}
|
|
@@ -16,12 +16,11 @@ export function entityHref(kind, id) {
|
|
|
16
16
|
case 'hook': return q('hooks');
|
|
17
17
|
case 'phase': return q('phases');
|
|
18
18
|
case 'guide': return `/guide/${encodeURIComponent(id)}`;
|
|
19
|
-
case 'rubric': return q('rubrics');
|
|
20
19
|
default: return '#';
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
// `standards/backend/dotnet/testing.md` (as a plan may cite it) → the registry `path`.
|
|
25
24
|
export function standardKey(ref) {
|
|
26
25
|
return String(ref).replace(/^\.?\/?(\.morph\/framework\/)?standards\//, '');
|
|
27
26
|
}
|
|
@@ -7,7 +7,7 @@ const REMARK = [remarkGfm];
|
|
|
7
7
|
const REHYPE = [rehypeSlug];
|
|
8
8
|
|
|
9
9
|
/** Frontmatter keys worth showing, in the order a reader wants them. */
|
|
10
|
-
const META_ORDER = ['name', 'description', '
|
|
10
|
+
const META_ORDER = ['name', 'description', 'when_to_use', 'user-invocable', 'stacks', 'version', 'cliVersion'];
|
|
11
11
|
|
|
12
12
|
function MetaBlock({ meta }) {
|
|
13
13
|
const keys = Object.keys(meta).sort((a, b) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NavLink, useLocation, useNavigate } from 'react-router-dom';
|
|
2
2
|
import {
|
|
3
|
-
MagnifyingGlass, SquaresFour, Kanban, Pulse, BookOpen, FlowArrow, PuzzlePiece, UsersThree, Books, Ruler, Plug,
|
|
3
|
+
MagnifyingGlass, SquaresFour, Kanban, Pulse, BookOpen, FlowArrow, PuzzlePiece, UsersThree, Books, Ruler, Plug,
|
|
4
4
|
} from '@phosphor-icons/react';
|
|
5
5
|
import { projectHref } from '../lib/project.js';
|
|
6
6
|
|
|
@@ -12,7 +12,6 @@ const KNOWLEDGE = [
|
|
|
12
12
|
{ to: '/standards', label: 'Standards', icon: Books },
|
|
13
13
|
{ to: '/rules', label: 'Rules', icon: Ruler },
|
|
14
14
|
{ to: '/hooks', label: 'Hooks', icon: Plug },
|
|
15
|
-
{ to: '/rubrics', label: 'Rubricas', icon: Scales },
|
|
16
15
|
];
|
|
17
16
|
|
|
18
17
|
function Badge({ pulse }) {
|
|
@@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
5
5
|
* reports that the project's `.morph/` changed in the given scope.
|
|
6
6
|
*
|
|
7
7
|
* `scope` matches the server's invalidation: 'features' (feature.json,
|
|
8
|
-
*
|
|
8
|
+
* slice reports, artifacts) or 'activity' (events.jsonl). A refetch keeps the old
|
|
9
9
|
* data on screen until the new one arrives — no flash of "loading" while an
|
|
10
10
|
* agent is working.
|
|
11
11
|
*
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
import morphData from 'virtual:morph-data';
|
|
7
7
|
|
|
8
8
|
const LABELS = {
|
|
9
|
-
proposal: 'Proposta',
|
|
10
|
-
uiux: 'UI/UX',
|
|
11
9
|
plan: 'Plano',
|
|
10
|
+
uiux: 'UI/UX',
|
|
12
11
|
implement: 'Implementação',
|
|
13
12
|
review: 'Revisão',
|
|
14
13
|
};
|
|
@@ -29,9 +28,8 @@ export function phaseMeta(id) {
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
export const GATE_LABEL = {
|
|
32
|
-
|
|
31
|
+
plan: 'Gate do plano',
|
|
33
32
|
uiux: 'Gate UI/UX',
|
|
34
|
-
plan: 'Gate 2 (plano)',
|
|
35
33
|
review: 'Gate 3 (revisão)',
|
|
36
34
|
};
|
|
37
35
|
|
|
@@ -13,7 +13,7 @@ export default function Activity() {
|
|
|
13
13
|
<h1 className="mt-1 text-4xl font-semibold tracking-tight">Atividade</h1>
|
|
14
14
|
<p className="mt-3 max-w-2xl text-sm leading-relaxed text-[var(--color-dim)]">
|
|
15
15
|
O que o Claude fez no projeto, contado a partir da telemetria dos hooks: suas mensagens, as skills carregadas,
|
|
16
|
-
os sub-agents despachados,
|
|
16
|
+
os sub-agents despachados, os verifies e os gates. Atualiza sozinho enquanto ele trabalha.
|
|
17
17
|
</p>
|
|
18
18
|
</header>
|
|
19
19
|
<div className="rise-2 bezel">
|