@polymorphism-tech/morph-spec 9.8.0 → 10.1.1
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 +12 -10
- package/README.md +4 -4
- package/bin/morph-spec.js +40 -43
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +28 -36
- package/docs/QUICKSTART.md +31 -191
- package/framework/CLAUDE.md +24 -30
- package/framework/MORPH.md +47 -123
- package/framework/agents.json +6 -374
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-archive.md +4 -3
- package/framework/commands/morph-hotfix.md +8 -30
- package/framework/commands/morph-infra.md +2 -2
- package/framework/commands/morph-media.md +10 -9
- package/framework/commands/morph-preflight.md +3 -3
- package/framework/commands/morph-review.md +15 -0
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +84 -95
- package/framework/evals/review-checklist.md +45 -53
- package/framework/hooks/README.md +4 -4
- package/framework/hooks/claude-code/core/README.md +1 -1
- package/framework/hooks/claude-code/core/denylist.json +4 -4
- package/framework/hooks/claude-code/core/gate-guard.js +9 -0
- package/framework/hooks/claude-code/core/masked-exit.js +49 -0
- package/framework/hooks/claude-code/core/state-sync.js +4 -1
- package/framework/hooks/claude-code/core/telemetry-log.js +1 -2
- package/framework/hooks/claude-code/core/test-budget.js +10 -70
- package/framework/hooks/claude-code/core/validate-completion.js +11 -24
- package/framework/hooks/claude-code/core/worktree-context.js +5 -22
- package/framework/hooks/claude-code/core/worktree-create.js +6 -45
- package/framework/hooks/claude-code/core/worktree-env-inject.js +2 -10
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/gate-event.js +2 -2
- package/framework/hooks/shared/graph-refresh.js +20 -3
- package/framework/hooks/shared/heavy-command.js +32 -1
- package/framework/hooks/shared/heavy-config.js +1 -1
- package/framework/hooks/shared/next-step.js +63 -24
- package/framework/hooks/shared/phase-utils.js +6 -9
- package/framework/hooks/shared/report-progress.js +86 -0
- package/framework/hooks/shared/state-reader.js +3 -2
- package/framework/hooks/statusline.js +332 -0
- package/framework/mcp/registry.json +4 -4
- package/framework/phases.json +32 -10
- package/framework/rules/ai-agents-standards.md +10 -2
- package/framework/rules/csharp-standards.md +4 -0
- package/framework/rules/data-standards.md +1 -1
- package/framework/rules/ef-standards.md +20 -0
- package/framework/rules/frontend-standards.md +1 -1
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/implement-report.md +25 -0
- package/framework/rules/infrastructure-standards.md +2 -0
- package/framework/rules/neon-auth-standards.md +1 -1
- package/framework/rules/neon-standards.md +3 -3
- package/framework/rules/nextjs-standards.md +7 -0
- package/framework/rules/plan-grammar.md +53 -0
- package/framework/rules/ui-design.md +27 -0
- package/framework/schemas/config.schema.json +11 -18
- package/framework/schemas/feature.schema.json +7 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/create-umbraco-lp/references/umbraco-lp-patterns.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +2 -2
- package/framework/skills/core/morph-apply/SKILL.md +39 -166
- package/framework/skills/core/morph-apply/references/debugging.md +8 -0
- package/framework/skills/core/morph-apply/references/hotfix.md +42 -0
- package/framework/skills/core/morph-apply/references/machine.md +29 -0
- package/framework/skills/core/{morph-media/SKILL.md → morph-apply/references/media.md} +11 -74
- package/framework/skills/core/morph-apply/references/review.md +65 -0
- package/framework/skills/core/morph-plan/SKILL.md +49 -109
- package/framework/skills/core/morph-plan/references/critique.md +43 -0
- package/framework/skills/core/morph-plan/references/interview.md +37 -0
- package/framework/skills/core/morph-uiux/SKILL.md +47 -64
- package/framework/skills/core/morph-uiux/surfaces.json +32 -0
- package/framework/standards/STANDARDS.json +51 -28
- package/framework/standards/STANDARDS.meta.json +16 -19
- package/framework/standards/ai-agents/agent-session.md +2 -3
- package/framework/standards/ai-agents/context-providers.md +2 -2
- package/framework/standards/ai-agents/durable-workflows-hitl.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +2 -2
- package/framework/standards/ai-agents/rag-custom-pgvector.md +7 -6
- package/framework/standards/ai-agents/setup.md +1 -1
- package/framework/standards/ai-agents/tool-adapters.md +2 -2
- package/framework/standards/architecture/stack-baseline.md +70 -0
- package/framework/standards/backend/database/postgresql/database.md +22 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +33 -1
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +2 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +12 -13
- package/framework/standards/frontend/design-system/ai-image-generation.md +51 -12
- package/framework/standards/frontend/design-system/ai-video-generation.md +50 -4
- package/framework/standards/frontend/design-system/art-direction.md +2 -2
- package/framework/standards/frontend/scroll-driven/frame-scrub.md +12 -2
- package/framework/standards/frontend/scroll-driven/scroll-components.md +76 -4
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +2 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +62 -20
- package/framework/standards/infrastructure/neon/neon-platform.md +61 -0
- package/framework/standards/infrastructure/neon/neon-setup.md +14 -13
- package/framework/standards/integration/mcp/mcp-tools.md +20 -20
- package/framework/templates/REGISTRY.json +1 -2
- package/framework/templates/context/ARCHITECTURE.md +1 -1
- package/framework/templates/docs/plan.md +15 -12
- package/framework/templates/docs/ui-mockups.md +17 -105
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +1 -1
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +88 -23
- package/src/commands/graph/diff.js +74 -0
- package/src/commands/graph/status.js +1 -1
- package/src/commands/observability/cost.js +2 -4
- package/src/commands/observability/telemetry.js +1 -1
- package/src/commands/project/doctor.js +23 -39
- package/src/commands/project/fleet.js +5 -6
- package/src/commands/project/init.js +63 -36
- package/src/commands/project/stack-scan.js +3 -42
- package/src/commands/project/stack-set.js +26 -0
- package/src/commands/project/status.js +12 -13
- package/src/commands/project/update.js +39 -3
- package/src/commands/project/worktree.js +3 -14
- package/src/commands/state/advance.js +17 -17
- package/src/commands/state/approve.js +0 -6
- package/src/commands/state/archive.js +27 -1
- package/src/commands/state/auto-approval.js +2 -2
- package/src/commands/state/create.js +1 -6
- package/src/commands/state/gate-check.js +38 -31
- package/src/commands/state/gate-decision.js +3 -2
- package/src/commands/state/next.js +1 -0
- package/src/commands/state/recap.js +67 -0
- package/src/commands/state/report-check.js +36 -178
- package/src/commands/validation/plan-check.js +16 -11
- package/src/commands/validation/validate-feature.js +3 -3
- package/src/commands/validation/verify.js +3 -6
- package/src/core/paths/output-schema.js +3 -3
- package/src/core/state/state-manager.js +2 -4
- package/src/dashboard/app/components/CommandPalette.jsx +2 -5
- package/src/dashboard/app/components/GatePauses.jsx +116 -0
- package/src/dashboard/app/components/SessionMap.jsx +77 -0
- package/src/dashboard/app/pages/Activity.jsx +4 -0
- package/src/dashboard/app/pages/AgentMap.jsx +1 -2
- package/src/dashboard/app/pages/FeatureDetail.jsx +129 -186
- package/src/dashboard/app/pages/Features.jsx +7 -7
- package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
- package/src/dashboard/app/pages/Portfolio.jsx +40 -5
- package/src/dashboard/scanner/activity-scanner.js +29 -0
- package/src/dashboard/scanner/agents-scanner.js +11 -14
- package/src/dashboard/scanner/features-scanner.js +45 -24
- package/src/dashboard/scanner/project-api.js +11 -2
- package/src/dashboard/scanner/skills-scanner.js +11 -1
- package/src/lib/detectors/index.js +1 -1
- package/src/lib/e2e/applicability.js +3 -1
- package/src/lib/git/tree-state.js +1 -16
- package/src/lib/graph/diff-tree.js +127 -0
- package/src/lib/graph/graphify-runtime.js +12 -5
- package/src/lib/installers/memory-installer.js +3 -3
- package/src/lib/installers/rules-installer.js +57 -0
- package/src/lib/installers/stack-assets.js +17 -0
- package/src/lib/installers/stack-sync.js +219 -0
- package/src/lib/plan/checks.js +61 -109
- package/src/lib/plan/critique.js +28 -0
- package/src/lib/plan/media.js +42 -0
- package/src/lib/plan/plan-html.js +125 -0
- package/src/lib/plan/plan-parser.js +34 -45
- package/src/lib/recap/piper.js +36 -0
- package/src/lib/recap/recap.js +232 -0
- package/src/lib/review-reports.js +28 -4
- package/src/lib/risk-detector.js +90 -11
- package/src/lib/specs/screen-checklist.js +33 -48
- package/src/lib/stack/dotnet-build-ignores.js +8 -9
- package/src/lib/telemetry/metrics.js +27 -29
- package/src/lib/validators/reuse/reuse-validator.js +1 -9
- package/src/lib/validators/verify-runner.js +0 -7
- package/src/lib/worktree/fleet-scanner.js +7 -20
- package/src/lib/worktree/worktree-audit.js +0 -37
- package/src/scripts/setup-infra.js +15 -50
- package/src/utils/agents-installer.js +27 -201
- package/src/utils/hooks-installer.js +11 -95
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +0 -23
- package/framework/hooks/claude-code/statusline.py +0 -699
- package/framework/hooks/claude-code/statusline.sh +0 -11
- package/framework/hooks/shared/agent-reap.js +0 -74
- package/framework/hooks/shared/slice-progress.js +0 -110
- package/framework/skills/core/morph-hotfix/SKILL.md +0 -133
- package/framework/skills/core/morph-machine/SKILL.md +0 -46
- package/framework/skills/core/morph-review/SKILL.md +0 -129
- package/framework/skills/core/morph-standards/SKILL.md +0 -52
- package/framework/standards/PROVENANCE.json +0 -119
- package/framework/standards/infrastructure/neon/neon-pgrag.md +0 -243
- package/src/commands/project/worktree-reap.js +0 -34
- package/src/commands/state/handoff.js +0 -65
- package/src/commands/validation/mutate.js +0 -263
- package/src/commands/validation/scout-check.js +0 -28
- package/src/lib/generators/handoff-generator.js +0 -172
- package/src/lib/mutation/adapters.js +0 -79
- package/src/lib/mutation/commands.js +0 -61
- package/src/lib/mutation/dotnet-introspection.js +0 -281
- package/src/lib/mutation/manifest.js +0 -144
- package/src/lib/mutation/plan.js +0 -44
- package/src/lib/mutation/report.js +0 -68
- package/src/lib/mutation/runner.js +0 -301
- package/src/lib/mutation/schemata.js +0 -60
- package/src/lib/mutation/snapshot.js +0 -65
- package/src/lib/mutation/subset.js +0 -109
- package/src/lib/mutation/trx.js +0 -50
- package/src/lib/scout/scout-check.js +0 -293
- package/src/lib/validators/traceability-check.js +0 -103
- package/src/lib/worktree/agent-reaper.js +0 -247
|
@@ -14,6 +14,7 @@ import { afterHeavyRun } from '../../lib/heavy/cleanup.js';
|
|
|
14
14
|
import { memoryBusyHint } from '../../lib/heavy/live-stacks.js';
|
|
15
15
|
import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
|
|
16
16
|
import { runCiVerify, CiUsageError, reusableLocalStamp } from '../../lib/ci/ci-verify.js';
|
|
17
|
+
import { syncStackRules } from '../../lib/installers/stack-sync.js';
|
|
17
18
|
|
|
18
19
|
function statusIcon(status) {
|
|
19
20
|
if (status === 'pass') return chalk.green('✓');
|
|
@@ -57,12 +58,6 @@ function formatVerifyResult(result) {
|
|
|
57
58
|
for (const line of result.feedback) lines.push(` ${line}`);
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
if (result.traceability?.warnings?.length > 0) {
|
|
61
|
-
lines.push('');
|
|
62
|
-
lines.push(chalk.yellow('Traceability warnings (advisory — never fails verify):'));
|
|
63
|
-
for (const w of result.traceability.warnings) lines.push(` ⚠ ${w}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
61
|
lines.push('');
|
|
67
62
|
return lines.join('\n');
|
|
68
63
|
}
|
|
@@ -180,6 +175,8 @@ export async function verifyCommand(featureName, options = {}) {
|
|
|
180
175
|
process.exit(2);
|
|
181
176
|
}
|
|
182
177
|
|
|
178
|
+
syncStackRules(process.cwd(), { persistTags: false, log: (line) => console.error(chalk.gray(line)) });
|
|
179
|
+
|
|
183
180
|
if (options.ci) {
|
|
184
181
|
const conflict = CI_EXCLUSIVE_FLAGS.find(([flag]) => options[flag])?.[1];
|
|
185
182
|
if (conflict) {
|
|
@@ -3,12 +3,12 @@ import { join } from 'path';
|
|
|
3
3
|
export const OUTPUT_SCHEMA = {
|
|
4
4
|
plan: { filename: 'plan.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
5
5
|
checks: { filename: 'checks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
6
|
+
planCritique: { filename: 'critica.md', phaseDir: '2-plan', phase: 'plan', protected: false },
|
|
7
|
+
planView: { filename: 'plan.html', phaseDir: '2-plan', phase: 'plan', protected: false },
|
|
6
8
|
|
|
7
|
-
uiDesignSystem: { filename: 'design-system.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
8
9
|
uiMockups: { filename: 'mockups.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
9
|
-
uiComponents: { filename: 'components.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
10
|
-
uiFlows: { filename: 'flows.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
11
10
|
|
|
11
|
+
implementReport: { filename: 'report.md', phaseDir: '3-implement', phase: 'implement', protected: false },
|
|
12
12
|
evidencias: { dirname: 'evidencias', filenamePattern: '^C\\d+\\.md$', displayFilename: '{Cn}.md',
|
|
13
13
|
phaseDir: '3-implement', phase: 'implement', protected: false, multi: true },
|
|
14
14
|
|
|
@@ -450,10 +450,8 @@ export async function markOutput(featureName, outputType) {
|
|
|
450
450
|
if (suggestion) {
|
|
451
451
|
errorMsg += `\n\nDid you mean '${suggestion}'?`;
|
|
452
452
|
const kebabAlternative = {
|
|
453
|
-
'uiDesignSystem': 'ui-design-system',
|
|
454
453
|
'uiMockups': 'ui-mockups',
|
|
455
|
-
'
|
|
456
|
-
'uiFlows': 'ui-flows'
|
|
454
|
+
'implementReport': 'implement-report'
|
|
457
455
|
}[suggestion];
|
|
458
456
|
if (kebabAlternative) {
|
|
459
457
|
errorMsg += ` (also accepts '${kebabAlternative}')`;
|
|
@@ -462,7 +460,7 @@ export async function markOutput(featureName, outputType) {
|
|
|
462
460
|
|
|
463
461
|
errorMsg += `\n\nValid types:\n`;
|
|
464
462
|
errorMsg += ` ${validCamelCaseTypes.join(', ')}\n`;
|
|
465
|
-
errorMsg += `\nNote: kebab-case is also accepted (e.g., 'ui-
|
|
463
|
+
errorMsg += `\nNote: kebab-case is also accepted (e.g., 'ui-mockups', 'implement-report')`;
|
|
466
464
|
|
|
467
465
|
throw new Error(errorMsg);
|
|
468
466
|
}
|
|
@@ -8,7 +8,7 @@ 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',
|
|
11
|
+
page: 'Página', project: 'Projeto', feature: 'Feature', skill: 'Skill', persona: 'Persona',
|
|
12
12
|
standard: 'Standard', rule: 'Rule', hook: 'Hook', guide: 'Guia',
|
|
13
13
|
};
|
|
14
14
|
|
|
@@ -46,9 +46,6 @@ function featureIndex(project, features, projectName = null) {
|
|
|
46
46
|
const where = (text) => (projectName ? (text ? `${projectName}: ${text}` : projectName) : text);
|
|
47
47
|
for (const f of features || []) {
|
|
48
48
|
out.push({ kind: 'feature', title: f.name, hint: where(f.description), to: featureHref(project, f.name), hay: `${f.name} ${f.description || ''} ${projectName || ''}` });
|
|
49
|
-
for (const sl of f.sliceIndex || []) {
|
|
50
|
-
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 || ''}` });
|
|
51
|
-
}
|
|
52
49
|
}
|
|
53
50
|
return out;
|
|
54
51
|
}
|
|
@@ -112,7 +109,7 @@ export default function CommandPalette({ open, onClose, portfolio, project }) {
|
|
|
112
109
|
value={query}
|
|
113
110
|
onChange={(e) => setQuery(e.target.value)}
|
|
114
111
|
onKeyDown={onKey}
|
|
115
|
-
placeholder="Buscar features,
|
|
112
|
+
placeholder="Buscar features, skills, personas, standards, seções do guia…"
|
|
116
113
|
className="w-full bg-transparent text-base text-[var(--color-fg)] outline-none focus-visible:outline-none placeholder:text-[var(--color-faint)]"
|
|
117
114
|
aria-controls="palette-results"
|
|
118
115
|
aria-activedescendant={results[active] ? `palette-${active}` : undefined}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useMorphQuery } from '../hooks/useMorphQuery.js';
|
|
2
|
+
import { apiBase } from '../lib/project.js';
|
|
3
|
+
|
|
4
|
+
// Validated with the dataviz palette checker against --color-core (#12151b), dark mode.
|
|
5
|
+
const SESSION = '#26a69a';
|
|
6
|
+
const HUMAN = '#d07f27';
|
|
7
|
+
// Not a series: pauses logged before the resolver existed, so nobody knows who cleared them.
|
|
8
|
+
const UNKNOWN = 'var(--color-gray-500)';
|
|
9
|
+
const GATE_ORDER = ['plan', 'uiux', 'review'];
|
|
10
|
+
const GATE_LABEL = { plan: 'Plano', uiux: 'Design', review: 'Gate 3' };
|
|
11
|
+
|
|
12
|
+
function split(s = {}) {
|
|
13
|
+
const unknown = s.pausedUnattributed ?? 0;
|
|
14
|
+
const human = Math.max(0, (s.pausedForHuman ?? 0) - unknown);
|
|
15
|
+
return { human, unknown, session: Math.max(0, (s.paused ?? 0) - human - unknown) };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function gateRows(gates = {}) {
|
|
19
|
+
const known = GATE_ORDER.filter((g) => gates[g]);
|
|
20
|
+
const extra = Object.keys(gates).filter((g) => !GATE_ORDER.includes(g)).sort();
|
|
21
|
+
return [...known, ...extra].map((g) => ({ gate: g, label: GATE_LABEL[g] ?? g, decisions: gates[g].decisions ?? 0, ...split(gates[g]) }));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function humanSignals(gates = {}, top = 5) {
|
|
25
|
+
return Object.entries(gates)
|
|
26
|
+
.flatMap(([gate, slot]) => Object.entries(slot.bySignal ?? {}).map(([signal, s]) => ({ gate, signal, ...split(s) })))
|
|
27
|
+
.filter((r) => r.human > 0 || r.unknown > 0)
|
|
28
|
+
.sort((a, b) => b.human - a.human || b.unknown - a.unknown || a.signal.localeCompare(b.signal))
|
|
29
|
+
.slice(0, top);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function Swatch({ color, children }) {
|
|
33
|
+
return <span className="flex items-center gap-1.5"><span className="h-2.5 w-2.5 rounded-sm" style={{ background: color }} />{children}</span>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function Bar({ row, max }) {
|
|
37
|
+
const parts = [
|
|
38
|
+
['session', row.session, SESSION, 'a sessão resolveu'],
|
|
39
|
+
['human', row.human, HUMAN, 'foi para você'],
|
|
40
|
+
['unknown', row.unknown, UNKNOWN, 'antes do registro'],
|
|
41
|
+
].filter(([, n]) => n > 0);
|
|
42
|
+
const total = row.session + row.human + row.unknown;
|
|
43
|
+
const describe = parts.map(([, n, , what]) => `${what} ${n}`).join(', ');
|
|
44
|
+
return (
|
|
45
|
+
<div className="grid grid-cols-[4.5rem_1fr_auto] items-center gap-3">
|
|
46
|
+
<span className="text-sm text-[var(--color-fg)]">{row.label}</span>
|
|
47
|
+
<div className="flex h-3 gap-[2px]" role="img" aria-label={`${row.label}: ${describe || 'nenhuma pausa'}`}>
|
|
48
|
+
{parts.map(([key, n, color, what], i) => (
|
|
49
|
+
<span key={key} className={`h-full ${i === 0 ? 'rounded-l-[4px]' : ''} ${i === parts.length - 1 ? 'rounded-r-[4px]' : ''}`}
|
|
50
|
+
style={{ width: `${(n / max) * 100}%`, background: color }} title={`${row.label} · ${what}: ${n}`} />
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
<span className="text-right text-xs tabular-nums text-[var(--color-dim)]">
|
|
54
|
+
{total === 0 ? `nenhuma pausa em ${row.decisions}` : `${row.human} de ${total} para você`}
|
|
55
|
+
</span>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function GatePausesView({ gates }) {
|
|
61
|
+
const rows = gateRows(gates);
|
|
62
|
+
if (rows.length === 0) {
|
|
63
|
+
return <p className="text-sm text-[var(--color-faint)]">Nenhuma decisão de gate na telemetria ainda.</p>;
|
|
64
|
+
}
|
|
65
|
+
const max = Math.max(1, ...rows.map((r) => r.session + r.human + r.unknown));
|
|
66
|
+
const signals = humanSignals(gates);
|
|
67
|
+
const anyUnknown = rows.some((r) => r.unknown > 0);
|
|
68
|
+
return (
|
|
69
|
+
<div className="space-y-5">
|
|
70
|
+
<div className="flex flex-wrap items-baseline justify-between gap-2">
|
|
71
|
+
<h2 className="text-base font-semibold">Pausas por gate</h2>
|
|
72
|
+
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-[var(--color-dim)]" aria-hidden="true">
|
|
73
|
+
<Swatch color={SESSION}>a sessão resolveu</Swatch>
|
|
74
|
+
<Swatch color={HUMAN}>foi para você</Swatch>
|
|
75
|
+
{anyUnknown && <Swatch color={UNKNOWN}>antes do registro</Swatch>}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
<div className="space-y-2.5">{rows.map((r) => <Bar key={r.gate} row={r} max={max} />)}</div>
|
|
79
|
+
{anyUnknown && (
|
|
80
|
+
<p className="text-xs text-[var(--color-faint)]">Pausas gravadas antes de o morph-spec registrar quem resolve cada uma: não entram na conta de "para você".</p>
|
|
81
|
+
)}
|
|
82
|
+
{signals.length > 0 && (
|
|
83
|
+
<table className="w-full text-sm">
|
|
84
|
+
<caption className="mb-2 text-left text-xs text-[var(--color-dim)]">O que mais te chamou</caption>
|
|
85
|
+
<thead className="text-xs text-[var(--color-faint)]">
|
|
86
|
+
<tr>
|
|
87
|
+
<th className="pb-1 text-left font-normal">sinal</th>
|
|
88
|
+
<th className="pb-1 text-left font-normal">gate</th>
|
|
89
|
+
<th className="pb-1 text-right font-normal">para você</th>
|
|
90
|
+
<th className="pb-1 text-right font-normal">sessão</th>
|
|
91
|
+
{anyUnknown && <th className="pb-1 text-right font-normal">antes</th>}
|
|
92
|
+
</tr>
|
|
93
|
+
</thead>
|
|
94
|
+
<tbody>
|
|
95
|
+
{signals.map((s) => (
|
|
96
|
+
<tr key={`${s.gate}:${s.signal}`} className="border-t border-[var(--color-hair)]">
|
|
97
|
+
<td className="py-1.5 font-mono text-xs text-[var(--color-fg)]">{s.signal}</td>
|
|
98
|
+
<td className="py-1.5 text-[var(--color-dim)]">{GATE_LABEL[s.gate] ?? s.gate}</td>
|
|
99
|
+
<td className="py-1.5 text-right tabular-nums text-[var(--color-fg)]">{s.human}</td>
|
|
100
|
+
<td className="py-1.5 text-right tabular-nums text-[var(--color-dim)]">{s.session}</td>
|
|
101
|
+
{anyUnknown && <td className="py-1.5 text-right tabular-nums text-[var(--color-faint)]">{s.unknown}</td>}
|
|
102
|
+
</tr>
|
|
103
|
+
))}
|
|
104
|
+
</tbody>
|
|
105
|
+
</table>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default function GatePauses({ project }) {
|
|
112
|
+
const { data, error } = useMorphQuery(`${apiBase(project)}/gates`, { scope: 'activity', project });
|
|
113
|
+
if (error) return <p className="text-sm text-[var(--color-bad)]">Não consegui ler os gates: {error}</p>;
|
|
114
|
+
if (!data) return <p className="text-sm text-[var(--color-faint)]">Lendo a telemetria…</p>;
|
|
115
|
+
return <GatePausesView gates={data.gates} />;
|
|
116
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { clock, timeAgo } from '../lib/format.js';
|
|
2
|
+
|
|
3
|
+
const MARK = {
|
|
4
|
+
verify: { label: 'verify', color: (m) => (m.status === 'fail' ? 'var(--color-bad)' : 'var(--color-ok)') },
|
|
5
|
+
failure: { label: 'falha', color: () => 'var(--color-bad)' },
|
|
6
|
+
gate: { label: 'gate', color: () => 'var(--color-fg)' },
|
|
7
|
+
dispatch: { label: 'sub-agent', color: () => 'var(--color-phase-uiux)' },
|
|
8
|
+
handback: { label: 'sub-agent', color: () => 'var(--color-phase-uiux)' },
|
|
9
|
+
prompt: { label: 'você', color: () => 'var(--color-dim)' },
|
|
10
|
+
question: { label: 'você', color: () => 'var(--color-dim)' },
|
|
11
|
+
};
|
|
12
|
+
const LEGEND = [
|
|
13
|
+
['verify verde', 'var(--color-ok)'], ['verify vermelho ou falha', 'var(--color-bad)'],
|
|
14
|
+
['gate', 'var(--color-fg)'], ['sub-agent', 'var(--color-phase-uiux)'], ['você', 'var(--color-dim)'],
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
function Strip({ session }) {
|
|
18
|
+
const start = Date.parse(session.start);
|
|
19
|
+
const span = Math.max(1, Date.parse(session.end) - start);
|
|
20
|
+
return (
|
|
21
|
+
<div className="relative h-6" role="img"
|
|
22
|
+
aria-label={`${session.marks.length} marcos entre ${clock(session.start)} e ${clock(session.end)}`}>
|
|
23
|
+
<div className="absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-[var(--color-hair-strong)]" />
|
|
24
|
+
{session.marks.map((m, i) => {
|
|
25
|
+
const at = ((Date.parse(m.ts) - start) / span) * 100;
|
|
26
|
+
return (
|
|
27
|
+
<span key={`${m.ts}:${i}`} title={`${clock(m.ts)} · ${m.title}`}
|
|
28
|
+
className="absolute top-1/2 h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full ring-2 ring-[var(--color-core)]"
|
|
29
|
+
style={{ left: `${at}%`, background: (MARK[m.kind] ?? MARK.gate).color(m) }} />
|
|
30
|
+
);
|
|
31
|
+
})}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default function SessionMap({ session, now = Date.now() }) {
|
|
37
|
+
if (session === undefined) return <p className="text-sm text-[var(--color-faint)]">Lendo a telemetria…</p>;
|
|
38
|
+
if (!session) return <p className="text-sm text-[var(--color-faint)]">Nenhuma ação registrada para esta feature ainda.</p>;
|
|
39
|
+
return (
|
|
40
|
+
<div className="space-y-4">
|
|
41
|
+
<p className="text-sm text-[var(--color-dim)]">
|
|
42
|
+
Última sessão: <span className="text-[var(--color-fg)]">{clock(session.start)}–{clock(session.end)}</span>,
|
|
43
|
+
{' '}{timeAgo(session.end, now)} · {session.actions} ações
|
|
44
|
+
</p>
|
|
45
|
+
<div>
|
|
46
|
+
<Strip session={session} />
|
|
47
|
+
<div className="mt-1 flex justify-between font-mono text-[11px] text-[var(--color-faint)]">
|
|
48
|
+
<span>{clock(session.start)}</span><span>{clock(session.end)}</span>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-[var(--color-dim)]" aria-hidden="true">
|
|
52
|
+
{LEGEND.map(([label, color]) => (
|
|
53
|
+
<span key={label} className="flex items-center gap-1.5"><span className="h-2 w-2 rounded-full" style={{ background: color }} />{label}</span>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
{session.lastFailure && (
|
|
57
|
+
<p className="text-sm text-[var(--color-bad)]">
|
|
58
|
+
Parou em: {session.lastFailure.title}, {clock(session.lastFailure.ts)}
|
|
59
|
+
{session.lastFailure.detail && <span className="mt-0.5 block truncate font-mono text-xs text-[var(--color-dim)]">{session.lastFailure.detail}</span>}
|
|
60
|
+
</p>
|
|
61
|
+
)}
|
|
62
|
+
{session.files.length > 0 && (
|
|
63
|
+
<div>
|
|
64
|
+
<p className="mb-1.5 text-xs text-[var(--color-faint)]">Mexeu mais em</p>
|
|
65
|
+
<ul className="space-y-1">
|
|
66
|
+
{session.files.map((f) => (
|
|
67
|
+
<li key={f.path} className="flex items-baseline justify-between gap-3 text-sm">
|
|
68
|
+
<span className="min-w-0 truncate font-mono text-xs text-[var(--color-fg)]" title={f.path}>{f.path}</span>
|
|
69
|
+
<span className="shrink-0 tabular-nums text-xs text-[var(--color-dim)]">{f.edits}×</span>
|
|
70
|
+
</li>
|
|
71
|
+
))}
|
|
72
|
+
</ul>
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useParams } from 'react-router-dom';
|
|
2
2
|
import ActivityFeed from '../components/ActivityFeed.jsx';
|
|
3
|
+
import GatePauses from '../components/GatePauses.jsx';
|
|
3
4
|
import { useProjects } from '../lib/project.js';
|
|
4
5
|
|
|
5
6
|
export default function Activity() {
|
|
@@ -16,6 +17,9 @@ export default function Activity() {
|
|
|
16
17
|
os sub-agents despachados, os verifies e os gates. Atualiza sozinho enquanto ele trabalha.
|
|
17
18
|
</p>
|
|
18
19
|
</header>
|
|
20
|
+
<div className="rise-2 bezel mb-6">
|
|
21
|
+
<div className="bezel-core px-5 py-5"><GatePauses project={project} /></div>
|
|
22
|
+
</div>
|
|
19
23
|
<div className="rise-2 bezel">
|
|
20
24
|
<div className="bezel-core px-5 py-5"><ActivityFeed project={project} showFeature /></div>
|
|
21
25
|
</div>
|
|
@@ -76,9 +76,8 @@ function buildLayout(nodes, edges) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
function dispatchingSkills(agent) {
|
|
79
|
-
const implementer = (agent.activePhases || []).includes('implement');
|
|
80
79
|
return (morphData.skills || []).filter((s) => (s.dispatches || []).some(
|
|
81
|
-
(d) => (d.personas || []).includes(agent.id)
|
|
80
|
+
(d) => (d.personas || []).includes(agent.id),
|
|
82
81
|
));
|
|
83
82
|
}
|
|
84
83
|
|