chati-dev 4.2.2 → 4.3.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/README.md +80 -53
- package/bin/chati.js +150 -5
- package/framework/agents/build/dev.md +509 -76
- package/framework/agents/deploy/devops.md +40 -25
- package/framework/agents/discover/brief.md +156 -22
- package/framework/agents/discover/brownfield-wu.md +24 -14
- package/framework/agents/discover/greenfield-wu.md +100 -14
- package/framework/agents/plan/architect-data-engineer.md +6 -6
- package/framework/agents/plan/architect-system.md +46 -12
- package/framework/agents/plan/architect.md +40 -20
- package/framework/agents/plan/detail.md +36 -24
- package/framework/agents/plan/phases.md +36 -26
- package/framework/agents/plan/tasks.md +114 -33
- package/framework/agents/plan/ux-brand-architect.md +240 -8
- package/framework/agents/plan/ux-component-engineer.md +28 -13
- package/framework/agents/plan/ux-researcher.md +7 -6
- package/framework/agents/plan/ux.md +55 -22
- package/framework/agents/quality/qa-implementation.md +143 -74
- package/framework/agents/quality/qa-planning.md +115 -42
- package/framework/agents/quality/qa-visual.md +439 -0
- package/framework/agents/shared/visualizer.md +128 -0
- package/framework/config.yaml +7 -6
- package/framework/constitution.md +127 -44
- package/framework/context/governance.md +12 -7
- package/framework/context/quality.md +6 -5
- package/framework/context/root.md +6 -6
- package/framework/data/entity-registry.yaml +377 -4
- package/framework/data/model-limits.json +19 -0
- package/framework/domains/agents/qa-visual.yaml +74 -0
- package/framework/domains/constitution.yaml +46 -2
- package/framework/domains/workflows/greenfield-fullstack.yaml +2 -2
- package/framework/hooks/advance-trigger.js +131 -0
- package/framework/hooks/brief-validator.js +83 -0
- package/framework/hooks/constitution-guard.js +24 -5
- package/framework/hooks/license-guard.js +62 -27
- package/framework/hooks/mode-governance.js +13 -2
- package/framework/hooks/model-governance.js +1 -0
- package/framework/hooks/post-dev.js +207 -0
- package/framework/hooks/prism-engine.js +274 -105
- package/framework/hooks/reasoning-escalator.js +371 -0
- package/framework/hooks/reference-trigger.js +117 -0
- package/framework/hooks/session-digest.js +50 -1
- package/framework/hooks/settings.json +32 -1
- package/framework/hooks/style-guard.js +25 -6
- package/framework/hooks/team-quality-gate.js +19 -12
- package/framework/hooks/undercover-guard.js +4 -2
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/context-engine.md +4 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/intelligence/hooks-performance.md +3 -3
- package/framework/migrations/v1.0-to-v1.1.yaml +1 -1
- package/framework/migrations/v1.4-to-v2.0.yaml +11 -11
- package/framework/migrations/v4.0-to-v4.1.yaml +2 -2
- package/framework/migrations/v4.2-to-v4.3.yaml +29 -0
- package/framework/orchestrator/chati-router.js +387 -0
- package/framework/orchestrator/chati-update.md +40 -40
- package/framework/orchestrator/chati.md +294 -94
- package/framework/scaffold/motion-premium/README.md +89 -0
- package/framework/scaffold/motion-premium/app/globals.css.template +400 -0
- package/framework/scaffold/motion-premium/app/layout.tsx.template +110 -0
- package/framework/scaffold/motion-premium/components/animation/BackgroundCrossfadeProvider.tsx.template +170 -0
- package/framework/scaffold/motion-premium/components/animation/LenisProvider.tsx.template +49 -0
- package/framework/scaffold/motion-premium/components/animation/PageTransitionWrapper.tsx.template +83 -0
- package/framework/scaffold/motion-premium/components/animation/Preloader.tsx.template +171 -0
- package/framework/scaffold/motion-premium/components/ui/Container.tsx.template +69 -0
- package/framework/scaffold/motion-premium/components/ui/PageSection.tsx.template +74 -0
- package/framework/scaffold/motion-premium/lib/animations/gsap.ts.template +112 -0
- package/framework/scaffold/motion-premium/lib/animations/refreshCoordinator.ts.template +75 -0
- package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +119 -0
- package/framework/scaffold/motion-premium/lib/animations/useGsapContext.ts.template +92 -0
- package/framework/scaffold/motion-premium/lib/animations/useScrollSnapStepper.ts.template +265 -0
- package/framework/scaffold/motion-premium/lib/animations/useSmoothScroll.ts.template +67 -0
- package/framework/scaffold/motion-premium/lib/brand.ts.template +43 -0
- package/framework/scaffold/motion-premium/scaffold.yaml +174 -0
- package/framework/scaffold/motion-premium-3d/README.md +80 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoCanvas.tsx.template +81 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoClient.tsx.template +75 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/page.tsx.template +26 -0
- package/framework/scaffold/motion-premium-3d/components/3d/CameraRig.tsx.template +100 -0
- package/framework/scaffold/motion-premium-3d/components/3d/CanvasProvider.tsx.template +85 -0
- package/framework/scaffold/motion-premium-3d/components/3d/InvalidateOnScroll.tsx.template +51 -0
- package/framework/scaffold/motion-premium-3d/components/3d/MeshCrossfade.tsx.template +79 -0
- package/framework/scaffold/motion-premium-3d/components/3d/ScrollCrossfade.tsx.template +88 -0
- package/framework/scaffold/motion-premium-3d/components/3d/ScrollScene.tsx.template +121 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/SceneFallback.tsx.template +65 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +68 -0
- package/framework/scaffold/motion-premium-3d/lib/webgl/detect.ts.template +69 -0
- package/framework/scaffold/motion-premium-3d/scaffold.yaml +133 -0
- package/framework/schemas/session.schema.json +109 -21
- package/framework/scripts/reference-capture.js +430 -0
- package/framework/scripts/visual-qa.js +674 -0
- package/framework/tasks/orchestrator-handoff.md +1 -1
- package/framework/tasks/orchestrator-resume.md +1 -1
- package/framework/tasks/orchestrator-route.md +1 -1
- package/framework/tasks/orchestrator-status.md +3 -3
- package/framework/tasks/qa-planning-gate-define.md +1 -1
- package/framework/templates/brandbook-html-tmpl.md +1 -1
- package/framework/templates/brandbook-tmpl.yaml +1 -1
- package/framework/templates/component-spec-tmpl.yaml +1 -1
- package/framework/templates/design-token-tmpl.yaml +1 -1
- package/framework/templates/icon-system-tmpl.yaml +1 -1
- package/framework/templates/team-planning-tasks.yaml +6 -5
- package/framework/workflows/brownfield-discovery.yaml +2 -2
- package/framework/workflows/brownfield-fullstack.yaml +15 -11
- package/framework/workflows/brownfield-service.yaml +14 -10
- package/framework/workflows/brownfield-ui.yaml +15 -11
- package/framework/workflows/greenfield-fullstack.yaml +16 -13
- package/framework/workflows/quick-flow.yaml +3 -3
- package/framework/workflows/standard-flow.yaml +12 -9
- package/package.json +10 -5
- package/src/autonomy/autonomous-gate.js +1 -0
- package/src/autonomy/build-state.js +1 -2
- package/src/autonomy/progress-reporter.js +1 -1
- package/src/config/agent-customizer.js +11 -3
- package/src/config/claude-settings-generator.js +27 -7
- package/src/config/context-file-generator.js +41 -21
- package/src/config/framework-adapter.js +1 -0
- package/src/config/gemini-hooks-generator.js +19 -7
- package/src/config/mcp-configs.js +1 -0
- package/src/context/layers/l1-global.js +2 -1
- package/src/dashboard/data-reader.js +4 -3
- package/src/dashboard/layout.js +2 -1
- package/src/decision/analyzer.js +6 -30
- package/src/decision/engine.js +4 -28
- package/src/decision/registry-healer.js +3 -2
- package/src/decision/registry-updater.js +23 -14
- package/src/extensions/loader.js +2 -8
- package/src/gates/g1-planning-complete.js +2 -1
- package/src/gates/g2-qa-planning.js +2 -1
- package/src/gates/g3-implementation.js +2 -1
- package/src/gates/g4-qa-implementation.js +3 -2
- package/src/gates/g5-deploy-ready.js +2 -1
- package/src/health/engine.js +4 -3
- package/src/installer/core.js +422 -81
- package/src/installer/preflight.js +131 -0
- package/src/installer/provider-overlay.js +3 -3
- package/src/installer/scaffold-applier.js +358 -0
- package/src/installer/templates.js +46 -29
- package/src/installer/validator.js +17 -12
- package/src/intelligence/registry-manager.js +22 -29
- package/src/intelligence/timeline.js +11 -6
- package/src/license/commands.js +1 -1
- package/src/license/wait.js +102 -0
- package/src/memory/agent-memory.js +81 -0
- package/src/memory/dream.js +32 -1
- package/src/merger/replace-merger.js +28 -15
- package/src/orchestrator/agent-selector.js +2 -1
- package/src/orchestrator/cli.js +1869 -71
- package/src/orchestrator/doctor.js +270 -0
- package/src/orchestrator/handoff-engine.js +4 -3
- package/src/orchestrator/index.js +2 -0
- package/src/orchestrator/pipeline-manager.js +306 -15
- package/src/orchestrator/session-manager.js +331 -6
- package/src/tasks/handoff.js +3 -2
- package/src/telemetry/config.js +4 -3
- package/src/telemetry/schema.js +1 -0
- package/src/terminal/collector.js +3 -2
- package/src/terminal/index.js +1 -2
- package/src/terminal/isolation.js +52 -18
- package/src/terminal/prompt-builder.js +42 -25
- package/src/terminal/run-parallel.js +1 -1
- package/src/terminal/run-team.js +3 -3
- package/src/terminal/team-task-list.js +43 -4
- package/src/upgrade/backup.js +3 -2
- package/src/upgrade/checker.js +3 -2
- package/src/upgrade/migrator.js +65 -7
- package/src/upgrade/tracked-files-detector.js +86 -0
- package/src/upgrade/user-messages.js +94 -0
- package/src/utils/config-parser.js +2 -1
- package/src/utils/feature-flags.js +2 -1
- package/src/utils/flatten-entities.js +69 -0
- package/src/utils/framework-dir.js +16 -0
- package/src/utils/model-id.js +85 -0
- package/src/utils/provider-limits.js +84 -23
- package/src/utils/schema-validator.js +1 -1
- package/src/wizard/i18n.js +5 -4
- package/src/wizard/index.js +14 -0
- package/assets/logo - c/303/263pia.png +0 -0
- package/assets/logo.svg +0 -42
- package/assets/logo2.png +0 -0
- package/assets/social-preview.png +0 -0
- package/scripts/bundle-framework.js +0 -69
- package/scripts/changelog-generator.js +0 -222
- package/scripts/codebase-mapper.js +0 -728
- package/scripts/commit-message-generator.js +0 -167
- package/scripts/coverage-analyzer.js +0 -260
- package/scripts/dependency-analyzer.js +0 -280
- package/scripts/doctor/checks/agents.js +0 -77
- package/scripts/doctor/checks/constitution.js +0 -41
- package/scripts/doctor/checks/domain-alignment.js +0 -58
- package/scripts/doctor/checks/prism-layers.js +0 -84
- package/scripts/doctor/checks/registry.js +0 -55
- package/scripts/doctor/checks/schemas.js +0 -61
- package/scripts/doctor/fixes/reference-fix.js +0 -100
- package/scripts/doctor/fixes/registry-fix.js +0 -56
- package/scripts/doctor/index.js +0 -212
- package/scripts/framework-analyzer.js +0 -308
- package/scripts/generate-constitution-domain.js +0 -253
- package/scripts/generate-signing-key.js +0 -33
- package/scripts/health-check.js +0 -481
- package/scripts/ide-sync.js +0 -326
- package/scripts/performance-analyzer.js +0 -325
- package/scripts/plan-tracker.js +0 -278
- package/scripts/populate-entity-registry.js +0 -481
- package/scripts/pr-review.js +0 -317
- package/scripts/rollback-manager.js +0 -310
- package/scripts/semantic-lint.js +0 -328
- package/scripts/sign-manifest.js +0 -53
- package/scripts/stuck-detector.js +0 -343
- package/scripts/test-quality-assessment.js +0 -257
- package/scripts/validate-agents.js +0 -368
- package/scripts/validate-package.js +0 -505
- package/scripts/validate-tasks.js +0 -465
- package/src/autonomy/worktree-manager.js +0 -250
- package/src/intelligence/decision-engine.js +0 -256
- package/src/intelligence/document-sharder.js +0 -221
- package/src/intelligence/elicitation.js +0 -265
|
@@ -14,7 +14,7 @@ const SESSION_FILE = '.chati/session.yaml';
|
|
|
14
14
|
/**
|
|
15
15
|
* Default session template.
|
|
16
16
|
*/
|
|
17
|
-
const CURRENT_SCHEMA_VERSION = '1.
|
|
17
|
+
const CURRENT_SCHEMA_VERSION = '1.2';
|
|
18
18
|
|
|
19
19
|
const DEFAULT_SESSION = {
|
|
20
20
|
schema_version: CURRENT_SCHEMA_VERSION,
|
|
@@ -35,6 +35,31 @@ const DEFAULT_SESSION = {
|
|
|
35
35
|
agent_results: {},
|
|
36
36
|
mode_transitions: [],
|
|
37
37
|
deviations: [],
|
|
38
|
+
// Scaffold Premium (Article XXI / Fase 10) — auto-gate wiring.
|
|
39
|
+
// scaffold_candidates: presets eligible for this project (e.g. ['motion-premium'])
|
|
40
|
+
// written by greenfield-wu when stack answer signals a match.
|
|
41
|
+
// scaffold_signals: per-preset confidence/evidence map, written by
|
|
42
|
+
// ux-brand-architect when reference analysis detects premium patterns.
|
|
43
|
+
// scaffold_applied: presets already applied by the decision dispatcher
|
|
44
|
+
// (prevents double-apply when advance is re-run).
|
|
45
|
+
scaffold_candidates: [],
|
|
46
|
+
scaffold_signals: {},
|
|
47
|
+
scaffold_applied: [],
|
|
48
|
+
// Context Window Detection (Article XII v2) — model-aware brackets.
|
|
49
|
+
// active_model: normalized key from parseClaudeModelId() (e.g. 'opus[1m]'),
|
|
50
|
+
// written by /chati boot; drives context limit resolution.
|
|
51
|
+
// active_provider: redundant with providers_enabled[0] but flat for hooks.
|
|
52
|
+
// context_tokens_used: rolling cumulative estimate (char-based); written
|
|
53
|
+
// by prism-engine on every UserPromptSubmit. Replaces turn-count as the
|
|
54
|
+
// primary metric for bracket determination.
|
|
55
|
+
// context_window_tokens: cached limit resolved from active_model+provider
|
|
56
|
+
// so hooks avoid re-loading model-limits.json every fire.
|
|
57
|
+
// context_last_bracket: last computed bracket — telemetry + /chati status.
|
|
58
|
+
active_model: null,
|
|
59
|
+
active_provider: null,
|
|
60
|
+
context_tokens_used: 0,
|
|
61
|
+
context_window_tokens: null,
|
|
62
|
+
context_last_bracket: null,
|
|
38
63
|
agents: {
|
|
39
64
|
'greenfield-wu': { status: 'pending', score: 0, criteria_count: 0, completed_at: null },
|
|
40
65
|
'brownfield-wu': { status: 'pending', score: 0, criteria_count: 0, completed_at: null },
|
|
@@ -47,10 +72,32 @@ const DEFAULT_SESSION = {
|
|
|
47
72
|
'qa-planning': { status: 'pending', score: 0, criteria_count: 0, completed_at: null },
|
|
48
73
|
'dev': { status: 'pending', score: 0, criteria_count: 0, completed_at: null },
|
|
49
74
|
'qa-implementation': { status: 'pending', score: 0, criteria_count: 0, completed_at: null },
|
|
75
|
+
'qa-visual': { status: 'pending', score: 0, criteria_count: 0, completed_at: null },
|
|
50
76
|
'devops': { status: 'pending', score: 0, criteria_count: 0, completed_at: null },
|
|
51
77
|
},
|
|
52
78
|
backlog: [],
|
|
53
79
|
last_handoff: '',
|
|
80
|
+
// Fault Vector Protocol (Article XXII) — append-only known-bad state log.
|
|
81
|
+
// Consumed by agents before iterating so the same defect is not re-introduced.
|
|
82
|
+
decision_trail: [],
|
|
83
|
+
// Agent Teams (Article XXI) — empty until spawn-team is called.
|
|
84
|
+
teams: [],
|
|
85
|
+
team_events: [],
|
|
86
|
+
// Execution Mode (Article XVII) — canonical. Article XVIII was deprecated
|
|
87
|
+
// 2026-04-18: execution_profile + profile_transitions were folded into
|
|
88
|
+
// execution_mode (set in DEFAULT_SESSION above as 'interactive'/'autonomous').
|
|
89
|
+
providers_enabled: ['claude'],
|
|
90
|
+
// BUILD → DEPLOY preview state (user approves before devops ships).
|
|
91
|
+
preview: {
|
|
92
|
+
status: 'inactive',
|
|
93
|
+
url: null,
|
|
94
|
+
port: null,
|
|
95
|
+
framework: null,
|
|
96
|
+
logs_captured: 0,
|
|
97
|
+
adjustment_count: 0,
|
|
98
|
+
},
|
|
99
|
+
// Model Governance (Article XVI) — per-agent model selection log.
|
|
100
|
+
model_selections: [],
|
|
54
101
|
};
|
|
55
102
|
|
|
56
103
|
/**
|
|
@@ -120,7 +167,7 @@ export function migrateSession(session) {
|
|
|
120
167
|
return { migrated: false, fromVersion: session.schema_version, toVersion: CURRENT_SCHEMA_VERSION };
|
|
121
168
|
}
|
|
122
169
|
|
|
123
|
-
// v0 (no schema_version) → v1.0
|
|
170
|
+
// v0 (no schema_version) → v1.0 → v1.1 → v1.2
|
|
124
171
|
const fromVersion = session.schema_version || null;
|
|
125
172
|
session.schema_version = CURRENT_SCHEMA_VERSION;
|
|
126
173
|
|
|
@@ -130,6 +177,53 @@ export function migrateSession(session) {
|
|
|
130
177
|
if (!session.deviations) session.deviations = [];
|
|
131
178
|
if (!session.mode_transitions) session.mode_transitions = [];
|
|
132
179
|
|
|
180
|
+
// v1.1 — scaffold auto-gate fields (Fase 10).
|
|
181
|
+
if (!Array.isArray(session.scaffold_candidates)) session.scaffold_candidates = [];
|
|
182
|
+
if (!session.scaffold_signals || typeof session.scaffold_signals !== 'object') {
|
|
183
|
+
session.scaffold_signals = {};
|
|
184
|
+
}
|
|
185
|
+
if (!Array.isArray(session.scaffold_applied)) session.scaffold_applied = [];
|
|
186
|
+
|
|
187
|
+
// v1.2 — context window detection fields (Article XII v2).
|
|
188
|
+
// Defaults null/0 — /chati boot or prism-engine will populate on first hook.
|
|
189
|
+
if (!('active_model' in session)) session.active_model = null;
|
|
190
|
+
if (!('active_provider' in session)) session.active_provider = null;
|
|
191
|
+
if (typeof session.context_tokens_used !== 'number') session.context_tokens_used = 0;
|
|
192
|
+
if (!('context_window_tokens' in session)) session.context_window_tokens = null;
|
|
193
|
+
if (!('context_last_bracket' in session)) session.context_last_bracket = null;
|
|
194
|
+
|
|
195
|
+
// Schema parity catch-up (audit-fixes-v1 P4, additive). These fields live in
|
|
196
|
+
// session.schema.json but were not seeded into DEFAULT_SESSION historically.
|
|
197
|
+
// Runtime guards (`|| []`, `|| {}`) elsewhere kept things safe, but older
|
|
198
|
+
// sessions re-loaded here now get proper defaults without those guards.
|
|
199
|
+
if (!Array.isArray(session.decision_trail)) session.decision_trail = [];
|
|
200
|
+
if (!Array.isArray(session.teams)) session.teams = [];
|
|
201
|
+
if (!Array.isArray(session.team_events)) session.team_events = [];
|
|
202
|
+
if (!Array.isArray(session.providers_enabled)) session.providers_enabled = ['claude'];
|
|
203
|
+
if (!Array.isArray(session.model_selections)) session.model_selections = [];
|
|
204
|
+
if (!session.preview || typeof session.preview !== 'object') {
|
|
205
|
+
session.preview = {
|
|
206
|
+
status: 'inactive', url: null, port: null, framework: null,
|
|
207
|
+
logs_captured: 0, adjustment_count: 0,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Article XVIII deprecation (2026-04-18): collapse execution_profile into
|
|
212
|
+
// execution_mode per the migration table. Runs LAST so it overrides any
|
|
213
|
+
// default seeded above. See constitution Article XVIII for rationale.
|
|
214
|
+
if (typeof session.execution_profile === 'string') {
|
|
215
|
+
const map = { explore: 'interactive', guided: 'interactive', autonomous: 'autonomous' };
|
|
216
|
+
const mapped = map[session.execution_profile.toLowerCase()];
|
|
217
|
+
if (mapped && session.execution_mode !== 'autonomous') {
|
|
218
|
+
// Only downgrade if current execution_mode is not already autonomous.
|
|
219
|
+
session.execution_mode = mapped;
|
|
220
|
+
}
|
|
221
|
+
delete session.execution_profile;
|
|
222
|
+
}
|
|
223
|
+
if (Array.isArray(session.profile_transitions)) {
|
|
224
|
+
delete session.profile_transitions;
|
|
225
|
+
}
|
|
226
|
+
|
|
133
227
|
return { migrated: true, fromVersion, toVersion: CURRENT_SCHEMA_VERSION };
|
|
134
228
|
}
|
|
135
229
|
|
|
@@ -275,13 +369,35 @@ export function recordModeTransition(projectDir, transition) {
|
|
|
275
369
|
session.project.state = transition.to;
|
|
276
370
|
}
|
|
277
371
|
|
|
278
|
-
return
|
|
372
|
+
return writeSessionToDisk(projectDir, session);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Persist the full session object to disk. Avoids routing through updateSession's
|
|
377
|
+
* spread+deep-merge logic (which can corrupt list fields like `completed_agents`
|
|
378
|
+
* when the full session is passed as the updates arg).
|
|
379
|
+
* @param {string} projectDir
|
|
380
|
+
* @param {object} session
|
|
381
|
+
* @returns {{ saved: boolean, session?: object, error?: string }}
|
|
382
|
+
*/
|
|
383
|
+
function writeSessionToDisk(projectDir, session) {
|
|
384
|
+
const sessionPath = join(projectDir, SESSION_FILE);
|
|
385
|
+
try {
|
|
386
|
+
const yamlContent = yaml.dump(session, { lineWidth: -1, noRefs: true });
|
|
387
|
+
writeFileSync(sessionPath, yamlContent, 'utf-8');
|
|
388
|
+
return { saved: true, session };
|
|
389
|
+
} catch (err) {
|
|
390
|
+
return { saved: false, error: `Failed to write session: ${err.message}` };
|
|
391
|
+
}
|
|
279
392
|
}
|
|
280
393
|
|
|
281
394
|
/**
|
|
282
395
|
* Record an agent completion in session.
|
|
283
396
|
* @param {string} projectDir
|
|
284
|
-
* @param {object} completion - { agent, status, score, outputs }
|
|
397
|
+
* @param {object} completion - { agent, status, score, outputs, handoffData }
|
|
398
|
+
* handoffData may include:
|
|
399
|
+
* - decision_trail_entries: array of new Decision Trail objects to append
|
|
400
|
+
* - resolved_decision_trail: array of DT entry IDs to mark resolved: true
|
|
285
401
|
* @returns {{ saved: boolean }}
|
|
286
402
|
*/
|
|
287
403
|
export function recordAgentCompletion(projectDir, completion) {
|
|
@@ -295,7 +411,7 @@ export function recordAgentCompletion(projectDir, completion) {
|
|
|
295
411
|
}
|
|
296
412
|
|
|
297
413
|
const session = loadResult.session;
|
|
298
|
-
const { agent, status, score, outputs = [] } = completion;
|
|
414
|
+
const { agent, status, score, outputs = [], handoffData = {} } = completion;
|
|
299
415
|
|
|
300
416
|
// Update agent data
|
|
301
417
|
if (!session.agents[agent]) {
|
|
@@ -329,7 +445,216 @@ export function recordAgentCompletion(projectDir, completion) {
|
|
|
329
445
|
// Update last handoff
|
|
330
446
|
session.last_handoff = agent;
|
|
331
447
|
|
|
332
|
-
|
|
448
|
+
// Merge Decision Trail entries from agent handoff document.
|
|
449
|
+
// Agents (e.g. qa-implementation) write Decision Trail entries to their handoff
|
|
450
|
+
// document instead of directly editing session.yaml (which is blocked by the
|
|
451
|
+
// mode-governance hook). The CLI reads them here and merges into session.yaml.
|
|
452
|
+
if (Array.isArray(handoffData.decision_trail_entries) && handoffData.decision_trail_entries.length > 0) {
|
|
453
|
+
session.decision_trail = [
|
|
454
|
+
...(session.decision_trail || []),
|
|
455
|
+
...handoffData.decision_trail_entries,
|
|
456
|
+
];
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Mark resolved Decision Trail entries from agent handoff document.
|
|
460
|
+
if (Array.isArray(handoffData.resolved_decision_trail) && handoffData.resolved_decision_trail.length > 0) {
|
|
461
|
+
const trail = session.decision_trail || [];
|
|
462
|
+
for (const resolvedId of handoffData.resolved_decision_trail) {
|
|
463
|
+
const entry = trail.find(e => e.id === resolvedId);
|
|
464
|
+
if (entry) {
|
|
465
|
+
entry.resolved = true;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
session.decision_trail = trail;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Merge scaffold candidates/signals from handoff (Fase 10 auto-gate).
|
|
472
|
+
// - candidates: union into session.scaffold_candidates (dedup).
|
|
473
|
+
// - signals: overwrite session.scaffold_signals[key] (later signal wins)
|
|
474
|
+
// and stamp source_agent + updated_at.
|
|
475
|
+
if (Array.isArray(handoffData.scaffold_candidates) && handoffData.scaffold_candidates.length > 0) {
|
|
476
|
+
session.scaffold_candidates = session.scaffold_candidates || [];
|
|
477
|
+
for (const preset of handoffData.scaffold_candidates) {
|
|
478
|
+
if (!session.scaffold_candidates.includes(preset)) {
|
|
479
|
+
session.scaffold_candidates.push(preset);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (handoffData.scaffold_signals && typeof handoffData.scaffold_signals === 'object') {
|
|
484
|
+
session.scaffold_signals = session.scaffold_signals || {};
|
|
485
|
+
for (const [key, signal] of Object.entries(handoffData.scaffold_signals)) {
|
|
486
|
+
session.scaffold_signals[key] = {
|
|
487
|
+
...signal,
|
|
488
|
+
source_agent: agent,
|
|
489
|
+
updated_at: new Date().toISOString(),
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return writeSessionToDisk(projectDir, session);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Parse Decision Trail entries from an agent's handoff markdown document.
|
|
499
|
+
*
|
|
500
|
+
* The agent writes Decision Trail entries under a "## Decision Trail" heading
|
|
501
|
+
* and resolved IDs under "## Resolved Decision Trail". This function extracts
|
|
502
|
+
* both so they can be passed to recordAgentCompletion() as handoffData.
|
|
503
|
+
*
|
|
504
|
+
* @param {string} handoffMarkdown - Full text of the handoff .md file
|
|
505
|
+
* @returns {{ decision_trail_entries: object[], resolved_decision_trail: string[] }}
|
|
506
|
+
*/
|
|
507
|
+
export function parseHandoffDecisionTrail(handoffMarkdown) {
|
|
508
|
+
const decision_trail_entries = [];
|
|
509
|
+
const resolved_decision_trail = [];
|
|
510
|
+
|
|
511
|
+
if (!handoffMarkdown || typeof handoffMarkdown !== 'string') {
|
|
512
|
+
return { decision_trail_entries, resolved_decision_trail };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Extract ## Decision Trail section (stop at next ## heading or end of input).
|
|
516
|
+
// Note: JS regex does not support \z (end-of-input anchor). Using $(?![\s\S])
|
|
517
|
+
// — an $ followed by negative lookahead "no more chars" — gives the same
|
|
518
|
+
// semantics. The previous \z was treated as literal 'z', silently truncating
|
|
519
|
+
// content at the first 'z' character in the Decision Trail block.
|
|
520
|
+
const dtMatch = handoffMarkdown.match(
|
|
521
|
+
/^## Decision Trail\s*\n([\s\S]*?)(?=^## |$(?![\s\S]))/m,
|
|
522
|
+
);
|
|
523
|
+
if (dtMatch) {
|
|
524
|
+
const block = dtMatch[1];
|
|
525
|
+
// Parse YAML-like bullet blocks: lines starting with " id: DT-"
|
|
526
|
+
// Split on entries that start a new id: field
|
|
527
|
+
const entryBlocks = block.split(/(?=^\s*-?\s*id:\s*DT-)/m).filter(Boolean);
|
|
528
|
+
for (const entryBlock of entryBlocks) {
|
|
529
|
+
try {
|
|
530
|
+
// Build a minimal object from key: value lines
|
|
531
|
+
const entry = {};
|
|
532
|
+
for (const line of entryBlock.split('\n')) {
|
|
533
|
+
const kv = line.match(/^\s*-?\s*(\w+(?:_\w+)*):\s*(.+)$/);
|
|
534
|
+
if (kv) {
|
|
535
|
+
const [, key, value] = kv;
|
|
536
|
+
// Coerce resolved field to boolean
|
|
537
|
+
if (key === 'resolved') {
|
|
538
|
+
entry[key] = value.trim() === 'true';
|
|
539
|
+
} else {
|
|
540
|
+
entry[key] = value.trim().replace(/^["']|["']$/g, '');
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (entry.id && entry.id.startsWith('DT-')) {
|
|
545
|
+
decision_trail_entries.push(entry);
|
|
546
|
+
}
|
|
547
|
+
} catch {
|
|
548
|
+
// Malformed entry — skip silently
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Extract ## Resolved Decision Trail section (same \z → $(?![\s\S]) fix)
|
|
554
|
+
const resolvedMatch = handoffMarkdown.match(
|
|
555
|
+
/^## Resolved Decision Trail\s*\n([\s\S]*?)(?=^## |$(?![\s\S]))/m,
|
|
556
|
+
);
|
|
557
|
+
if (resolvedMatch) {
|
|
558
|
+
const block = resolvedMatch[1];
|
|
559
|
+
// Each line may be "- DT-001" or just "DT-001"
|
|
560
|
+
for (const line of block.split('\n')) {
|
|
561
|
+
const idMatch = line.match(/\bDT-\d+\b/);
|
|
562
|
+
if (idMatch) {
|
|
563
|
+
resolved_decision_trail.push(idMatch[0]);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return { decision_trail_entries, resolved_decision_trail };
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Parse scaffold-auto-gate metadata from an agent's handoff markdown.
|
|
573
|
+
*
|
|
574
|
+
* Two sections are recognized (both optional):
|
|
575
|
+
*
|
|
576
|
+
* ## Scaffold Candidates
|
|
577
|
+
* - <preset-id>
|
|
578
|
+
* - <preset-id>
|
|
579
|
+
*
|
|
580
|
+
* ## Scaffold Signals
|
|
581
|
+
* - preset: <preset-id>
|
|
582
|
+
* candidate: true
|
|
583
|
+
* confidence: 0.0–1.0
|
|
584
|
+
* evidence:
|
|
585
|
+
* - "<short match string>"
|
|
586
|
+
*
|
|
587
|
+
* Returns arrays/objects the CLI merges into session via recordAgentCompletion.
|
|
588
|
+
*
|
|
589
|
+
* @param {string} handoffMarkdown
|
|
590
|
+
* @returns {{ scaffold_candidates: string[], scaffold_signals: object }}
|
|
591
|
+
*/
|
|
592
|
+
export function parseHandoffScaffoldSignals(handoffMarkdown) {
|
|
593
|
+
const scaffold_candidates = [];
|
|
594
|
+
const scaffold_signals = {};
|
|
595
|
+
|
|
596
|
+
if (!handoffMarkdown || typeof handoffMarkdown !== 'string') {
|
|
597
|
+
return { scaffold_candidates, scaffold_signals };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ## Scaffold Candidates — bullet list of preset ids.
|
|
601
|
+
const candidatesMatch = handoffMarkdown.match(
|
|
602
|
+
/^## Scaffold Candidates\s*\n([\s\S]*?)(?=^## |$(?![\s\S]))/m,
|
|
603
|
+
);
|
|
604
|
+
if (candidatesMatch) {
|
|
605
|
+
const block = candidatesMatch[1];
|
|
606
|
+
for (const line of block.split('\n')) {
|
|
607
|
+
const m = line.match(/^\s*[-*]\s+([a-z][a-z0-9-]*)\s*$/);
|
|
608
|
+
if (m && !scaffold_candidates.includes(m[1])) {
|
|
609
|
+
scaffold_candidates.push(m[1]);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// ## Scaffold Signals — one YAML-ish entry per preset.
|
|
615
|
+
const signalsMatch = handoffMarkdown.match(
|
|
616
|
+
/^## Scaffold Signals\s*\n([\s\S]*?)(?=^## |$(?![\s\S]))/m,
|
|
617
|
+
);
|
|
618
|
+
if (signalsMatch) {
|
|
619
|
+
const block = signalsMatch[1];
|
|
620
|
+
// Split on lines that start a new "- preset:" entry.
|
|
621
|
+
const entries = block.split(/(?=^\s*-\s*preset:\s*)/m).filter(Boolean);
|
|
622
|
+
for (const entry of entries) {
|
|
623
|
+
const presetMatch = entry.match(/preset:\s*([a-z][a-z0-9-]*)/);
|
|
624
|
+
if (!presetMatch) continue;
|
|
625
|
+
const preset = presetMatch[1];
|
|
626
|
+
// Normalize for YAML keys (underscore, safer than dash for downstream).
|
|
627
|
+
const key = preset.replace(/-/g, '_');
|
|
628
|
+
|
|
629
|
+
const confMatch = entry.match(/confidence:\s*([\d.]+)/);
|
|
630
|
+
const confidence = confMatch ? Math.max(0, Math.min(1, parseFloat(confMatch[1]))) : 0;
|
|
631
|
+
|
|
632
|
+
const candidateMatch = entry.match(/candidate:\s*(true|false)/);
|
|
633
|
+
const isCandidate = candidateMatch ? candidateMatch[1] === 'true' : confidence > 0;
|
|
634
|
+
if (!isCandidate) continue;
|
|
635
|
+
|
|
636
|
+
const evidence = [];
|
|
637
|
+
const evidenceSection = entry.match(/evidence:\s*\n([\s\S]*?)(?=\n\s*[-*]\s*preset:|$)/);
|
|
638
|
+
if (evidenceSection) {
|
|
639
|
+
for (const line of evidenceSection[1].split('\n')) {
|
|
640
|
+
const em = line.match(/^\s*[-*]\s+"?(.+?)"?\s*$/);
|
|
641
|
+
if (em && em[1].trim()) evidence.push(em[1].trim());
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
scaffold_signals[key] = {
|
|
646
|
+
confidence,
|
|
647
|
+
evidence,
|
|
648
|
+
source_agent: null, // filled in by recordAgentCompletion
|
|
649
|
+
updated_at: null,
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// If the signal is emitted, the preset is an implicit candidate too.
|
|
653
|
+
if (!scaffold_candidates.includes(preset)) scaffold_candidates.push(preset);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return { scaffold_candidates, scaffold_signals };
|
|
333
658
|
}
|
|
334
659
|
|
|
335
660
|
/**
|
package/src/tasks/handoff.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
10
10
|
import { join } from 'path';
|
|
11
|
+
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Build a handoff document from task execution results.
|
|
@@ -123,7 +124,7 @@ export function formatHandoff(handoff) {
|
|
|
123
124
|
*/
|
|
124
125
|
export function saveHandoff(projectDir, handoff) {
|
|
125
126
|
try {
|
|
126
|
-
const handoffsDir = join(projectDir,
|
|
127
|
+
const handoffsDir = join(projectDir, resolveFrameworkDir(projectDir), 'artifacts', 'handoffs');
|
|
127
128
|
if (!existsSync(handoffsDir)) {
|
|
128
129
|
mkdirSync(handoffsDir, { recursive: true });
|
|
129
130
|
}
|
|
@@ -147,7 +148,7 @@ export function saveHandoff(projectDir, handoff) {
|
|
|
147
148
|
* @returns {{ loaded: boolean, handoff: object|null, error: string|null }}
|
|
148
149
|
*/
|
|
149
150
|
export function loadHandoff(projectDir, agentName) {
|
|
150
|
-
const filePath = join(projectDir,
|
|
151
|
+
const filePath = join(projectDir, resolveFrameworkDir(projectDir), 'artifacts', 'handoffs', `${agentName}-handoff.md`);
|
|
151
152
|
|
|
152
153
|
if (!existsSync(filePath)) {
|
|
153
154
|
return { loaded: false, handoff: null, error: `No handoff found from agent '${agentName}'.` };
|
package/src/telemetry/config.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
9
9
|
import { join } from 'path';
|
|
10
|
+
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
10
11
|
import { randomUUID } from 'crypto';
|
|
11
12
|
import yaml from 'js-yaml';
|
|
12
13
|
|
|
@@ -21,7 +22,7 @@ import yaml from 'js-yaml';
|
|
|
21
22
|
* @returns {{ enabled: boolean, anonymousId: string | null, endpoint: string }}
|
|
22
23
|
*/
|
|
23
24
|
export function getTelemetryConfig(targetDir) {
|
|
24
|
-
const configPath = join(targetDir,
|
|
25
|
+
const configPath = join(targetDir, resolveFrameworkDir(targetDir), 'config.yaml');
|
|
25
26
|
|
|
26
27
|
const defaults = {
|
|
27
28
|
enabled: true,
|
|
@@ -64,7 +65,7 @@ export function isEnabled(targetDir) {
|
|
|
64
65
|
* @param {boolean} enabled
|
|
65
66
|
*/
|
|
66
67
|
export function setEnabled(targetDir, enabled) {
|
|
67
|
-
const configPath = join(targetDir,
|
|
68
|
+
const configPath = join(targetDir, resolveFrameworkDir(targetDir), 'config.yaml');
|
|
68
69
|
|
|
69
70
|
if (!existsSync(configPath)) return;
|
|
70
71
|
|
|
@@ -99,7 +100,7 @@ export function getAnonymousId(targetDir) {
|
|
|
99
100
|
|
|
100
101
|
// Generate and persist
|
|
101
102
|
const id = randomUUID();
|
|
102
|
-
const configPath = join(targetDir,
|
|
103
|
+
const configPath = join(targetDir, resolveFrameworkDir(targetDir), 'config.yaml');
|
|
103
104
|
|
|
104
105
|
if (existsSync(configPath)) {
|
|
105
106
|
try {
|
package/src/telemetry/schema.js
CHANGED
|
@@ -59,9 +59,10 @@ export function collectResults(groupId, terminals) {
|
|
|
59
59
|
* { agent, outputs[], decisions{}, blockers[], summary }
|
|
60
60
|
*
|
|
61
61
|
* @param {object[]} results - Per-terminal result objects (from collectResults)
|
|
62
|
+
* @param {string} [projectDir] - Project root directory (used for write scope resolution)
|
|
62
63
|
* @returns {{ merged: boolean, outputs: string[], decisions: Record<string,string>, blockers: string[], summary: string }}
|
|
63
64
|
*/
|
|
64
|
-
export function mergeHandoffs(results) {
|
|
65
|
+
export function mergeHandoffs(results, projectDir) {
|
|
65
66
|
if (!Array.isArray(results) || results.length === 0) {
|
|
66
67
|
return {
|
|
67
68
|
merged: false,
|
|
@@ -82,7 +83,7 @@ export function mergeHandoffs(results) {
|
|
|
82
83
|
if (Array.isArray(result.outputs)) {
|
|
83
84
|
outputs.push(...result.outputs);
|
|
84
85
|
} else {
|
|
85
|
-
const scope = getWriteScope(result.agent);
|
|
86
|
+
const scope = getWriteScope(result.agent, projectDir);
|
|
86
87
|
if (scope.length > 0) {
|
|
87
88
|
outputs.push(...scope.map(s => `${result.agent}: ${s}`));
|
|
88
89
|
}
|
package/src/terminal/index.js
CHANGED
|
@@ -22,7 +22,6 @@ export {
|
|
|
22
22
|
} from './collector.js';
|
|
23
23
|
|
|
24
24
|
export {
|
|
25
|
-
WRITE_SCOPES,
|
|
26
25
|
getWriteScope,
|
|
27
26
|
validateWriteScopes,
|
|
28
27
|
isPathAllowed,
|
|
@@ -32,7 +31,7 @@ export {
|
|
|
32
31
|
|
|
33
32
|
export {
|
|
34
33
|
buildAgentPrompt,
|
|
35
|
-
|
|
34
|
+
getAgentFile,
|
|
36
35
|
} from './prompt-builder.js';
|
|
37
36
|
|
|
38
37
|
export {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { posix } from 'path';
|
|
2
|
+
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @fileoverview Write scope isolation for multi-terminal execution.
|
|
@@ -12,38 +13,71 @@ import { posix } from 'path';
|
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Default write scope mapping per agent.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* Paths are relative to the framework directory (e.g. 'artifacts/0-WU/').
|
|
17
|
+
* Non-framework paths (src/, test/, etc.) are stored as-is.
|
|
18
|
+
*
|
|
19
|
+
* Use getWriteScope(agent, projectDir) to get resolved paths.
|
|
17
20
|
*
|
|
18
21
|
* @type {Record<string, string[]>}
|
|
19
22
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
23
|
+
// IMPORTANT: artifacts/ lives at PROJECT ROOT (visible user output), NOT inside
|
|
24
|
+
// the framework dir. All artifact paths use the :non-framework: prefix so they
|
|
25
|
+
// resolve to <projectDir>/artifacts/, not <projectDir>/.chati.dev/artifacts/.
|
|
26
|
+
const WRITE_SCOPE_TEMPLATES = {
|
|
27
|
+
'greenfield-wu': [':non-framework:artifacts/0-WU/', ':non-framework:artifacts/handoffs/greenfield-wu-handoff.md'],
|
|
28
|
+
'brownfield-wu': [':non-framework:artifacts/0-WU/', ':non-framework:artifacts/handoffs/brownfield-wu-handoff.md'],
|
|
29
|
+
'brief': [':non-framework:artifacts/1-Brief/', ':non-framework:artifacts/handoffs/brief-handoff.md'],
|
|
30
|
+
'detail': [':non-framework:artifacts/2-PRD/', ':non-framework:artifacts/handoffs/detail-handoff.md'],
|
|
31
|
+
'architect': [':non-framework:artifacts/3-Architecture/', ':non-framework:artifacts/handoffs/architect-handoff.md'],
|
|
32
|
+
'ux': [':non-framework:artifacts/4-UX/', ':non-framework:artifacts/handoffs/ux-handoff.md'],
|
|
33
|
+
'phases': [':non-framework:artifacts/5-Phases/', ':non-framework:artifacts/handoffs/phases-handoff.md'],
|
|
34
|
+
'tasks': [':non-framework:artifacts/6-Tasks/', ':non-framework:artifacts/handoffs/tasks-handoff.md'],
|
|
35
|
+
'qa-planning': [':non-framework:artifacts/7-QA-Planning/', ':non-framework:artifacts/handoffs/qa-planning-handoff.md'],
|
|
36
|
+
'dev': [':non-framework:src/', ':non-framework:test/', ':non-framework:package.json', ':non-framework:artifacts/8-Implementation/', ':non-framework:artifacts/handoffs/dev-handoff.md'],
|
|
37
|
+
'qa-implementation': [':non-framework:test/', ':non-framework:artifacts/9-QA-Implementation/', ':non-framework:artifacts/handoffs/qa-implementation-handoff.md'],
|
|
38
|
+
'qa-visual': [':non-framework:artifacts/handoffs/qa-visual-handoff.md', ':non-framework:artifacts/9-QA-Implementation/qa-visual-report.md'],
|
|
39
|
+
'devops': [':non-framework:.github/', ':non-framework:Dockerfile', ':non-framework:docker-compose.yml', ':non-framework:artifacts/10-Deploy/', ':non-framework:artifacts/handoffs/devops-handoff.md'],
|
|
33
40
|
};
|
|
34
41
|
|
|
35
42
|
/**
|
|
36
|
-
*
|
|
43
|
+
* Resolved write scopes keyed as 'agent:fwDir' for memoization.
|
|
44
|
+
* @type {Map<string, string[]>}
|
|
45
|
+
*/
|
|
46
|
+
const _writeScopeCache = new Map();
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Return the write scope for a given agent, resolved against the project structure.
|
|
37
51
|
*
|
|
38
52
|
* @param {string} agent - Agent name (e.g. "architect")
|
|
53
|
+
* @param {string} [projectDir] - Project root directory. When provided, paths
|
|
54
|
+
* are resolved using resolveFrameworkDir() so .chati.dev/ vs chati.dev/ is correct.
|
|
39
55
|
* @returns {string[]} Array of path prefixes the agent may write to.
|
|
40
56
|
* Returns an empty array for unknown agents.
|
|
41
57
|
*/
|
|
42
|
-
export function getWriteScope(agent) {
|
|
58
|
+
export function getWriteScope(agent, projectDir) {
|
|
43
59
|
if (!agent || typeof agent !== 'string') {
|
|
44
60
|
return [];
|
|
45
61
|
}
|
|
46
|
-
|
|
62
|
+
const templates = WRITE_SCOPE_TEMPLATES[agent];
|
|
63
|
+
if (!templates) return [];
|
|
64
|
+
|
|
65
|
+
// Without projectDir, return legacy chati.dev/ paths (backward compat)
|
|
66
|
+
if (!projectDir) {
|
|
67
|
+
return templates.map(p => p.startsWith(':non-framework:') ? p.slice(':non-framework:'.length) : `chati.dev/${p}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fwDir = resolveFrameworkDir(projectDir);
|
|
71
|
+
const cacheKey = `${agent}:${fwDir}`;
|
|
72
|
+
if (_writeScopeCache.has(cacheKey)) {
|
|
73
|
+
return _writeScopeCache.get(cacheKey);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const resolved = templates.map(p =>
|
|
77
|
+
p.startsWith(':non-framework:') ? p.slice(':non-framework:'.length) : `${fwDir}/${p}`
|
|
78
|
+
);
|
|
79
|
+
_writeScopeCache.set(cacheKey, resolved);
|
|
80
|
+
return resolved;
|
|
47
81
|
}
|
|
48
82
|
|
|
49
83
|
/**
|