@open-mercato/cli 0.6.7 → 0.6.8-develop.6875.1.871a4afc94
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/.turbo/turbo-build.log +4 -4
- package/build.mjs +24 -7
- package/dist/agentic/claude-code/hooks/entity-migration-check.ts +32 -88
- package/dist/agentic/codex/enforcement-rules.md +2 -16
- package/dist/agentic/cursor/hooks/entity-migration-check.mjs +19 -50
- package/dist/agentic/cursor/rules/entity-guard.mdc +9 -21
- package/dist/agentic/cursor/rules/generated-guard.mdc +3 -5
- package/dist/agentic/cursor/rules/open-mercato.mdc +3 -29
- package/dist/agentic/guides/ai-workflows.md +76 -0
- package/dist/agentic/guides/architecture.md +107 -0
- package/dist/agentic/guides/backend-ui.md +80 -0
- package/dist/agentic/guides/contracts.md +92 -0
- package/dist/agentic/guides/extensions.md +121 -0
- package/dist/agentic/guides/framework-extension-points.md +36 -0
- package/dist/agentic/guides/integrations.md +81 -0
- package/dist/agentic/guides/module-facts.json +115537 -3951
- package/dist/agentic/guides/module-system.md +12 -127
- package/dist/agentic/guides/modules/ai_assistant.md +227 -28
- package/dist/agentic/guides/modules/api_docs.md +58 -2
- package/dist/agentic/guides/modules/api_keys.md +92 -7
- package/dist/agentic/guides/modules/attachments.md +119 -14
- package/dist/agentic/guides/modules/audit_logs.md +131 -10
- package/dist/agentic/guides/modules/auth.md +276 -33
- package/dist/agentic/guides/modules/business_rules.md +149 -14
- package/dist/agentic/guides/modules/catalog.md +475 -43
- package/dist/agentic/guides/modules/channel_gmail.md +82 -3
- package/dist/agentic/guides/modules/channel_imap.md +82 -3
- package/dist/agentic/guides/modules/checkout.md +299 -27
- package/dist/agentic/guides/modules/communication_channels.md +315 -33
- package/dist/agentic/guides/modules/configs.md +104 -8
- package/dist/agentic/guides/modules/content.md +57 -2
- package/dist/agentic/guides/modules/currencies.md +152 -17
- package/dist/agentic/guides/modules/customer_accounts.md +387 -45
- package/dist/agentic/guides/modules/customers.md +746 -85
- package/dist/agentic/guides/modules/dashboards.md +155 -10
- package/dist/agentic/guides/modules/data_sync.md +149 -16
- package/dist/agentic/guides/modules/design_system.md +102 -0
- package/dist/agentic/guides/modules/dictionaries.md +107 -12
- package/dist/agentic/guides/modules/directory.md +148 -15
- package/dist/agentic/guides/modules/entities.md +145 -13
- package/dist/agentic/guides/modules/events.md +59 -2
- package/dist/agentic/guides/modules/feature_toggles.md +119 -9
- package/dist/agentic/guides/modules/gateway_stripe.md +92 -4
- package/dist/agentic/guides/modules/generators.md +45 -2
- package/dist/agentic/guides/modules/inbox_ops.md +207 -27
- package/dist/agentic/guides/modules/integrations.md +146 -16
- package/dist/agentic/guides/modules/messages.md +223 -24
- package/dist/agentic/guides/modules/notifications.md +96 -8
- package/dist/agentic/guides/modules/onboarding.md +77 -6
- package/dist/agentic/guides/modules/payment_gateways.md +164 -21
- package/dist/agentic/guides/modules/perspectives.md +77 -7
- package/dist/agentic/guides/modules/planner.md +136 -18
- package/dist/agentic/guides/modules/portal.md +93 -2
- package/dist/agentic/guides/modules/progress.md +104 -14
- package/dist/agentic/guides/modules/query_index.md +154 -15
- package/dist/agentic/guides/modules/record_locks.md +206 -21
- package/dist/agentic/guides/modules/resources.md +197 -30
- package/dist/agentic/guides/modules/sales.md +678 -84
- package/dist/agentic/guides/modules/scheduler.md +133 -14
- package/dist/agentic/guides/modules/search.md +170 -6
- package/dist/agentic/guides/modules/security.md +294 -35
- package/dist/agentic/guides/modules/shipping_carriers.md +134 -18
- package/dist/agentic/guides/modules/sso.md +151 -26
- package/dist/agentic/guides/modules/staff.md +369 -57
- package/dist/agentic/guides/modules/storage_s3.md +95 -5
- package/dist/agentic/guides/modules/sync_akeneo.md +85 -3
- package/dist/agentic/guides/modules/sync_excel.md +75 -6
- package/dist/agentic/guides/modules/system_status_overlays.md +60 -2
- package/dist/agentic/guides/modules/translations.md +104 -10
- package/dist/agentic/guides/modules/webhooks.md +203 -24
- package/dist/agentic/guides/modules/wms.md +441 -46
- package/dist/agentic/guides/modules/workflows.md +302 -43
- package/dist/agentic/guides/spec-delivery.md +22 -0
- package/dist/agentic/guides/testing-debugging.md +80 -0
- package/dist/agentic/guides/upstream/AGENTS.md +299 -0
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +316 -0
- package/dist/agentic/guides/upstream/manifest.json +8 -0
- package/dist/agentic/shared/AGENTS.md.template +124 -311
- package/dist/agentic/shared/ai/agentic.config.json +2 -0
- package/dist/agentic/shared/ai/harness/README.md +50 -0
- package/dist/agentic/shared/ai/harness/RELEASE.md +57 -0
- package/dist/agentic/shared/ai/harness/cases.json +13680 -0
- package/dist/agentic/shared/ai/harness/cases.schema.json +133 -0
- package/dist/agentic/shared/ai/harness/fixtures/index.json +49 -0
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +162 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +21 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +96 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +172 -0
- package/dist/agentic/shared/ai/harness/release-matrix.json +158 -0
- package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
- package/dist/agentic/shared/ai/harness/result.schema.json +76 -0
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +12 -0
- package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
- package/dist/agentic/shared/ai/harness/validators.json +78 -0
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +2121 -0
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +1096 -0
- package/dist/agentic/shared/ai/lessons/_template.md +16 -0
- package/dist/agentic/shared/ai/lessons.md +26 -3
- package/dist/agentic/shared/ai/review-checklist.md +36 -0
- package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +21 -0
- package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +4 -0
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +98 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +20 -241
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +16 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +53 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +13 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +11 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +30 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +12 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +13 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +14 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +28 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +22 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +19 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +16 -601
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +19 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +13 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +14 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +10 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +14 -310
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +17 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +29 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +100 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +16 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +17 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +25 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +11 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +13 -0
- package/dist/agentic/shared/ai/skills/om-help/SKILL.md +15 -97
- package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +18 -0
- package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +20 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +33 -152
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +33 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +40 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +27 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +24 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +24 -708
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +16 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +14 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +15 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +12 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +34 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +4 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +11 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +31 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +39 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +43 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +11 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +20 -820
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +19 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +172 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +12 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +83 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +21 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +11 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +22 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +13 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +41 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +19 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +44 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +39 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +20 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +57 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +11 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +486 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +19 -843
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +18 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +27 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +13 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +41 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +11 -0
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +15 -63
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +15 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +16 -467
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +16 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +14 -0
- package/dist/agentic/shared/ai/skills/tiers.json +143 -21
- package/dist/agentic/shared/ai/skills/tiers.schema.json +52 -40
- package/dist/agentic/shared/ai/specs/README.md +4 -3
- package/dist/agentic/shared/ai/specs/SPEC-000-template.md +226 -10
- package/dist/agentic/shared/ai/trackers/github.md +108 -4
- package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +187 -0
- package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +2958 -0
- package/dist/agentic/shared/scripts/execution-sandbox.mjs +329 -0
- package/dist/agentic/shared/scripts/framework-context.mjs +888 -0
- package/dist/agentic/shared/scripts/install-skills.mjs +1029 -0
- package/dist/agentic/shared/scripts/install-skills.sh +4 -694
- package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +222 -0
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1774 -0
- package/dist/bin.js +13 -1
- package/dist/bin.js.map +2 -2
- package/dist/lib/__integration__/TC-INT-008.spec.js +88 -8
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-init.js +13 -3
- package/dist/lib/agentic-init.js.map +2 -2
- package/dist/lib/agentic-setup.js +434 -186
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/extensions/injection-widgets.js +20 -0
- package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
- package/dist/lib/generators/module-extension-facts.js +1840 -0
- package/dist/lib/generators/module-extension-facts.js.map +7 -0
- package/dist/lib/generators/module-fact-sources.js +70 -0
- package/dist/lib/generators/module-fact-sources.js.map +7 -0
- package/dist/lib/generators/module-facts-discovery.js +41 -7
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +1387 -45
- package/dist/lib/generators/module-facts.js.map +3 -3
- package/dist/lib/generators/module-override-targets.js +688 -0
- package/dist/lib/generators/module-override-targets.js.map +7 -0
- package/dist/lib/generators/module-registry.js +11 -5
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/load-env.js +24 -0
- package/dist/lib/load-env.js.map +7 -0
- package/dist/lib/resolver.js +2 -0
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/telemetry-init.js +358 -0
- package/dist/lib/telemetry-init.js.map +7 -0
- package/dist/lib/testing/integration.js +5 -0
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +23 -1
- package/dist/mercato.js.map +2 -2
- package/package.json +13 -11
- package/src/bin.ts +33 -1
- package/src/lib/__integration__/TC-INT-008.spec.ts +107 -9
- package/src/lib/__tests__/agentic-init.test.ts +41 -5
- package/src/lib/__tests__/agentic-setup.ownership.test.ts +386 -0
- package/src/lib/__tests__/injection-widget-id-validation.test.ts +184 -0
- package/src/lib/__tests__/load-env.test.ts +65 -0
- package/src/lib/__tests__/telemetry-init.test.ts +298 -0
- package/src/lib/agentic-init.ts +15 -3
- package/src/lib/agentic-setup.ts +573 -222
- package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
- package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +404 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +88 -2
- package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +1 -0
- package/src/lib/generators/__tests__/module-facts.discovery.test.ts +54 -10
- package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +108 -0
- package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +119 -0
- package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
- package/src/lib/generators/__tests__/module-override-targets.test.ts +304 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +18 -0
- package/src/lib/generators/extensions/injection-widgets.ts +51 -0
- package/src/lib/generators/module-extension-facts.ts +2365 -0
- package/src/lib/generators/module-fact-sources.ts +108 -0
- package/src/lib/generators/module-facts-discovery.ts +58 -7
- package/src/lib/generators/module-facts.ts +1851 -46
- package/src/lib/generators/module-override-targets.ts +863 -0
- package/src/lib/generators/module-registry.ts +16 -6
- package/src/lib/load-env.ts +30 -0
- package/src/lib/resolver.ts +3 -0
- package/src/lib/telemetry-init.ts +461 -0
- package/src/lib/testing/__tests__/integration.test.ts +4 -0
- package/src/lib/testing/integration.ts +5 -0
- package/src/mercato.ts +26 -1
- package/dist/agentic/guides/cache.md +0 -45
- package/dist/agentic/guides/core.md +0 -453
- package/dist/agentic/guides/events.md +0 -117
- package/dist/agentic/guides/queue.md +0 -53
- package/dist/agentic/guides/search.md +0 -115
- package/dist/agentic/guides/shared.md +0 -108
- package/dist/agentic/guides/ui.md +0 -347
- package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +0 -27
- package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +0 -27
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +0 -260
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +0 -146
- package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +0 -84
- package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +0 -193
- package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +0 -34
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +0 -762
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +0 -50
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +0 -97
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +0 -271
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +0 -101
package/src/lib/agentic-setup.ts
CHANGED
|
@@ -6,16 +6,35 @@
|
|
|
6
6
|
* This module reads those files at runtime — no embedded string constants.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
chmodSync,
|
|
11
|
+
copyFileSync,
|
|
12
|
+
existsSync,
|
|
13
|
+
lstatSync,
|
|
14
|
+
mkdirSync,
|
|
15
|
+
mkdtempSync,
|
|
16
|
+
readFileSync,
|
|
17
|
+
readdirSync,
|
|
18
|
+
realpathSync,
|
|
19
|
+
renameSync,
|
|
20
|
+
rmSync,
|
|
21
|
+
statSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
} from 'node:fs'
|
|
24
|
+
import { createHash } from 'node:crypto'
|
|
10
25
|
import { spawnSync } from 'node:child_process'
|
|
11
|
-
import {
|
|
26
|
+
import { tmpdir } from 'node:os'
|
|
27
|
+
import { join, dirname, basename, relative, resolve, sep } from 'node:path'
|
|
12
28
|
import { fileURLToPath } from 'node:url'
|
|
13
29
|
import { Project, SyntaxKind } from 'ts-morph'
|
|
14
30
|
|
|
15
|
-
const
|
|
16
|
-
// In the built output (dist/lib/agentic-setup.js),
|
|
31
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url))
|
|
32
|
+
// In the built output (dist/lib/agentic-setup.js), moduleDir is dist/lib/.
|
|
17
33
|
// agentic/ is copied to dist/agentic/ by build.mjs.
|
|
18
|
-
const
|
|
34
|
+
const bundledAgenticDir = join(moduleDir, '..', 'agentic')
|
|
35
|
+
const AGENTIC_DIR = existsSync(bundledAgenticDir)
|
|
36
|
+
? bundledAgenticDir
|
|
37
|
+
: join(moduleDir, '..', '..', '..', 'create-app', 'agentic')
|
|
19
38
|
const GUIDES_DIR = join(AGENTIC_DIR, 'guides')
|
|
20
39
|
|
|
21
40
|
type AskFn = (question: string) => Promise<string>
|
|
@@ -23,6 +42,7 @@ type AskFn = (question: string) => Promise<string>
|
|
|
23
42
|
interface AgenticSetupOptions {
|
|
24
43
|
tool?: string
|
|
25
44
|
force?: boolean
|
|
45
|
+
updateHarness?: boolean
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
interface AgenticConfig {
|
|
@@ -30,6 +50,19 @@ interface AgenticConfig {
|
|
|
30
50
|
targetDir: string
|
|
31
51
|
}
|
|
32
52
|
|
|
53
|
+
interface HarnessManifestFile {
|
|
54
|
+
path: string
|
|
55
|
+
sha256: string
|
|
56
|
+
source: string
|
|
57
|
+
userEditable: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface HarnessManifest {
|
|
61
|
+
version: number
|
|
62
|
+
generator: string
|
|
63
|
+
files: HarnessManifestFile[]
|
|
64
|
+
}
|
|
65
|
+
|
|
33
66
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
34
67
|
|
|
35
68
|
function resolvePlaceholders(content: string, config: AgenticConfig): string {
|
|
@@ -59,14 +92,14 @@ function copyFile(srcDir: string, srcRelative: string, destPath: string): void {
|
|
|
59
92
|
// AST-parse the static `enabledModules` array literal in the app's src/modules.ts
|
|
60
93
|
// and collect each entry's `id`. Only the static literal is read (conditional
|
|
61
94
|
// .push()/spread entries are intentionally not seen — see spec D6).
|
|
62
|
-
function readEnabledModuleIds(modulesPath: string): string[] {
|
|
63
|
-
if (!existsSync(modulesPath)) return []
|
|
95
|
+
function readEnabledModuleIds(modulesPath: string): { parsed: boolean; ids: string[] } {
|
|
96
|
+
if (!existsSync(modulesPath)) return { parsed: false, ids: [] }
|
|
64
97
|
try {
|
|
65
98
|
const project = new Project({ useInMemoryFileSystem: true })
|
|
66
99
|
const sourceFile = project.createSourceFile('modules.ts', readFileSync(modulesPath, 'utf-8'))
|
|
67
100
|
const declaration = sourceFile.getVariableDeclaration('enabledModules')
|
|
68
101
|
const arrayLiteral = declaration?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression)
|
|
69
|
-
if (!arrayLiteral) return []
|
|
102
|
+
if (!arrayLiteral) return { parsed: false, ids: [] }
|
|
70
103
|
const ids: string[] = []
|
|
71
104
|
for (const element of arrayLiteral.getElements()) {
|
|
72
105
|
const objectLiteral = element.asKind(SyntaxKind.ObjectLiteralExpression)
|
|
@@ -75,9 +108,9 @@ function readEnabledModuleIds(modulesPath: string): string[] {
|
|
|
75
108
|
const idValue = idProperty?.getInitializerIfKind(SyntaxKind.StringLiteral)?.getLiteralValue()
|
|
76
109
|
if (idValue) ids.push(idValue)
|
|
77
110
|
}
|
|
78
|
-
return ids
|
|
111
|
+
return { parsed: true, ids }
|
|
79
112
|
} catch {
|
|
80
|
-
return []
|
|
113
|
+
return { parsed: false, ids: [] }
|
|
81
114
|
}
|
|
82
115
|
}
|
|
83
116
|
|
|
@@ -92,48 +125,40 @@ function selectModuleFactSheets(targetDir: string, modulesSubdir: string): strin
|
|
|
92
125
|
.map((file) => file.replace(/\.md$/, ''))
|
|
93
126
|
: []
|
|
94
127
|
if (available.length === 0) return []
|
|
95
|
-
const
|
|
128
|
+
const parsed = readEnabledModuleIds(join(targetDir, 'src', 'modules.ts'))
|
|
129
|
+
if (!parsed.parsed) return available
|
|
130
|
+
const enabled = new Set(parsed.ids)
|
|
96
131
|
const selected = available.filter((moduleId) => enabled.has(moduleId))
|
|
97
|
-
return selected
|
|
132
|
+
return selected
|
|
98
133
|
}
|
|
99
134
|
|
|
100
135
|
const MODULE_GUIDES_START = '<!-- om:module-guides:start -->'
|
|
101
136
|
const MODULE_GUIDES_END = '<!-- om:module-guides:end -->'
|
|
102
137
|
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const parsed = JSON.parse(readFileSync(factsPath, 'utf-8')) as Record<
|
|
113
|
-
string,
|
|
114
|
-
{ description?: unknown; title?: unknown }
|
|
115
|
-
>
|
|
116
|
-
const labels: Record<string, string> = {}
|
|
117
|
-
for (const [moduleId, entry] of Object.entries(parsed)) {
|
|
118
|
-
const label =
|
|
119
|
-
(entry && typeof entry.description === 'string' && entry.description) ||
|
|
120
|
-
(entry && typeof entry.title === 'string' && entry.title) ||
|
|
121
|
-
''
|
|
122
|
-
if (label) labels[moduleId] = label
|
|
123
|
-
}
|
|
124
|
-
return labels
|
|
125
|
-
} catch {
|
|
126
|
-
return {}
|
|
127
|
-
}
|
|
138
|
+
// The generated standalone root must stay well under Codex's 32 KiB
|
|
139
|
+
// project_doc_max_bytes so a routed chain (root + guide + skill) still fits.
|
|
140
|
+
export const STANDALONE_ROOT_TARGET_BYTES = 12 * 1024
|
|
141
|
+
|
|
142
|
+
export type ModuleGuidesRenderOptions = {
|
|
143
|
+
// Emit the O(1) pointer form instead of enumerating every id. The inline index
|
|
144
|
+
// is the better prompt, so callers only ask for this when the enumerated one
|
|
145
|
+
// would push the root past its byte target.
|
|
146
|
+
compact?: boolean
|
|
128
147
|
}
|
|
129
148
|
|
|
130
|
-
function renderModuleGuidesBlock(
|
|
149
|
+
export function renderModuleGuidesBlock(
|
|
150
|
+
selected: string[],
|
|
151
|
+
options: ModuleGuidesRenderOptions = {},
|
|
152
|
+
): string {
|
|
131
153
|
if (selected.length === 0) return '_No module fact-sheets are bundled for this app._'
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
154
|
+
const index = options.compact
|
|
155
|
+
? `Enabled module facts: ${selected.length} sheets bundled, too many to index inline — list the module facts directory to see which modules have one.`
|
|
156
|
+
: `Enabled module facts: ${selected.map((moduleId) => `\`${moduleId}\``).join(',')}.`
|
|
157
|
+
return [
|
|
158
|
+
index,
|
|
159
|
+
'',
|
|
160
|
+
'Load `.ai/guides/modules/<id>.md` only for a named or targeted installed module/host; never preload all module facts.',
|
|
161
|
+
].join('\n')
|
|
137
162
|
}
|
|
138
163
|
|
|
139
164
|
// Regenerate the marker-delimited Module-Specific Guides block in the written
|
|
@@ -142,7 +167,7 @@ function renderModuleGuidesBlock(selected: string[], labels: Record<string, stri
|
|
|
142
167
|
function injectModuleGuides(
|
|
143
168
|
agentsMdPath: string,
|
|
144
169
|
selected: string[],
|
|
145
|
-
|
|
170
|
+
options: ModuleGuidesRenderOptions = {},
|
|
146
171
|
): void {
|
|
147
172
|
if (!existsSync(agentsMdPath)) return
|
|
148
173
|
const content = readFileSync(agentsMdPath, 'utf-8')
|
|
@@ -156,188 +181,443 @@ function injectModuleGuides(
|
|
|
156
181
|
}
|
|
157
182
|
const before = content.slice(0, startIndex + MODULE_GUIDES_START.length)
|
|
158
183
|
const after = content.slice(endIndex)
|
|
159
|
-
const next = `${before}\n${renderModuleGuidesBlock(selected,
|
|
184
|
+
const next = `${before}\n${renderModuleGuidesBlock(selected, options)}\n${after}`
|
|
160
185
|
if (next !== content) writeFileSync(agentsMdPath, next)
|
|
161
186
|
}
|
|
162
187
|
|
|
163
|
-
//
|
|
188
|
+
// Last step of harness generation: every tool generator has finished patching
|
|
189
|
+
// AGENTS.md, so this is the only point where the final root size is known. The
|
|
190
|
+
// enumerated module index is the one block that grows with the app, so it is
|
|
191
|
+
// also the one that sheds bytes when the root would otherwise blow its target.
|
|
192
|
+
export function enforceRootInstructionBudget(
|
|
193
|
+
agentsMdPath: string,
|
|
194
|
+
selected: string[],
|
|
195
|
+
maxBytes: number = STANDALONE_ROOT_TARGET_BYTES,
|
|
196
|
+
): boolean {
|
|
197
|
+
if (!existsSync(agentsMdPath)) return false
|
|
198
|
+
if (Buffer.byteLength(readFileSync(agentsMdPath)) <= maxBytes) return false
|
|
199
|
+
injectModuleGuides(agentsMdPath, selected, { compact: true })
|
|
200
|
+
return true
|
|
201
|
+
}
|
|
164
202
|
|
|
165
|
-
|
|
166
|
-
const { targetDir } = config
|
|
167
|
-
const srcDir = join(AGENTIC_DIR, 'shared')
|
|
203
|
+
const TEXT_EXTENSIONS = new Set(['.cjs', '.json', '.md', '.mdc', '.mjs', '.sh', '.ts', '.txt'])
|
|
168
204
|
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
205
|
+
function isTextAsset(path: string): boolean {
|
|
206
|
+
const dot = path.lastIndexOf('.')
|
|
207
|
+
return dot === -1 || TEXT_EXTENSIONS.has(path.slice(dot))
|
|
208
|
+
}
|
|
172
209
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
copyFile(srcDir, 'ai/lessons.md', join(targetDir, '.ai', 'lessons.md'))
|
|
181
|
-
|
|
182
|
-
// .ai/skills/ — the shared skills-mixin manifest + tracker + external installer.
|
|
183
|
-
// Skills owned by the external open-mercato/skills collection (code review, spec
|
|
184
|
-
// writing, integration tests, prepare-issue, the auto-* PR family) are NOT copied
|
|
185
|
-
// here; the user installs them with `yarn install-skills` (npx skills add). Only
|
|
186
|
-
// standalone-only + kept-local skills and repo-local override folders are copied.
|
|
187
|
-
copyFile(srcDir, 'ai/skills/tiers.json', join(targetDir, '.ai', 'skills', 'tiers.json'))
|
|
188
|
-
copyFile(srcDir, 'ai/skills/tiers.schema.json', join(targetDir, '.ai', 'skills', 'tiers.schema.json'))
|
|
189
|
-
copyFile(srcDir, 'ai/agentic.config.json', join(targetDir, '.ai', 'agentic.config.json'))
|
|
190
|
-
copyFile(srcDir, 'ai/trackers/github.md', join(targetDir, '.ai', 'trackers', 'github.md'))
|
|
191
|
-
copyFile(srcDir, 'scripts/install-skills.sh', join(targetDir, 'scripts', 'install-skills.sh'))
|
|
192
|
-
|
|
193
|
-
copyFile(
|
|
194
|
-
srcDir,
|
|
195
|
-
'ai/skills/om-backend-ui-design/SKILL.md',
|
|
196
|
-
join(targetDir, '.ai', 'skills', 'om-backend-ui-design', 'SKILL.md'),
|
|
197
|
-
)
|
|
198
|
-
copyFile(
|
|
199
|
-
srcDir,
|
|
200
|
-
'ai/skills/om-backend-ui-design/references/ui-components.md',
|
|
201
|
-
join(targetDir, '.ai', 'skills', 'om-backend-ui-design', 'references', 'ui-components.md'),
|
|
202
|
-
)
|
|
203
|
-
copyFile(srcDir, 'ai/skills/om-integration-builder/SKILL.md', join(targetDir, '.ai', 'skills', 'om-integration-builder', 'SKILL.md'))
|
|
204
|
-
if (existsSync(join(srcDir, 'ai', 'skills', 'om-integration-builder', 'STANDALONE.md'))) {
|
|
205
|
-
copyFile(
|
|
206
|
-
srcDir,
|
|
207
|
-
'ai/skills/om-integration-builder/STANDALONE.md',
|
|
208
|
-
join(targetDir, '.ai', 'skills', 'om-integration-builder', 'STANDALONE.md'),
|
|
209
|
-
)
|
|
210
|
+
function listFiles(root: string): string[] {
|
|
211
|
+
if (!existsSync(root)) return []
|
|
212
|
+
const files: string[] = []
|
|
213
|
+
for (const entry of readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
214
|
+
const absolute = join(root, entry.name)
|
|
215
|
+
if (entry.isDirectory()) files.push(...listFiles(absolute))
|
|
216
|
+
else if (entry.isFile()) files.push(absolute)
|
|
210
217
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
'
|
|
220
|
-
|
|
221
|
-
|
|
218
|
+
return files
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function copyTree(sourceRoot: string, destinationRoot: string, config: AgenticConfig): void {
|
|
222
|
+
for (const sourcePath of listFiles(sourceRoot)) {
|
|
223
|
+
const destinationPath = join(destinationRoot, relative(sourceRoot, sourcePath))
|
|
224
|
+
ensureDir(destinationPath)
|
|
225
|
+
if (isTextAsset(sourcePath)) {
|
|
226
|
+
writeFileSync(destinationPath, resolvePlaceholders(readFileSync(sourcePath, 'utf8'), config))
|
|
227
|
+
} else {
|
|
228
|
+
copyFileSync(sourcePath, destinationPath)
|
|
229
|
+
}
|
|
230
|
+
if (process.platform !== 'win32') chmodSync(destinationPath, statSync(sourcePath).mode & 0o777)
|
|
222
231
|
}
|
|
232
|
+
}
|
|
223
233
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
join(targetDir, '.ai', 'skills', 'om-system-extension', 'SKILL.md'),
|
|
228
|
-
)
|
|
229
|
-
copyFile(
|
|
230
|
-
srcDir,
|
|
231
|
-
'ai/skills/om-system-extension/references/extension-contracts.md',
|
|
232
|
-
join(targetDir, '.ai', 'skills', 'om-system-extension', 'references', 'extension-contracts.md'),
|
|
233
|
-
)
|
|
234
|
+
function hashFile(path: string): string {
|
|
235
|
+
return createHash('sha256').update(readFileSync(path)).digest('hex')
|
|
236
|
+
}
|
|
234
237
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
'ai/skills/om-module-scaffold/references/navigation-patterns.md',
|
|
248
|
-
join(targetDir, '.ai', 'skills', 'om-module-scaffold', 'references', 'navigation-patterns.md'),
|
|
249
|
-
)
|
|
238
|
+
function canonicalHarnessRoot(root: string): string {
|
|
239
|
+
const resolved = resolve(root)
|
|
240
|
+
if (!existsSync(resolved)) {
|
|
241
|
+
throw new Error(`Harness root must be an existing directory: ${resolved}`)
|
|
242
|
+
}
|
|
243
|
+
const canonical = realpathSync(resolved)
|
|
244
|
+
const entry = lstatSync(canonical, { throwIfNoEntry: false })
|
|
245
|
+
if (!entry || !entry.isDirectory()) {
|
|
246
|
+
throw new Error(`Harness root must be an existing directory: ${resolved}`)
|
|
247
|
+
}
|
|
248
|
+
return canonical
|
|
249
|
+
}
|
|
250
250
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Validate a managed path without following any link below the canonical app
|
|
253
|
+
* root. A lexical containment check alone is insufficient: `app/.ai -> /tmp`
|
|
254
|
+
* would otherwise make an apparently safe update write outside the app.
|
|
255
|
+
*/
|
|
256
|
+
function assertManagedPath(root: string, path: string, options: { leaf?: 'file' | 'any' } = {}): void {
|
|
257
|
+
const absolutePath = resolve(path)
|
|
258
|
+
const normalizedRelative = relative(root, absolutePath)
|
|
259
|
+
if (
|
|
260
|
+
normalizedRelative === '..' ||
|
|
261
|
+
normalizedRelative.startsWith(`..${sep}`)
|
|
262
|
+
) {
|
|
263
|
+
throw new Error(`Harness-managed path escapes its root: ${absolutePath}`)
|
|
264
|
+
}
|
|
265
|
+
if (normalizedRelative === '') return
|
|
266
|
+
|
|
267
|
+
const components = normalizedRelative.split(sep).filter(Boolean)
|
|
268
|
+
let current = root
|
|
269
|
+
for (let index = 0; index < components.length; index += 1) {
|
|
270
|
+
current = join(current, components[index])
|
|
271
|
+
const entry = lstatSync(current, { throwIfNoEntry: false })
|
|
272
|
+
if (!entry) return
|
|
273
|
+
if (entry.isSymbolicLink()) {
|
|
274
|
+
throw new Error(`Harness-managed path contains a symbolic-link component: ${current}`)
|
|
275
|
+
}
|
|
276
|
+
const isLeaf = index === components.length - 1
|
|
277
|
+
if (!isLeaf && !entry.isDirectory()) {
|
|
278
|
+
throw new Error(`Harness-managed path component is not a directory: ${current}`)
|
|
279
|
+
}
|
|
280
|
+
if (isLeaf && options.leaf === 'file' && !entry.isFile()) {
|
|
281
|
+
throw new Error(`Harness-managed file is not a regular file: ${current}`)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
261
285
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
286
|
+
function ensureManagedParent(root: string, filePath: string): void {
|
|
287
|
+
const parent = dirname(filePath)
|
|
288
|
+
const normalizedRelative = relative(root, parent)
|
|
289
|
+
if (normalizedRelative === '..' || normalizedRelative.startsWith(`..${sep}`)) {
|
|
290
|
+
throw new Error(`Harness-managed path escapes its root: ${filePath}`)
|
|
291
|
+
}
|
|
292
|
+
let current = root
|
|
293
|
+
for (const component of normalizedRelative.split(sep).filter(Boolean)) {
|
|
294
|
+
assertManagedPath(root, current)
|
|
295
|
+
current = join(current, component)
|
|
296
|
+
const entry = lstatSync(current, { throwIfNoEntry: false })
|
|
297
|
+
if (!entry) mkdirSync(current)
|
|
298
|
+
const created = lstatSync(current, { throwIfNoEntry: false })
|
|
299
|
+
if (!created || created.isSymbolicLink() || !created.isDirectory()) {
|
|
300
|
+
throw new Error(`Harness-managed path component is not a real directory: ${current}`)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
267
304
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
'
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
305
|
+
function harnessGeneratorId(): string {
|
|
306
|
+
try {
|
|
307
|
+
const upstream = JSON.parse(readFileSync(join(GUIDES_DIR, 'upstream', 'manifest.json'), 'utf8')) as {
|
|
308
|
+
generator?: unknown
|
|
309
|
+
}
|
|
310
|
+
const generator = typeof upstream.generator === 'string' ? upstream.generator : ''
|
|
311
|
+
const version = generator.slice(generator.lastIndexOf('@') + 1)
|
|
312
|
+
if (version && version !== generator) return `open-mercato-agentic@${version}`
|
|
313
|
+
} catch {
|
|
314
|
+
// Source-mode tests may not have built upstream snapshots yet.
|
|
315
|
+
}
|
|
316
|
+
return 'open-mercato-agentic@unknown'
|
|
317
|
+
}
|
|
278
318
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
'ai
|
|
282
|
-
|
|
283
|
-
|
|
319
|
+
function externalSkillNames(targetDir: string): Set<string> {
|
|
320
|
+
try {
|
|
321
|
+
const tiers = JSON.parse(readFileSync(join(targetDir, '.ai', 'skills', 'tiers.json'), 'utf8')) as {
|
|
322
|
+
external?: { skills?: unknown }
|
|
323
|
+
}
|
|
324
|
+
return new Set(Array.isArray(tiers.external?.skills) ? tiers.external.skills.filter((name): name is string => typeof name === 'string') : [])
|
|
325
|
+
} catch {
|
|
326
|
+
return new Set()
|
|
327
|
+
}
|
|
328
|
+
}
|
|
284
329
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
'
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
330
|
+
function atomicCopyFile(sourcePath: string, destinationPath: string, managedRoot?: string): void {
|
|
331
|
+
if (managedRoot) {
|
|
332
|
+
assertManagedPath(managedRoot, destinationPath, { leaf: 'file' })
|
|
333
|
+
ensureManagedParent(managedRoot, destinationPath)
|
|
334
|
+
} else {
|
|
335
|
+
ensureDir(destinationPath)
|
|
336
|
+
}
|
|
337
|
+
const temporaryPath = `${destinationPath}.tmp-${process.pid}-${Date.now()}`
|
|
338
|
+
try {
|
|
339
|
+
if (managedRoot) assertManagedPath(managedRoot, temporaryPath, { leaf: 'file' })
|
|
340
|
+
copyFileSync(sourcePath, temporaryPath)
|
|
341
|
+
if (process.platform !== 'win32') chmodSync(temporaryPath, statSync(sourcePath).mode & 0o777)
|
|
342
|
+
if (managedRoot) {
|
|
343
|
+
assertManagedPath(managedRoot, dirname(destinationPath))
|
|
344
|
+
assertManagedPath(managedRoot, destinationPath, { leaf: 'file' })
|
|
345
|
+
assertManagedPath(managedRoot, temporaryPath, { leaf: 'file' })
|
|
346
|
+
}
|
|
347
|
+
renameSync(temporaryPath, destinationPath)
|
|
348
|
+
} finally {
|
|
349
|
+
if (!managedRoot || !lstatSync(temporaryPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
350
|
+
rmSync(temporaryPath, { force: true })
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
300
354
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
'
|
|
304
|
-
|
|
355
|
+
function atomicWriteFile(destinationPath: string, content: string, managedRoot?: string): void {
|
|
356
|
+
if (managedRoot) {
|
|
357
|
+
assertManagedPath(managedRoot, destinationPath, { leaf: 'file' })
|
|
358
|
+
ensureManagedParent(managedRoot, destinationPath)
|
|
359
|
+
} else {
|
|
360
|
+
ensureDir(destinationPath)
|
|
361
|
+
}
|
|
362
|
+
const temporaryPath = `${destinationPath}.tmp-${process.pid}-${Date.now()}`
|
|
363
|
+
try {
|
|
364
|
+
if (managedRoot) assertManagedPath(managedRoot, temporaryPath, { leaf: 'file' })
|
|
365
|
+
writeFileSync(temporaryPath, content)
|
|
366
|
+
if (managedRoot) {
|
|
367
|
+
assertManagedPath(managedRoot, dirname(destinationPath))
|
|
368
|
+
assertManagedPath(managedRoot, destinationPath, { leaf: 'file' })
|
|
369
|
+
assertManagedPath(managedRoot, temporaryPath, { leaf: 'file' })
|
|
370
|
+
}
|
|
371
|
+
renameSync(temporaryPath, destinationPath)
|
|
372
|
+
} finally {
|
|
373
|
+
if (!managedRoot || !lstatSync(temporaryPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
374
|
+
rmSync(temporaryPath, { force: true })
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function resolveManifestPath(root: string, relativePath: string): string | null {
|
|
380
|
+
if (!relativePath || relativePath.includes('\0')) return null
|
|
381
|
+
const absolutePath = resolve(root, relativePath)
|
|
382
|
+
const normalizedRelative = relative(root, absolutePath)
|
|
383
|
+
if (
|
|
384
|
+
normalizedRelative === '' ||
|
|
385
|
+
normalizedRelative === '..' ||
|
|
386
|
+
normalizedRelative.startsWith(`..${sep}`)
|
|
387
|
+
) {
|
|
388
|
+
return null
|
|
389
|
+
}
|
|
390
|
+
return absolutePath
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function normalizeManifestRelativePath(path: string): string {
|
|
394
|
+
return path.replace(/\\/g, '/')
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function readHarnessManifest(manifestPath: string): HarnessManifest | null {
|
|
398
|
+
if (!existsSync(manifestPath)) return null
|
|
399
|
+
try {
|
|
400
|
+
const parsed = JSON.parse(readFileSync(manifestPath, 'utf8')) as Partial<HarnessManifest>
|
|
401
|
+
if (
|
|
402
|
+
typeof parsed.version !== 'number' ||
|
|
403
|
+
typeof parsed.generator !== 'string' ||
|
|
404
|
+
!Array.isArray(parsed.files)
|
|
405
|
+
) {
|
|
406
|
+
return null
|
|
407
|
+
}
|
|
408
|
+
const files: HarnessManifestFile[] = []
|
|
409
|
+
for (const entry of parsed.files) {
|
|
410
|
+
if (
|
|
411
|
+
!entry ||
|
|
412
|
+
typeof entry.path !== 'string' ||
|
|
413
|
+
typeof entry.sha256 !== 'string' ||
|
|
414
|
+
!/^[a-f0-9]{64}$/.test(entry.sha256) ||
|
|
415
|
+
typeof entry.source !== 'string' ||
|
|
416
|
+
typeof entry.userEditable !== 'boolean'
|
|
417
|
+
) {
|
|
418
|
+
return null
|
|
419
|
+
}
|
|
420
|
+
files.push({ ...entry, path: normalizeManifestRelativePath(entry.path) })
|
|
421
|
+
}
|
|
422
|
+
return { version: parsed.version, generator: parsed.generator, files }
|
|
423
|
+
} catch {
|
|
424
|
+
return null
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function targetPathsForTree(sourceRoot: string, destinationRoot: string): string[] {
|
|
429
|
+
return listFiles(sourceRoot).map((sourcePath) => join(destinationRoot, relative(sourceRoot, sourcePath)))
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function finalizeHarnessManifest(config: AgenticConfig, selectedTools: string[]): void {
|
|
433
|
+
const { targetDir } = config
|
|
434
|
+
const srcDir = join(AGENTIC_DIR, 'shared')
|
|
435
|
+
const selectedModules = selectModuleFactSheets(targetDir, join(GUIDES_DIR, 'modules'))
|
|
436
|
+
const paths = new Set<string>([
|
|
437
|
+
join(targetDir, 'AGENTS.md'),
|
|
438
|
+
...targetPathsForTree(join(srcDir, 'ai'), join(targetDir, '.ai')),
|
|
439
|
+
...targetPathsForTree(join(srcDir, 'scripts'), join(targetDir, 'scripts')),
|
|
440
|
+
])
|
|
441
|
+
for (const file of readdirSync(GUIDES_DIR)) {
|
|
442
|
+
if (file.endsWith('.md') || file === 'module-facts.json') paths.add(join(targetDir, '.ai', 'guides', file))
|
|
443
|
+
}
|
|
444
|
+
for (const file of listFiles(join(GUIDES_DIR, 'upstream'))) {
|
|
445
|
+
paths.add(join(targetDir, '.ai', 'guides', 'upstream', relative(join(GUIDES_DIR, 'upstream'), file)))
|
|
446
|
+
}
|
|
447
|
+
for (const moduleId of selectedModules) paths.add(join(targetDir, '.ai', 'guides', 'modules', `${moduleId}.md`))
|
|
448
|
+
if (selectedTools.includes('claude-code')) {
|
|
449
|
+
paths.add(join(targetDir, 'CLAUDE.md'))
|
|
450
|
+
paths.add(join(targetDir, '.claude', 'settings.json'))
|
|
451
|
+
paths.add(join(targetDir, '.claude', 'hooks', 'entity-migration-check.ts'))
|
|
452
|
+
paths.add(join(targetDir, '.mcp.json.example'))
|
|
453
|
+
}
|
|
454
|
+
if (selectedTools.includes('codex')) paths.add(join(targetDir, '.codex', 'mcp.json.example'))
|
|
455
|
+
if (selectedTools.includes('cursor')) {
|
|
456
|
+
for (const file of listFiles(join(AGENTIC_DIR, 'cursor'))) {
|
|
457
|
+
const rel = relative(join(AGENTIC_DIR, 'cursor'), file)
|
|
458
|
+
paths.add(join(targetDir, '.cursor', rel))
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
const manifestPath = join(targetDir, '.ai', 'harness', 'manifest.json')
|
|
462
|
+
const externalSkills = externalSkillNames(targetDir)
|
|
463
|
+
paths.delete(manifestPath)
|
|
464
|
+
const files = [...paths]
|
|
465
|
+
.filter((path) => existsSync(path))
|
|
466
|
+
.sort((left, right) => left.localeCompare(right))
|
|
467
|
+
.map((path) => {
|
|
468
|
+
const relativePath = normalizeManifestRelativePath(relative(targetDir, path))
|
|
469
|
+
const skillName = relativePath.match(/^\.ai\/skills\/([^/]+)\//)?.[1]
|
|
470
|
+
return {
|
|
471
|
+
path: relativePath,
|
|
472
|
+
sha256: hashFile(path),
|
|
473
|
+
source: skillName ? (externalSkills.has(skillName) ? 'external-override' : 'local-skill') : 'generated',
|
|
474
|
+
userEditable:
|
|
475
|
+
relativePath === 'AGENTS.md'
|
|
476
|
+
|| relativePath === '.ai/agentic.config.json'
|
|
477
|
+
|| relativePath === '.ai/lessons.md'
|
|
478
|
+
|| relativePath.startsWith('.ai/lessons/'),
|
|
479
|
+
}
|
|
480
|
+
})
|
|
481
|
+
atomicWriteFile(
|
|
482
|
+
manifestPath,
|
|
483
|
+
`${JSON.stringify({ version: 1, generator: harnessGeneratorId(), files }, null, 2)}\n`,
|
|
305
484
|
)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Publish a fully generated harness candidate using the previous ownership
|
|
489
|
+
* manifest as the only source of ownership. Modified owned files and exact-path
|
|
490
|
+
* collisions with unknown files stay untouched; their candidate is written next
|
|
491
|
+
* to them as `<path>.incoming` for explicit review.
|
|
492
|
+
*/
|
|
493
|
+
export type HarnessUpdateConflict = { path: string; candidate: string | null }
|
|
494
|
+
|
|
495
|
+
function preserveIncomingCandidate(
|
|
496
|
+
targetRoot: string,
|
|
497
|
+
destinationPath: string,
|
|
498
|
+
sourcePath: string,
|
|
499
|
+
relativePath: string,
|
|
500
|
+
): string {
|
|
501
|
+
for (let suffix = 1; ; suffix += 1) {
|
|
502
|
+
const label = suffix === 1 ? '.incoming' : `.incoming.${suffix}`
|
|
503
|
+
const candidatePath = `${destinationPath}${label}`
|
|
504
|
+
assertManagedPath(targetRoot, candidatePath, { leaf: 'file' })
|
|
505
|
+
if (!existsSync(candidatePath)) {
|
|
506
|
+
atomicCopyFile(sourcePath, candidatePath, targetRoot)
|
|
507
|
+
return `${relativePath}${label}`
|
|
508
|
+
}
|
|
509
|
+
if (hashFile(candidatePath) === hashFile(sourcePath)) return `${relativePath}${label}`
|
|
510
|
+
}
|
|
511
|
+
}
|
|
306
512
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
'
|
|
321
|
-
|
|
322
|
-
|
|
513
|
+
export function applyHarnessUpdate(
|
|
514
|
+
targetDir: string,
|
|
515
|
+
stagingDir: string,
|
|
516
|
+
options: { force?: boolean } = {},
|
|
517
|
+
): HarnessUpdateConflict[] {
|
|
518
|
+
const targetRoot = canonicalHarnessRoot(targetDir)
|
|
519
|
+
const stagingRoot = canonicalHarnessRoot(stagingDir)
|
|
520
|
+
const targetManifestPath = join(targetRoot, '.ai', 'harness', 'manifest.json')
|
|
521
|
+
const stagingManifestPath = join(stagingRoot, '.ai', 'harness', 'manifest.json')
|
|
522
|
+
assertManagedPath(targetRoot, targetManifestPath, { leaf: 'file' })
|
|
523
|
+
assertManagedPath(stagingRoot, stagingManifestPath, { leaf: 'file' })
|
|
524
|
+
const candidateManifest = readHarnessManifest(stagingManifestPath)
|
|
525
|
+
if (!candidateManifest) {
|
|
526
|
+
throw new Error('Generated harness candidate has a missing or invalid ownership manifest.')
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const previousManifest = readHarnessManifest(targetManifestPath)
|
|
530
|
+
const previousFiles = new Map(previousManifest?.files.map((entry) => [entry.path, entry]) ?? [])
|
|
531
|
+
const candidatePaths = new Set(candidateManifest.files.map((entry) => entry.path))
|
|
532
|
+
const candidates: Array<{
|
|
533
|
+
entry: HarnessManifestFile
|
|
534
|
+
sourcePath: string
|
|
535
|
+
destinationPath: string
|
|
536
|
+
}> = []
|
|
537
|
+
|
|
538
|
+
// Validate the complete candidate before writing anything into the app.
|
|
539
|
+
for (const entry of candidateManifest.files) {
|
|
540
|
+
const sourcePath = resolveManifestPath(stagingRoot, entry.path)
|
|
541
|
+
const destinationPath = resolveManifestPath(targetRoot, entry.path)
|
|
542
|
+
if (sourcePath) assertManagedPath(stagingRoot, sourcePath, { leaf: 'file' })
|
|
543
|
+
if (destinationPath) assertManagedPath(targetRoot, destinationPath, { leaf: 'file' })
|
|
544
|
+
if (!sourcePath || !destinationPath || !existsSync(sourcePath) || hashFile(sourcePath) !== entry.sha256) {
|
|
545
|
+
throw new Error(`Generated harness candidate is invalid for ${JSON.stringify(entry.path)}.`)
|
|
546
|
+
}
|
|
547
|
+
candidates.push({ entry, sourcePath, destinationPath })
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const conflicts: HarnessUpdateConflict[] = []
|
|
551
|
+
for (const { entry, sourcePath, destinationPath } of candidates) {
|
|
552
|
+
if (options.force) {
|
|
553
|
+
atomicCopyFile(sourcePath, destinationPath, targetRoot)
|
|
323
554
|
continue
|
|
324
555
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
556
|
+
if (!existsSync(destinationPath)) {
|
|
557
|
+
atomicCopyFile(sourcePath, destinationPath, targetRoot)
|
|
558
|
+
continue
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const currentHash = hashFile(destinationPath)
|
|
562
|
+
const previousEntry = previousFiles.get(entry.path)
|
|
563
|
+
if (currentHash === entry.sha256 || (previousEntry && currentHash === previousEntry.sha256)) {
|
|
564
|
+
atomicCopyFile(sourcePath, destinationPath, targetRoot)
|
|
565
|
+
continue
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const candidate = preserveIncomingCandidate(targetRoot, destinationPath, sourcePath, entry.path)
|
|
569
|
+
conflicts.push({ path: entry.path, candidate })
|
|
330
570
|
}
|
|
331
571
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
572
|
+
const retainedRetiredEntries: HarnessManifestFile[] = []
|
|
573
|
+
for (const previousEntry of previousManifest?.files ?? []) {
|
|
574
|
+
if (candidatePaths.has(previousEntry.path)) continue
|
|
575
|
+
const destinationPath = resolveManifestPath(targetRoot, previousEntry.path)
|
|
576
|
+
if (!destinationPath || !existsSync(destinationPath)) continue
|
|
577
|
+
assertManagedPath(targetRoot, destinationPath, { leaf: 'file' })
|
|
578
|
+
let unchanged = false
|
|
579
|
+
try {
|
|
580
|
+
unchanged = hashFile(destinationPath) === previousEntry.sha256
|
|
581
|
+
} catch {
|
|
582
|
+
// A user may replace a generated file with another filesystem object.
|
|
583
|
+
// Preserve it and retain the ownership tombstone for a later review.
|
|
584
|
+
}
|
|
585
|
+
if (unchanged) {
|
|
586
|
+
assertManagedPath(targetRoot, dirname(destinationPath))
|
|
587
|
+
assertManagedPath(targetRoot, destinationPath, { leaf: 'file' })
|
|
588
|
+
rmSync(destinationPath, { force: true })
|
|
589
|
+
} else {
|
|
590
|
+
retainedRetiredEntries.push(previousEntry)
|
|
591
|
+
conflicts.push({ path: previousEntry.path, candidate: null })
|
|
592
|
+
}
|
|
593
|
+
}
|
|
337
594
|
|
|
338
|
-
|
|
595
|
+
// The candidate hashes deliberately remain in the manifest for conflicts. If
|
|
596
|
+
// the user accepts an .incoming file, a later update can recognize it as an
|
|
597
|
+
// unmodified owned file. Until then the hash mismatch keeps preserving it.
|
|
598
|
+
const finalManifest: HarnessManifest = {
|
|
599
|
+
...candidateManifest,
|
|
600
|
+
files: [...candidateManifest.files, ...retainedRetiredEntries],
|
|
601
|
+
}
|
|
602
|
+
atomicWriteFile(targetManifestPath, `${JSON.stringify(finalManifest, null, 2)}\n`, targetRoot)
|
|
603
|
+
return conflicts
|
|
604
|
+
}
|
|
339
605
|
|
|
340
|
-
|
|
606
|
+
// ─── Generators ──────────────────────────────────────────────────────────
|
|
607
|
+
|
|
608
|
+
function generateShared(config: AgenticConfig): void {
|
|
609
|
+
const { targetDir } = config
|
|
610
|
+
const srcDir = join(AGENTIC_DIR, 'shared')
|
|
611
|
+
|
|
612
|
+
// Resolve which per-module fact-sheets this app gets (enabled ∩ bundled allowlist).
|
|
613
|
+
const selectedModules = selectModuleFactSheets(targetDir, join(GUIDES_DIR, 'modules'))
|
|
614
|
+
|
|
615
|
+
// One recursive mapping mirrors create-app's shared emitter.
|
|
616
|
+
writeTemplate(srcDir, 'AGENTS.md.template', join(targetDir, 'AGENTS.md'), config)
|
|
617
|
+
copyTree(join(srcDir, 'ai'), join(targetDir, '.ai'), config)
|
|
618
|
+
copyTree(join(srcDir, 'scripts'), join(targetDir, 'scripts'), config)
|
|
619
|
+
|
|
620
|
+
// Routed conceptual guides are copied wholesale (framework-wide). Per-module
|
|
341
621
|
// fact-sheets (.ai/guides/modules/<module>.md) are filtered to the app's enabled
|
|
342
622
|
// module set; the combined module-facts.json sidecar is copied as-is.
|
|
343
623
|
if (existsSync(GUIDES_DIR)) {
|
|
@@ -357,6 +637,9 @@ function generateShared(config: AgenticConfig): void {
|
|
|
357
637
|
copyFileSync(moduleFactsPath, destPath)
|
|
358
638
|
}
|
|
359
639
|
|
|
640
|
+
|
|
641
|
+
copyTree(join(GUIDES_DIR, 'upstream'), join(guidesDestDir, 'upstream'), config)
|
|
642
|
+
|
|
360
643
|
const modulesSubdir = join(GUIDES_DIR, 'modules')
|
|
361
644
|
for (const moduleId of selectedModules) {
|
|
362
645
|
const destPath = join(guidesDestDir, 'modules', `${moduleId}.md`)
|
|
@@ -364,6 +647,8 @@ function generateShared(config: AgenticConfig): void {
|
|
|
364
647
|
copyFileSync(join(modulesSubdir, `${moduleId}.md`), destPath)
|
|
365
648
|
}
|
|
366
649
|
}
|
|
650
|
+
|
|
651
|
+
injectModuleGuides(join(targetDir, 'AGENTS.md'), selectedModules)
|
|
367
652
|
}
|
|
368
653
|
|
|
369
654
|
function generateClaudeCode(config: AgenticConfig): void {
|
|
@@ -375,8 +660,7 @@ function generateClaudeCode(config: AgenticConfig): void {
|
|
|
375
660
|
copyFile(srcDir, 'hooks/entity-migration-check.ts', join(targetDir, '.claude', 'hooks', 'entity-migration-check.ts'))
|
|
376
661
|
copyFile(srcDir, 'mcp.json.example', join(targetDir, '.mcp.json.example'))
|
|
377
662
|
|
|
378
|
-
//
|
|
379
|
-
ensureSkillsLink(join(targetDir, '.claude', 'skills'), join('..', '.ai', 'skills'))
|
|
663
|
+
// The installer exclusively owns Claude's per-skill compatibility links.
|
|
380
664
|
}
|
|
381
665
|
|
|
382
666
|
function generateCodex(config: AgenticConfig): void {
|
|
@@ -428,17 +712,6 @@ function generateCursor(config: AgenticConfig): void {
|
|
|
428
712
|
// which scripts/install-skills.sh populates.
|
|
429
713
|
}
|
|
430
714
|
|
|
431
|
-
function ensureSkillsLink(linkPath: string, target: string): void {
|
|
432
|
-
ensureDir(linkPath)
|
|
433
|
-
if (existsSync(linkPath) && !lstatSync(linkPath).isSymbolicLink()) {
|
|
434
|
-
return
|
|
435
|
-
}
|
|
436
|
-
if (lstatSync(linkPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
437
|
-
unlinkSync(linkPath)
|
|
438
|
-
}
|
|
439
|
-
symlinkSync(target, linkPath)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
715
|
// ─── Wizard ──────────────────────────────────────────────────────────────
|
|
443
716
|
|
|
444
717
|
const TOOLS = [
|
|
@@ -451,6 +724,35 @@ const TOOLS = [
|
|
|
451
724
|
|
|
452
725
|
const SELECTABLE = TOOLS.filter((t) => t.id !== 'multiple' && t.id !== 'skip')
|
|
453
726
|
|
|
727
|
+
function persistedAgentSelection(targetDir: string): string[] | null {
|
|
728
|
+
const selectableIds = SELECTABLE.map((tool) => tool.id)
|
|
729
|
+
const tiersPath = join(targetDir, '.ai', 'skills', 'tiers.json')
|
|
730
|
+
try {
|
|
731
|
+
const tiers = JSON.parse(readFileSync(tiersPath, 'utf8')) as { agents?: { ignore?: unknown } }
|
|
732
|
+
if (Array.isArray(tiers.agents?.ignore)
|
|
733
|
+
&& tiers.agents.ignore.every((id): id is string => typeof id === 'string')) {
|
|
734
|
+
const ignored = new Set(tiers.agents.ignore)
|
|
735
|
+
return selectableIds.filter((id) => !ignored.has(id))
|
|
736
|
+
}
|
|
737
|
+
} catch {
|
|
738
|
+
// Fall through to the ownership manifest and existing generated assets.
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const manifest = readHarnessManifest(join(targetDir, '.ai', 'harness', 'manifest.json'))
|
|
742
|
+
const owned = new Set(manifest?.files.map((entry) => entry.path) ?? [])
|
|
743
|
+
const selected = SELECTABLE.filter((tool) => {
|
|
744
|
+
if (tool.id === 'claude-code') {
|
|
745
|
+
return owned.has('CLAUDE.md') || owned.has('.claude/settings.json')
|
|
746
|
+
|| existsSync(join(targetDir, 'CLAUDE.md')) || existsSync(join(targetDir, '.claude', 'settings.json'))
|
|
747
|
+
}
|
|
748
|
+
if (tool.id === 'codex') {
|
|
749
|
+
return owned.has('.codex/mcp.json.example') || existsSync(join(targetDir, '.codex', 'mcp.json.example'))
|
|
750
|
+
}
|
|
751
|
+
return owned.has('.cursor/hooks.json') || existsSync(join(targetDir, '.cursor', 'hooks.json'))
|
|
752
|
+
}).map((tool) => tool.id)
|
|
753
|
+
return selected.length > 0 ? selected : null
|
|
754
|
+
}
|
|
755
|
+
|
|
454
756
|
async function promptSelection(ask: AskFn): Promise<string[]> {
|
|
455
757
|
console.log('')
|
|
456
758
|
console.log('🤖 Agentic workflow setup')
|
|
@@ -497,6 +799,8 @@ export async function runAgenticSetup(
|
|
|
497
799
|
|
|
498
800
|
if (options?.tool) {
|
|
499
801
|
selectedIds = options.tool.split(',').map((t) => t.trim())
|
|
802
|
+
} else if (options?.updateHarness) {
|
|
803
|
+
selectedIds = persistedAgentSelection(targetDir) ?? await promptSelection(ask)
|
|
500
804
|
} else {
|
|
501
805
|
selectedIds = await promptSelection(ask)
|
|
502
806
|
}
|
|
@@ -513,12 +817,37 @@ export async function runAgenticSetup(
|
|
|
513
817
|
targetDir,
|
|
514
818
|
}
|
|
515
819
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
820
|
+
const stagingDir = mkdtempSync(join(tmpdir(), 'open-mercato-harness-'))
|
|
821
|
+
try {
|
|
822
|
+
const modulesSourcePath = join(targetDir, 'src', 'modules.ts')
|
|
823
|
+
const modulesCandidatePath = join(stagingDir, 'src', 'modules.ts')
|
|
824
|
+
if (existsSync(modulesSourcePath)) {
|
|
825
|
+
ensureDir(modulesCandidatePath)
|
|
826
|
+
copyFileSync(modulesSourcePath, modulesCandidatePath)
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const stagingConfig: AgenticConfig = {
|
|
830
|
+
projectName: config.projectName,
|
|
831
|
+
targetDir: stagingDir,
|
|
832
|
+
}
|
|
833
|
+
generateHarness(stagingConfig, selectedIds)
|
|
834
|
+
const conflicts = applyHarnessUpdate(targetDir, stagingDir, {
|
|
835
|
+
force: options?.force,
|
|
836
|
+
})
|
|
837
|
+
if (conflicts.length > 0) {
|
|
838
|
+
console.warn('')
|
|
839
|
+
console.warn(' ⚠ Preserved locally modified harness files:')
|
|
840
|
+
for (const conflict of conflicts) {
|
|
841
|
+
const detail = conflict.candidate
|
|
842
|
+
? `candidate: ${conflict.candidate}`
|
|
843
|
+
: 'retired asset kept because it has local changes'
|
|
844
|
+
console.warn(` • ${conflict.path} (${detail})`)
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
} finally {
|
|
848
|
+
rmSync(stagingDir, { recursive: true, force: true })
|
|
849
|
+
}
|
|
520
850
|
|
|
521
|
-
persistAgentSelection(targetDir, selectedIds)
|
|
522
851
|
installSkills(targetDir)
|
|
523
852
|
|
|
524
853
|
console.log('')
|
|
@@ -540,6 +869,28 @@ export async function runAgenticSetup(
|
|
|
540
869
|
console.log('')
|
|
541
870
|
}
|
|
542
871
|
|
|
872
|
+
function generateHarness(config: AgenticConfig, selectedIds: string[]): void {
|
|
873
|
+
generateShared(config)
|
|
874
|
+
if (selectedIds.includes('claude-code')) generateClaudeCode(config)
|
|
875
|
+
if (selectedIds.includes('codex')) generateCodex(config)
|
|
876
|
+
if (selectedIds.includes('cursor')) generateCursor(config)
|
|
877
|
+
|
|
878
|
+
enforceGeneratedRootBudget(config)
|
|
879
|
+
|
|
880
|
+
persistAgentSelection(config.targetDir, selectedIds)
|
|
881
|
+
finalizeHarnessManifest(config, selectedIds)
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/** Run after every tool generator has patched AGENTS.md, before the manifest is hashed. */
|
|
885
|
+
function enforceGeneratedRootBudget(
|
|
886
|
+
config: AgenticConfig,
|
|
887
|
+
maxBytes: number = STANDALONE_ROOT_TARGET_BYTES,
|
|
888
|
+
): boolean {
|
|
889
|
+
const { targetDir } = config
|
|
890
|
+
const selectedModules = selectModuleFactSheets(targetDir, join(GUIDES_DIR, 'modules'))
|
|
891
|
+
return enforceRootInstructionBudget(join(targetDir, 'AGENTS.md'), selectedModules, maxBytes)
|
|
892
|
+
}
|
|
893
|
+
|
|
543
894
|
/**
|
|
544
895
|
* Persist the agent selection so later `yarn install-skills` runs keep honoring
|
|
545
896
|
* it: agents the user did not pick go into `agents.ignore` in tiers.json and
|
|
@@ -559,11 +910,11 @@ function persistAgentSelection(targetDir: string, selectedIds: string[]): void {
|
|
|
559
910
|
}
|
|
560
911
|
|
|
561
912
|
function installSkills(targetDir: string): void {
|
|
562
|
-
const installScript = join(targetDir, 'scripts', 'install-skills.
|
|
913
|
+
const installScript = join(targetDir, 'scripts', 'install-skills.mjs')
|
|
563
914
|
if (!existsSync(installScript)) return
|
|
564
915
|
console.log('')
|
|
565
916
|
console.log(' Installing agent skills (local tiers + external open-mercato/skills subset)...')
|
|
566
|
-
const result = spawnSync(
|
|
917
|
+
const result = spawnSync(process.execPath, [installScript], { cwd: targetDir, stdio: 'inherit' })
|
|
567
918
|
if (result.error || result.status !== 0) {
|
|
568
919
|
console.log(' ⚠ Skill installation did not complete; run `yarn install-skills` inside the app when online.')
|
|
569
920
|
}
|