@open-mercato/cli 0.6.7-develop.6795.1.8a3f27921c → 0.6.7-develop.6824.1.21fff90682
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 +1 -1
- package/build.mjs +1 -1
- package/dist/agentic/guides/module-facts.json +90266 -20749
- package/dist/agentic/guides/modules/ai_assistant.md +170 -60
- package/dist/agentic/guides/modules/api_docs.md +26 -1
- package/dist/agentic/guides/modules/api_keys.md +57 -17
- package/dist/agentic/guides/modules/attachments.md +72 -19
- package/dist/agentic/guides/modules/audit_logs.md +95 -29
- package/dist/agentic/guides/modules/auth.md +218 -83
- package/dist/agentic/guides/modules/business_rules.md +108 -34
- package/dist/agentic/guides/modules/catalog.md +386 -157
- package/dist/agentic/guides/modules/channel_gmail.md +55 -6
- package/dist/agentic/guides/modules/channel_imap.md +55 -6
- package/dist/agentic/guides/modules/checkout.md +254 -129
- package/dist/agentic/guides/modules/communication_channels.md +280 -109
- package/dist/agentic/guides/modules/configs.md +68 -11
- package/dist/agentic/guides/modules/content.md +26 -1
- package/dist/agentic/guides/modules/currencies.md +109 -44
- package/dist/agentic/guides/modules/customer_accounts.md +350 -156
- package/dist/agentic/guides/modules/customers.md +642 -294
- package/dist/agentic/guides/modules/dashboards.md +122 -28
- package/dist/agentic/guides/modules/data_sync.md +117 -37
- package/dist/agentic/guides/modules/design_system.md +36 -2
- package/dist/agentic/guides/modules/dictionaries.md +78 -20
- package/dist/agentic/guides/modules/directory.md +110 -45
- package/dist/agentic/guides/modules/entities.md +95 -38
- package/dist/agentic/guides/modules/events.md +31 -1
- package/dist/agentic/guides/modules/feature_toggles.md +79 -12
- package/dist/agentic/guides/modules/gateway_stripe.md +61 -6
- package/dist/agentic/guides/modules/generators.md +17 -1
- package/dist/agentic/guides/modules/inbox_ops.md +171 -60
- package/dist/agentic/guides/modules/integrations.md +116 -30
- package/dist/agentic/guides/modules/messages.md +187 -66
- package/dist/agentic/guides/modules/notifications.md +64 -9
- package/dist/agentic/guides/modules/onboarding.md +47 -8
- package/dist/agentic/guides/modules/payment_gateways.md +133 -48
- package/dist/agentic/guides/modules/perspectives.md +50 -12
- package/dist/agentic/guides/modules/planner.md +102 -32
- package/dist/agentic/guides/modules/portal.md +58 -15
- package/dist/agentic/guides/modules/progress.md +78 -30
- package/dist/agentic/guides/modules/query_index.md +115 -53
- package/dist/agentic/guides/modules/record_locks.md +178 -59
- package/dist/agentic/guides/modules/resources.md +155 -78
- package/dist/agentic/guides/modules/sales.md +620 -257
- package/dist/agentic/guides/modules/scheduler.md +95 -19
- package/dist/agentic/guides/modules/search.md +124 -20
- package/dist/agentic/guides/modules/security.md +251 -105
- package/dist/agentic/guides/modules/shipping_carriers.md +106 -37
- package/dist/agentic/guides/modules/sso.md +121 -49
- package/dist/agentic/guides/modules/staff.md +314 -126
- package/dist/agentic/guides/modules/storage_s3.md +55 -6
- package/dist/agentic/guides/modules/sync_akeneo.md +54 -4
- package/dist/agentic/guides/modules/sync_excel.md +49 -11
- package/dist/agentic/guides/modules/system_status_overlays.md +33 -5
- package/dist/agentic/guides/modules/translations.md +76 -21
- package/dist/agentic/guides/modules/webhooks.md +166 -82
- package/dist/agentic/guides/modules/wms.md +383 -223
- package/dist/agentic/guides/modules/workflows.md +248 -112
- package/dist/agentic/guides/upstream/AGENTS.md +1 -1
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +2 -0
- package/dist/agentic/guides/upstream/manifest.json +3 -3
- package/dist/agentic/shared/AGENTS.md.template +6 -5
- package/dist/agentic/shared/ai/harness/README.md +2 -2
- package/dist/agentic/shared/ai/harness/RELEASE.md +4 -4
- package/dist/agentic/shared/ai/harness/cases.json +94 -1
- package/dist/agentic/shared/ai/harness/cases.schema.json +3 -3
- package/dist/agentic/shared/ai/harness/validators.json +1 -1
- package/dist/agentic/shared/ai/lessons/_template.md +16 -0
- package/dist/agentic/shared/ai/lessons.md +26 -3
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +7 -6
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +1 -1
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +4 -2
- package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +37 -12
- package/dist/lib/agentic-setup.js +2 -2
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/module-extension-facts.js +652 -24
- package/dist/lib/generators/module-extension-facts.js.map +3 -3
- 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.js +1001 -26
- 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/testing/integration.js +5 -0
- package/dist/lib/testing/integration.js.map +2 -2
- package/package.json +5 -5
- package/src/lib/__tests__/agentic-setup.ownership.test.ts +6 -0
- package/src/lib/agentic-setup.ts +6 -2
- 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 +4 -2
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +37 -3
- package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -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/module-extension-facts.ts +923 -27
- package/src/lib/generators/module-fact-sources.ts +108 -0
- package/src/lib/generators/module-facts.ts +1360 -25
- package/src/lib/generators/module-override-targets.ts +863 -0
- package/src/lib/testing/__tests__/integration.test.ts +4 -0
- package/src/lib/testing/integration.ts +5 -0
|
@@ -6,7 +6,7 @@ Run the complete per-release gate from a generated standalone app with one comma
|
|
|
6
6
|
yarn harness:release --runner codex --prepare-targets /absolute/empty-release-targets --acknowledge-writes
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Choose exactly one blocking primary runner with `--runner codex` or `--runner claude`. That runner owns the complete
|
|
9
|
+
Choose exactly one blocking primary runner with `--runner codex` or `--runner claude`. That runner owns the complete 203-case routing gate and every writable/review lane. To add cross-model portability evidence, explicitly pass the other runner as `--portability-runner claude` or `--portability-runner codex`; it runs only the exact 46-case representative read-only set. The two runners must differ. Omitting the portability option is valid and is recorded as `portabilityRunner: null`; no secondary result is claimed. There is no per-case fallback or mixed primary ownership. Once requested, a portability failure or unavailable runner fails that extended release run.
|
|
10
10
|
|
|
11
11
|
`--prepare-targets` accepts only an absolute, new or empty regular directory outside the controller app. The controller must be a sanitized fresh scaffold: automatic preparation fails before copying when it finds `.env`, `.env.*` (except `.env.example`, `.env.sample`, and `.env.template`), credential files, or private-key files. Never use a configured development or production app as the controller. It copies the fresh scaffold once per catalog case whose `evaluationKind` is `implementation` or `regression`, while excluding `.git`, `node_modules`, build/cache/coverage output, `.ai/harness/results`, `.ai/reports`, and `.ai/framework-context`. Each target receives a guarded link to the controller's installed dependency tree. The OS sandbox resolves that link as read-only during both the writable model run and the target command gate. The release gate also hashes every dependency entry and regular-file body once before execution and once after the complete suite, and fails if any nested content or metadata changed. A generated `release-targets.json` records the local mapping.
|
|
12
12
|
|
|
@@ -34,17 +34,17 @@ For externally prepared apps, `--writable-targets /absolute/release-targets.json
|
|
|
34
34
|
}
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
The current catalog contains
|
|
37
|
+
The current catalog contains 203 cases, including 46 writable implementation/regression cases (22.7%). The command still derives all counts and case IDs from `cases.json`, `validators.json`, and `release-matrix.json`; those figures are documented release facts, not runner constants. The matrix keeps both supported runner model selectors, an exact all-case primary profile, an exact 46-case portability profile, and runner-neutral writable assignments. Run `yarn install-skills` first so the pinned external `om-code-review` skill and ownership evidence are present; the reusable local `om-judge-agent-session` skill ships with the scaffold. Before running a model or writing a fixture, the release command requires complete deterministic, primary live-routing, writable, trusted-oracle, target, generated-test, and generative-judge coverage. Every one of the 46 writable cases must have a judge assignment composing both skills. Missing business fixtures or release-matrix entries fail preflight and are listed by exact case ID in the report.
|
|
38
38
|
|
|
39
39
|
## PR #4529 remediation evidence
|
|
40
40
|
|
|
41
|
-
The PR's focused remediation evidence is not a release-certification substitute. Fresh emitted controllers pass deterministic 192/192, and the field-tested OMH-188–192 generative cohort passes on default Codex, Claude Sonnet, and high-effort gpt-5.4-mini. Fresh OMH-185 writable attempts fixed concrete organization-scope, command-object, module-activation, command-snapshot, schema, custom-field, UI, and Jest guidance defects at their routed owners without relaxing trusted oracles. The final attempt reached the case's fixed 600-second ceiling and is excluded from pass evidence. Issue #4670 now owns the complete selected-primary
|
|
41
|
+
The PR's focused remediation evidence is not a release-certification substitute. Fresh emitted controllers pass deterministic 192/192, and the field-tested OMH-188–192 generative cohort passes on default Codex, Claude Sonnet, and high-effort gpt-5.4-mini. Fresh OMH-185 writable attempts fixed concrete organization-scope, command-object, module-activation, command-snapshot, schema, custom-field, UI, and Jest guidance defects at their routed owners without relaxing trusted oracles. The final attempt reached the case's fixed 600-second ceiling and is excluded from pass evidence. Issue #4670 now owns the complete selected-primary 203-case routing and 46-case writable/generated-test/review certification, prioritizing the generative cohort and recording unavailable Claude lanes without fallback or mixed-runner ownership.
|
|
42
42
|
|
|
43
43
|
After preflight it runs, in order:
|
|
44
44
|
|
|
45
45
|
1. deterministic validation for the complete catalog;
|
|
46
46
|
2. the release matrix's fixed `yarn generate`, `yarn typecheck`, `yarn lint`, and `yarn build` foundation;
|
|
47
|
-
3. the selected primary runner across all
|
|
47
|
+
3. the selected primary runner across all 203 live-routing cases, followed by the optional distinct portability runner across the exact 46-case read-only sample when requested;
|
|
48
48
|
4. fixture preparation and the selected primary runner for every writable case, including the controller-owned AST/behavior oracles and target typecheck;
|
|
49
49
|
5. `yarn generate`, `yarn typecheck`, `yarn lint`, and `yarn build` in every writable target, after its trusted oracles;
|
|
50
50
|
6. real generated-code execution for OMH-163 and OMH-192 through fixed Jest, OMH-164 through API-only Playwright, and OMH-165 through real-browser Playwright; and
|
|
@@ -1079,6 +1079,9 @@
|
|
|
1079
1079
|
".ai/guides/backend-ui.md",
|
|
1080
1080
|
".ai/skills/om-backend-ui-design/SKILL.md"
|
|
1081
1081
|
],
|
|
1082
|
+
"allowedExtra": [
|
|
1083
|
+
".ai/guides/modules/design_system.md"
|
|
1084
|
+
],
|
|
1082
1085
|
"warn": ["node_modules/@open-mercato/*/src/**"], "forbidden": [
|
|
1083
1086
|
".env*",
|
|
1084
1087
|
".git/**"
|
|
@@ -13583,5 +13586,95 @@
|
|
|
13583
13586
|
{"id":"OMH-199","title":"Load a supplier spreadsheet in bulk without a hand-written parser","family":"architecture","mode":"analysis","evaluationKind":"routing","risk":"medium","prompt":"In a freshly scaffolded standalone Open Mercato app, a supplier sends a monthly spreadsheet and back-office staff retype several hundred rows by hand. They want to upload that file themselves and have the rows loaded, with a record of who uploaded what and whether the load succeeded. Decide the smallest safe design against what the installed modules already provide, keep tenant and organization boundaries intact, state the access-control posture for viewing an upload versus running one, and identify the smallest relevant validation. Do not write a bespoke spreadsheet parser or upload endpoint before establishing whether an installed module already owns this capability.","tags":["architecture","module-facts","reuse-installed","bulk-import"],"owner":{"kind":"facts","path":".ai/guides/modules/sync_excel.md","ruleIds":["BC-01","BC-04"]},"expectedRouter":{"required":["architecture"],"allowedExtra":["integration","module-data","framework-context","umes"]},"requiredSkills":["om-integration-builder"],"context":{"required":["AGENTS.md",".ai/guides/architecture.md",".ai/skills/om-integration-builder/SKILL.md",".ai/guides/modules/sync_excel.md"],"allowedExtra":[".ai/guides/integrations.md",".ai/guides/contracts.md",".ai/guides/modules/data_sync.md",".ai/skills/om-help/SKILL.md"],"warn":["node_modules/@open-mercato/*/src/**"],"forbidden":[".env*",".git/**"]},"requiredDecisions":["facts-first","tenant-scope","acl-features","smallest-validation"],"forbiddenPatterns":["node_modules.{0,40}(?:write|edit|patch)","(?:tenant|organization).{0,30}(?:unscoped|scope optional)"],"validators":["catalog.schema","owner.reference","skills.reference","router.contract","context.budget","context.forbidden","patterns.forbidden"],"maxContextFiles":11,"maxInitialContextBytes":57344,"maxTotalContextBytes":147456,"relatedCases":["OMH-002","OMH-195"]},
|
|
13584
13587
|
{"id":"OMH-200","title":"Charge cards through the installed payment provider rather than a bespoke client","family":"architecture","mode":"analysis","evaluationKind":"routing","risk":"high","prompt":"In a freshly scaffolded standalone Open Mercato app, the business has a Stripe account and wants to start charging cards at checkout this week. Decide the smallest safe design against what the installed modules already provide, say where a payment provider belongs in a standalone app and how it is turned on for this one, state the access-control posture for viewing versus configuring it and how the provider credential is held, keep tenant and organization boundaries intact, and identify the smallest relevant validation. Do not write a bespoke provider client or a new payments module before establishing whether an installed module already owns this capability.","tags":["architecture","module-facts","reuse-installed","payment-provider"],"owner":{"kind":"facts","path":".ai/guides/modules/gateway_stripe.md","ruleIds":["BC-01","BC-04"]},"expectedRouter":{"required":["architecture"],"allowedExtra":["integration","module-data","framework-context","umes"]},"requiredSkills":["om-integration-builder"],"context":{"required":["AGENTS.md",".ai/guides/architecture.md",".ai/skills/om-integration-builder/SKILL.md",".ai/guides/modules/gateway_stripe.md"],"allowedExtra":[".ai/guides/integrations.md",".ai/guides/contracts.md",".ai/guides/modules/payment_gateways.md",".ai/skills/om-help/SKILL.md"],"warn":["node_modules/@open-mercato/*/src/**"],"forbidden":[".env*",".git/**"]},"requiredDecisions":["facts-first","app-module-activation","acl-features","smallest-validation"],"forbiddenPatterns":["node_modules.{0,40}(?:write|edit|patch)","(?:tenant|organization).{0,30}(?:unscoped|scope optional)"],"validators":["catalog.schema","owner.reference","skills.reference","router.contract","context.budget","context.forbidden","patterns.forbidden"],"maxContextFiles":11,"maxInitialContextBytes":57344,"maxTotalContextBytes":147456,"relatedCases":["OMH-002","OMH-195"]},
|
|
13585
13588
|
{"id":"OMH-201","title":"Pull product data from the PIM the business already runs without a new connector","family":"architecture","mode":"analysis","evaluationKind":"routing","risk":"medium","prompt":"In a freshly scaffolded standalone Open Mercato app, the merchandising team maintains product data in Akeneo and wants it to reach this application on a schedule instead of being copied over by hand. Decide the smallest safe design against what the installed modules already provide, say how that capability is turned on for this application, keep tenant and organization boundaries intact, and identify the smallest relevant validation. Do not design a bespoke connector, mapping layer, or import endpoint before establishing whether an installed module already owns this capability.","tags":["architecture","module-facts","reuse-installed","pim-sync"],"owner":{"kind":"facts","path":".ai/guides/modules/sync_akeneo.md","ruleIds":["BC-01","BC-04"]},"expectedRouter":{"required":["architecture"],"allowedExtra":["integration","module-data","framework-context","umes"]},"requiredSkills":["om-integration-builder"],"context":{"required":["AGENTS.md",".ai/guides/architecture.md",".ai/skills/om-integration-builder/SKILL.md",".ai/guides/modules/sync_akeneo.md"],"allowedExtra":[".ai/guides/integrations.md",".ai/guides/contracts.md",".ai/guides/modules/data_sync.md",".ai/skills/om-help/SKILL.md"],"warn":["node_modules/@open-mercato/*/src/**"],"forbidden":[".env*",".git/**"]},"requiredDecisions":["facts-first","app-module-activation","tenant-scope","smallest-validation"],"forbiddenPatterns":["node_modules.{0,40}(?:write|edit|patch)","(?:tenant|organization).{0,30}(?:unscoped|scope optional)"],"validators":["catalog.schema","owner.reference","skills.reference","router.contract","context.budget","context.forbidden","patterns.forbidden"],"maxContextFiles":11,"maxInitialContextBytes":57344,"maxTotalContextBytes":147456,"relatedCases":["OMH-002","OMH-195"]},
|
|
13586
|
-
{"id":"OMH-202","title":"Keep stock counts per warehouse honest and hold goods for confirmed orders without a new module","family":"architecture","mode":"analysis","evaluationKind":"routing","risk":"medium","prompt":"In a freshly scaffolded standalone Open Mercato app, the operations team keeps stock for two storage sites in a spreadsheet: how much of each product sits in which aisle, how much is already promised to confirmed orders, and what has been received, moved, or counted since the last check. They also want a heads-up when an item drops below the level they consider safe. Decide the smallest safe design against what the installed modules already provide, say how that capability is turned on for this application, keep tenant and organization boundaries intact, state the access-control posture for viewing counts versus adjusting them, and identify the smallest relevant validation. Do not model your own stock, location, or reservation tables before establishing whether an installed module already owns this capability.","tags":["architecture","module-facts","reuse-installed","stock-on-hand"],"owner":{"kind":"facts","path":".ai/guides/modules/wms.md","ruleIds":["BC-01","BC-04"]},"expectedRouter":{"required":["architecture"],"allowedExtra":["module-data","backend-ui","umes","framework-context"]},"requiredSkills":["om-help"],"context":{"required":["AGENTS.md",".ai/guides/architecture.md",".ai/skills/om-help/SKILL.md",".ai/guides/modules/wms.md"],"allowedExtra":[".ai/guides/contracts.md",".ai/guides/modules/catalog.md",".ai/guides/modules/sales.md",".ai/skills/om-data-model-design/SKILL.md"],"warn":["node_modules/@open-mercato/*/src/**"],"forbidden":[".env*",".git/**"]},"requiredDecisions":["facts-first","app-module-activation","tenant-scope","acl-features","smallest-validation"],"forbiddenPatterns":["node_modules.{0,40}(?:write|edit|patch)","(?:tenant|organization).{0,30}(?:unscoped|scope optional)"],"validators":["catalog.schema","owner.reference","skills.reference","router.contract","context.budget","context.forbidden","patterns.forbidden"],"maxContextFiles":11,"maxInitialContextBytes":57344,"maxTotalContextBytes":147456,"relatedCases":["OMH-002","OMH-194"]}
|
|
13589
|
+
{"id":"OMH-202","title":"Keep stock counts per warehouse honest and hold goods for confirmed orders without a new module","family":"architecture","mode":"analysis","evaluationKind":"routing","risk":"medium","prompt":"In a freshly scaffolded standalone Open Mercato app, the operations team keeps stock for two storage sites in a spreadsheet: how much of each product sits in which aisle, how much is already promised to confirmed orders, and what has been received, moved, or counted since the last check. They also want a heads-up when an item drops below the level they consider safe. Decide the smallest safe design against what the installed modules already provide, say how that capability is turned on for this application, keep tenant and organization boundaries intact, state the access-control posture for viewing counts versus adjusting them, and identify the smallest relevant validation. Do not model your own stock, location, or reservation tables before establishing whether an installed module already owns this capability.","tags":["architecture","module-facts","reuse-installed","stock-on-hand"],"owner":{"kind":"facts","path":".ai/guides/modules/wms.md","ruleIds":["BC-01","BC-04"]},"expectedRouter":{"required":["architecture"],"allowedExtra":["module-data","backend-ui","umes","framework-context"]},"requiredSkills":["om-help"],"context":{"required":["AGENTS.md",".ai/guides/architecture.md",".ai/skills/om-help/SKILL.md",".ai/guides/modules/wms.md"],"allowedExtra":[".ai/guides/contracts.md",".ai/guides/modules/catalog.md",".ai/guides/modules/sales.md",".ai/skills/om-data-model-design/SKILL.md"],"warn":["node_modules/@open-mercato/*/src/**"],"forbidden":[".env*",".git/**"]},"requiredDecisions":["facts-first","app-module-activation","tenant-scope","acl-features","smallest-validation"],"forbiddenPatterns":["node_modules.{0,40}(?:write|edit|patch)","(?:tenant|organization).{0,30}(?:unscoped|scope optional)"],"validators":["catalog.schema","owner.reference","skills.reference","router.contract","context.budget","context.forbidden","patterns.forbidden"],"maxContextFiles":11,"maxInitialContextBytes":57344,"maxTotalContextBytes":147456,"relatedCases":["OMH-002","OMH-194"]},
|
|
13590
|
+
{
|
|
13591
|
+
"id": "OMH-203",
|
|
13592
|
+
"title": "Evaluate CRM detail-tab UMES routing",
|
|
13593
|
+
"family": "umes",
|
|
13594
|
+
"mode": "analysis",
|
|
13595
|
+
"evaluationKind": "routing",
|
|
13596
|
+
"risk": "medium",
|
|
13597
|
+
"prompt": "How can I add a tab to the customer page?",
|
|
13598
|
+
"tags": [
|
|
13599
|
+
"umes",
|
|
13600
|
+
"backend-ui",
|
|
13601
|
+
"framework-context"
|
|
13602
|
+
],
|
|
13603
|
+
"owner": {
|
|
13604
|
+
"kind": "skill",
|
|
13605
|
+
"path": ".ai/skills/om-system-extension/SKILL.md",
|
|
13606
|
+
"ruleIds": [
|
|
13607
|
+
"BC-06"
|
|
13608
|
+
]
|
|
13609
|
+
},
|
|
13610
|
+
"expectedRouter": {
|
|
13611
|
+
"required": [
|
|
13612
|
+
"umes",
|
|
13613
|
+
"backend-ui",
|
|
13614
|
+
"framework-context"
|
|
13615
|
+
]
|
|
13616
|
+
},
|
|
13617
|
+
"requiredSkills": [
|
|
13618
|
+
"om-system-extension",
|
|
13619
|
+
"om-backend-ui-design",
|
|
13620
|
+
"om-framework-context"
|
|
13621
|
+
],
|
|
13622
|
+
"context": {
|
|
13623
|
+
"required": [
|
|
13624
|
+
"AGENTS.md",
|
|
13625
|
+
".ai/guides/extensions.md",
|
|
13626
|
+
".ai/guides/backend-ui.md",
|
|
13627
|
+
".ai/guides/modules/customers.md",
|
|
13628
|
+
".ai/skills/om-system-extension/SKILL.md",
|
|
13629
|
+
".ai/skills/om-backend-ui-design/SKILL.md",
|
|
13630
|
+
".ai/skills/om-framework-context/SKILL.md"
|
|
13631
|
+
],
|
|
13632
|
+
"allowedExtra": [
|
|
13633
|
+
".ai/skills/om-system-extension/references/extension-branches.md",
|
|
13634
|
+
".ai/skills/om-system-extension/references/mechanism-selector.md"
|
|
13635
|
+
],
|
|
13636
|
+
"forbidden": [
|
|
13637
|
+
".env*",
|
|
13638
|
+
".git/**",
|
|
13639
|
+
"node_modules/**"
|
|
13640
|
+
]
|
|
13641
|
+
},
|
|
13642
|
+
"requiredDecisions": [
|
|
13643
|
+
"extension-mechanism",
|
|
13644
|
+
"additive-before-replacement",
|
|
13645
|
+
"extension-entity",
|
|
13646
|
+
"eject-last",
|
|
13647
|
+
"widget-injection-files",
|
|
13648
|
+
"person-detail-tab-spot",
|
|
13649
|
+
"company-detail-tab-spot",
|
|
13650
|
+
"guidance-before-framework-context",
|
|
13651
|
+
"installed-packages-read-only"
|
|
13652
|
+
],
|
|
13653
|
+
"forbiddenPatterns": [
|
|
13654
|
+
"node_modules.{0,40}(?:write|edit|patch)",
|
|
13655
|
+
"(?:tenant|organization).{0,30}(?:unscoped|scope optional)"
|
|
13656
|
+
],
|
|
13657
|
+
"validators": [
|
|
13658
|
+
"catalog.schema",
|
|
13659
|
+
"owner.reference",
|
|
13660
|
+
"skills.reference",
|
|
13661
|
+
"router.contract",
|
|
13662
|
+
"context.budget",
|
|
13663
|
+
"context.forbidden",
|
|
13664
|
+
"patterns.forbidden"
|
|
13665
|
+
],
|
|
13666
|
+
"frameworkContext": [
|
|
13667
|
+
{
|
|
13668
|
+
"module": "customers",
|
|
13669
|
+
"query": "detail:customers."
|
|
13670
|
+
}
|
|
13671
|
+
],
|
|
13672
|
+
"maxContextFiles": 10,
|
|
13673
|
+
"maxInitialContextBytes": 57344,
|
|
13674
|
+
"maxTotalContextBytes": 131072,
|
|
13675
|
+
"relatedCases": [
|
|
13676
|
+
"OMH-026",
|
|
13677
|
+
"OMH-027"
|
|
13678
|
+
]
|
|
13679
|
+
}
|
|
13587
13680
|
]
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"$id": "https://open-mercato.dev/schemas/standalone-harness-cases.schema.json",
|
|
4
4
|
"title": "Open Mercato standalone harness case catalog",
|
|
5
5
|
"type": "array",
|
|
6
|
-
"minItems":
|
|
7
|
-
"maxItems":
|
|
6
|
+
"minItems": 203,
|
|
7
|
+
"maxItems": 203,
|
|
8
8
|
"items": {
|
|
9
9
|
"type": "object",
|
|
10
10
|
"additionalProperties": false,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"maxTotalContextBytes", "relatedCases"
|
|
16
16
|
],
|
|
17
17
|
"properties": {
|
|
18
|
-
"id": { "type": "string", "pattern": "^OMH-(00[1-9]|0[1-9][0-9]|1[0-9][0-9]|20[0-
|
|
18
|
+
"id": { "type": "string", "pattern": "^OMH-(00[1-9]|0[1-9][0-9]|1[0-9][0-9]|20[0-3])$" },
|
|
19
19
|
"title": { "type": "string", "minLength": 12, "maxLength": 180 },
|
|
20
20
|
"family": { "enum": ["architecture", "module", "umes", "integration", "ai-workflow", "bugfix", "business", "testing"] },
|
|
21
21
|
"mode": { "enum": ["analysis", "one-shot", "spec", "bugfix", "review"] },
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Replace with a concise durable rule"
|
|
3
|
+
modules: ["platform"]
|
|
4
|
+
areas: ["architecture"]
|
|
5
|
+
topics: ["replace-me", "second-topic"]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Replace with a concise durable rule
|
|
9
|
+
|
|
10
|
+
**Context**: Describe the concrete failure or correction that produced the lesson.
|
|
11
|
+
|
|
12
|
+
**Problem**: Explain why the failure recurs or why the obvious approach is unsafe.
|
|
13
|
+
|
|
14
|
+
**Rule**: State the reusable instruction future agents should follow.
|
|
15
|
+
|
|
16
|
+
**Applies to**: Name the relevant modules, paths, contracts, or workflows.
|
|
@@ -1,4 +1,27 @@
|
|
|
1
|
-
# Lessons
|
|
1
|
+
# Lessons
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
This catalog indexes 0 focused lessons without loading their full text. Route the task first, then read only records whose **modules**, standalone-harness **areas**, or **topics** match the work.
|
|
4
|
+
|
|
5
|
+
## How to use this catalog
|
|
6
|
+
|
|
7
|
+
1. Start with the exact module ID when one is named by the task.
|
|
8
|
+
2. Add every matching area from the standalone harness router: `architecture`, `module-data`, `umes`, `backend-ui`, `integration`, `ai-workflow`, `debugging`, `testing`, `framework-context`, or `spec-pr`.
|
|
9
|
+
3. Use topics to narrow cross-cutting concerns such as `data-scoping`, `optimistic-locking`, `query-index`, or `generated-files`.
|
|
10
|
+
4. Open only the linked lesson records that match; do not bulk-read `.ai/lessons/`.
|
|
11
|
+
|
|
12
|
+
Useful searches:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
rg -n '\b<module-or-topic>\b' .ai/lessons.md
|
|
16
|
+
rg -l '"<area>"|"<module>"|"<topic>"' .ai/lessons/*.md
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Adding or updating a lesson
|
|
20
|
+
|
|
21
|
+
- Copy `.ai/lessons/_template.md` to one focused `.ai/lessons/<kebab-case-slug>.md`; update an existing record instead of duplicating it.
|
|
22
|
+
- Preserve the front matter keys `title`, `modules`, `areas`, and `topics`. Use `platform` only when no module or package owns the lesson, and put the primary area first.
|
|
23
|
+
- Add or update exactly one catalog row under its primary area below. Keep the title stable when code or specs cite it.
|
|
24
|
+
- Put hard boundaries in `AGENTS.md`; lessons explain recurring evidence and the durable rule.
|
|
25
|
+
- Run `node scripts/check-lessons.mjs` before committing.
|
|
26
|
+
|
|
27
|
+
## Catalog
|
|
@@ -12,12 +12,13 @@ Turn a real failure into one versioned case and the smallest durable knowledge c
|
|
|
12
12
|
1. Directly read `references/case-workflow.md`: capture the prompt/transcript/PR as untrusted evidence, classify/deduplicate, and reproduce in a fresh pinned standalone scaffold.
|
|
13
13
|
2. Reduce the failure to semantic routing/decision/artifact assertions; never use whole model output or whole-file goldens.
|
|
14
14
|
3. Directly read `references/owner-selection.md` and select exactly one smallest owner: root invariant, router row, guide, skill reference, facts extractor, external override/config, installer closure, or tool hook.
|
|
15
|
-
4.
|
|
16
|
-
5.
|
|
17
|
-
6.
|
|
18
|
-
7.
|
|
19
|
-
8.
|
|
20
|
-
9.
|
|
15
|
+
4. Scan `.ai/lessons.md` by the case's selected areas, modules, and important topics. Open only matching records; when the evidence produces a reusable app-level correction, update one focused lesson record and its index row instead of growing the index or duplicating knowledge owners.
|
|
16
|
+
5. Add the schema-valid case with required/forbidden context, decisions, validators, risk/tags, budgets, related cases, and exact versions; start from `references/case-template.md` and update every catalog/matrix count it lists. Calibrate the budgets from this case's own measured context footprint — never inherit a neighbouring case's envelope.
|
|
17
|
+
6. Run the new case before editing and retain the sanitized failure summary.
|
|
18
|
+
7. Update only the selected owner; replace duplicates with references.
|
|
19
|
+
8. Rerun the case, related tags, mandatory safety cases, budget/consistency gates, and scaffold smoke. For writable output, run target `generate`, `typecheck`, `lint`, and `build`, plus the smallest generated unit/integration tests when applicable.
|
|
20
|
+
9. Run mandatory review: review the harness diff with `om-code-review`, and use the isolated `om-judge-agent-session` lane for every eligible implementation result. Resolve artifact findings and improve the named smallest harness owners before continuing.
|
|
21
|
+
10. From a fresh controller scaffold, finish with `yarn harness:release --runner <codex|claude> --prepare-targets <absolute-empty-dir> --acknowledge-writes`; require its sanitized release report to pass. One selected primary owns every blocking lane. Optionally add the different authenticated runner with `--portability-runner <runner>` for the representative read-only portability lane. Report before/after evidence and exact tool/model versions.
|
|
21
22
|
|
|
22
23
|
## Rules
|
|
23
24
|
|
|
@@ -13,7 +13,7 @@ Route before reading: choose routes from the request and mechanism selector, the
|
|
|
13
13
|
|
|
14
14
|
1. Read `.ai/guides/extensions.md` and `references/mechanism-selector.md`; choose UMES, supported override, package, or eject.
|
|
15
15
|
2. Resolve host entity/route/spot/component/event IDs from generated facts. Invoke `om-framework-context` only when facts omit the needed contract.
|
|
16
|
-
3. Follow the
|
|
16
|
+
3. Follow the matching `references/extension-branches.md` branch. UI widgets live in `src/modules/<id>/widgets/injection/**` and register an exact facts- or context-resolved spot in `src/modules/<id>/widgets/injection-table.ts`. For `entry.overrides`, load `references/unified-overrides.md` and select the exact domain/key.
|
|
17
17
|
4. Invoke `om-data-model-design` only when the extension adds app-owned persistence; an enricher/interceptor/widget-only round trip does not need it.
|
|
18
18
|
5. For editable additions, follow `references/read-write-roundtrip.md`; implement input, authenticated write, stored data, list/detail read, UI hydration, clear-to-null, and conflict behavior. An editable addition that must survive reload is necessarily a persisted field: select `module-data`, read contracts, and invoke `om-data-model-design`.
|
|
19
19
|
6. Run `yarn generate`; verify host-present/absent, authorized/denied/wildcard, cache/search, and failure fallback using `references/verification.md`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Unified Override Domains
|
|
2
2
|
|
|
3
|
-
Load this reference when `src/modules.ts` must disable or replace an installed contribution. `entry.overrides` is the only canonical app-level override umbrella. Resolve
|
|
3
|
+
Load this reference when `src/modules.ts` must disable or replace an installed contribution. `entry.overrides` is the only canonical app-level override umbrella. Resolve the exact key from the named module sheet's generated `overrideTargets` facts first: each target carries the exact top-level `domain`, the complete `path` segments, the terminal `key` for keyed-record domains, the supported `modes`, a `factRef` to the module-owned fact being overridden, and a portable `source`. Follow `factRef` provenance to the underlying route/page/event/worker/widget/agent/tool/setup/ACL/DI/encryption fact. When the module sheet reports an `overrideTargetDiagnostics` entry (`missing-owned-fact`, `missing-source`, `unsupported-dynamic-key`, `unknown-framework-domain`) for the domain you need, treat it as a blocker and escalate to the named source for one missing detail — never guess a key. Framework-only settings such as `nav.groupOrder` live in `framework-extension-points.md`, not in a module's `overrideTargets`. For keyed override maps, `null` disables a supported contribution and a typed value replaces it; AI extensions and setup hooks have the special shapes below.
|
|
4
4
|
|
|
5
5
|
| Domain | Shape/key |
|
|
6
6
|
|---|---|
|
|
@@ -30,7 +30,9 @@ Load this reference when `src/modules.ts` must disable or replace an installed c
|
|
|
30
30
|
- API route methods are case-insensitive and paths normalize a leading/trailing slash; use the canonical displayed key. Disabling all methods removes the route entry.
|
|
31
31
|
- Page keys name generated frontend/backend URL paths, not filesystem paths. After hiding a landing page, provide a safe accessible redirect.
|
|
32
32
|
- Injection/dashboard keys are generated entry keys and may differ from widget metadata IDs. Worker keys are resolved descriptor IDs; inspect generated facts/source instead of constructing them from memory.
|
|
33
|
-
- Require the generated
|
|
33
|
+
- Require the generated `overrideTargets` entry's exact `domain`, `path`/`key`, and `modes` (`disable-replace`, `replace`, or `additive`). A stale or `unresolved` first-party correlation, or an `overrideTargetDiagnostics` row for that domain, is a blocker; it is not a fallback to a guessed key. Copy the `path` verbatim — do not flatten nested widget/AI segments.
|
|
34
|
+
- `overrides.guards` targets backend/frontend page route middleware only (the `page-middleware` owned fact). A `data/guards.ts` mutation guard is a different contract with no override domain; it surfaces as an extension activation, never under `overrides.guards`. The target's `page-middleware-not-mutation-guard` note flags this.
|
|
35
|
+
- Before adding a contribution, check the target module sheet's `incoming` index for an already-installed contributor of the same kind/target, and require a `bound` `activation` (not a broad capability host) before assuming a route/entity extension actually runs. A `capability-only` host is available but unproven; do not claim runtime invocation from it.
|
|
34
36
|
- Keep AI extensions additive and ordered. Use agent/tool maps only for replacement or disable; do not encode an extension as a `null`-capable map entry.
|
|
35
37
|
- Preserve replacement type/signature, auth/features, scope, stable ID, and observable side effects. A replacement may strengthen safety but never silently narrow a public contract.
|
|
36
38
|
- Treat unknown/stale override diagnostics as failures. Run `yarn generate`, clear structural nav/module caches with the app's documented command, and verify the old contribution is absent in every bootstrap.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
6
|
+
|
|
7
|
+
export const LESSON_AREAS = new Set([
|
|
8
|
+
'architecture',
|
|
9
|
+
'module-data',
|
|
10
|
+
'umes',
|
|
11
|
+
'backend-ui',
|
|
12
|
+
'integration',
|
|
13
|
+
'ai-workflow',
|
|
14
|
+
'debugging',
|
|
15
|
+
'testing',
|
|
16
|
+
'framework-context',
|
|
17
|
+
'spec-pr',
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
const INDEX_MAX_BYTES = 30 * 1024
|
|
21
|
+
const RECORD_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*\.md$/
|
|
22
|
+
const MODULE_PATTERN = /^[a-z0-9]+(?:_[a-z0-9]+)*$/
|
|
23
|
+
const TOPIC_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
|
24
|
+
const INDEX_ROW_PATTERN = /^- \[(.+)]\(lessons\/([a-z0-9-]+\.md)\) — area:([^;]+); module:([^;]+); topic:(.+)$/
|
|
25
|
+
|
|
26
|
+
function parseFrontMatter(source, relativePath, errors) {
|
|
27
|
+
const match = source.match(/^---\n([\s\S]*?)\n---\n\n# (.+)\n/)
|
|
28
|
+
if (!match) {
|
|
29
|
+
errors.push(`${relativePath}: expected JSON-valued front matter followed by one H1`)
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const metadata = {}
|
|
34
|
+
for (const line of match[1].split('\n')) {
|
|
35
|
+
const separator = line.indexOf(':')
|
|
36
|
+
if (separator < 1) {
|
|
37
|
+
errors.push(`${relativePath}: malformed front matter line ${JSON.stringify(line)}`)
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
const key = line.slice(0, separator)
|
|
41
|
+
try {
|
|
42
|
+
metadata[key] = JSON.parse(line.slice(separator + 1).trim())
|
|
43
|
+
} catch {
|
|
44
|
+
errors.push(`${relativePath}: ${key} must use valid JSON syntax`)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const expectedKeys = ['title', 'modules', 'areas', 'topics']
|
|
49
|
+
const actualKeys = Object.keys(metadata).sort()
|
|
50
|
+
if (JSON.stringify(actualKeys) !== JSON.stringify([...expectedKeys].sort())) {
|
|
51
|
+
errors.push(`${relativePath}: front matter keys must be exactly ${expectedKeys.join(', ')}`)
|
|
52
|
+
}
|
|
53
|
+
if (typeof metadata.title !== 'string' || metadata.title.length === 0) {
|
|
54
|
+
errors.push(`${relativePath}: title must be a non-empty string`)
|
|
55
|
+
} else if (metadata.title !== match[2]) {
|
|
56
|
+
errors.push(`${relativePath}: front matter title must match the H1`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const key of ['modules', 'areas', 'topics']) {
|
|
60
|
+
if (!Array.isArray(metadata[key])) errors.push(`${relativePath}: ${key} must be an array`)
|
|
61
|
+
}
|
|
62
|
+
if (!Array.isArray(metadata.modules) || metadata.modules.length === 0) {
|
|
63
|
+
errors.push(`${relativePath}: modules must contain an owning module/package or platform`)
|
|
64
|
+
} else if (metadata.modules.some((value) => typeof value !== 'string' || !MODULE_PATTERN.test(value))) {
|
|
65
|
+
errors.push(`${relativePath}: modules must use lowercase snake_case tags`)
|
|
66
|
+
}
|
|
67
|
+
if (!Array.isArray(metadata.areas) || metadata.areas.length === 0) {
|
|
68
|
+
errors.push(`${relativePath}: areas must contain at least one standalone router area`)
|
|
69
|
+
} else if (metadata.areas.some((value) => !LESSON_AREAS.has(value))) {
|
|
70
|
+
errors.push(`${relativePath}: areas contain a value outside the standalone router taxonomy`)
|
|
71
|
+
}
|
|
72
|
+
if (!Array.isArray(metadata.topics) || metadata.topics.length < 2 || metadata.topics.length > 6) {
|
|
73
|
+
errors.push(`${relativePath}: topics must contain between two and six important concepts`)
|
|
74
|
+
} else if (metadata.topics.some((value) => typeof value !== 'string' || !TOPIC_PATTERN.test(value))) {
|
|
75
|
+
errors.push(`${relativePath}: topics must use lowercase kebab-case tags`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const key of ['modules', 'areas', 'topics']) {
|
|
79
|
+
if (Array.isArray(metadata[key]) && new Set(metadata[key]).size !== metadata[key].length) {
|
|
80
|
+
errors.push(`${relativePath}: ${key} must not contain duplicates`)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return metadata
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function checkLessonsCatalog(rootDir) {
|
|
88
|
+
const errors = []
|
|
89
|
+
const indexPath = path.join(rootDir, '.ai', 'lessons.md')
|
|
90
|
+
const lessonsDir = path.join(rootDir, '.ai', 'lessons')
|
|
91
|
+
if (!fs.existsSync(indexPath)) return [`${indexPath}: lesson index is missing`]
|
|
92
|
+
|
|
93
|
+
const indexSource = fs.readFileSync(indexPath, 'utf8')
|
|
94
|
+
const indexBytes = Buffer.byteLength(indexSource)
|
|
95
|
+
if (indexBytes > INDEX_MAX_BYTES) {
|
|
96
|
+
errors.push(`.ai/lessons.md: ${indexBytes} bytes exceeds the ${INDEX_MAX_BYTES}-byte progressive-loading budget`)
|
|
97
|
+
}
|
|
98
|
+
if (/\*\*(?:Context|Problem|Rule|Applies to)\*\*:/.test(indexSource)) {
|
|
99
|
+
errors.push('.ai/lessons.md: full lesson prose belongs in .ai/lessons/, not the catalog')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const recordNames = fs.existsSync(lessonsDir)
|
|
103
|
+
? fs.readdirSync(lessonsDir, { withFileTypes: true })
|
|
104
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.md') && !entry.name.startsWith('_'))
|
|
105
|
+
.map((entry) => entry.name)
|
|
106
|
+
.sort()
|
|
107
|
+
: []
|
|
108
|
+
const records = new Map()
|
|
109
|
+
const titles = new Set()
|
|
110
|
+
for (const fileName of recordNames) {
|
|
111
|
+
const relativePath = `.ai/lessons/${fileName}`
|
|
112
|
+
if (!RECORD_NAME_PATTERN.test(fileName)) errors.push(`${relativePath}: filename must use kebab-case`)
|
|
113
|
+
const metadata = parseFrontMatter(fs.readFileSync(path.join(lessonsDir, fileName), 'utf8'), relativePath, errors)
|
|
114
|
+
if (!metadata) continue
|
|
115
|
+
if (titles.has(metadata.title)) errors.push(`${relativePath}: duplicate lesson title ${JSON.stringify(metadata.title)}`)
|
|
116
|
+
titles.add(metadata.title)
|
|
117
|
+
records.set(fileName, metadata)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const indexed = new Set()
|
|
121
|
+
for (const line of indexSource.split('\n')) {
|
|
122
|
+
if (!line.startsWith('- [')) continue
|
|
123
|
+
const match = line.match(INDEX_ROW_PATTERN)
|
|
124
|
+
if (!match) {
|
|
125
|
+
errors.push(`.ai/lessons.md: malformed catalog row ${JSON.stringify(line)}`)
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
128
|
+
const [, title, fileName, areaText, moduleText, topicText] = match
|
|
129
|
+
if (indexed.has(fileName)) errors.push(`.ai/lessons.md: duplicate link to lessons/${fileName}`)
|
|
130
|
+
indexed.add(fileName)
|
|
131
|
+
const metadata = records.get(fileName)
|
|
132
|
+
if (!metadata) {
|
|
133
|
+
errors.push(`.ai/lessons.md: lessons/${fileName} does not exist`)
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
const indexedMetadata = {
|
|
137
|
+
title,
|
|
138
|
+
areas: areaText.split(','),
|
|
139
|
+
modules: moduleText.split(','),
|
|
140
|
+
topics: topicText.split(','),
|
|
141
|
+
}
|
|
142
|
+
for (const key of ['title', 'areas', 'modules', 'topics']) {
|
|
143
|
+
if (JSON.stringify(indexedMetadata[key]) !== JSON.stringify(metadata[key])) {
|
|
144
|
+
errors.push(`.ai/lessons.md: ${key} drift for lessons/${fileName}`)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const fileName of records.keys()) {
|
|
150
|
+
if (!indexed.has(fileName)) errors.push(`.ai/lessons/${fileName}: record is missing from the catalog`)
|
|
151
|
+
}
|
|
152
|
+
const declaredCount = indexSource.match(/catalog indexes (\d+) focused lessons/)
|
|
153
|
+
if (!declaredCount || Number(declaredCount[1]) !== records.size) {
|
|
154
|
+
errors.push(`.ai/lessons.md: declared lesson count must equal ${records.size}`)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return errors
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function parseRoot(args) {
|
|
161
|
+
if (args.length === 0) return process.cwd()
|
|
162
|
+
if (args.length === 2 && args[0] === '--root') return path.resolve(args[1])
|
|
163
|
+
throw new Error('Usage: check-lessons.mjs [--root <app-root>]')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function main(args = process.argv.slice(2)) {
|
|
167
|
+
const rootDir = parseRoot(args)
|
|
168
|
+
const errors = checkLessonsCatalog(rootDir)
|
|
169
|
+
if (errors.length > 0) {
|
|
170
|
+
for (const error of errors) console.error(`- ${error}`)
|
|
171
|
+
process.exitCode = 1
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
console.log(`Lessons catalog is valid: ${rootDir}`)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const entryPath = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : null
|
|
178
|
+
if (entryPath === import.meta.url) main()
|