@documentation-robotics/cli 0.1.13 → 0.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 +74 -2
- package/dist/analysis/relationship-classifier.js +2 -9
- package/dist/analysis/relationship-classifier.js.map +2 -2
- package/dist/analyzers/call-graph-utils.js +2 -2
- package/dist/analyzers/call-graph-utils.js.map +2 -2
- package/dist/analyzers/cards/build.js +171 -0
- package/dist/analyzers/cards/build.js.map +7 -0
- package/dist/analyzers/cards/classify.js +42 -0
- package/dist/analyzers/cards/classify.js.map +7 -0
- package/dist/analyzers/cards/edge-index.js +44 -0
- package/dist/analyzers/cards/edge-index.js.map +7 -0
- package/dist/analyzers/cards/facts.js +46 -0
- package/dist/analyzers/cards/facts.js.map +7 -0
- package/dist/analyzers/cards/graph.js +42 -0
- package/dist/analyzers/cards/graph.js.map +7 -0
- package/dist/analyzers/cards/index.js +8 -0
- package/dist/analyzers/cards/index.js.map +7 -0
- package/dist/analyzers/cards/tier0.js +18 -0
- package/dist/analyzers/cards/tier0.js.map +7 -0
- package/dist/analyzers/cards/types.js +1 -0
- package/dist/analyzers/cards/types.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +195 -171
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +336 -0
- package/dist/analyzers/cbm-responses.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +45 -0
- package/dist/analyzers/edge-relationships.js.map +7 -0
- package/dist/analyzers/mapping-loader.js +9 -0
- package/dist/analyzers/mapping-loader.js.map +2 -2
- package/dist/analyzers/tier0-rules.js +153 -0
- package/dist/analyzers/tier0-rules.js.map +7 -0
- package/dist/analyzers/types.js.map +1 -1
- package/dist/analyzers/verify-engine.js +22 -19
- package/dist/analyzers/verify-engine.js.map +2 -2
- package/dist/audit/relationships/ai/prompts.js +10 -10
- package/dist/audit/relationships/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/analysis/layer-templates.js +52 -52
- package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
- package/dist/cli.js +112 -28
- package/dist/cli.js.map +3 -3
- package/dist/coding-agents/agents/claude-code.js +3 -1
- package/dist/coding-agents/agents/claude-code.js.map +2 -2
- package/dist/coding-agents/claude-code-client.js +2 -1
- package/dist/coding-agents/claude-code-client.js.map +2 -2
- package/dist/coding-agents/copilot-client.js +17 -12
- package/dist/coding-agents/copilot-client.js.map +2 -2
- package/dist/coding-agents/default-permissions.js +152 -0
- package/dist/coding-agents/default-permissions.js.map +7 -0
- package/dist/coding-agents/drbot-mcp.js +167 -0
- package/dist/coding-agents/drbot-mcp.js.map +7 -0
- package/dist/commands/add.js +12 -7
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +134 -23
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/audit.js +6 -6
- package/dist/commands/audit.js.map +2 -2
- package/dist/commands/changeset-verify.js +55 -0
- package/dist/commands/changeset-verify.js.map +7 -0
- package/dist/commands/changeset.js +261 -147
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +52 -19
- package/dist/commands/delete.js.map +3 -3
- package/dist/commands/element.js +3 -2
- package/dist/commands/element.js.map +2 -2
- package/dist/commands/farm.js +773 -0
- package/dist/commands/farm.js.map +7 -0
- package/dist/commands/init.js +11 -1
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +122 -0
- package/dist/commands/mcp.js.map +7 -0
- package/dist/commands/relationship.js +251 -47
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/reports.js +1 -1
- package/dist/commands/reports.js.map +2 -2
- package/dist/commands/update.js +56 -24
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +99 -11
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/validate.js +41 -3
- package/dist/commands/validate.js.map +2 -2
- package/dist/commands/version.js +2 -2
- package/dist/commands/version.js.map +2 -2
- package/dist/commands/visualize.js +4 -3
- package/dist/commands/visualize.js.map +2 -2
- package/dist/config.js +83 -0
- package/dist/config.js.map +7 -0
- package/dist/core/annotation-store.js +138 -0
- package/dist/core/annotation-store.js.map +7 -0
- package/dist/core/catalog-migration.js +33 -0
- package/dist/core/catalog-migration.js.map +7 -0
- package/dist/core/changeset-analysis.js +421 -0
- package/dist/core/changeset-analysis.js.map +7 -0
- package/dist/core/changeset-base-index.js +153 -0
- package/dist/core/changeset-base-index.js.map +7 -0
- package/dist/core/changeset-brief-delta.js +102 -0
- package/dist/core/changeset-brief-delta.js.map +7 -0
- package/dist/core/changeset-graph.js +281 -0
- package/dist/core/changeset-graph.js.map +7 -0
- package/dist/core/changeset-only-guard.js +32 -0
- package/dist/core/changeset-only-guard.js.map +7 -0
- package/dist/core/changeset-summary.js +68 -0
- package/dist/core/changeset-summary.js.map +7 -0
- package/dist/core/changeset-validator.js +66 -20
- package/dist/core/changeset-validator.js.map +2 -2
- package/dist/core/changeset-verify.js +518 -0
- package/dist/core/changeset-verify.js.map +7 -0
- package/dist/core/changeset.js +56 -9
- package/dist/core/changeset.js.map +2 -2
- package/dist/core/element-type-migration.js +145 -0
- package/dist/core/element-type-migration.js.map +7 -0
- package/dist/core/farm-manifest.js +140 -0
- package/dist/core/farm-manifest.js.map +7 -0
- package/dist/core/farm-sync-engine.js +346 -0
- package/dist/core/farm-sync-engine.js.map +7 -0
- package/dist/core/farm-sync-state.js +167 -0
- package/dist/core/farm-sync-state.js.map +7 -0
- package/dist/core/manifest.js +6 -3
- package/dist/core/manifest.js.map +2 -2
- package/dist/core/migration-registry.js +84 -1
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model-upgrade.js +102 -0
- package/dist/core/model-upgrade.js.map +7 -0
- package/dist/core/model.js +257 -44
- package/dist/core/model.js.map +3 -3
- package/dist/core/mutation-handler.js +71 -6
- package/dist/core/mutation-handler.js.map +2 -2
- package/dist/core/relationship-migration-table.js +6639 -0
- package/dist/core/relationship-migration-table.js.map +7 -0
- package/dist/core/relationship-migration.js +272 -0
- package/dist/core/relationship-migration.js.map +7 -0
- package/dist/core/relationship-prune.js +49 -0
- package/dist/core/relationship-prune.js.map +7 -0
- package/dist/core/relationship-staging.js +68 -0
- package/dist/core/relationship-staging.js.map +7 -0
- package/dist/core/relationships.js +26 -4
- package/dist/core/relationships.js.map +2 -2
- package/dist/core/report-data-model.js +7 -13
- package/dist/core/report-data-model.js.map +2 -2
- package/dist/core/staged-changeset-storage.js +64 -17
- package/dist/core/staged-changeset-storage.js.map +2 -2
- package/dist/core/staged-source-reference.js +28 -0
- package/dist/core/staged-source-reference.js.map +7 -0
- package/dist/core/staging-area.js +83 -18
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/virtual-projection.js +14 -5
- package/dist/core/virtual-projection.js.map +2 -2
- package/dist/export/archimate-exporter.js +1 -0
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/generated/compiled-validators.js +2 -2
- package/dist/generated/compiled-validators.js.map +2 -2
- package/dist/generated/layer-registry.js +16 -16
- package/dist/generated/layer-registry.js.map +2 -2
- package/dist/generated/node-types.js +186 -168
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +2209 -7727
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
- package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
- package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
- package/dist/integrations/claude_code/commands/dr-map.md +13 -0
- package/dist/integrations/claude_code/commands/dr-plan.md +40 -0
- package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
- package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +79 -0
- package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
- package/dist/integrations/claude_code/docs/README.md +52 -1
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
- package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
- package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
- package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
- package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
- package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
- package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
- package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
- package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
- package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
- package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
- package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
- package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +92 -15
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
- package/dist/integrations/github_copilot/agents/dr-architect.md +1 -1
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
- package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
- package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
- package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
- package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
- package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
- package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
- package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
- package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
- package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
- package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
- package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
- package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +222 -102
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/hash-utils.js.map +2 -2
- package/dist/mcp/api-key-manager.js +111 -0
- package/dist/mcp/api-key-manager.js.map +7 -0
- package/dist/mcp/changeset-only.js +34 -0
- package/dist/mcp/changeset-only.js.map +7 -0
- package/dist/mcp/http-transport.js +176 -0
- package/dist/mcp/http-transport.js.map +7 -0
- package/dist/mcp/resource-registry.js +97 -0
- package/dist/mcp/resource-registry.js.map +7 -0
- package/dist/mcp/tool-registry.js +60 -0
- package/dist/mcp/tool-registry.js.map +7 -0
- package/dist/mcp/tools/annotation-create.js +39 -0
- package/dist/mcp/tools/annotation-create.js.map +7 -0
- package/dist/mcp/tools/annotation-delete.js +29 -0
- package/dist/mcp/tools/annotation-delete.js.map +7 -0
- package/dist/mcp/tools/annotation-get.js +29 -0
- package/dist/mcp/tools/annotation-get.js.map +7 -0
- package/dist/mcp/tools/annotation-list.js +24 -0
- package/dist/mcp/tools/annotation-list.js.map +7 -0
- package/dist/mcp/tools/annotation-reply.js +34 -0
- package/dist/mcp/tools/annotation-reply.js.map +7 -0
- package/dist/mcp/tools/annotation-shared.js +10 -0
- package/dist/mcp/tools/annotation-shared.js.map +7 -0
- package/dist/mcp/tools/annotation-update.js +39 -0
- package/dist/mcp/tools/annotation-update.js.map +7 -0
- package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
- package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +184 -0
- package/dist/mcp/tools/changeset-editing.js.map +7 -0
- package/dist/mcp/tools/changeset-lifecycle.js +83 -0
- package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
- package/dist/mcp/tools/changeset-list.js +33 -0
- package/dist/mcp/tools/changeset-list.js.map +7 -0
- package/dist/mcp/tools/changeset-shared.js +31 -0
- package/dist/mcp/tools/changeset-shared.js.map +7 -0
- package/dist/mcp/tools/changeset-show.js +55 -0
- package/dist/mcp/tools/changeset-show.js.map +7 -0
- package/dist/mcp/tools/changeset-verify.js +43 -0
- package/dist/mcp/tools/changeset-verify.js.map +7 -0
- package/dist/mcp/tools/chat-launch.js +50 -0
- package/dist/mcp/tools/chat-launch.js.map +7 -0
- package/dist/mcp/tools/chat-status.js +26 -0
- package/dist/mcp/tools/chat-status.js.map +7 -0
- package/dist/mcp/tools/index.js +65 -0
- package/dist/mcp/tools/index.js.map +7 -0
- package/dist/mcp/tools/model-add.js +107 -0
- package/dist/mcp/tools/model-add.js.map +7 -0
- package/dist/mcp/tools/model-delete.js +99 -0
- package/dist/mcp/tools/model-delete.js.map +7 -0
- package/dist/mcp/tools/model-export.js +72 -0
- package/dist/mcp/tools/model-export.js.map +7 -0
- package/dist/mcp/tools/model-info.js +51 -0
- package/dist/mcp/tools/model-info.js.map +7 -0
- package/dist/mcp/tools/model-list.js +53 -0
- package/dist/mcp/tools/model-list.js.map +7 -0
- package/dist/mcp/tools/model-reload.js +34 -0
- package/dist/mcp/tools/model-reload.js.map +7 -0
- package/dist/mcp/tools/model-search.js +102 -0
- package/dist/mcp/tools/model-search.js.map +7 -0
- package/dist/mcp/tools/model-show.js +68 -0
- package/dist/mcp/tools/model-show.js.map +7 -0
- package/dist/mcp/tools/model-stats.js +22 -0
- package/dist/mcp/tools/model-stats.js.map +7 -0
- package/dist/mcp/tools/model-trace.js +118 -0
- package/dist/mcp/tools/model-trace.js.map +7 -0
- package/dist/mcp/tools/model-update.js +121 -0
- package/dist/mcp/tools/model-update.js.map +7 -0
- package/dist/mcp/tools/model-validate.js +78 -0
- package/dist/mcp/tools/model-validate.js.map +7 -0
- package/dist/mcp/tools/relationship-tools.js +98 -0
- package/dist/mcp/tools/relationship-tools.js.map +7 -0
- package/dist/mcp/tools/shared.js +126 -0
- package/dist/mcp/tools/shared.js.map +7 -0
- package/dist/mcp/tools/spec-type.js +90 -0
- package/dist/mcp/tools/spec-type.js.map +7 -0
- package/dist/reports/model-layer-report-generator.js +5 -3
- package/dist/reports/model-layer-report-generator.js.map +2 -2
- package/dist/reports/model-readme-generator.js +116 -0
- package/dist/reports/model-readme-generator.js.map +7 -0
- package/dist/reports/model-report-data.js +37 -1
- package/dist/reports/model-report-data.js.map +2 -2
- package/dist/reports/model-report-orchestrator.js +29 -6
- package/dist/reports/model-report-orchestrator.js.map +2 -2
- package/dist/reports/model-report-utils.js +9 -0
- package/dist/reports/model-report-utils.js.map +7 -0
- package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1029 -1658
- package/dist/schemas/bundled/apm.json +499 -734
- package/dist/schemas/bundled/application.json +745 -370
- package/dist/schemas/bundled/base.json +3 -220
- package/dist/schemas/bundled/business.json +452 -366
- package/dist/schemas/bundled/data-model.json +403 -383
- package/dist/schemas/bundled/data-store.json +334 -558
- package/dist/schemas/bundled/manifest.json +18 -18
- package/dist/schemas/bundled/motivation.json +349 -368
- package/dist/schemas/bundled/navigation.json +415 -731
- package/dist/schemas/bundled/product.json +325 -159
- package/dist/schemas/bundled/security.json +864 -1232
- package/dist/schemas/bundled/technology.json +574 -703
- package/dist/schemas/bundled/testing.json +718 -1643
- package/dist/schemas/bundled/ux.json +739 -1352
- package/dist/server/schemas.js +8 -0
- package/dist/server/schemas.js.map +2 -2
- package/dist/server/server.js +234 -31
- package/dist/server/server.js.map +2 -2
- package/dist/server-entry.js +2 -1
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/config.js +2 -40
- package/dist/telemetry/config.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/types/index.js.map +2 -2
- package/dist/utils/ansi-suppressor.js +23 -0
- package/dist/utils/ansi-suppressor.js.map +7 -0
- package/dist/utils/attribute-patch.js +41 -0
- package/dist/utils/attribute-patch.js.map +7 -0
- package/dist/utils/color-utils.js +34 -0
- package/dist/utils/color-utils.js.map +7 -0
- package/dist/utils/element-reference-shape.js +15 -1
- package/dist/utils/element-reference-shape.js.map +3 -3
- package/dist/utils/error-hints.js +29 -0
- package/dist/utils/error-hints.js.map +7 -0
- package/dist/utils/errors.js +75 -10
- package/dist/utils/errors.js.map +2 -2
- package/dist/utils/globals.js +17 -1
- package/dist/utils/globals.js.map +2 -2
- package/dist/utils/project-paths.js +50 -3
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/reference-path-parser.js +49 -0
- package/dist/utils/reference-path-parser.js.map +7 -0
- package/dist/utils/source-reference.js +17 -10
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +10 -2
- package/dist/utils/spec-version.js.map +2 -2
- package/dist/validators/composed-reference-validator.js +240 -0
- package/dist/validators/composed-reference-validator.js.map +7 -0
- package/dist/validators/reference-validator.js +57 -13
- package/dist/validators/reference-validator.js.map +3 -3
- package/dist/validators/relationship-schema-validator.js +33 -5
- package/dist/validators/relationship-schema-validator.js.map +3 -3
- package/dist/validators/schema-validator.js +27 -9
- package/dist/validators/schema-validator.js.map +2 -2
- package/dist/validators/types.js.map +1 -1
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-CxzcuU_o.js +84 -0
- package/dist/viewer/assets/main-j-7ULYpm.css +1 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +7 -3
- package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
- package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import yaml from "yaml";
|
|
2
|
+
import { readFile, writeFile } from "../utils/file-io.js";
|
|
3
|
+
function validateFarmProject(project, projectKey) {
|
|
4
|
+
if (!project || typeof project != "object")
|
|
5
|
+
throw new Error(`Invalid project entry "${projectKey}": expected object, got ${typeof project}`);
|
|
6
|
+
const p = project;
|
|
7
|
+
if (typeof p.name != "string" || p.name === "")
|
|
8
|
+
throw new Error(`Project "${projectKey}" is missing required 'name' field. Each project must have name, source, and model fields.`);
|
|
9
|
+
if (typeof p.source != "string" || p.source === "")
|
|
10
|
+
throw new Error(`Project "${projectKey}" is missing required 'source' field. Each project must have name, source, and model fields.`);
|
|
11
|
+
if (typeof p.model != "string" || p.model === "")
|
|
12
|
+
throw new Error(`Project "${projectKey}" is missing required 'model' field. Each project must have name, source, and model fields.`);
|
|
13
|
+
if (p.name !== projectKey)
|
|
14
|
+
throw new Error(`Project name mismatch: key is "${projectKey}" but project.name is "${p.name}". They must match for consistent sync state tracking.`);
|
|
15
|
+
const validatedProject = {
|
|
16
|
+
name: p.name,
|
|
17
|
+
source: p.source,
|
|
18
|
+
model: p.model
|
|
19
|
+
};
|
|
20
|
+
return typeof p.remote == "string" && (validatedProject.remote = p.remote), typeof p.branch == "string" && (validatedProject.branch = p.branch), validatedProject;
|
|
21
|
+
}
|
|
22
|
+
class FarmManifest {
|
|
23
|
+
schema;
|
|
24
|
+
name;
|
|
25
|
+
created;
|
|
26
|
+
modified;
|
|
27
|
+
projects;
|
|
28
|
+
platform_view;
|
|
29
|
+
sync;
|
|
30
|
+
filePath;
|
|
31
|
+
constructor(data) {
|
|
32
|
+
this.schema = data.schema, this.name = data.name, this.created = data.created ?? (/* @__PURE__ */ new Date()).toISOString(), this.modified = data.modified ?? (/* @__PURE__ */ new Date()).toISOString(), this.projects = new Map(Object.entries(data.projects || {})), this.platform_view = data.platform_view, this.sync = data.sync;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Load farm manifest from file
|
|
36
|
+
* @param filePath - Path to farm.yaml
|
|
37
|
+
* @returns FarmManifest instance
|
|
38
|
+
* @throws Error if manifest is malformed or projects lack required fields
|
|
39
|
+
*/
|
|
40
|
+
static async load(filePath) {
|
|
41
|
+
const content = await readFile(filePath), data = yaml.parse(content);
|
|
42
|
+
if (!data)
|
|
43
|
+
throw new Error(`Invalid farm manifest: file is empty or contains invalid YAML at ${filePath}`);
|
|
44
|
+
if (!data.schema)
|
|
45
|
+
throw new Error("Farm manifest must have a 'schema' field");
|
|
46
|
+
if (!data.name)
|
|
47
|
+
throw new Error("Farm manifest must have a 'name' field");
|
|
48
|
+
if (!data.projects)
|
|
49
|
+
data.projects = {};
|
|
50
|
+
else
|
|
51
|
+
for (const [key, project] of Object.entries(data.projects))
|
|
52
|
+
data.projects[key] = validateFarmProject(project, key);
|
|
53
|
+
const manifest = new FarmManifest(data);
|
|
54
|
+
return manifest.filePath = filePath, manifest;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create a new farm manifest
|
|
58
|
+
* @param name - Farm name
|
|
59
|
+
* @param options - Optional additional properties
|
|
60
|
+
* @returns FarmManifest instance
|
|
61
|
+
*/
|
|
62
|
+
static create(name, options = {}) {
|
|
63
|
+
return new FarmManifest({
|
|
64
|
+
schema: "dr-farm-v1",
|
|
65
|
+
name,
|
|
66
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
67
|
+
modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
68
|
+
projects: {},
|
|
69
|
+
platform_view: options.platform_view,
|
|
70
|
+
sync: options.sync
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Save manifest to file
|
|
75
|
+
* @param filePath - Path where to save farm.yaml
|
|
76
|
+
*/
|
|
77
|
+
async save(filePath) {
|
|
78
|
+
const targetPath = filePath || this.filePath;
|
|
79
|
+
if (!targetPath)
|
|
80
|
+
throw new Error("No file path specified for saving farm manifest");
|
|
81
|
+
this.modified = (/* @__PURE__ */ new Date()).toISOString();
|
|
82
|
+
const data = this.toJSON(), yamlContent = yaml.stringify(data, { lineWidth: 0 });
|
|
83
|
+
await writeFile(targetPath, yamlContent), this.filePath = targetPath;
|
|
84
|
+
}
|
|
85
|
+
// Add a project to the farm; ensures name parameter matches project.name
|
|
86
|
+
addProject(name, project) {
|
|
87
|
+
if (project.name !== name)
|
|
88
|
+
throw new Error(`Project name mismatch: key is "${name}" but project.name is "${project.name}". They must match for consistent sync state tracking. Downstream code reads project.name for changeset names and sync state paths.`);
|
|
89
|
+
this.projects.set(name, project), this.modified = (/* @__PURE__ */ new Date()).toISOString();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Remove a project from the farm
|
|
93
|
+
* @param name - Project name to remove
|
|
94
|
+
* @returns true if project was removed, false if not found
|
|
95
|
+
*/
|
|
96
|
+
removeProject(name) {
|
|
97
|
+
const existed = this.projects.has(name);
|
|
98
|
+
return this.projects.delete(name), existed && (this.modified = (/* @__PURE__ */ new Date()).toISOString()), existed;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get a project by name
|
|
102
|
+
* @param name - Project name
|
|
103
|
+
* @returns Project configuration or undefined
|
|
104
|
+
*/
|
|
105
|
+
getProject(name) {
|
|
106
|
+
return this.projects.get(name);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get all projects
|
|
110
|
+
* @returns Array of all projects
|
|
111
|
+
*/
|
|
112
|
+
getAllProjects() {
|
|
113
|
+
return Array.from(this.projects.values());
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check if platform-view is enabled on this farm
|
|
117
|
+
* @returns true if platform-view is enabled
|
|
118
|
+
*/
|
|
119
|
+
isPlatformViewEnabled() {
|
|
120
|
+
return this.platform_view === !0;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Serialize to JSON representation for saving
|
|
124
|
+
*/
|
|
125
|
+
toJSON() {
|
|
126
|
+
return {
|
|
127
|
+
schema: this.schema,
|
|
128
|
+
name: this.name,
|
|
129
|
+
created: this.created,
|
|
130
|
+
modified: this.modified,
|
|
131
|
+
projects: Object.fromEntries(this.projects),
|
|
132
|
+
...this.platform_view !== void 0 && { platform_view: this.platform_view },
|
|
133
|
+
...this.sync && { sync: this.sync }
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
FarmManifest
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=farm-manifest.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/farm-manifest.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Farm Manifest - Manages farm.yaml configuration\n *\n * Represents a farm context containing multiple projects with detached model folders.\n * The farm manifest tracks project registrations and their model folder locations.\n */\n\nimport yaml from \"yaml\";\nimport { readFile, writeFile } from \"../utils/file-io.js\";\n\n/**\n * Represents a project registered in a farm\n */\nexport interface FarmProject {\n name: string;\n source: string; // Path to the codebase folder (relative to farm root)\n model: string; // Path to the model folder (relative to farm root)\n remote?: string; // Optional git remote URL\n branch?: string; // Optional git branch\n}\n\n// Validate project object has required fields and key/name consistency\nfunction validateFarmProject(project: unknown, projectKey: string): FarmProject {\n if (!project || typeof project !== \"object\") {\n throw new Error(\n `Invalid project entry \"${projectKey}\": expected object, got ${typeof project}`\n );\n }\n\n const p = project as Record<string, unknown>;\n\n if (typeof p.name !== \"string\" || p.name === \"\") {\n throw new Error(\n `Project \"${projectKey}\" is missing required 'name' field. ` +\n `Each project must have name, source, and model fields.`\n );\n }\n\n if (typeof p.source !== \"string\" || p.source === \"\") {\n throw new Error(\n `Project \"${projectKey}\" is missing required 'source' field. ` +\n `Each project must have name, source, and model fields.`\n );\n }\n\n if (typeof p.model !== \"string\" || p.model === \"\") {\n throw new Error(\n `Project \"${projectKey}\" is missing required 'model' field. ` +\n `Each project must have name, source, and model fields.`\n );\n }\n\n if (p.name !== projectKey) {\n throw new Error(\n `Project name mismatch: key is \"${projectKey}\" but project.name is \"${p.name}\". ` +\n `They must match for consistent sync state tracking.`\n );\n }\n\n const validatedProject: FarmProject = {\n name: p.name,\n source: p.source,\n model: p.model,\n };\n\n if (typeof p.remote === \"string\") {\n validatedProject.remote = p.remote;\n }\n\n if (typeof p.branch === \"string\") {\n validatedProject.branch = p.branch;\n }\n\n return validatedProject;\n}\n\n/**\n * Farm manifest data structure\n */\nexport interface FarmManifestData {\n schema: string; // Schema version (e.g., \"dr-farm-v1\")\n name: string; // Human-readable farm name\n created?: string; // ISO 8601 timestamp\n modified?: string; // ISO 8601 timestamp\n projects: Record<string, FarmProject>; // Projects indexed by name\n platform_view?: boolean; // Optional: Enable platform view mode\n sync?: {\n track_commits?: boolean; // Optional: Track commits during sync\n };\n}\n\n/**\n * FarmManifest class - Load, validate, and save farm.yaml\n */\nexport class FarmManifest {\n schema: string;\n name: string;\n created: string;\n modified: string;\n projects: Map<string, FarmProject>;\n platform_view?: boolean;\n sync?: {\n track_commits?: boolean;\n };\n filePath?: string;\n\n constructor(data: FarmManifestData) {\n this.schema = data.schema;\n this.name = data.name;\n this.created = data.created ?? new Date().toISOString();\n this.modified = data.modified ?? new Date().toISOString();\n this.projects = new Map(Object.entries(data.projects || {}));\n this.platform_view = data.platform_view;\n this.sync = data.sync;\n }\n\n /**\n * Load farm manifest from file\n * @param filePath - Path to farm.yaml\n * @returns FarmManifest instance\n * @throws Error if manifest is malformed or projects lack required fields\n */\n static async load(filePath: string): Promise<FarmManifest> {\n const content = await readFile(filePath);\n const data = yaml.parse(content) as FarmManifestData;\n\n if (!data) {\n throw new Error(\n `Invalid farm manifest: file is empty or contains invalid YAML at ${filePath}`\n );\n }\n\n if (!data.schema) {\n throw new Error(\"Farm manifest must have a 'schema' field\");\n }\n\n if (!data.name) {\n throw new Error(\"Farm manifest must have a 'name' field\");\n }\n\n if (!data.projects) {\n data.projects = {};\n } else {\n // Validate each project has required fields before proceeding\n for (const [key, project] of Object.entries(data.projects)) {\n data.projects[key] = validateFarmProject(project, key);\n }\n }\n\n const manifest = new FarmManifest(data);\n manifest.filePath = filePath;\n return manifest;\n }\n\n /**\n * Create a new farm manifest\n * @param name - Farm name\n * @param options - Optional additional properties\n * @returns FarmManifest instance\n */\n static create(\n name: string,\n options: {\n platform_view?: boolean;\n sync?: {\n track_commits?: boolean;\n };\n } = {}\n ): FarmManifest {\n return new FarmManifest({\n schema: \"dr-farm-v1\",\n name,\n created: new Date().toISOString(),\n modified: new Date().toISOString(),\n projects: {},\n platform_view: options.platform_view,\n sync: options.sync,\n });\n }\n\n /**\n * Save manifest to file\n * @param filePath - Path where to save farm.yaml\n */\n async save(filePath?: string): Promise<void> {\n const targetPath = filePath || this.filePath;\n if (!targetPath) {\n throw new Error(\"No file path specified for saving farm manifest\");\n }\n\n this.modified = new Date().toISOString();\n const data = this.toJSON();\n const yamlContent = yaml.stringify(data, { lineWidth: 0 });\n\n await writeFile(targetPath, yamlContent);\n this.filePath = targetPath;\n }\n\n // Add a project to the farm; ensures name parameter matches project.name\n addProject(name: string, project: FarmProject): void {\n if (project.name !== name) {\n throw new Error(\n `Project name mismatch: key is \"${name}\" but project.name is \"${project.name}\". ` +\n `They must match for consistent sync state tracking. ` +\n `Downstream code reads project.name for changeset names and sync state paths.`\n );\n }\n\n this.projects.set(name, project);\n this.modified = new Date().toISOString();\n }\n\n /**\n * Remove a project from the farm\n * @param name - Project name to remove\n * @returns true if project was removed, false if not found\n */\n removeProject(name: string): boolean {\n const existed = this.projects.has(name);\n this.projects.delete(name);\n if (existed) {\n this.modified = new Date().toISOString();\n }\n return existed;\n }\n\n /**\n * Get a project by name\n * @param name - Project name\n * @returns Project configuration or undefined\n */\n getProject(name: string): FarmProject | undefined {\n return this.projects.get(name);\n }\n\n /**\n * Get all projects\n * @returns Array of all projects\n */\n getAllProjects(): FarmProject[] {\n return Array.from(this.projects.values());\n }\n\n /**\n * Check if platform-view is enabled on this farm\n * @returns true if platform-view is enabled\n */\n isPlatformViewEnabled(): boolean {\n return this.platform_view === true;\n }\n\n /**\n * Serialize to JSON representation for saving\n */\n toJSON(): FarmManifestData {\n return {\n schema: this.schema,\n name: this.name,\n created: this.created,\n modified: this.modified,\n projects: Object.fromEntries(this.projects),\n ...(this.platform_view !== undefined && { platform_view: this.platform_view }),\n ...(this.sync && { sync: this.sync }),\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { execSync, execFileSync } from "child_process";
|
|
3
|
+
import { fileExists, ensureDir } from "../utils/file-io.js";
|
|
4
|
+
import { FarmSyncState } from "./farm-sync-state.js";
|
|
5
|
+
import { StagingAreaManager } from "./staging-area.js";
|
|
6
|
+
import { getErrorMessage } from "../utils/errors.js";
|
|
7
|
+
class FarmSyncEngine {
|
|
8
|
+
farmRoot;
|
|
9
|
+
model;
|
|
10
|
+
constructor(farmRoot, model) {
|
|
11
|
+
this.farmRoot = farmRoot, this.model = model || null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Pull latest changes from remote for a codebase
|
|
15
|
+
* @param codebasePath - Relative path to codebase folder
|
|
16
|
+
* @returns Updated commit SHA
|
|
17
|
+
*/
|
|
18
|
+
async pullCodebase(codebasePath) {
|
|
19
|
+
const fullPath = path.join(this.farmRoot, codebasePath);
|
|
20
|
+
if (!await fileExists(fullPath))
|
|
21
|
+
throw new Error(`Codebase directory not found: ${codebasePath}`);
|
|
22
|
+
try {
|
|
23
|
+
try {
|
|
24
|
+
execSync("git fetch --prune", { cwd: fullPath, stdio: "pipe" });
|
|
25
|
+
} catch (fetchError) {
|
|
26
|
+
const fetchMsg = getErrorMessage(fetchError);
|
|
27
|
+
if (!(fetchMsg.includes("No remote repository specified") || fetchMsg.includes("no remote")))
|
|
28
|
+
throw new Error(`git fetch failed: ${fetchMsg}`);
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
execSync("git pull --no-rebase", { cwd: fullPath, stdio: "pipe" });
|
|
32
|
+
} catch (pullError) {
|
|
33
|
+
const pullMsg = getErrorMessage(pullError);
|
|
34
|
+
if (!(pullMsg.includes("No remote repository specified") || pullMsg.includes("no remote") || pullMsg.includes("no tracking information")))
|
|
35
|
+
throw new Error(`git pull failed: ${pullMsg}`);
|
|
36
|
+
}
|
|
37
|
+
return execSync("git rev-parse HEAD", { cwd: fullPath, encoding: "utf-8" }).trim();
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error(`Failed to pull codebase '${codebasePath}': ${getErrorMessage(error)}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get the current HEAD commit SHA for a codebase
|
|
44
|
+
*/
|
|
45
|
+
async getCurrentCommit(codebasePath) {
|
|
46
|
+
const fullPath = path.join(this.farmRoot, codebasePath);
|
|
47
|
+
if (!await fileExists(fullPath))
|
|
48
|
+
throw new Error(`Codebase directory not found: ${codebasePath}`);
|
|
49
|
+
try {
|
|
50
|
+
return execSync("git rev-parse HEAD", { cwd: fullPath, encoding: "utf-8" }).trim();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error(`Failed to get current commit for '${codebasePath}': ${getErrorMessage(error)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Compute diff between two commits
|
|
57
|
+
* Commit SHAs are validated before use to prevent command injection
|
|
58
|
+
*/
|
|
59
|
+
async computeDiff(codebasePath, fromCommit, toCommit) {
|
|
60
|
+
const fullPath = path.join(this.farmRoot, codebasePath);
|
|
61
|
+
if (!await fileExists(fullPath))
|
|
62
|
+
throw new Error(`Codebase directory not found: ${codebasePath}`);
|
|
63
|
+
const commitShaRegex = /^[0-9a-f]{7,40}$/i;
|
|
64
|
+
if (!commitShaRegex.test(fromCommit) || !commitShaRegex.test(toCommit))
|
|
65
|
+
throw new Error(`Invalid commit SHA format. Expected 7-40 character hex string. Got: ${fromCommit}, ${toCommit}`);
|
|
66
|
+
try {
|
|
67
|
+
const diffOutput = execFileSync("git", ["diff", "--name-status", `${fromCommit}...${toCommit}`], {
|
|
68
|
+
cwd: fullPath,
|
|
69
|
+
encoding: "utf-8"
|
|
70
|
+
}), added = [], modified = [], deleted = [];
|
|
71
|
+
for (const line of diffOutput.trim().split(`
|
|
72
|
+
`)) {
|
|
73
|
+
if (!line)
|
|
74
|
+
continue;
|
|
75
|
+
const [status, ...fileParts] = line.split(" "), filePath = fileParts.join(" ");
|
|
76
|
+
switch (status) {
|
|
77
|
+
case "A":
|
|
78
|
+
added.push(filePath);
|
|
79
|
+
break;
|
|
80
|
+
case "M":
|
|
81
|
+
modified.push(filePath);
|
|
82
|
+
break;
|
|
83
|
+
case "D":
|
|
84
|
+
deleted.push(filePath);
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
if (status.startsWith("R")) {
|
|
88
|
+
const [oldPath, newPath] = fileParts;
|
|
89
|
+
deleted.push(oldPath), added.push(newPath);
|
|
90
|
+
} else status.startsWith("C") && added.push(fileParts[1]);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { added, modified, deleted };
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw new Error(`Failed to compute diff for '${codebasePath}': ${getErrorMessage(error)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Map changed files to model elements using source references
|
|
101
|
+
* Looks at each model element's source references and matches against changed files
|
|
102
|
+
* Returns empty results if model is not available
|
|
103
|
+
*/
|
|
104
|
+
async mapFilesToElements() {
|
|
105
|
+
if (!this.model)
|
|
106
|
+
return { confident: [], ambiguous: [] };
|
|
107
|
+
const confident = /* @__PURE__ */ new Map(), ambiguous = /* @__PURE__ */ new Map();
|
|
108
|
+
for (const layer of this.model.layers.values())
|
|
109
|
+
for (const element of layer.listElements()) {
|
|
110
|
+
if (!element.source_reference)
|
|
111
|
+
continue;
|
|
112
|
+
const sourceRef = element.source_reference;
|
|
113
|
+
if (!(!sourceRef.locations || sourceRef.locations.length === 0))
|
|
114
|
+
for (const location of sourceRef.locations) {
|
|
115
|
+
const filePath = location.file;
|
|
116
|
+
if (!confident.has(filePath) && !ambiguous.has(filePath)) {
|
|
117
|
+
const mapping = {
|
|
118
|
+
filePath,
|
|
119
|
+
possibleElements: [
|
|
120
|
+
{
|
|
121
|
+
elementId: element.id,
|
|
122
|
+
layer: layer.name,
|
|
123
|
+
confidence: 100,
|
|
124
|
+
// Exact match from source reference
|
|
125
|
+
sourceRef: location
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
};
|
|
129
|
+
confident.set(filePath, mapping);
|
|
130
|
+
} else if (confident.has(filePath)) {
|
|
131
|
+
const existing = confident.get(filePath);
|
|
132
|
+
confident.delete(filePath), existing.possibleElements.push({
|
|
133
|
+
elementId: element.id,
|
|
134
|
+
layer: layer.name,
|
|
135
|
+
confidence: 100,
|
|
136
|
+
sourceRef: location
|
|
137
|
+
}), ambiguous.set(filePath, existing);
|
|
138
|
+
} else
|
|
139
|
+
ambiguous.get(filePath).possibleElements.push({
|
|
140
|
+
elementId: element.id,
|
|
141
|
+
layer: layer.name,
|
|
142
|
+
confidence: 100,
|
|
143
|
+
sourceRef: location
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
confident: Array.from(confident.values()),
|
|
149
|
+
ambiguous: Array.from(ambiguous.values())
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Generate a changeset from file-to-element mappings
|
|
154
|
+
* Creates add/update/delete changes based on the diff and mappings
|
|
155
|
+
* Stages changes into the changeset for review before commit
|
|
156
|
+
*/
|
|
157
|
+
async generateChangeset(project, diff, mappings, options = {}) {
|
|
158
|
+
if (!this.model)
|
|
159
|
+
throw new Error("Model is required for changeset generation");
|
|
160
|
+
const stagingManager = new StagingAreaManager(this.model.rootPath, this.model), changesetName = `farm-sync-${project.name}-${Date.now()}`, changeset = await stagingManager.create(changesetName, `Auto-generated from farm sync of ${project.name}`);
|
|
161
|
+
if (!changeset.id)
|
|
162
|
+
throw new Error("Failed to create changeset - no ID assigned");
|
|
163
|
+
const warnings = [];
|
|
164
|
+
let changeCount = 0;
|
|
165
|
+
const confidentMap = new Map(mappings.confident.map((m) => [m.filePath, m]));
|
|
166
|
+
for (const filePath of diff.added) {
|
|
167
|
+
const mapping = confidentMap.get(filePath);
|
|
168
|
+
if (mapping && mapping.possibleElements.length === 1) {
|
|
169
|
+
const element = mapping.possibleElements[0];
|
|
170
|
+
await stagingManager.stage(changeset.id, {
|
|
171
|
+
type: "add",
|
|
172
|
+
elementId: element.elementId,
|
|
173
|
+
layerName: element.layer,
|
|
174
|
+
after: {
|
|
175
|
+
sourceFile: filePath,
|
|
176
|
+
syncSource: "codebase",
|
|
177
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
178
|
+
},
|
|
179
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
180
|
+
}), warnings.push(`File added: ${filePath} -> ${element.elementId} (staged for review, verify content accuracy)`), changeCount++;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
for (const filePath of diff.modified) {
|
|
184
|
+
const mapping = confidentMap.get(filePath);
|
|
185
|
+
if (mapping && mapping.possibleElements.length === 1) {
|
|
186
|
+
const element = mapping.possibleElements[0];
|
|
187
|
+
await stagingManager.stage(changeset.id, {
|
|
188
|
+
type: "update",
|
|
189
|
+
elementId: element.elementId,
|
|
190
|
+
layerName: element.layer,
|
|
191
|
+
before: {
|
|
192
|
+
sourceFile: filePath,
|
|
193
|
+
syncSource: "codebase"
|
|
194
|
+
},
|
|
195
|
+
after: {
|
|
196
|
+
sourceFile: filePath,
|
|
197
|
+
syncSource: "codebase",
|
|
198
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
199
|
+
},
|
|
200
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
201
|
+
}), warnings.push(`File modified: ${filePath} -> ${element.elementId} (staged for review)`), changeCount++;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const filePath of diff.deleted) {
|
|
205
|
+
const mapping = confidentMap.get(filePath);
|
|
206
|
+
if (mapping && mapping.possibleElements.length === 1) {
|
|
207
|
+
const element = mapping.possibleElements[0];
|
|
208
|
+
await stagingManager.stage(changeset.id, {
|
|
209
|
+
type: "delete",
|
|
210
|
+
elementId: element.elementId,
|
|
211
|
+
layerName: element.layer,
|
|
212
|
+
before: {
|
|
213
|
+
sourceFile: filePath,
|
|
214
|
+
syncSource: "codebase"
|
|
215
|
+
},
|
|
216
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
217
|
+
}), warnings.push(`File deleted: ${filePath} -> ${element.elementId} (staged for review)`), changeCount++;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (mappings.ambiguous.length > 0 && !options.force)
|
|
221
|
+
for (const ambiguity of mappings.ambiguous)
|
|
222
|
+
warnings.push(`Ambiguous mapping: ${ambiguity.filePath} -> ${ambiguity.possibleElements.map((e) => e.elementId).join(", ")} (skipped, use --force to proceed)`);
|
|
223
|
+
return {
|
|
224
|
+
changesetId: changeset.id,
|
|
225
|
+
changeCount,
|
|
226
|
+
warnings
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Execute a complete sync operation for a project
|
|
231
|
+
*/
|
|
232
|
+
async syncProject(project, options = {}) {
|
|
233
|
+
const codebasePath = project.source, notes = [];
|
|
234
|
+
options.verbose && !options.dryRun && notes.push(`Pulling latest from ${codebasePath}...`);
|
|
235
|
+
const currentCommit = options.dryRun ? await this.getCurrentCommit(codebasePath) : await this.pullCodebase(codebasePath);
|
|
236
|
+
options.dryRun && notes.push("(DRY RUN - skipping git pull)");
|
|
237
|
+
const syncStatePath = path.join(this.farmRoot, project.model, ".farm-sync.yaml");
|
|
238
|
+
await ensureDir(path.dirname(syncStatePath));
|
|
239
|
+
const syncState = await FarmSyncState.loadOrCreate(syncStatePath, project.name), previousCommit = syncState.lastSyncCommit;
|
|
240
|
+
if (notes.push(`Current commit: ${currentCommit}`), notes.push(`Previous sync commit: ${previousCommit || "none"}`), !previousCommit)
|
|
241
|
+
return notes.push("Initial sync - recording baseline only"), options.dryRun ? notes.push("(DRY RUN - sync state not recorded)") : (syncState.recordSync({
|
|
242
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
243
|
+
commit: currentCommit,
|
|
244
|
+
status: "success",
|
|
245
|
+
files_changed: 0,
|
|
246
|
+
elements_affected: 0,
|
|
247
|
+
notes: "Initial sync, no changes to sync"
|
|
248
|
+
}), await syncState.save(syncStatePath)), {
|
|
249
|
+
status: "success",
|
|
250
|
+
projectName: project.name,
|
|
251
|
+
commitsBefore: "none",
|
|
252
|
+
commitsAfter: currentCommit.substring(0, 8),
|
|
253
|
+
filesChanged: { added: [], modified: [], deleted: [] },
|
|
254
|
+
elementMappings: [],
|
|
255
|
+
ambiguities: [],
|
|
256
|
+
changeCount: 0,
|
|
257
|
+
notes,
|
|
258
|
+
dryRun: options.dryRun
|
|
259
|
+
};
|
|
260
|
+
options.verbose && notes.push(`Computing diff between ${previousCommit.substring(0, 8)} and ${currentCommit.substring(0, 8)}...`);
|
|
261
|
+
const diff = await this.computeDiff(codebasePath, previousCommit, currentCommit);
|
|
262
|
+
if (diff.added.length === 0 && diff.modified.length === 0 && diff.deleted.length === 0)
|
|
263
|
+
return notes.push("No changes detected"), options.dryRun ? notes.push("(DRY RUN - sync state not recorded)") : (syncState.recordSync({
|
|
264
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
265
|
+
commit: currentCommit,
|
|
266
|
+
status: "success",
|
|
267
|
+
files_changed: 0,
|
|
268
|
+
elements_affected: 0,
|
|
269
|
+
notes: "No changes to sync"
|
|
270
|
+
}), await syncState.save(syncStatePath)), {
|
|
271
|
+
status: "success",
|
|
272
|
+
projectName: project.name,
|
|
273
|
+
commitsBefore: previousCommit.substring(0, 8),
|
|
274
|
+
commitsAfter: currentCommit.substring(0, 8),
|
|
275
|
+
filesChanged: diff,
|
|
276
|
+
elementMappings: [],
|
|
277
|
+
ambiguities: [],
|
|
278
|
+
changeCount: 0,
|
|
279
|
+
notes,
|
|
280
|
+
dryRun: options.dryRun
|
|
281
|
+
};
|
|
282
|
+
if (notes.push(`Files changed: ${diff.added.length} added, ${diff.modified.length} modified, ${diff.deleted.length} deleted`), !this.model)
|
|
283
|
+
return notes.push("CRITICAL: Model is not loaded, cannot map files to elements"), options.dryRun ? notes.push("(DRY RUN - sync state not recorded)") : (syncState.recordSync({
|
|
284
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
285
|
+
commit: previousCommit,
|
|
286
|
+
// Don't advance lastSyncCommit - stay at previous commit
|
|
287
|
+
status: "failed",
|
|
288
|
+
files_changed: diff.added.length + diff.modified.length + diff.deleted.length,
|
|
289
|
+
elements_affected: 0,
|
|
290
|
+
notes: "Model not loaded - file changes not synced. Load the model and retry to process these files."
|
|
291
|
+
}), await syncState.save(syncStatePath)), {
|
|
292
|
+
status: "failed",
|
|
293
|
+
projectName: project.name,
|
|
294
|
+
commitsBefore: previousCommit.substring(0, 8),
|
|
295
|
+
commitsAfter: currentCommit.substring(0, 8),
|
|
296
|
+
filesChanged: diff,
|
|
297
|
+
elementMappings: [],
|
|
298
|
+
ambiguities: [],
|
|
299
|
+
changeCount: 0,
|
|
300
|
+
notes,
|
|
301
|
+
dryRun: options.dryRun
|
|
302
|
+
};
|
|
303
|
+
options.verbose && notes.push("Mapping changed files to model elements...");
|
|
304
|
+
const mappings = await this.mapFilesToElements();
|
|
305
|
+
mappings.ambiguous.length > 0 && (syncState.recordAmbiguities(mappings.ambiguous.map((m) => ({
|
|
306
|
+
filePath: m.filePath,
|
|
307
|
+
possibleElements: m.possibleElements.map((e) => ({
|
|
308
|
+
elementId: e.elementId,
|
|
309
|
+
layer: e.layer,
|
|
310
|
+
confidence: e.confidence
|
|
311
|
+
}))
|
|
312
|
+
}))), notes.push(`Ambiguities detected: ${mappings.ambiguous.length} files`));
|
|
313
|
+
let changesetId, changeCount = 0;
|
|
314
|
+
if (!options.dryRun) {
|
|
315
|
+
const changesetResult = await this.generateChangeset(project, diff, mappings, options);
|
|
316
|
+
changesetId = changesetResult.changesetId, changeCount = changesetResult.changeCount, notes.push(...changesetResult.warnings);
|
|
317
|
+
}
|
|
318
|
+
const syncStatus = mappings.ambiguous.length > 0 && !options.force ? "partial" : "success";
|
|
319
|
+
return options.dryRun ? notes.push("(DRY RUN - sync state not recorded)") : (syncState.recordSync({
|
|
320
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
321
|
+
commit: currentCommit,
|
|
322
|
+
status: syncStatus,
|
|
323
|
+
changeset: changesetId,
|
|
324
|
+
files_changed: diff.added.length + diff.modified.length + diff.deleted.length,
|
|
325
|
+
elements_affected: mappings.confident.length + mappings.ambiguous.length,
|
|
326
|
+
notes: notes.join(`
|
|
327
|
+
`)
|
|
328
|
+
}), await syncState.save(syncStatePath)), {
|
|
329
|
+
status: syncStatus,
|
|
330
|
+
projectName: project.name,
|
|
331
|
+
commitsBefore: previousCommit.substring(0, 8),
|
|
332
|
+
commitsAfter: currentCommit.substring(0, 8),
|
|
333
|
+
filesChanged: diff,
|
|
334
|
+
elementMappings: mappings.confident,
|
|
335
|
+
ambiguities: mappings.ambiguous,
|
|
336
|
+
changesetId,
|
|
337
|
+
changeCount,
|
|
338
|
+
notes,
|
|
339
|
+
dryRun: options.dryRun
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
export {
|
|
344
|
+
FarmSyncEngine
|
|
345
|
+
};
|
|
346
|
+
//# sourceMappingURL=farm-sync-engine.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/farm-sync-engine.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Farm Sync Engine - Orchestrates headless sync pipeline\n *\n * Implements:\n * - Git pull for tracked codebases\n * - Diff computation between sync commits\n * - File-to-element mapping via source references\n * - Changeset generation for affected elements\n * - Staged changeset integration\n */\n\nimport path from \"path\";\nimport { execSync, execFileSync } from \"child_process\";\nimport { fileExists, ensureDir } from \"../utils/file-io.js\";\nimport { FarmSyncState } from \"./farm-sync-state.js\";\nimport { Model } from \"./model.js\";\nimport { StagingAreaManager } from \"./staging-area.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport type { FarmProject } from \"./farm-manifest.js\";\n\n/**\n * Options for sync operation\n */\nexport interface SyncOptions {\n dryRun?: boolean; // Preview without creating changeset\n force?: boolean; // Proceed despite ambiguities\n verbose?: boolean; // Detailed output\n}\n\n/**\n * Result of file diff analysis\n */\nexport interface FileDiffResult {\n added: string[];\n modified: string[];\n deleted: string[];\n}\n\n/**\n * Mapping of file path to model elements\n */\nexport interface FileElementMapping {\n filePath: string;\n possibleElements: Array<{\n elementId: string;\n layer: string;\n confidence: number; // 0-100\n sourceRef: {\n file: string;\n symbol?: string;\n };\n }>;\n}\n\n/**\n * Result of a sync operation\n */\nexport interface SyncResult {\n status: \"success\" | \"partial\" | \"failed\";\n projectName: string;\n commitsBefore: string;\n commitsAfter: string;\n filesChanged: FileDiffResult;\n elementMappings: FileElementMapping[];\n ambiguities: FileElementMapping[]; // Mappings with low confidence\n changesetId?: string; // ID of generated changeset if applicable\n changeCount: number; // Total staged changes\n notes: string[];\n dryRun?: boolean; // True if this was a dry run (no state changed)\n}\n\n/**\n * FarmSyncEngine - Orchestrates the sync pipeline\n * Model parameter is optional - only required for sync operations, not for pull\n */\nexport class FarmSyncEngine {\n private farmRoot: string;\n private model: Model | null;\n\n constructor(farmRoot: string, model?: Model) {\n this.farmRoot = farmRoot;\n this.model = model || null;\n }\n\n /**\n * Pull latest changes from remote for a codebase\n * @param codebasePath - Relative path to codebase folder\n * @returns Updated commit SHA\n */\n async pullCodebase(codebasePath: string): Promise<string> {\n const fullPath = path.join(this.farmRoot, codebasePath);\n\n if (!(await fileExists(fullPath))) {\n throw new Error(`Codebase directory not found: ${codebasePath}`);\n }\n\n try {\n // Try to fetch latest if remote exists\n try {\n execSync(\"git fetch --prune\", { cwd: fullPath, stdio: \"pipe\" });\n } catch (fetchError) {\n const fetchMsg = getErrorMessage(fetchError);\n // Only treat \"no remote\" as acceptable; re-throw auth, merge, and network errors\n const isNoRemote = fetchMsg.includes(\"No remote repository specified\") ||\n fetchMsg.includes(\"no remote\");\n if (!isNoRemote) {\n throw new Error(`git fetch failed: ${fetchMsg}`);\n }\n }\n\n // Try to pull if remote exists\n try {\n execSync(\"git pull --no-rebase\", { cwd: fullPath, stdio: \"pipe\" });\n } catch (pullError) {\n const pullMsg = getErrorMessage(pullError);\n // Only treat \"no remote\" or \"no tracking information\" as acceptable; re-throw auth, merge, and network errors\n const isAcceptable = pullMsg.includes(\"No remote repository specified\") ||\n pullMsg.includes(\"no remote\") ||\n pullMsg.includes(\"no tracking information\");\n if (!isAcceptable) {\n throw new Error(`git pull failed: ${pullMsg}`);\n }\n }\n\n // Get current HEAD commit\n const commit = execSync(\"git rev-parse HEAD\", { cwd: fullPath, encoding: \"utf-8\" }).trim();\n\n return commit;\n } catch (error) {\n throw new Error(`Failed to pull codebase '${codebasePath}': ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Get the current HEAD commit SHA for a codebase\n */\n async getCurrentCommit(codebasePath: string): Promise<string> {\n const fullPath = path.join(this.farmRoot, codebasePath);\n\n if (!(await fileExists(fullPath))) {\n throw new Error(`Codebase directory not found: ${codebasePath}`);\n }\n\n try {\n const commit = execSync(\"git rev-parse HEAD\", { cwd: fullPath, encoding: \"utf-8\" }).trim();\n return commit;\n } catch (error) {\n throw new Error(`Failed to get current commit for '${codebasePath}': ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Compute diff between two commits\n * Commit SHAs are validated before use to prevent command injection\n */\n async computeDiff(codebasePath: string, fromCommit: string, toCommit: string): Promise<FileDiffResult> {\n const fullPath = path.join(this.farmRoot, codebasePath);\n\n if (!(await fileExists(fullPath))) {\n throw new Error(`Codebase directory not found: ${codebasePath}`);\n }\n\n // Validate commit SHAs to prevent command injection\n const commitShaRegex = /^[0-9a-f]{7,40}$/i;\n if (!commitShaRegex.test(fromCommit) || !commitShaRegex.test(toCommit)) {\n throw new Error(\n `Invalid commit SHA format. Expected 7-40 character hex string. Got: ${fromCommit}, ${toCommit}`\n );\n }\n\n try {\n // Get diff status between commits using execFileSync to avoid shell interpretation\n const diffOutput = execFileSync(\"git\", [\"diff\", \"--name-status\", `${fromCommit}...${toCommit}`], {\n cwd: fullPath,\n encoding: \"utf-8\",\n });\n\n const added: string[] = [];\n const modified: string[] = [];\n const deleted: string[] = [];\n\n for (const line of diffOutput.trim().split(\"\\n\")) {\n if (!line) continue;\n\n const [status, ...fileParts] = line.split(\"\\t\");\n const filePath = fileParts.join(\"\\t\"); // Handle paths with tabs\n\n switch (status) {\n case \"A\":\n added.push(filePath);\n break;\n case \"M\":\n modified.push(filePath);\n break;\n case \"D\":\n deleted.push(filePath);\n break;\n default:\n if (status.startsWith(\"R\")) {\n // Handle renames (R100, R087, R095, etc. - any similarity index)\n // Report as both a delete of the old name and add of the new name\n const [oldPath, newPath] = fileParts;\n deleted.push(oldPath);\n added.push(newPath);\n } else if (status.startsWith(\"C\")) {\n // Handle copies (C100, C087, etc. - any similarity index)\n // Report as add of new file\n added.push(fileParts[1]);\n }\n break;\n // Ignore other statuses\n }\n }\n\n return { added, modified, deleted };\n } catch (error) {\n throw new Error(`Failed to compute diff for '${codebasePath}': ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Map changed files to model elements using source references\n * Looks at each model element's source references and matches against changed files\n * Returns empty results if model is not available\n */\n async mapFilesToElements(): Promise<{ confident: FileElementMapping[]; ambiguous: FileElementMapping[] }> {\n if (!this.model) {\n return { confident: [], ambiguous: [] };\n }\n\n const confident: Map<string, FileElementMapping> = new Map();\n const ambiguous: Map<string, FileElementMapping> = new Map();\n\n // Iterate through all layers and elements\n for (const layer of this.model.layers.values()) {\n for (const element of layer.listElements()) {\n if (!element.source_reference) continue;\n\n const sourceRef = element.source_reference;\n if (!sourceRef.locations || sourceRef.locations.length === 0) continue;\n\n // Match each source location against potential changed files\n for (const location of sourceRef.locations) {\n // Use file path as key\n const filePath = location.file;\n\n if (!confident.has(filePath) && !ambiguous.has(filePath)) {\n const mapping: FileElementMapping = {\n filePath,\n possibleElements: [\n {\n elementId: element.id,\n layer: layer.name,\n confidence: 100, // Exact match from source reference\n sourceRef: location,\n },\n ],\n };\n confident.set(filePath, mapping);\n } else if (confident.has(filePath)) {\n // Multiple elements reference the same file - mark as ambiguous\n const existing = confident.get(filePath)!;\n confident.delete(filePath);\n\n existing.possibleElements.push({\n elementId: element.id,\n layer: layer.name,\n confidence: 100,\n sourceRef: location,\n });\n\n ambiguous.set(filePath, existing);\n } else {\n // Already ambiguous, add another possibility\n const existing = ambiguous.get(filePath)!;\n existing.possibleElements.push({\n elementId: element.id,\n layer: layer.name,\n confidence: 100,\n sourceRef: location,\n });\n }\n }\n }\n }\n\n return {\n confident: Array.from(confident.values()),\n ambiguous: Array.from(ambiguous.values()),\n };\n }\n\n /**\n * Generate a changeset from file-to-element mappings\n * Creates add/update/delete changes based on the diff and mappings\n * Stages changes into the changeset for review before commit\n */\n async generateChangeset(\n project: FarmProject,\n diff: FileDiffResult,\n mappings: { confident: FileElementMapping[]; ambiguous: FileElementMapping[] },\n options: SyncOptions = {}\n ): Promise<{ changesetId: string; changeCount: number; warnings: string[] }> {\n if (!this.model) {\n throw new Error(\"Model is required for changeset generation\");\n }\n\n // The staging area lives under the model's project root. For a model the farm\n // scaffolded that is <farm>/<model>; for one adopted from a codebase it is the codebase\n // itself, so farm.yaml's `model` (the model directory) must not be used here.\n const stagingManager = new StagingAreaManager(this.model.rootPath, this.model);\n\n // Create a new changeset\n const changesetName = `farm-sync-${project.name}-${Date.now()}`;\n const changeset = await stagingManager.create(changesetName, `Auto-generated from farm sync of ${project.name}`);\n\n if (!changeset.id) {\n throw new Error(\"Failed to create changeset - no ID assigned\");\n }\n\n const warnings: string[] = [];\n let changeCount = 0;\n\n // Create a map of file path -> confident mapping for quick lookup\n const confidentMap = new Map(mappings.confident.map((m) => [m.filePath, m]));\n\n // Process added files\n for (const filePath of diff.added) {\n const mapping = confidentMap.get(filePath);\n if (mapping && mapping.possibleElements.length === 1) {\n const element = mapping.possibleElements[0];\n // Stage the change\n await stagingManager.stage(changeset.id, {\n type: \"add\",\n elementId: element.elementId,\n layerName: element.layer,\n after: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n timestamp: new Date().toISOString(),\n },\n timestamp: new Date().toISOString(),\n });\n warnings.push(\n `File added: ${filePath} -> ${element.elementId} (staged for review, verify content accuracy)`\n );\n changeCount++;\n }\n }\n\n // Process modified files\n for (const filePath of diff.modified) {\n const mapping = confidentMap.get(filePath);\n if (mapping && mapping.possibleElements.length === 1) {\n const element = mapping.possibleElements[0];\n // Stage the change\n await stagingManager.stage(changeset.id, {\n type: \"update\",\n elementId: element.elementId,\n layerName: element.layer,\n before: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n },\n after: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n timestamp: new Date().toISOString(),\n },\n timestamp: new Date().toISOString(),\n });\n warnings.push(`File modified: ${filePath} -> ${element.elementId} (staged for review)`);\n changeCount++;\n }\n }\n\n // Process deleted files\n for (const filePath of diff.deleted) {\n const mapping = confidentMap.get(filePath);\n if (mapping && mapping.possibleElements.length === 1) {\n const element = mapping.possibleElements[0];\n // Stage the change\n await stagingManager.stage(changeset.id, {\n type: \"delete\",\n elementId: element.elementId,\n layerName: element.layer,\n before: {\n sourceFile: filePath,\n syncSource: \"codebase\",\n },\n timestamp: new Date().toISOString(),\n });\n warnings.push(`File deleted: ${filePath} -> ${element.elementId} (staged for review)`);\n changeCount++;\n }\n }\n\n // Flag ambiguities\n if (mappings.ambiguous.length > 0 && !options.force) {\n for (const ambiguity of mappings.ambiguous) {\n warnings.push(\n `Ambiguous mapping: ${ambiguity.filePath} -> ${ambiguity.possibleElements.map((e) => e.elementId).join(\", \")} (skipped, use --force to proceed)`\n );\n }\n }\n\n return {\n changesetId: changeset.id,\n changeCount,\n warnings,\n };\n }\n\n /**\n * Execute a complete sync operation for a project\n */\n async syncProject(project: FarmProject, options: SyncOptions = {}): Promise<SyncResult> {\n const codebasePath = project.source;\n const notes: string[] = [];\n\n // Step 1: Get current commit (pull only if not dry run)\n if (options.verbose && !options.dryRun) {\n notes.push(`Pulling latest from ${codebasePath}...`);\n }\n const currentCommit = options.dryRun\n ? await this.getCurrentCommit(codebasePath)\n : await this.pullCodebase(codebasePath);\n if (options.dryRun) {\n notes.push(\"(DRY RUN - skipping git pull)\");\n }\n\n // Step 2: Load sync state\n const syncStatePath = path.join(this.farmRoot, project.model, \".farm-sync.yaml\");\n await ensureDir(path.dirname(syncStatePath));\n const syncState = await FarmSyncState.loadOrCreate(syncStatePath, project.name);\n\n const previousCommit = syncState.lastSyncCommit;\n notes.push(`Current commit: ${currentCommit}`);\n notes.push(`Previous sync commit: ${previousCommit || \"none\"}`);\n\n // Step 3: If no previous sync, this is initial sync - just record state\n if (!previousCommit) {\n notes.push(\"Initial sync - recording baseline only\");\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: currentCommit,\n status: \"success\",\n files_changed: 0,\n elements_affected: 0,\n notes: \"Initial sync, no changes to sync\",\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: \"success\",\n projectName: project.name,\n commitsBefore: \"none\",\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: { added: [], modified: [], deleted: [] },\n elementMappings: [],\n ambiguities: [],\n changeCount: 0,\n notes,\n dryRun: options.dryRun,\n };\n }\n\n // Step 4: Compute diff\n if (options.verbose) {\n notes.push(`Computing diff between ${previousCommit.substring(0, 8)} and ${currentCommit.substring(0, 8)}...`);\n }\n const diff = await this.computeDiff(codebasePath, previousCommit, currentCommit);\n\n // If no changes, return early\n if (diff.added.length === 0 && diff.modified.length === 0 && diff.deleted.length === 0) {\n notes.push(\"No changes detected\");\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: currentCommit,\n status: \"success\",\n files_changed: 0,\n elements_affected: 0,\n notes: \"No changes to sync\",\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: \"success\",\n projectName: project.name,\n commitsBefore: previousCommit.substring(0, 8),\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: diff,\n elementMappings: [],\n ambiguities: [],\n changeCount: 0,\n notes,\n dryRun: options.dryRun,\n };\n }\n\n notes.push(\n `Files changed: ${diff.added.length} added, ${diff.modified.length} modified, ${diff.deleted.length} deleted`\n );\n\n // Step 5: Check that model is available before attempting to map files\n if (!this.model) {\n notes.push(\"CRITICAL: Model is not loaded, cannot map files to elements\");\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: previousCommit, // Don't advance lastSyncCommit - stay at previous commit\n status: \"failed\",\n files_changed: diff.added.length + diff.modified.length + diff.deleted.length,\n elements_affected: 0,\n notes: \"Model not loaded - file changes not synced. Load the model and retry to process these files.\",\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: \"failed\",\n projectName: project.name,\n commitsBefore: previousCommit.substring(0, 8),\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: diff,\n elementMappings: [],\n ambiguities: [],\n changeCount: 0,\n notes,\n dryRun: options.dryRun,\n };\n }\n\n // Step 6: Map files to elements\n if (options.verbose) {\n notes.push(\"Mapping changed files to model elements...\");\n }\n const mappings = await this.mapFilesToElements();\n\n // Step 7: Record ambiguities in sync state\n if (mappings.ambiguous.length > 0) {\n syncState.recordAmbiguities(\n mappings.ambiguous.map((m) => ({\n filePath: m.filePath,\n possibleElements: m.possibleElements.map((e) => ({\n elementId: e.elementId,\n layer: e.layer,\n confidence: e.confidence,\n })),\n }))\n );\n notes.push(`Ambiguities detected: ${mappings.ambiguous.length} files`);\n }\n\n // Step 8: Generate changeset\n let changesetId: string | undefined;\n let changeCount = 0;\n\n if (!options.dryRun) {\n const changesetResult = await this.generateChangeset(project, diff, mappings, options);\n changesetId = changesetResult.changesetId;\n changeCount = changesetResult.changeCount;\n notes.push(...changesetResult.warnings);\n }\n\n // Step 9: Record sync in state (skip for dry run)\n const syncStatus = mappings.ambiguous.length > 0 && !options.force ? \"partial\" : \"success\";\n\n if (!options.dryRun) {\n syncState.recordSync({\n timestamp: new Date().toISOString(),\n commit: currentCommit,\n status: syncStatus,\n changeset: changesetId,\n files_changed: diff.added.length + diff.modified.length + diff.deleted.length,\n elements_affected: mappings.confident.length + mappings.ambiguous.length,\n notes: notes.join(\"\\n\"),\n });\n await syncState.save(syncStatePath);\n } else {\n notes.push(\"(DRY RUN - sync state not recorded)\");\n }\n\n return {\n status: syncStatus,\n projectName: project.name,\n commitsBefore: previousCommit.substring(0, 8),\n commitsAfter: currentCommit.substring(0, 8),\n filesChanged: diff,\n elementMappings: mappings.confident,\n ambiguities: mappings.ambiguous,\n changesetId,\n changeCount,\n notes,\n dryRun: options.dryRun,\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|