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
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IDS Decision Engine — Intelligent Deduplication & Similarity scoring.
|
|
3
|
-
*
|
|
4
|
-
* Before creating any new artifact, scores existing registry entities
|
|
5
|
-
* for similarity. Enforces Constitution Article XIV: REUSE > ADAPT > CREATE.
|
|
6
|
-
*
|
|
7
|
-
* NOTE: This module handles intent-level similarity with weighted scoring.
|
|
8
|
-
* For entity-level routing (COMPASS registry lookup), see `decision/engine.js`.
|
|
9
|
-
*
|
|
10
|
-
* Algorithm:
|
|
11
|
-
* similarity = (keywordOverlap * 0.6) + (purposeSimilarity * 0.4)
|
|
12
|
-
* >= 90% → REUSE, 60-89% → ADAPT, < 60% → CREATE
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { existsSync, readFileSync } from 'fs';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @typedef {object} DecisionIntent
|
|
20
|
-
* @property {string[]} keywords - Keywords describing what we want to create
|
|
21
|
-
* @property {string} purpose - One-line description of the intended artifact
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @typedef {object} DecisionResult
|
|
26
|
-
* @property {string} decision - 'REUSE' | 'ADAPT' | 'CREATE'
|
|
27
|
-
* @property {string|null} matchId - The best matching entity ID (null for CREATE)
|
|
28
|
-
* @property {number} similarity - 0-100 similarity score
|
|
29
|
-
* @property {string} reasoning - Human-readable explanation
|
|
30
|
-
* @property {object|null} match - The best matching entity data
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Evaluate a creation intent against the entity registry.
|
|
35
|
-
*
|
|
36
|
-
* @param {DecisionIntent} intent - What we want to create
|
|
37
|
-
* @param {string} registryPath - Absolute path to entity-registry.yaml
|
|
38
|
-
* @returns {DecisionResult}
|
|
39
|
-
*/
|
|
40
|
-
export function evaluateDecision(intent, registryPath) {
|
|
41
|
-
if (!intent || !intent.keywords || !intent.purpose) {
|
|
42
|
-
return {
|
|
43
|
-
decision: 'CREATE',
|
|
44
|
-
matchId: null,
|
|
45
|
-
similarity: 0,
|
|
46
|
-
reasoning: 'No intent provided, defaulting to CREATE.',
|
|
47
|
-
match: null,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const entities = loadRegistryEntities(registryPath);
|
|
52
|
-
if (entities.length === 0) {
|
|
53
|
-
return {
|
|
54
|
-
decision: 'CREATE',
|
|
55
|
-
matchId: null,
|
|
56
|
-
similarity: 0,
|
|
57
|
-
reasoning: 'Registry empty or not found, defaulting to CREATE.',
|
|
58
|
-
match: null,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
let bestMatch = null;
|
|
63
|
-
let bestScore = 0;
|
|
64
|
-
let bestId = null;
|
|
65
|
-
|
|
66
|
-
for (const entity of entities) {
|
|
67
|
-
const score = calculateIntentSimilarity(intent, entity);
|
|
68
|
-
if (score > bestScore) {
|
|
69
|
-
bestScore = score;
|
|
70
|
-
bestMatch = entity;
|
|
71
|
-
bestId = entity.id;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const similarity = Math.round(bestScore * 100);
|
|
76
|
-
let decision;
|
|
77
|
-
let reasoning;
|
|
78
|
-
|
|
79
|
-
if (similarity >= 90) {
|
|
80
|
-
decision = 'REUSE';
|
|
81
|
-
reasoning = `Entity "${bestId}" is ${similarity}% similar. Reuse as-is (Article XIV).`;
|
|
82
|
-
} else if (similarity >= 60) {
|
|
83
|
-
decision = 'ADAPT';
|
|
84
|
-
reasoning = `Entity "${bestId}" is ${similarity}% similar. Adapt for current needs (adaptability: ${bestMatch?.adaptability ?? 'unknown'}).`;
|
|
85
|
-
} else {
|
|
86
|
-
decision = 'CREATE';
|
|
87
|
-
reasoning = bestId
|
|
88
|
-
? `Best match "${bestId}" is only ${similarity}% similar. Create new artifact.`
|
|
89
|
-
: 'No sufficiently similar entity found. Create new artifact.';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
decision,
|
|
94
|
-
matchId: similarity >= 60 ? bestId : null,
|
|
95
|
-
similarity,
|
|
96
|
-
reasoning,
|
|
97
|
-
match: similarity >= 60 ? bestMatch : null,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Calculate combined similarity between an intent and an entity.
|
|
103
|
-
*
|
|
104
|
-
* similarity = (keywordOverlap * 0.6) + (purposeSimilarity * 0.4)
|
|
105
|
-
*
|
|
106
|
-
* @param {DecisionIntent} intent
|
|
107
|
-
* @param {{ keywords: string[], purpose: string }} entity
|
|
108
|
-
* @returns {number} 0.0 to 1.0
|
|
109
|
-
*/
|
|
110
|
-
export function calculateIntentSimilarity(intent, entity) {
|
|
111
|
-
const kwScore = keywordOverlap(intent.keywords, entity.keywords || []);
|
|
112
|
-
const purposeScore = purposeSimilarity(intent.purpose, entity.purpose || '');
|
|
113
|
-
return (kwScore * 0.6) + (purposeScore * 0.4);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Calculate keyword overlap using token-based Jaccard similarity.
|
|
118
|
-
*
|
|
119
|
-
* @param {string[]} a - Intent keywords
|
|
120
|
-
* @param {string[]} b - Entity keywords
|
|
121
|
-
* @returns {number} 0.0 to 1.0
|
|
122
|
-
*/
|
|
123
|
-
export function keywordOverlap(a, b) {
|
|
124
|
-
if (!a || !b || a.length === 0 || b.length === 0) return 0;
|
|
125
|
-
|
|
126
|
-
const setA = new Set(a.map(k => k.toLowerCase().trim()));
|
|
127
|
-
const setB = new Set(b.map(k => k.toLowerCase().trim()));
|
|
128
|
-
|
|
129
|
-
let intersection = 0;
|
|
130
|
-
for (const k of setA) {
|
|
131
|
-
if (setB.has(k)) intersection++;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const union = new Set([...setA, ...setB]).size;
|
|
135
|
-
return union === 0 ? 0 : intersection / union;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Calculate purpose similarity using token overlap (bag-of-words).
|
|
140
|
-
*
|
|
141
|
-
* @param {string} a - Intent purpose
|
|
142
|
-
* @param {string} b - Entity purpose
|
|
143
|
-
* @returns {number} 0.0 to 1.0
|
|
144
|
-
*/
|
|
145
|
-
export function purposeSimilarity(a, b) {
|
|
146
|
-
if (!a || !b) return 0;
|
|
147
|
-
|
|
148
|
-
const tokenize = (text) => {
|
|
149
|
-
return text
|
|
150
|
-
.toLowerCase()
|
|
151
|
-
.replace(/[^a-z0-9\s]/g, '')
|
|
152
|
-
.split(/\s+/)
|
|
153
|
-
.filter(t => t.length > 2); // Skip tiny words
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const tokensA = new Set(tokenize(a));
|
|
157
|
-
const tokensB = new Set(tokenize(b));
|
|
158
|
-
|
|
159
|
-
if (tokensA.size === 0 || tokensB.size === 0) return 0;
|
|
160
|
-
|
|
161
|
-
let intersection = 0;
|
|
162
|
-
for (const t of tokensA) {
|
|
163
|
-
if (tokensB.has(t)) intersection++;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const union = new Set([...tokensA, ...tokensB]).size;
|
|
167
|
-
return union === 0 ? 0 : intersection / union;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Load and flatten entity registry into a searchable array.
|
|
172
|
-
*
|
|
173
|
-
* @param {string} registryPath - Absolute path to entity-registry.yaml
|
|
174
|
-
* @returns {Array<{ id: string, keywords: string[], purpose: string, path: string, type: string, adaptability: number }>}
|
|
175
|
-
*/
|
|
176
|
-
export function loadRegistryEntities(registryPath) {
|
|
177
|
-
if (!registryPath || !existsSync(registryPath)) return [];
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
const raw = readFileSync(registryPath, 'utf-8');
|
|
181
|
-
return parseRegistryEntities(raw);
|
|
182
|
-
} catch { /* expected: file may not exist */
|
|
183
|
-
return [];
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Parse registry YAML into flat entity array.
|
|
189
|
-
* Uses simple regex parsing consistent with the codebase pattern.
|
|
190
|
-
*
|
|
191
|
-
* @param {string} raw - Raw YAML content
|
|
192
|
-
* @returns {Array<{ id: string, keywords: string[], purpose: string, path: string, type: string, adaptability: number }>}
|
|
193
|
-
*/
|
|
194
|
-
export function parseRegistryEntities(raw) {
|
|
195
|
-
const entities = [];
|
|
196
|
-
const lines = raw.split('\n');
|
|
197
|
-
|
|
198
|
-
let currentCategory = null;
|
|
199
|
-
let currentEntity = null;
|
|
200
|
-
let currentData = {};
|
|
201
|
-
|
|
202
|
-
for (const line of lines) {
|
|
203
|
-
// Category header (agents:, templates:, etc.) — 2-space indent under entities:
|
|
204
|
-
const categoryMatch = line.match(/^ {2}(\w[\w-]*):\s*$/);
|
|
205
|
-
if (categoryMatch && !line.match(/^\s{4}/)) {
|
|
206
|
-
// Save previous entity
|
|
207
|
-
if (currentEntity && currentData.purpose) {
|
|
208
|
-
entities.push({ id: `${currentCategory}/${currentEntity}`, ...currentData });
|
|
209
|
-
}
|
|
210
|
-
currentCategory = categoryMatch[1];
|
|
211
|
-
currentEntity = null;
|
|
212
|
-
currentData = {};
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Entity header (e.g., " orchestrator:") — 4-space indent
|
|
217
|
-
const entityMatch = line.match(/^ {4}([\w][\w-]*):\s*$/);
|
|
218
|
-
if (entityMatch) {
|
|
219
|
-
// Save previous entity
|
|
220
|
-
if (currentEntity && currentData.purpose) {
|
|
221
|
-
entities.push({ id: `${currentCategory}/${currentEntity}`, ...currentData });
|
|
222
|
-
}
|
|
223
|
-
currentEntity = entityMatch[1];
|
|
224
|
-
currentData = { keywords: [], purpose: '', path: '', type: '', adaptability: 0.5 };
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (!currentEntity) continue;
|
|
229
|
-
|
|
230
|
-
// Entity fields (6-space indent)
|
|
231
|
-
const pathMatch = line.match(/^\s{6}path:\s*(.+)/);
|
|
232
|
-
if (pathMatch) { currentData.path = pathMatch[1].trim(); continue; }
|
|
233
|
-
|
|
234
|
-
const typeMatch = line.match(/^\s{6}type:\s*(.+)/);
|
|
235
|
-
if (typeMatch) { currentData.type = typeMatch[1].trim(); continue; }
|
|
236
|
-
|
|
237
|
-
const purposeMatch = line.match(/^\s{6}purpose:\s*"?([^"]*)"?\s*$/);
|
|
238
|
-
if (purposeMatch) { currentData.purpose = purposeMatch[1].trim(); continue; }
|
|
239
|
-
|
|
240
|
-
const adaptMatch = line.match(/^\s{6}adaptability:\s*([\d.]+)/);
|
|
241
|
-
if (adaptMatch) { currentData.adaptability = parseFloat(adaptMatch[1]); continue; }
|
|
242
|
-
|
|
243
|
-
const kwMatch = line.match(/^\s{6}keywords:\s*\[(.+)\]/);
|
|
244
|
-
if (kwMatch) {
|
|
245
|
-
currentData.keywords = kwMatch[1].split(',').map(k => k.trim().replace(/^['"]|['"]$/g, ''));
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Save last entity
|
|
251
|
-
if (currentEntity && currentData.purpose) {
|
|
252
|
-
entities.push({ id: `${currentCategory}/${currentEntity}`, ...currentData });
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return entities;
|
|
256
|
-
}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Document sharding for large PRDs and specifications.
|
|
3
|
-
*
|
|
4
|
-
* Splits large documents into manageable shards while respecting
|
|
5
|
-
* heading boundaries and providing overlap for context continuity.
|
|
6
|
-
*
|
|
7
|
-
* Constitution Article XII — Context Bracket Management.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
// Constants
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
/** Default maximum shard size in characters (~2K tokens). */
|
|
15
|
-
const DEFAULT_MAX_SHARD_SIZE = 8_000;
|
|
16
|
-
|
|
17
|
-
/** Overlap characters between shards for context continuity. */
|
|
18
|
-
const DEFAULT_OVERLAP = 800;
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// Public API
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @typedef {object} Shard
|
|
26
|
-
* @property {number} index - Shard index (0-based)
|
|
27
|
-
* @property {string} content - Shard content
|
|
28
|
-
* @property {string[]} sections - Section headings in this shard
|
|
29
|
-
* @property {number} startOffset - Character offset in original document
|
|
30
|
-
* @property {number} endOffset - End character offset
|
|
31
|
-
* @property {boolean} hasOverlap - Whether this shard has overlap from previous
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Split a document into shards, respecting heading boundaries.
|
|
36
|
-
*
|
|
37
|
-
* @param {string} content - Full document content
|
|
38
|
-
* @param {{ maxShardSize?: number, overlap?: number }} [options={}]
|
|
39
|
-
* @returns {Shard[]}
|
|
40
|
-
*/
|
|
41
|
-
export function shardDocument(content, options = {}) {
|
|
42
|
-
if (!content || typeof content !== 'string') {
|
|
43
|
-
return [];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const maxSize = options.maxShardSize || DEFAULT_MAX_SHARD_SIZE;
|
|
47
|
-
const overlap = options.overlap || DEFAULT_OVERLAP;
|
|
48
|
-
|
|
49
|
-
// If content fits in one shard, return as-is
|
|
50
|
-
if (content.length <= maxSize) {
|
|
51
|
-
return [{
|
|
52
|
-
index: 0,
|
|
53
|
-
content,
|
|
54
|
-
sections: extractSections(content),
|
|
55
|
-
startOffset: 0,
|
|
56
|
-
endOffset: content.length,
|
|
57
|
-
hasOverlap: false,
|
|
58
|
-
}];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Find heading boundaries (## or # markers)
|
|
62
|
-
const headingPattern = /^#{1,3}\s+.+$/gm;
|
|
63
|
-
const headings = [];
|
|
64
|
-
let match;
|
|
65
|
-
|
|
66
|
-
while ((match = headingPattern.exec(content)) !== null) {
|
|
67
|
-
headings.push({ offset: match.index, text: match[0].trim() });
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Build shards at heading boundaries
|
|
71
|
-
const shards = [];
|
|
72
|
-
let currentStart = 0;
|
|
73
|
-
|
|
74
|
-
while (currentStart < content.length) {
|
|
75
|
-
let endPos = currentStart + maxSize;
|
|
76
|
-
|
|
77
|
-
if (endPos >= content.length) {
|
|
78
|
-
// Last shard
|
|
79
|
-
endPos = content.length;
|
|
80
|
-
} else {
|
|
81
|
-
// Find the best heading boundary to split at
|
|
82
|
-
const bestBreak = findBestBreak(headings, currentStart, endPos);
|
|
83
|
-
if (bestBreak > currentStart) {
|
|
84
|
-
endPos = bestBreak;
|
|
85
|
-
} else {
|
|
86
|
-
// No heading found — break at paragraph boundary
|
|
87
|
-
const paragraphBreak = content.lastIndexOf('\n\n', endPos);
|
|
88
|
-
if (paragraphBreak > currentStart) {
|
|
89
|
-
endPos = paragraphBreak;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const shardContent = content.slice(currentStart, endPos);
|
|
95
|
-
const hasOverlap = currentStart > 0;
|
|
96
|
-
|
|
97
|
-
shards.push({
|
|
98
|
-
index: shards.length,
|
|
99
|
-
content: shardContent,
|
|
100
|
-
sections: extractSections(shardContent),
|
|
101
|
-
startOffset: currentStart,
|
|
102
|
-
endOffset: endPos,
|
|
103
|
-
hasOverlap,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Advance with overlap
|
|
107
|
-
currentStart = Math.max(currentStart + 1, endPos - overlap);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return shards;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Reassemble shards back into a document, removing overlaps.
|
|
115
|
-
*
|
|
116
|
-
* @param {Shard[]} shards
|
|
117
|
-
* @returns {string}
|
|
118
|
-
*/
|
|
119
|
-
export function reassembleShards(shards) {
|
|
120
|
-
if (!shards || shards.length === 0) return '';
|
|
121
|
-
if (shards.length === 1) return shards[0].content;
|
|
122
|
-
|
|
123
|
-
// Sort by index
|
|
124
|
-
const sorted = [...shards].sort((a, b) => a.index - b.index);
|
|
125
|
-
|
|
126
|
-
// Use non-overlapping portions
|
|
127
|
-
const parts = [];
|
|
128
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
129
|
-
const shard = sorted[i];
|
|
130
|
-
if (i === 0) {
|
|
131
|
-
// First shard: use everything up to where next shard's unique content starts
|
|
132
|
-
if (sorted.length > 1) {
|
|
133
|
-
const nextStart = sorted[1].startOffset;
|
|
134
|
-
const overlapStart = shard.endOffset - nextStart;
|
|
135
|
-
if (overlapStart > 0) {
|
|
136
|
-
parts.push(shard.content.slice(0, shard.content.length - overlapStart));
|
|
137
|
-
} else {
|
|
138
|
-
parts.push(shard.content);
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
parts.push(shard.content);
|
|
142
|
-
}
|
|
143
|
-
} else if (i === sorted.length - 1) {
|
|
144
|
-
// Last shard: use from overlap end to end
|
|
145
|
-
const prevEnd = sorted[i - 1].endOffset;
|
|
146
|
-
const overlapLength = prevEnd - shard.startOffset;
|
|
147
|
-
if (overlapLength > 0) {
|
|
148
|
-
parts.push(shard.content.slice(overlapLength));
|
|
149
|
-
} else {
|
|
150
|
-
parts.push(shard.content);
|
|
151
|
-
}
|
|
152
|
-
} else {
|
|
153
|
-
// Middle shard: use from overlap end to before next shard's overlap
|
|
154
|
-
const prevEnd = sorted[i - 1].endOffset;
|
|
155
|
-
const startOverlap = prevEnd - shard.startOffset;
|
|
156
|
-
const nextStart = sorted[i + 1].startOffset;
|
|
157
|
-
const endOverlap = shard.endOffset - nextStart;
|
|
158
|
-
|
|
159
|
-
const start = Math.max(0, startOverlap);
|
|
160
|
-
const end = endOverlap > 0 ? shard.content.length - endOverlap : shard.content.length;
|
|
161
|
-
parts.push(shard.content.slice(start, end));
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return parts.join('');
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Find the shard containing a specific section heading.
|
|
170
|
-
*
|
|
171
|
-
* @param {Shard[]} shards
|
|
172
|
-
* @param {string} sectionName - Heading text to find (partial match)
|
|
173
|
-
* @returns {Shard|null}
|
|
174
|
-
*/
|
|
175
|
-
export function getShardForSection(shards, sectionName) {
|
|
176
|
-
if (!shards || !sectionName) return null;
|
|
177
|
-
|
|
178
|
-
const lower = sectionName.toLowerCase();
|
|
179
|
-
return shards.find(shard =>
|
|
180
|
-
shard.sections.some(s => s.toLowerCase().includes(lower))
|
|
181
|
-
) || null;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ---------------------------------------------------------------------------
|
|
185
|
-
// Helpers
|
|
186
|
-
// ---------------------------------------------------------------------------
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Find the best heading boundary to break at within a range.
|
|
190
|
-
*
|
|
191
|
-
* @param {{ offset: number, text: string }[]} headings
|
|
192
|
-
* @param {number} start
|
|
193
|
-
* @param {number} end
|
|
194
|
-
* @returns {number} Offset of best break point, or 0 if none found
|
|
195
|
-
*/
|
|
196
|
-
function findBestBreak(headings, start, end) {
|
|
197
|
-
// Find the last heading that starts before end and after start
|
|
198
|
-
let bestOffset = 0;
|
|
199
|
-
for (const heading of headings) {
|
|
200
|
-
if (heading.offset > start && heading.offset <= end) {
|
|
201
|
-
bestOffset = heading.offset;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return bestOffset;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Extract section headings from content.
|
|
209
|
-
*
|
|
210
|
-
* @param {string} content
|
|
211
|
-
* @returns {string[]}
|
|
212
|
-
*/
|
|
213
|
-
function extractSections(content) {
|
|
214
|
-
const headings = content.match(/^#{1,3}\s+.+$/gm) || [];
|
|
215
|
-
return headings.map(h => h.replace(/^#+\s+/, '').trim());
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Exported constants for testing.
|
|
220
|
-
*/
|
|
221
|
-
export { DEFAULT_MAX_SHARD_SIZE, DEFAULT_OVERLAP };
|