@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13
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/CHANGELOG.md +3448 -0
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +47 -23
- package/QUICKSTART.md +189 -12
- package/README.md +274 -65
- package/ROADMAP.md +572 -31
- package/SCHEMA.md +1709 -155
- package/WORKFLOW.md +298 -72
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/adopt/render.js +50 -9
- package/dist/commands/adopt/render.js.map +1 -1
- package/dist/commands/archive/archive.js +16 -19
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/closing.js +42 -0
- package/dist/commands/archive/closing.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +77 -43
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/document/head.js +151 -0
- package/dist/commands/archive/plan/document/head.js.map +1 -0
- package/dist/commands/archive/plan/document/links.js +284 -0
- package/dist/commands/archive/plan/document/links.js.map +1 -0
- package/dist/commands/archive/plan/document/run.js +136 -0
- package/dist/commands/archive/plan/document/run.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +21 -19
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +167 -30
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +14 -19
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +137 -56
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +3 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +134 -30
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +85 -18
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +29 -7
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +102 -14
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +17 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/doctor.js +60 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +89 -13
- package/dist/commands/explain/explain.js.map +1 -1
- package/dist/commands/explore.js +16 -10
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +5 -7
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +44 -29
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +52 -9
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +37 -34
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +55 -26
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +158 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +13 -11
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/feature.js +9 -5
- package/dist/commands/show/feature.js.map +1 -1
- package/dist/commands/show/introduced.js +157 -0
- package/dist/commands/show/introduced.js.map +1 -0
- package/dist/commands/show/marks.js +35 -6
- package/dist/commands/show/marks.js.map +1 -1
- package/dist/commands/show/service.js +41 -33
- package/dist/commands/show/service.js.map +1 -1
- package/dist/commands/show/show.js +17 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/fleet-next.js +90 -0
- package/dist/commands/status/fleet-next.js.map +1 -0
- package/dist/commands/status/json.js +2 -0
- package/dist/commands/status/json.js.map +1 -1
- package/dist/commands/status/print.js +89 -37
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +103 -23
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +7 -7
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +106 -46
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +289 -74
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/links.js +128 -0
- package/dist/commands/subsystem/txn/links.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +147 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +200 -21
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/actions.js +107 -0
- package/dist/commands/tasks/actions.js.map +1 -0
- package/dist/commands/tasks/render.js +253 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +119 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +42 -26
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +83 -33
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +78 -27
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +34 -126
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.js.map +1 -1
- package/dist/commands/validate/checks/introduced.js +102 -0
- package/dist/commands/validate/checks/introduced.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +11 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/coverage/covers-after.js +105 -0
- package/dist/commands/validate/coverage/covers-after.js.map +1 -0
- package/dist/commands/validate/feature.js +26 -20
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +37 -13
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +144 -126
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/load.js +66 -0
- package/dist/commands/validate/fleet/load.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +98 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +107 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +244 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +54 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +44 -14
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +10 -2
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +50 -15
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +83 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +40 -47
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/render/repeats.js +63 -0
- package/dist/commands/validate/render/repeats.js.map +1 -0
- package/dist/commands/validate/render/rollups.js +115 -0
- package/dist/commands/validate/render/rollups.js.map +1 -0
- package/dist/commands/validate/render/text.js +199 -0
- package/dist/commands/validate/render/text.js.map +1 -0
- package/dist/commands/validate/report.js +36 -160
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +21 -107
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +36 -14
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +121 -61
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +88 -28
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +38 -3
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +85 -60
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +55 -52
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +81 -106
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +21 -9
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +24 -8
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +80 -8
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +88 -10
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +49 -22
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +39 -6
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/protocol.js +1 -1
- package/dist/core/agent/protocol.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/support/report.js +46 -5
- package/dist/core/agent/support/report.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +158 -11
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +78 -39
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +88 -27
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +128 -13
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +96 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +106 -4
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +6 -3
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +68 -27
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +32 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +33 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +12 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +96 -36
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +138 -3
- package/dist/core/brief/checks.js.map +1 -1
- package/dist/core/brief/landscape.js +209 -130
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +84 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +266 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +109 -31
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +14 -3
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/arch.js +90 -182
- package/dist/core/c4/arch.js.map +1 -1
- package/dist/core/c4/direction/direction.js +114 -0
- package/dist/core/c4/direction/direction.js.map +1 -0
- package/dist/core/c4/likec4.js +169 -14
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +160 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/parsed/view-ids.js +39 -1
- package/dist/core/c4/parsed/view-ids.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +134 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +123 -7
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +43 -12
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +79 -43
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +277 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +78 -17
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/resolve/steps.js +290 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/resolve/systems.js +154 -0
- package/dist/core/c4/resolve/systems.js.map +1 -0
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.js.map +1 -0
- package/dist/core/c4/seed/template.js +65 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/created/models.js +96 -0
- package/dist/core/c4/service-model/created/models.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +106 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-mask.js +297 -10
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +222 -141
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +79 -13
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/contract.js +20 -0
- package/dist/core/c4/splice/contract.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +20 -20
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/identity/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +37 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -1
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +182 -114
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +151 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +160 -56
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/splice/text/spot.js +131 -0
- package/dist/core/c4/splice/text/spot.js.map +1 -0
- package/dist/core/c4/workspace.js +13 -19
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/delta/uncovered.js +2 -2
- package/dist/core/capabilities/delta/uncovered.js.map +1 -1
- package/dist/core/capabilities/findings.js +18 -22
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/calls/called.js +52 -0
- package/dist/core/coherence/calls/called.js.map +1 -0
- package/dist/core/coherence/coherence.js +103 -22
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/covers/health.js +85 -0
- package/dist/core/coherence/covers/health.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +147 -16
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +28 -87
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/events/produced.js +100 -0
- package/dist/core/coherence/events/produced.js.map +1 -0
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +51 -6
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/dependencies/fanin.js +2 -2
- package/dist/core/dependencies/fanin.js.map +1 -1
- package/dist/core/deployment/delta.js +191 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +49 -21
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/docs.js +31 -22
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/config.js +7 -3
- package/dist/core/doctor/config.js.map +1 -1
- package/dist/core/doctor/doctor.js +91 -11
- package/dist/core/doctor/doctor.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/doctor/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.js.map +1 -0
- package/dist/core/document/apply.js +158 -11
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +26 -21
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/lookup.js +14 -1
- package/dist/core/explain/lookup.js.map +1 -1
- package/dist/core/explain/refusals.js +5 -5
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms/documents.js +227 -0
- package/dist/core/explain/terms/documents.js.map +1 -0
- package/dist/core/explain/terms/done-check.js +144 -0
- package/dist/core/explain/terms/done-check.js.map +1 -0
- package/dist/core/explain/terms/entry.js +14 -0
- package/dist/core/explain/terms/entry.js.map +1 -0
- package/dist/core/explain/terms/stages.js +71 -0
- package/dist/core/explain/terms/stages.js.map +1 -0
- package/dist/core/explain/terms/tasks.js +97 -0
- package/dist/core/explain/terms/tasks.js.map +1 -0
- package/dist/core/explain/terms.js +68 -109
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +40 -10
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +81 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +11 -4
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +69 -28
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/delta.js +10 -7
- package/dist/core/glossary/delta.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
- package/dist/core/kernel/path-safety.js +38 -0
- package/dist/core/kernel/path-safety.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +51 -37
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/links/findings.js.map +1 -1
- package/dist/core/links/parse.js +38 -9
- package/dist/core/links/parse.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +40 -10
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +301 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +28 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +17 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +43 -4
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/pack/pack.js +50 -6
- package/dist/core/pack/pack.js.map +1 -1
- package/dist/core/projection/arch-slice.js +2 -2
- package/dist/core/projection/arch-slice.js.map +1 -1
- package/dist/core/projection/introduced.js +14 -0
- package/dist/core/projection/introduced.js.map +1 -0
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/authored/paths.js +18 -0
- package/dist/core/repo/authored/paths.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +92 -6
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +74 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +258 -0
- package/dist/core/repo/tree/render/body.js.map +1 -0
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +42 -0
- package/dist/core/repo/tree/render/stale.js.map +1 -0
- package/dist/core/repo/tree/render/text.js +84 -0
- package/dist/core/repo/tree/render/text.js.map +1 -0
- package/dist/core/repo/tree/render/views.js +293 -0
- package/dist/core/repo/tree/render/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +53 -32
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +121 -13
- package/dist/core/scaffold/landscape.js.map +1 -1
- package/dist/core/scaffold/legacy.js +271 -0
- package/dist/core/scaffold/legacy.js.map +1 -0
- package/dist/core/staging/commit.js +26 -15
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +84 -6
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +188 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +24 -14
- package/dist/core/staging/txn/forward.js.map +1 -1
- package/dist/core/staging/writes.js.map +1 -1
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/contracts.js +39 -15
- package/dist/core/status/contracts.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +33 -8
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +24 -17
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/feature/next.js +20 -7
- package/dist/core/status/feature/next.js.map +1 -1
- package/dist/core/status/feature/words.js +96 -0
- package/dist/core/status/feature/words.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +43 -14
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +153 -58
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/scan.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +199 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/claims.js +31 -7
- package/dist/core/usecases/delta/claims.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/delta/modified.js +315 -0
- package/dist/core/usecases/delta/modified.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +32 -191
- package/dist/core/usecases/delta/overlay.js.map +1 -1
- package/dist/core/usecases/fleet.js +195 -36
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
- package/dist/core/usecases/grade/capability-tag.js.map +1 -0
- package/dist/core/usecases/grade/feature.js +88 -0
- package/dist/core/usecases/grade/feature.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
- package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
- package/dist/core/usecases/grade/steps.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
- package/dist/core/usecases/grade/usecases.js.map +1 -0
- package/dist/core/usecases/operations.js +26 -14
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/place.js.map +1 -1
- package/dist/core/usecases/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +164 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +70 -12
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +4 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +75 -4
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/dist/core/vocabulary/report.js +16 -1
- package/dist/core/vocabulary/report.js.map +1 -1
- package/examples/README.md +54 -11
- package/examples/docs/architecture/landscape.likec4 +62 -6
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +7 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/likec4.config.json +0 -1
- package/examples/docs/services/checkout-web/model.likec4 +39 -56
- package/examples/docs/services/order-service/arch.spec.md +13 -6
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +58 -70
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +38 -37
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -31
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -220
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -165
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -247
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/repo/tree/views.js +0 -172
- package/dist/core/repo/tree/views.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -12,10 +12,11 @@ import { constants, existsSync } from "node:fs";
|
|
|
12
12
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
13
13
|
import { CONFIG_FILENAME, configPath } from "../envelope/config.js";
|
|
14
14
|
import { LOAM_VERSION } from "../envelope/version.js";
|
|
15
|
-
import { landscapePath } from "../repo/paths.js";
|
|
15
|
+
import { landscapePath, LIKEC4_PROJECT_FILENAME } from "../repo/paths.js";
|
|
16
16
|
import { docsRepoState } from "../repo/state.js";
|
|
17
17
|
import { listFeatures, listServices } from "../repo/repo.js";
|
|
18
|
-
import {
|
|
18
|
+
import { introducingFeatures } from "../projection/introduced.js";
|
|
19
|
+
import { LIKEC4_PROJECT_CONFIG } from "../docs.js";
|
|
19
20
|
import { gherkinRoot } from "../gherkin/stamp.js";
|
|
20
21
|
import { UnsafePathError } from "../kernel/path-safety.js";
|
|
21
22
|
import { scanWritePathResidue } from "../staging/recovery/residue.js";
|
|
@@ -23,6 +24,7 @@ import {} from "./report.js";
|
|
|
23
24
|
import { canAccess, inspectConfig, inspectLandscape } from "./config.js";
|
|
24
25
|
import { inspectAgentSurface } from "./agents.js";
|
|
25
26
|
import { gradeWritePathResidue } from "./residue.js";
|
|
27
|
+
import { scanReports } from "./reports/scan.js";
|
|
26
28
|
export async function diagnose(cwd = process.cwd()) {
|
|
27
29
|
const inspected = await inspectConfig(cwd);
|
|
28
30
|
const path = configPath(cwd);
|
|
@@ -124,19 +126,31 @@ export async function diagnose(cwd = process.cwd()) {
|
|
|
124
126
|
// start` — the command loam's own brief recommends — refused to load at all.
|
|
125
127
|
// `loam init --create` writes the project file that scopes the root project to
|
|
126
128
|
// the landscape; a docs repo created before it exists has to be told, because
|
|
127
|
-
// nothing else will ever mention it.
|
|
128
|
-
//
|
|
129
|
+
// nothing else will ever mention it. The root file is also the gate on the
|
|
130
|
+
// per-service ones: `loam subsystem sync` writes a create-only
|
|
131
|
+
// likec4.config.json beside every service model — the LikeC4 project that
|
|
132
|
+
// makes each model render from the same root, beside the fleet map — and it
|
|
133
|
+
// writes none of them until this file exists, so a tree missing it renders
|
|
134
|
+
// nothing from the docs root, not only the map. (Only a feature delta still renders from
|
|
135
|
+
// its own directory; an archived feature would otherwise keep a dead project
|
|
136
|
+
// forever.) The fix below says both, because this finding is the one place a
|
|
137
|
+
// repo that predates either file is ever told. A warning, not a blocker: no
|
|
138
|
+
// loam check reads this file, and a repo without it is only unrenderable.
|
|
129
139
|
if (docsDir !== null && exists && readable && !existsSync(join(docsDir, LIKEC4_PROJECT_FILENAME))) {
|
|
130
140
|
findings.push({
|
|
131
141
|
severity: "warning",
|
|
132
142
|
code: "doctor.likec4-config-missing",
|
|
133
143
|
message: `${docsDir}/${LIKEC4_PROJECT_FILENAME} is missing, so this tree is not a loadable LikeC4 workspace — `
|
|
134
|
-
+ "
|
|
135
|
-
+ "
|
|
144
|
+
+ "a feature delta, and any service model that declares its own `specification` block, has to be "
|
|
145
|
+
+ "excluded from the root project, and pointing a renderer at the repo root with nothing excluded "
|
|
146
|
+
+ "merges them into one model and reports every declaration as a duplicate.",
|
|
136
147
|
fix: `Write ${docsDir}/${LIKEC4_PROJECT_FILENAME} with:\n${LIKEC4_PROJECT_CONFIG.trimEnd()}\n`
|
|
137
|
-
+ "That
|
|
138
|
-
+ "
|
|
139
|
-
+ "
|
|
148
|
+
+ "That makes the docs root one LikeC4 project holding the fleet map and every service model that "
|
|
149
|
+
+ "extends it, so `npx likec4 start` in the docs repo renders both. Then run `loam subsystem sync`: "
|
|
150
|
+
+ "it maintains the `exclude` list above — one `services/<tree>/**` entry per model that stands "
|
|
151
|
+
+ "alone — and writes one create-only likec4.config.json beside each of those, so a standalone model "
|
|
152
|
+
+ "renders from the same root as a project of its own. Only a feature delta still renders from its "
|
|
153
|
+
+ "own directory (`npx likec4 start features/<FEAT>`) — being readable alone is what that file is for.",
|
|
140
154
|
});
|
|
141
155
|
}
|
|
142
156
|
// What a killed writer left behind. Read from `scanWritePathResidue`, whose
|
|
@@ -171,8 +185,12 @@ export async function diagnose(cwd = process.cwd()) {
|
|
|
171
185
|
throw err;
|
|
172
186
|
}
|
|
173
187
|
}
|
|
188
|
+
const configuredService = inspected.config?.service ?? null;
|
|
174
189
|
let services = [];
|
|
175
190
|
let activeFeatures = [];
|
|
191
|
+
// The active features that introduce the bound service, when the living docs
|
|
192
|
+
// do not hold it — see the `doctor.service-unknown` finding below.
|
|
193
|
+
let introducedBy = [];
|
|
176
194
|
// Whether the fleet was actually READ, which is a narrower question than
|
|
177
195
|
// whether reading it was attempted — hence set inside the try and not from
|
|
178
196
|
// the guard, so an enumeration that threw leaves this false alongside its own
|
|
@@ -184,6 +202,13 @@ export async function diagnose(cwd = process.cwd()) {
|
|
|
184
202
|
listServices(docsDir),
|
|
185
203
|
listFeatures(docsDir),
|
|
186
204
|
]);
|
|
205
|
+
// Through the shared resolver, not a filter over the two lists just read,
|
|
206
|
+
// so `doctor` and `status`/`show`/`context`/`steps` agree on what
|
|
207
|
+
// "introduced" means. Inside the try because it reads the same two
|
|
208
|
+
// directories: if they throw here, they threw for the inventory too.
|
|
209
|
+
if (configuredService !== null && !services.some((service) => service.id === configuredService)) {
|
|
210
|
+
introducedBy = (await introducingFeatures(docsDir, configuredService)).map((f) => f.id);
|
|
211
|
+
}
|
|
187
212
|
inventoried = true;
|
|
188
213
|
}
|
|
189
214
|
catch (error) {
|
|
@@ -195,12 +220,13 @@ export async function diagnose(cwd = process.cwd()) {
|
|
|
195
220
|
});
|
|
196
221
|
}
|
|
197
222
|
}
|
|
198
|
-
const configuredService = inspected.config?.service ?? null;
|
|
199
223
|
const currentService = configuredService === null
|
|
200
224
|
? { configured: null, status: "unbound" }
|
|
201
225
|
: services.some((service) => service.id === configuredService)
|
|
202
226
|
? { configured: configuredService, status: "matched" }
|
|
203
|
-
:
|
|
227
|
+
: introducedBy.length > 0
|
|
228
|
+
? { configured: configuredService, status: "unknown", introducedBy }
|
|
229
|
+
: { configured: configuredService, status: "unknown" };
|
|
204
230
|
// Standing INSIDE the docs repo, an absent service binding is the correct
|
|
205
231
|
// state, not a gap: the docs repo is the fleet, it is not any one service, and
|
|
206
232
|
// the fix this finding prints — `loam init --service <id>` — would bind the
|
|
@@ -213,6 +239,7 @@ export async function diagnose(cwd = process.cwd()) {
|
|
|
213
239
|
// filename, is the test.
|
|
214
240
|
const inDocsRepo = configRoot !== undefined && docsDir !== null
|
|
215
241
|
&& resolve(configRoot) === resolve(docsDir);
|
|
242
|
+
const [lead, ...others] = introducedBy;
|
|
216
243
|
if (currentService.status === "unbound" && !inDocsRepo) {
|
|
217
244
|
findings.push({
|
|
218
245
|
severity: "warning",
|
|
@@ -221,6 +248,33 @@ export async function diagnose(cwd = process.cwd()) {
|
|
|
221
248
|
fix: `Run \`loam init --docs ${inspected.config?.docsDirAsWritten ?? "<dir>"} --service <id>\` here.`,
|
|
222
249
|
});
|
|
223
250
|
}
|
|
251
|
+
else if (currentService.status === "unknown" && inventoried && lead !== undefined) {
|
|
252
|
+
// A service a feature in flight INTRODUCES: its only documents are that
|
|
253
|
+
// feature's `specs/<service>/` until it archives, and `services/` learns
|
|
254
|
+
// the name at the merge. The finding used to send this repository's first
|
|
255
|
+
// developer to `loam adopt` — to describe code the feature has not built
|
|
256
|
+
// yet, and to write a `services/<service>/` that would make the service
|
|
257
|
+
// living ahead of the merge, so the feature no longer introduced it. The
|
|
258
|
+
// same code and severity: the binding still names nothing under services/,
|
|
259
|
+
// so a check that reads the living docs for it has nothing to read, which
|
|
260
|
+
// is worth knowing before trusting one. Only the advice was wrong.
|
|
261
|
+
const several = others.length > 0;
|
|
262
|
+
const alsoBy = several ? ` (so does ${others.join(", ")})` : "";
|
|
263
|
+
const until = several ? "one of them" : lead;
|
|
264
|
+
const documents = several
|
|
265
|
+
? `the specs/${configuredService}/ of ${introducedBy.join(", ")} are`
|
|
266
|
+
: `${lead}'s specs/${configuredService}/ are`;
|
|
267
|
+
const likewise = several ? `, and likewise for ${others.join(", ")},` : "";
|
|
268
|
+
findings.push({
|
|
269
|
+
severity: "warning",
|
|
270
|
+
code: "doctor.service-unknown",
|
|
271
|
+
message: `Configured service '${configuredService}' is not under services/ yet: ${lead} introduces it${alsoBy}, `
|
|
272
|
+
+ `and it is not in the living docs until ${until} archives.`,
|
|
273
|
+
fix: `Nothing to adopt — until ${until} archives, ${documents} this service's documents, and that archive `
|
|
274
|
+
+ `is what clears this warning. Run \`loam status ${lead}\`${likewise} to see what this repository owes `
|
|
275
|
+
+ `${several ? "each" : lead}. If this repository is a different service, fix "service" in ${path}.`,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
224
278
|
else if (currentService.status === "unknown" && inventoried) {
|
|
225
279
|
// Only when there was an inventory to be absent from. This finding is a
|
|
226
280
|
// claim about a fleet that was read; with no docsDir, an unreadable one, or
|
|
@@ -246,6 +300,31 @@ export async function diagnose(cwd = process.cwd()) {
|
|
|
246
300
|
// to the repo `init` ran in — the directory holding loam.json, which is not
|
|
247
301
|
// the cwd now that config discovery walks upward.
|
|
248
302
|
const agents = await inspectAgentSurface(dirname(path), inspected.config, docsDir, findings);
|
|
303
|
+
// The problem reports, read beside the config that RESOLVED — `config.root`,
|
|
304
|
+
// the directory the loam.json was found in — because the `loam-report`
|
|
305
|
+
// protocol says "at the current repository root", and config discovery walks
|
|
306
|
+
// upward, so the cwd is not that root.
|
|
307
|
+
//
|
|
308
|
+
// Where nothing PARSED, the directory of the file loam FOUND, which is what
|
|
309
|
+
// `dirname(path)` is: `configPath` returns the discovered loam.json, or the
|
|
310
|
+
// cwd's own spelling of it when there is none, so this is the found root when
|
|
311
|
+
// there is one and the cwd when there is not — the same root the agent surface
|
|
312
|
+
// above is inspected against. Falling back to the raw cwd instead meant that
|
|
313
|
+
// from a subdirectory of a repo whose loam.json is unparseable, `doctor`
|
|
314
|
+
// reported `total 0, next 001` while five reports sat beside the config it had
|
|
315
|
+
// just named in the same envelope, and handed out an ordinal already taken
|
|
316
|
+
// (verification 2026-09-04, second pass). A report corpus in a repo whose
|
|
317
|
+
// loam.json is broken is still worth counting — this is precisely the preflight
|
|
318
|
+
// that runs when the config does not resolve — and the blocker above already
|
|
319
|
+
// names the real problem.
|
|
320
|
+
//
|
|
321
|
+
// No findings come back, and that is the design: this is the one directory
|
|
322
|
+
// loam reads that loam never writes, its contents are the team's, and an open
|
|
323
|
+
// report is not a defect in the repository holding it. `doctor` reports it as
|
|
324
|
+
// state — like `docs`, `counts` and the write path — so the corpus is
|
|
325
|
+
// countable by the protocol that asks for it (`reports.next` is the ordinal
|
|
326
|
+
// the next report takes) without ever becoming a complaint.
|
|
327
|
+
const reports = await scanReports(inspected.config?.root ?? dirname(path));
|
|
249
328
|
return {
|
|
250
329
|
healthy: !findings.some((finding) => finding.severity === "blocker"),
|
|
251
330
|
runtime: {
|
|
@@ -267,6 +346,7 @@ export async function diagnose(cwd = process.cwd()) {
|
|
|
267
346
|
counts: { services: services.length, activeFeatures: activeFeatures.length },
|
|
268
347
|
currentService,
|
|
269
348
|
agents,
|
|
349
|
+
reports,
|
|
270
350
|
writePath,
|
|
271
351
|
serviceWritePath,
|
|
272
352
|
findings,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/core/doctor/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAyC,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAChD,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACnC,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,MAAM,eAAe,sBAAsB,GAAG,GAAG;YAC1D,GAAG,EAAE,2FAA2F;SACjG,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1C,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,WAAW,eAAe,KAAK,SAAS,CAAC,KAAK,IAAI,eAAe,EAAE;YAC5E,GAAG,EAAE,UAAU,IAAI,2CAA2C;SAC/D,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,EAAE,OAAO,IAAI,IAAI,CAAC;IAClD,MAAM,KAAK,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;IAC1D,MAAM,QAAQ,GAAG,MAAM,IAAI,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/F,MAAM,QAAQ,GAAG,MAAM,IAAI,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/F,MAAM,WAAW,GAAG,KAAK,EAAE,IAAI,KAAK,IAAI,CAAC;IACzC,MAAM,aAAa,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACvE,MAAM,SAAS,GAAG,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,aAAa,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IAElG,sEAAsE;IACtE,2EAA2E;IAC3E,6BAA6B;IAC7B,IAAI,KAAK,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,qBAAqB;YAC3B,OAAO,EAAE,wDAAwD,OAAO,EAAE;YAC1E,GAAG,EAAE,oBAAoB,IAAI,4BAA4B,OAAO,+CAA+C;SAChH,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,KAAK,EAAE,IAAI,KAAK,aAAa,EAAE,CAAC;QACzC,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,yBAAyB;YAC/B,OAAO,EAAE,iDAAiD,OAAO,kCAAkC;YACnG,GAAG,EAAE,sBAAsB,IAAI,uDAAuD,OAAO,cAAc;SAC5G,CAAC,CAAC;IACL,CAAC;IAED,6EAA6E;IAC7E,0EAA0E;IAC1E,4EAA4E;IAC5E,uBAAuB;IACvB,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,EAAE,gBAAgB,CAAC;IACrD,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC;IAC1C,IACE,SAAS,KAAK,SAAS;WACpB,UAAU,KAAK,SAAS;WACxB,UAAU,CAAC,SAAS,CAAC;WACrB,OAAO,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,UAAU,CAAC,EAC7C,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EACL,4CAA4C,SAAS,KAAK;gBAC1D,GAAG,eAAe,iEAAiE;YACrF,GAAG,EAAE,wBAAwB,IAAI,oDAAoD;SACtF,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,wBAAwB;YAC9B,OAAO,EAAE,4CAA4C,OAAO,EAAE;YAC9D,GAAG,EAAE,wBAAwB,OAAO,8BAA8B;SACnE,CAAC,CAAC;IACL,CAAC;IACD,IAAI,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EAAE,0EAA0E,OAAO,EAAE;YAC5F,GAAG,EAAE,yBAAyB,OAAO,gEAAgE;SACtG,CAAC,CAAC;IACL,CAAC;IACD,IAAI,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACzB,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,0BAA0B;YAChC,OAAO,EAAE,GAAG,aAAa,gEAAgE;YACzF,GAAG,EAAE,qIAAqI;SAC3I,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,MAAM,IAAI,aAAa,KAAK,IAAI,IAAI,QAAQ,EAAE,CAAC;QACxD,MAAM,gBAAgB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAClD,CAAC;IAED,0EAA0E;IAC1E,+EAA+E;IAC/E,+EAA+E;IAC/E,0EAA0E;IAC1E,6EAA6E;IAC7E,yEAAyE;IACzE,6EAA6E;IAC7E,+EAA+E;IAC/E,8EAA8E;IAC9E,6EAA6E;IAC7E,+DAA+D;IAC/D,IAAI,OAAO,KAAK,IAAI,IAAI,MAAM,IAAI,QAAQ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,EAAE,CAAC;QAClG,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,8BAA8B;YACpC,OAAO,EACL,GAAG,OAAO,IAAI,uBAAuB,iEAAiE;kBACpG,+FAA+F;kBAC/F,oGAAoG;YACxG,GAAG,EACD,SAAS,OAAO,IAAI,uBAAuB,WAAW,qBAAqB,CAAC,OAAO,EAAE,IAAI;kBACvF,gGAAgG;kBAChG,kGAAkG;kBAClG,kGAAkG;SACvG,CAAC,CAAC;IACL,CAAC;IAED,4EAA4E;IAC5E,yEAAyE;IACzE,2EAA2E;IAC3E,6DAA6D;IAC7D,MAAM,SAAS,GAAG,OAAO,KAAK,IAAI,IAAI,MAAM,IAAI,QAAQ;QACtD,CAAC,CAAC,MAAM,oBAAoB,CAAC,OAAO,CAAC;QACrC,CAAC,CAAC,IAAI,CAAC;IACT,IAAI,OAAO,KAAK,IAAI,IAAI,SAAS,KAAK,IAAI;QAAE,qBAAqB,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAEhG,uEAAuE;IACvE,4EAA4E;IAC5E,2EAA2E;IAC3E,2EAA2E;IAC3E,yEAAyE;IACzE,0EAA0E;IAC1E,qEAAqE;IACrE,IAAI,gBAAgB,GAAG,IAAI,CAAC;IAC5B,IAAI,SAAS,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACnF,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACrF,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC7B,gBAAgB,GAAG,MAAM,oBAAoB,CAAC,YAAY,CAAC,CAAC;gBAC5D,qBAAqB,CAAC,YAAY,EAAE,gBAAgB,EAAE,QAAQ,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,yEAAyE;YACzE,+DAA+D;YAC/D,IAAI,CAAC,CAAC,GAAG,YAAY,eAAe,CAAC;gBAAE,MAAM,GAAG,CAAC;QACnD,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,GAA6C,EAAE,CAAC;IAC5D,IAAI,cAAc,GAA6C,EAAE,CAAC;IAClE,yEAAyE;IACzE,2EAA2E;IAC3E,8EAA8E;IAC9E,iCAAiC;IACjC,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,OAAO,KAAK,IAAI,IAAI,QAAQ,IAAI,WAAW,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,CAAC,QAAQ,EAAE,cAAc,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBAC7C,YAAY,CAAC,OAAO,CAAC;gBACrB,YAAY,CAAC,OAAO,CAAC;aACtB,CAAC,CAAC;YACH,WAAW,GAAG,IAAI,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,6BAA6B;gBACnC,OAAO,EAAE,sCAAsC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;gBACvG,GAAG,EAAE,cAAc,OAAO,iBAAiB,OAAO,qCAAqC;aACxF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,MAAM,iBAAiB,GAAG,SAAS,CAAC,MAAM,EAAE,OAAO,IAAI,IAAI,CAAC;IAC5D,MAAM,cAAc,GAAmC,iBAAiB,KAAK,IAAI;QAC/E,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE;QACzC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,iBAAiB,CAAC;YAC5D,CAAC,CAAC,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,SAAS,EAAE;YACtD,CAAC,CAAC,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC3D,0EAA0E;IAC1E,+EAA+E;IAC/E,4EAA4E;IAC5E,6EAA6E;IAC7E,0EAA0E;IAC1E,0EAA0E;IAC1E,4EAA4E;IAC5E,2EAA2E;IAC3E,wEAAwE;IACxE,yBAAyB;IACzB,MAAM,UAAU,GAAG,UAAU,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI;WAC1D,OAAO,CAAC,UAAU,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,cAAc,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,UAAU,EAAE,CAAC;QACvD,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,wBAAwB;YAC9B,OAAO,EAAE,uEAAuE;YAChF,GAAG,EAAE,0BAA0B,SAAS,CAAC,MAAM,EAAE,gBAAgB,IAAI,OAAO,yBAAyB;SACtG,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,cAAc,CAAC,MAAM,KAAK,SAAS,IAAI,WAAW,EAAE,CAAC;QAC9D,wEAAwE;QACxE,4EAA4E;QAC5E,uEAAuE;QACvE,yEAAyE;QACzE,0EAA0E;QAC1E,sEAAsE;QACtE,wEAAwE;QACxE,yBAAyB;QACzB,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,wBAAwB;YAC9B,OAAO,EAAE,uBAAuB,iBAAiB,mCAAmC;YACpF,yEAAyE;YACzE,wEAAwE;YACxE,0EAA0E;YAC1E,wEAAwE;YACxE,oEAAoE;YACpE,GAAG,EAAE,8BAA8B,iBAAiB,yCAAyC,IAAI,GAAG;SACrG,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,4EAA4E;IAC5E,kDAAkD;IAClD,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAE7F,OAAO;QACL,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC;QACpE,OAAO,EAAE;YACP,OAAO,EAAE,cAAc;YACvB,OAAO,EAAE,YAAY;YACrB,IAAI,EAAE,OAAO,CAAC,OAAO;YACrB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;SACnB;QACD,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE;QAClE,IAAI,EAAE;YACJ,IAAI,EAAE,OAAO;YACb,MAAM;YACN,QAAQ;YACR,QAAQ;YACR,WAAW;YACX,SAAS;SACV;QACD,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,cAAc,CAAC,MAAM,EAAE;QAC5E,cAAc;QACd,MAAM;QACN,SAAS;QACT,gBAAgB;QAChB,QAAQ;KACT,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The preflight: can loam run here at all, and can its answers be trusted?\n *\n * The walk only. Each question it asks lives in its own module — `./config.ts`\n * for the config and the fleet map, `./agents.ts` for the agent surface,\n * `./residue.ts` for what a killed writer left — and every one of them returns\n * findings rather than printing. That is the whole contract this module keeps:\n * `doctor` is the command that must be able to describe a broken repository\n * without becoming the next thing that breaks in it.\n */\nimport { constants, existsSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { CONFIG_FILENAME, configPath } from \"../envelope/config.js\";\nimport { LOAM_VERSION } from \"../envelope/version.js\";\nimport { landscapePath } from \"../repo/paths.js\";\nimport { docsRepoState } from \"../repo/state.js\";\nimport { listFeatures, listServices } from \"../repo/repo.js\";\nimport { LIKEC4_PROJECT_CONFIG, LIKEC4_PROJECT_FILENAME } from \"../docs.js\";\nimport { gherkinRoot } from \"../gherkin/stamp.js\";\nimport { UnsafePathError } from \"../kernel/path-safety.js\";\nimport { scanWritePathResidue } from \"../staging/recovery/residue.js\";\nimport { type DoctorFinding, type DoctorReport } from \"./report.js\";\nimport { canAccess, inspectConfig, inspectLandscape } from \"./config.js\";\nimport { inspectAgentSurface } from \"./agents.js\";\nimport { gradeWritePathResidue } from \"./residue.js\";\n\nexport async function diagnose(cwd = process.cwd()): Promise<DoctorReport> {\n const inspected = await inspectConfig(cwd);\n const path = configPath(cwd);\n const findings: DoctorFinding[] = [];\n if (inspected.status === \"missing\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.config-missing\",\n message: `No ${CONFIG_FILENAME} found at or above ${cwd}.`,\n fix: \"Run `loam init --docs <path-to-docs-repo>` here (add `--create` to make a new docs repo).\",\n });\n } else if (inspected.status === \"invalid\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.config-invalid\",\n message: `Invalid ${CONFIG_FILENAME}: ${inspected.error ?? \"unknown error\"}`,\n fix: `Repair ${path} — or delete it and re-run \\`loam init\\`.`,\n });\n }\n\n const docsDir = inspected.config?.docsDir ?? null;\n const state = docsDir === null ? null : docsRepoState(docsDir);\n const exists = state !== null && state.kind !== \"missing\";\n const readable = exists && docsDir !== null ? await canAccess(docsDir, constants.R_OK) : false;\n const writable = exists && docsDir !== null ? await canAccess(docsDir, constants.W_OK) : false;\n const servicesDir = state?.kind === \"ok\";\n const landscapeFile = docsDir === null ? null : landscapePath(docsDir);\n const landscape = landscapeFile === null ? false : await canAccess(landscapeFile, constants.F_OK);\n\n // The docs-repo verdict comes from `docsRepoState`, the same function\n // `listServices` refuses on, so doctor cannot call a repo healthy that the\n // enumeration will not read.\n if (state?.kind === \"missing\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.docs-missing\",\n message: `Configured docsDir is missing or is not a directory: ${docsDir}`,\n fix: `Fix \"docsDir\" in ${path}, clone the docs repo to ${docsDir}, or run \\`loam init --docs <dir> --create\\`.`,\n });\n } else if (state?.kind === \"no-services\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.services-missing\",\n message: `Required services/ directory is missing under ${docsDir} — that path is not a docs repo.`,\n fix: `Point \"docsDir\" in ${path} at the shared docs repo, or run \\`loam init --docs ${docsDir} --create\\`.`,\n });\n }\n\n // An absolute docsDir in a committed config names a directory that exists on\n // exactly one machine. It is a warning and not a blocker because it works\n // perfectly — for the person who ran `loam init`, and for nobody who clones\n // the repo afterwards.\n const asWritten = inspected.config?.docsDirAsWritten;\n const configRoot = inspected.config?.root;\n if (\n asWritten !== undefined\n && configRoot !== undefined\n && isAbsolute(asWritten)\n && resolve(asWritten) !== resolve(configRoot)\n ) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-absolute\",\n message:\n `\"docsDir\" is stored as an absolute path (${asWritten}); ` +\n `${CONFIG_FILENAME} is committed, so it will not resolve on anyone else's machine.`,\n fix: `Rewrite \"docsDir\" in ${path} as a path relative to that file (e.g. \"../docs\").`,\n });\n }\n\n if (exists && !readable) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-unreadable\",\n message: `docsDir is not readable by this process: ${docsDir}`,\n fix: `Grant read access to ${docsDir} (check ownership and mode).`,\n });\n }\n if (exists && !writable) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-readonly\",\n message: `docsDir is not writable; read-only commands work, archive/init do not: ${docsDir}`,\n fix: `Grant write access to ${docsDir} if you need \\`loam new\\`, \\`loam adopt\\` or \\`loam archive\\`.`,\n });\n }\n if (exists && !landscape) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.landscape-missing\",\n message: `${landscapeFile} is missing — nothing cross-service can be checked without it.`,\n fix: \"Create architecture/landscape.likec4 with one element per service and an edge per call (`loam init --create` writes the empty map).\",\n });\n } else if (exists && landscapeFile !== null && readable) {\n await inspectLandscape(landscapeFile, findings);\n }\n\n // The renderer's view of the repo, which is not loam's. loam parses every\n // `.likec4` file ALONE, so each one declares its own `specification` block and\n // re-declares whatever elements it names; LikeC4's own workspace loader merges\n // the whole tree into one model, and those declarations then collide. The\n // result was two tools disagreeing in the most confusing possible direction:\n // `loam validate --all` reported zero errors on a tree where `npx likec4\n // start` — the command loam's own brief recommends — refused to load at all.\n // `loam init --create` writes the project file that scopes the root project to\n // the landscape; a docs repo created before it exists has to be told, because\n // nothing else will ever mention it. A warning, not a blocker: no loam check\n // reads this file, and a repo without it is only unrenderable.\n if (docsDir !== null && exists && readable && !existsSync(join(docsDir, LIKEC4_PROJECT_FILENAME))) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.likec4-config-missing\",\n message:\n `${docsDir}/${LIKEC4_PROJECT_FILENAME} is missing, so this tree is not a loadable LikeC4 workspace — `\n + \"every service model and feature delta declares its own `specification` block, and pointing a \"\n + \"renderer at the repo root merges them into one model and reports every declaration as a duplicate.\",\n fix:\n `Write ${docsDir}/${LIKEC4_PROJECT_FILENAME} with:\\n${LIKEC4_PROJECT_CONFIG.trimEnd()}\\n`\n + \"That scopes the root project to architecture/, so `npx likec4 start` in the docs repo renders \"\n + \"the fleet map. A service model or feature delta is rendered by pointing the renderer at its own \"\n + \"directory (`npx likec4 start services/<id>`) — being readable alone is what those files are for.\",\n });\n }\n\n // What a killed writer left behind. Read from `scanWritePathResidue`, whose\n // spellings of `.loam-lock` / `.loam-commit` / the temp-file pattern are\n // staging's own — doctor grades, it does not re-spell. This is the surface\n // that used to report `healthy: true` over half-merged docs.\n const writePath = docsDir !== null && exists && readable\n ? await scanWritePathResidue(docsDir)\n : null;\n if (docsDir !== null && writePath !== null) gradeWritePathResidue(docsDir, writePath, findings);\n\n // The one writer whose journal is NOT in the docs repo: `loam gherkin`\n // commits into the service repo's owned `<gherkinDir>/loam/` root, with the\n // same lock and journal files. When this repo IS a service repo, that root\n // gets the same scan and the same grades — the codes are the contract, the\n // prose says where — because \"doctor reports an interrupted commit until\n // recovery succeeds\" has to hold for every writer, not only the docs-repo\n // ones. Scanning it is cheap: the root is loam's own flat directory.\n let serviceWritePath = null;\n if (inspected.config?.service !== undefined && inspected.config.root !== undefined) {\n try {\n const emissionRoot = gherkinRoot(inspected.config.root, inspected.config.gherkinDir);\n if (existsSync(emissionRoot)) {\n serviceWritePath = await scanWritePathResidue(emissionRoot);\n gradeWritePathResidue(emissionRoot, serviceWritePath, findings);\n }\n } catch (err) {\n // An unsafe gherkinDir is `loam gherkin`'s refusal to make, with its own\n // sentence; doctor only declines to scan what it cannot trust.\n if (!(err instanceof UnsafePathError)) throw err;\n }\n }\n\n let services: Awaited<ReturnType<typeof listServices>> = [];\n let activeFeatures: Awaited<ReturnType<typeof listFeatures>> = [];\n // Whether the fleet was actually READ, which is a narrower question than\n // whether reading it was attempted — hence set inside the try and not from\n // the guard, so an enumeration that threw leaves this false alongside its own\n // `doctor.inventory-unreadable`.\n let inventoried = false;\n if (docsDir !== null && readable && servicesDir) {\n try {\n [services, activeFeatures] = await Promise.all([\n listServices(docsDir),\n listFeatures(docsDir),\n ]);\n inventoried = true;\n } catch (error) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.inventory-unreadable\",\n message: `Could not inventory the docs repo: ${error instanceof Error ? error.message : String(error)}`,\n fix: `Check that ${docsDir}/services and ${docsDir}/features are readable directories.`,\n });\n }\n }\n\n const configuredService = inspected.config?.service ?? null;\n const currentService: DoctorReport[\"currentService\"] = configuredService === null\n ? { configured: null, status: \"unbound\" }\n : services.some((service) => service.id === configuredService)\n ? { configured: configuredService, status: \"matched\" }\n : { configured: configuredService, status: \"unknown\" };\n // Standing INSIDE the docs repo, an absent service binding is the correct\n // state, not a gap: the docs repo is the fleet, it is not any one service, and\n // the fix this finding prints — `loam init --service <id>` — would bind the\n // shared repo to one of the services it holds, which is not a thing loam has\n // a meaning for. doctor already knew where it was standing (it prints the\n // fleet count two lines up) and reported the warning anyway, so the first\n // preflight anyone ran in a freshly created docs repo came back yellow with\n // advice that made it worse. The docs repo's own loam.json is the one that\n // resolves `docsDir` to the directory holding it — that identity, not a\n // filename, is the test.\n const inDocsRepo = configRoot !== undefined && docsDir !== null\n && resolve(configRoot) === resolve(docsDir);\n if (currentService.status === \"unbound\" && !inDocsRepo) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.service-unbound\",\n message: \"loam.json has no service binding; service-repo checks need `service`.\",\n fix: `Run \\`loam init --docs ${inspected.config?.docsDirAsWritten ?? \"<dir>\"} --service <id>\\` here.`,\n });\n } else if (currentService.status === \"unknown\" && inventoried) {\n // Only when there was an inventory to be absent from. This finding is a\n // claim about a fleet that was read; with no docsDir, an unreadable one, or\n // an enumeration that threw, `services` is empty for a reason that has\n // nothing to do with `service`, and doctor pointed the reader at the one\n // config field that was fine while the blocker it had already filed named\n // the real one. `currentService.status` still reports `unknown` — the\n // envelope describes what loam could and could not match, and an unread\n // fleet matches nothing.\n findings.push({\n severity: \"warning\",\n code: \"doctor.service-unknown\",\n message: `Configured service '${configuredService}' is not present under services/.`,\n // `--service <id>`, not a positional: `loam adopt` takes no argument, so\n // the spelling this finding used to print was refused by commander with\n // \"too many arguments\" — as the FIRST instruction a freshly bound service\n // repo ever receives. test/agent-commands-runnable.test.ts parses every\n // `loam …` loam prints against the real program so it cannot recur.\n fix: `Run \\`loam adopt --service ${configuredService}\\` to onboard it, or fix \"service\" in ${path}.`,\n });\n }\n\n // The slash commands (and everything else `init` lays down for a tool) belong\n // to the repo `init` ran in — the directory holding loam.json, which is not\n // the cwd now that config discovery walks upward.\n const agents = await inspectAgentSurface(dirname(path), inspected.config, docsDir, findings);\n\n return {\n healthy: !findings.some((finding) => finding.severity === \"blocker\"),\n runtime: {\n package: \"@ybotok/loam\",\n version: LOAM_VERSION,\n node: process.version,\n platform: process.platform,\n arch: process.arch,\n },\n config: { path, status: inspected.status, error: inspected.error },\n docs: {\n path: docsDir,\n exists,\n readable,\n writable,\n servicesDir,\n landscape,\n },\n counts: { services: services.length, activeFeatures: activeFeatures.length },\n currentService,\n agents,\n writePath,\n serviceWritePath,\n findings,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/core/doctor/doctor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The preflight: can loam run here at all, and can its answers be trusted?\n *\n * The walk only. Each question it asks lives in its own module — `./config.ts`\n * for the config and the fleet map, `./agents.ts` for the agent surface,\n * `./residue.ts` for what a killed writer left — and every one of them returns\n * findings rather than printing. That is the whole contract this module keeps:\n * `doctor` is the command that must be able to describe a broken repository\n * without becoming the next thing that breaks in it.\n */\nimport { constants, existsSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { CONFIG_FILENAME, configPath } from \"../envelope/config.js\";\nimport { LOAM_VERSION } from \"../envelope/version.js\";\nimport { landscapePath, LIKEC4_PROJECT_FILENAME } from \"../repo/paths.js\";\nimport { docsRepoState } from \"../repo/state.js\";\nimport { listFeatures, listServices } from \"../repo/repo.js\";\nimport { introducingFeatures } from \"../projection/introduced.js\";\nimport { LIKEC4_PROJECT_CONFIG } from \"../docs.js\";\nimport { gherkinRoot } from \"../gherkin/stamp.js\";\nimport { UnsafePathError } from \"../kernel/path-safety.js\";\nimport { scanWritePathResidue } from \"../staging/recovery/residue.js\";\nimport { type DoctorFinding, type DoctorReport } from \"./report.js\";\nimport { canAccess, inspectConfig, inspectLandscape } from \"./config.js\";\nimport { inspectAgentSurface } from \"./agents.js\";\nimport { gradeWritePathResidue } from \"./residue.js\";\nimport { scanReports } from \"./reports/scan.js\";\n\nexport async function diagnose(cwd = process.cwd()): Promise<DoctorReport> {\n const inspected = await inspectConfig(cwd);\n const path = configPath(cwd);\n const findings: DoctorFinding[] = [];\n if (inspected.status === \"missing\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.config-missing\",\n message: `No ${CONFIG_FILENAME} found at or above ${cwd}.`,\n fix: \"Run `loam init --docs <path-to-docs-repo>` here (add `--create` to make a new docs repo).\",\n });\n } else if (inspected.status === \"invalid\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.config-invalid\",\n message: `Invalid ${CONFIG_FILENAME}: ${inspected.error ?? \"unknown error\"}`,\n fix: `Repair ${path} — or delete it and re-run \\`loam init\\`.`,\n });\n }\n\n const docsDir = inspected.config?.docsDir ?? null;\n const state = docsDir === null ? null : docsRepoState(docsDir);\n const exists = state !== null && state.kind !== \"missing\";\n const readable = exists && docsDir !== null ? await canAccess(docsDir, constants.R_OK) : false;\n const writable = exists && docsDir !== null ? await canAccess(docsDir, constants.W_OK) : false;\n const servicesDir = state?.kind === \"ok\";\n const landscapeFile = docsDir === null ? null : landscapePath(docsDir);\n const landscape = landscapeFile === null ? false : await canAccess(landscapeFile, constants.F_OK);\n\n // The docs-repo verdict comes from `docsRepoState`, the same function\n // `listServices` refuses on, so doctor cannot call a repo healthy that the\n // enumeration will not read.\n if (state?.kind === \"missing\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.docs-missing\",\n message: `Configured docsDir is missing or is not a directory: ${docsDir}`,\n fix: `Fix \"docsDir\" in ${path}, clone the docs repo to ${docsDir}, or run \\`loam init --docs <dir> --create\\`.`,\n });\n } else if (state?.kind === \"no-services\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.services-missing\",\n message: `Required services/ directory is missing under ${docsDir} — that path is not a docs repo.`,\n fix: `Point \"docsDir\" in ${path} at the shared docs repo, or run \\`loam init --docs ${docsDir} --create\\`.`,\n });\n }\n\n // An absolute docsDir in a committed config names a directory that exists on\n // exactly one machine. It is a warning and not a blocker because it works\n // perfectly — for the person who ran `loam init`, and for nobody who clones\n // the repo afterwards.\n const asWritten = inspected.config?.docsDirAsWritten;\n const configRoot = inspected.config?.root;\n if (\n asWritten !== undefined\n && configRoot !== undefined\n && isAbsolute(asWritten)\n && resolve(asWritten) !== resolve(configRoot)\n ) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-absolute\",\n message:\n `\"docsDir\" is stored as an absolute path (${asWritten}); ` +\n `${CONFIG_FILENAME} is committed, so it will not resolve on anyone else's machine.`,\n fix: `Rewrite \"docsDir\" in ${path} as a path relative to that file (e.g. \"../docs\").`,\n });\n }\n\n if (exists && !readable) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-unreadable\",\n message: `docsDir is not readable by this process: ${docsDir}`,\n fix: `Grant read access to ${docsDir} (check ownership and mode).`,\n });\n }\n if (exists && !writable) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-readonly\",\n message: `docsDir is not writable; read-only commands work, archive/init do not: ${docsDir}`,\n fix: `Grant write access to ${docsDir} if you need \\`loam new\\`, \\`loam adopt\\` or \\`loam archive\\`.`,\n });\n }\n if (exists && !landscape) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.landscape-missing\",\n message: `${landscapeFile} is missing — nothing cross-service can be checked without it.`,\n fix: \"Create architecture/landscape.likec4 with one element per service and an edge per call (`loam init --create` writes the empty map).\",\n });\n } else if (exists && landscapeFile !== null && readable) {\n await inspectLandscape(landscapeFile, findings);\n }\n\n // The renderer's view of the repo, which is not loam's. loam parses every\n // `.likec4` file ALONE, so each one declares its own `specification` block and\n // re-declares whatever elements it names; LikeC4's own workspace loader merges\n // the whole tree into one model, and those declarations then collide. The\n // result was two tools disagreeing in the most confusing possible direction:\n // `loam validate --all` reported zero errors on a tree where `npx likec4\n // start` — the command loam's own brief recommends — refused to load at all.\n // `loam init --create` writes the project file that scopes the root project to\n // the landscape; a docs repo created before it exists has to be told, because\n // nothing else will ever mention it. The root file is also the gate on the\n // per-service ones: `loam subsystem sync` writes a create-only\n // likec4.config.json beside every service model — the LikeC4 project that\n // makes each model render from the same root, beside the fleet map — and it\n // writes none of them until this file exists, so a tree missing it renders\n // nothing from the docs root, not only the map. (Only a feature delta still renders from\n // its own directory; an archived feature would otherwise keep a dead project\n // forever.) The fix below says both, because this finding is the one place a\n // repo that predates either file is ever told. A warning, not a blocker: no\n // loam check reads this file, and a repo without it is only unrenderable.\n if (docsDir !== null && exists && readable && !existsSync(join(docsDir, LIKEC4_PROJECT_FILENAME))) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.likec4-config-missing\",\n message:\n `${docsDir}/${LIKEC4_PROJECT_FILENAME} is missing, so this tree is not a loadable LikeC4 workspace — `\n + \"a feature delta, and any service model that declares its own `specification` block, has to be \"\n + \"excluded from the root project, and pointing a renderer at the repo root with nothing excluded \"\n + \"merges them into one model and reports every declaration as a duplicate.\",\n fix:\n `Write ${docsDir}/${LIKEC4_PROJECT_FILENAME} with:\\n${LIKEC4_PROJECT_CONFIG.trimEnd()}\\n`\n + \"That makes the docs root one LikeC4 project holding the fleet map and every service model that \"\n + \"extends it, so `npx likec4 start` in the docs repo renders both. Then run `loam subsystem sync`: \"\n + \"it maintains the `exclude` list above — one `services/<tree>/**` entry per model that stands \"\n + \"alone — and writes one create-only likec4.config.json beside each of those, so a standalone model \"\n + \"renders from the same root as a project of its own. Only a feature delta still renders from its \"\n + \"own directory (`npx likec4 start features/<FEAT>`) — being readable alone is what that file is for.\",\n });\n }\n\n // What a killed writer left behind. Read from `scanWritePathResidue`, whose\n // spellings of `.loam-lock` / `.loam-commit` / the temp-file pattern are\n // staging's own — doctor grades, it does not re-spell. This is the surface\n // that used to report `healthy: true` over half-merged docs.\n const writePath = docsDir !== null && exists && readable\n ? await scanWritePathResidue(docsDir)\n : null;\n if (docsDir !== null && writePath !== null) gradeWritePathResidue(docsDir, writePath, findings);\n\n // The one writer whose journal is NOT in the docs repo: `loam gherkin`\n // commits into the service repo's owned `<gherkinDir>/loam/` root, with the\n // same lock and journal files. When this repo IS a service repo, that root\n // gets the same scan and the same grades — the codes are the contract, the\n // prose says where — because \"doctor reports an interrupted commit until\n // recovery succeeds\" has to hold for every writer, not only the docs-repo\n // ones. Scanning it is cheap: the root is loam's own flat directory.\n let serviceWritePath = null;\n if (inspected.config?.service !== undefined && inspected.config.root !== undefined) {\n try {\n const emissionRoot = gherkinRoot(inspected.config.root, inspected.config.gherkinDir);\n if (existsSync(emissionRoot)) {\n serviceWritePath = await scanWritePathResidue(emissionRoot);\n gradeWritePathResidue(emissionRoot, serviceWritePath, findings);\n }\n } catch (err) {\n // An unsafe gherkinDir is `loam gherkin`'s refusal to make, with its own\n // sentence; doctor only declines to scan what it cannot trust.\n if (!(err instanceof UnsafePathError)) throw err;\n }\n }\n\n const configuredService = inspected.config?.service ?? null;\n let services: Awaited<ReturnType<typeof listServices>> = [];\n let activeFeatures: Awaited<ReturnType<typeof listFeatures>> = [];\n // The active features that introduce the bound service, when the living docs\n // do not hold it — see the `doctor.service-unknown` finding below.\n let introducedBy: string[] = [];\n // Whether the fleet was actually READ, which is a narrower question than\n // whether reading it was attempted — hence set inside the try and not from\n // the guard, so an enumeration that threw leaves this false alongside its own\n // `doctor.inventory-unreadable`.\n let inventoried = false;\n if (docsDir !== null && readable && servicesDir) {\n try {\n [services, activeFeatures] = await Promise.all([\n listServices(docsDir),\n listFeatures(docsDir),\n ]);\n // Through the shared resolver, not a filter over the two lists just read,\n // so `doctor` and `status`/`show`/`context`/`steps` agree on what\n // \"introduced\" means. Inside the try because it reads the same two\n // directories: if they throw here, they threw for the inventory too.\n if (configuredService !== null && !services.some((service) => service.id === configuredService)) {\n introducedBy = (await introducingFeatures(docsDir, configuredService)).map((f) => f.id);\n }\n inventoried = true;\n } catch (error) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.inventory-unreadable\",\n message: `Could not inventory the docs repo: ${error instanceof Error ? error.message : String(error)}`,\n fix: `Check that ${docsDir}/services and ${docsDir}/features are readable directories.`,\n });\n }\n }\n\n const currentService: DoctorReport[\"currentService\"] = configuredService === null\n ? { configured: null, status: \"unbound\" }\n : services.some((service) => service.id === configuredService)\n ? { configured: configuredService, status: \"matched\" }\n : introducedBy.length > 0\n ? { configured: configuredService, status: \"unknown\", introducedBy }\n : { configured: configuredService, status: \"unknown\" };\n // Standing INSIDE the docs repo, an absent service binding is the correct\n // state, not a gap: the docs repo is the fleet, it is not any one service, and\n // the fix this finding prints — `loam init --service <id>` — would bind the\n // shared repo to one of the services it holds, which is not a thing loam has\n // a meaning for. doctor already knew where it was standing (it prints the\n // fleet count two lines up) and reported the warning anyway, so the first\n // preflight anyone ran in a freshly created docs repo came back yellow with\n // advice that made it worse. The docs repo's own loam.json is the one that\n // resolves `docsDir` to the directory holding it — that identity, not a\n // filename, is the test.\n const inDocsRepo = configRoot !== undefined && docsDir !== null\n && resolve(configRoot) === resolve(docsDir);\n const [lead, ...others] = introducedBy;\n if (currentService.status === \"unbound\" && !inDocsRepo) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.service-unbound\",\n message: \"loam.json has no service binding; service-repo checks need `service`.\",\n fix: `Run \\`loam init --docs ${inspected.config?.docsDirAsWritten ?? \"<dir>\"} --service <id>\\` here.`,\n });\n } else if (currentService.status === \"unknown\" && inventoried && lead !== undefined) {\n // A service a feature in flight INTRODUCES: its only documents are that\n // feature's `specs/<service>/` until it archives, and `services/` learns\n // the name at the merge. The finding used to send this repository's first\n // developer to `loam adopt` — to describe code the feature has not built\n // yet, and to write a `services/<service>/` that would make the service\n // living ahead of the merge, so the feature no longer introduced it. The\n // same code and severity: the binding still names nothing under services/,\n // so a check that reads the living docs for it has nothing to read, which\n // is worth knowing before trusting one. Only the advice was wrong.\n const several = others.length > 0;\n const alsoBy = several ? ` (so does ${others.join(\", \")})` : \"\";\n const until = several ? \"one of them\" : lead;\n const documents = several\n ? `the specs/${configuredService}/ of ${introducedBy.join(\", \")} are`\n : `${lead}'s specs/${configuredService}/ are`;\n const likewise = several ? `, and likewise for ${others.join(\", \")},` : \"\";\n findings.push({\n severity: \"warning\",\n code: \"doctor.service-unknown\",\n message:\n `Configured service '${configuredService}' is not under services/ yet: ${lead} introduces it${alsoBy}, `\n + `and it is not in the living docs until ${until} archives.`,\n fix:\n `Nothing to adopt — until ${until} archives, ${documents} this service's documents, and that archive `\n + `is what clears this warning. Run \\`loam status ${lead}\\`${likewise} to see what this repository owes `\n + `${several ? \"each\" : lead}. If this repository is a different service, fix \"service\" in ${path}.`,\n });\n } else if (currentService.status === \"unknown\" && inventoried) {\n // Only when there was an inventory to be absent from. This finding is a\n // claim about a fleet that was read; with no docsDir, an unreadable one, or\n // an enumeration that threw, `services` is empty for a reason that has\n // nothing to do with `service`, and doctor pointed the reader at the one\n // config field that was fine while the blocker it had already filed named\n // the real one. `currentService.status` still reports `unknown` — the\n // envelope describes what loam could and could not match, and an unread\n // fleet matches nothing.\n findings.push({\n severity: \"warning\",\n code: \"doctor.service-unknown\",\n message: `Configured service '${configuredService}' is not present under services/.`,\n // `--service <id>`, not a positional: `loam adopt` takes no argument, so\n // the spelling this finding used to print was refused by commander with\n // \"too many arguments\" — as the FIRST instruction a freshly bound service\n // repo ever receives. test/agent-commands-runnable.test.ts parses every\n // `loam …` loam prints against the real program so it cannot recur.\n fix: `Run \\`loam adopt --service ${configuredService}\\` to onboard it, or fix \"service\" in ${path}.`,\n });\n }\n\n // The slash commands (and everything else `init` lays down for a tool) belong\n // to the repo `init` ran in — the directory holding loam.json, which is not\n // the cwd now that config discovery walks upward.\n const agents = await inspectAgentSurface(dirname(path), inspected.config, docsDir, findings);\n\n // The problem reports, read beside the config that RESOLVED — `config.root`,\n // the directory the loam.json was found in — because the `loam-report`\n // protocol says \"at the current repository root\", and config discovery walks\n // upward, so the cwd is not that root.\n //\n // Where nothing PARSED, the directory of the file loam FOUND, which is what\n // `dirname(path)` is: `configPath` returns the discovered loam.json, or the\n // cwd's own spelling of it when there is none, so this is the found root when\n // there is one and the cwd when there is not — the same root the agent surface\n // above is inspected against. Falling back to the raw cwd instead meant that\n // from a subdirectory of a repo whose loam.json is unparseable, `doctor`\n // reported `total 0, next 001` while five reports sat beside the config it had\n // just named in the same envelope, and handed out an ordinal already taken\n // (verification 2026-09-04, second pass). A report corpus in a repo whose\n // loam.json is broken is still worth counting — this is precisely the preflight\n // that runs when the config does not resolve — and the blocker above already\n // names the real problem.\n //\n // No findings come back, and that is the design: this is the one directory\n // loam reads that loam never writes, its contents are the team's, and an open\n // report is not a defect in the repository holding it. `doctor` reports it as\n // state — like `docs`, `counts` and the write path — so the corpus is\n // countable by the protocol that asks for it (`reports.next` is the ordinal\n // the next report takes) without ever becoming a complaint.\n const reports = await scanReports(inspected.config?.root ?? dirname(path));\n\n return {\n healthy: !findings.some((finding) => finding.severity === \"blocker\"),\n runtime: {\n package: \"@ybotok/loam\",\n version: LOAM_VERSION,\n node: process.version,\n platform: process.platform,\n arch: process.arch,\n },\n config: { path, status: inspected.status, error: inspected.error },\n docs: {\n path: docsDir,\n exists,\n readable,\n writable,\n servicesDir,\n landscape,\n },\n counts: { services: services.length, activeFeatures: activeFeatures.length },\n currentService,\n agents,\n reports,\n writePath,\n serviceWritePath,\n findings,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/core/doctor/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAyB,MAAM,gCAAgC,CAAC","sourcesContent":["/**\n * What a diagnosis IS: the finding shape, the report shape, and the agent\n * surface inside it.\n *\n * A module of its own because five checks fill this in and the command prints\n * it, so every one of them agrees about what a blocker means here — something\n * that stops loam running at all, as against a warning, which is something a\n * person should know before trusting an answer. That distinction is the whole\n * value of `doctor`, and it lives in the type rather than in five checks'\n * judgement.\n */\nimport { type WritePathResidue } from \"../staging/recovery/residue.js\";\n\nexport type DoctorSeverity = \"blocker\" | \"warning\";\n\nexport interface DoctorFinding {\n severity: DoctorSeverity;\n code: string;\n message: string;\n /**\n * The next command or edit that clears this finding. Mandatory, not optional:\n * a diagnostic that names a problem without naming its fix is a diagnostic\n * the reader has to research, and `doctor` is what someone runs precisely\n * because they do not yet know what loam wants from them.\n */\n fix: string;\n}\n\nexport interface DoctorReport {\n healthy: boolean;\n runtime: {\n package: \"@ybotok/loam\";\n version: string;\n node: string;\n platform: NodeJS.Platform;\n arch: string;\n };\n config: {\n path: string;\n status: \"missing\" | \"invalid\" | \"valid\";\n error: string | null;\n };\n docs: {\n path: string | null;\n exists: boolean;\n readable: boolean;\n writable: boolean;\n servicesDir: boolean;\n landscape: boolean;\n };\n counts: { services: number; activeFeatures: number };\n currentService: {\n configured: string | null;\n status: \"unbound\" | \"matched\" | \"unknown\";\n };\n /** The agent surface of this repo — see `inspectAgentSurface`. */\n agents: AgentSurface;\n /**\n * What a write that did not finish left in the docs repo, or null when\n * `docsDir` never resolved. Reported as state, like `docs` and `counts`: a\n * lock somebody is legitimately holding right now is a fact, not a complaint.\n */\n writePath: WritePathResidue | null;\n /**\n * The same scan over the service repo's owned `<gherkinDir>/loam/` root —\n * the one writer whose journal is not in the docs repo. Null when this repo\n * is not a service repo, or the root does not exist. Additive: a consumer\n * of the original envelope never sees a key it did not ask about removed.\n */\n serviceWritePath: WritePathResidue | null;\n findings: DoctorFinding[];\n}\n\nexport interface AgentSurface {\n /** Tool ids this repo has an agent surface for. Empty is legal (`init --no-commands`). */\n tools: string[];\n /** Workflow subset managed for those tools. */\n profile: \"full\" | \"service\" | \"docs\";\n /** Whether `tools` came from loam.json's own record or from what is on disk. */\n toolsSource: \"config\" | \"disk\";\n /** How many files the running binary would lay down for `tools`. */\n plannedFiles: number;\n /** Repo-relative paths of those files that are not there. */\n missingFiles: string[];\n /**\n * Repo-relative paths of the ones that ARE there
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/core/doctor/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAyB,MAAM,gCAAgC,CAAC","sourcesContent":["/**\n * What a diagnosis IS: the finding shape, the report shape, and the agent\n * surface inside it.\n *\n * A module of its own because five checks fill this in and the command prints\n * it, so every one of them agrees about what a blocker means here — something\n * that stops loam running at all, as against a warning, which is something a\n * person should know before trusting an answer. That distinction is the whole\n * value of `doctor`, and it lives in the type rather than in five checks'\n * judgement.\n */\nimport { type WritePathResidue } from \"../staging/recovery/residue.js\";\n\nexport type DoctorSeverity = \"blocker\" | \"warning\";\n\nexport interface DoctorFinding {\n severity: DoctorSeverity;\n code: string;\n message: string;\n /**\n * The next command or edit that clears this finding. Mandatory, not optional:\n * a diagnostic that names a problem without naming its fix is a diagnostic\n * the reader has to research, and `doctor` is what someone runs precisely\n * because they do not yet know what loam wants from them.\n */\n fix: string;\n}\n\nexport interface DoctorReport {\n healthy: boolean;\n runtime: {\n package: \"@ybotok/loam\";\n version: string;\n node: string;\n platform: NodeJS.Platform;\n arch: string;\n };\n config: {\n path: string;\n status: \"missing\" | \"invalid\" | \"valid\";\n error: string | null;\n };\n docs: {\n path: string | null;\n exists: boolean;\n readable: boolean;\n writable: boolean;\n servicesDir: boolean;\n landscape: boolean;\n };\n counts: { services: number; activeFeatures: number };\n currentService: {\n configured: string | null;\n status: \"unbound\" | \"matched\" | \"unknown\";\n /**\n * The active features whose `specs/` introduce the configured service\n * (`introducingFeatures`), ordered by id — present only when `status` is\n * `unknown` and at least one does. `status` stays `unknown` then, because\n * it says what loam could match under `services/`, and that is still\n * nothing; this key says why that is not a gap anybody should adopt into.\n * Additive: absent everywhere it would be empty, as `show`'s is on a\n * living service.\n */\n introducedBy?: string[];\n };\n /** The agent surface of this repo — see `inspectAgentSurface`. */\n agents: AgentSurface;\n /**\n * The problem reports this repo has collected — see `scanReports`. State,\n * like `docs` and `counts`, and never a finding: a repository with eleven\n * open reports is not a broken repository, it is one that has been paying\n * attention. `healthy` does not move for anything in here.\n */\n reports: ProblemReports;\n /**\n * What a write that did not finish left in the docs repo, or null when\n * `docsDir` never resolved. Reported as state, like `docs` and `counts`: a\n * lock somebody is legitimately holding right now is a fact, not a complaint.\n */\n writePath: WritePathResidue | null;\n /**\n * The same scan over the service repo's owned `<gherkinDir>/loam/` root —\n * the one writer whose journal is not in the docs repo. Null when this repo\n * is not a service repo, or the root does not exist. Additive: a consumer\n * of the original envelope never sees a key it did not ask about removed.\n */\n serviceWritePath: WritePathResidue | null;\n findings: DoctorFinding[];\n}\n\n/**\n * Where one problem report got to, as its own `Status:` line says.\n *\n * `fixed` and `superseded` are the FIRST WORD of a longer line (`fixed in\n * 0.2.0-alpha.5`, `superseded by 010`), because the rest of that line is prose\n * an author writes for a reader and loam has no business parsing. `unstated`\n * covers both a report written before the field existed and one whose value is\n * a word this vocabulary does not know — loam counts what it can read and says\n * so, rather than inventing a sixth bucket for every synonym somebody types.\n */\nexport type ProblemReportStatus = \"open\" | \"sent\" | \"fixed\" | \"superseded\" | \"unstated\";\n\nexport interface ProblemReportEntry {\n /** The file's own name inside the reports directory — `012-2026-09-03-slug.md`. */\n file: string;\n /**\n * The leading zero-padded ordinal the protocol now asks for, or null for a\n * name written before it did (`2026-09-03-slug.md`). A report with no ordinal\n * is still a report: this directory predates the numbering.\n */\n ordinal: string | null;\n status: ProblemReportStatus;\n}\n\n/** The report corpus a repository has collected — see `scanReports`. */\nexport interface ProblemReports {\n /**\n * The ABSOLUTE path of the directory — `loam-reports/` beside the `loam.json`\n * that resolved, else beside the directory `doctor` ran in. Absolute because\n * the protocol tells an agent to write there, and a repo-relative spelling\n * left it to guess which repository \"relative\" meant: `doctor` is the\n * preflight that runs precisely when the config may not resolve, and in a\n * service repository the config's root is that repository, not the docs one.\n */\n dir: string;\n /** Whether that directory exists at all. A repo with no reports is normal. */\n present: boolean;\n total: number;\n /**\n * The ordinal the next report takes: one more than the highest present,\n * padded to at least three digits, `001` over an empty or absent directory —\n * it WIDENS past three rather than reusing a number once a corpus gets\n * there, which is what every page saying \"at least three digits\" describes.\n * This is the field the `loam-report` protocol tells an agent to read.\n */\n next: string;\n entries: ProblemReportEntry[];\n}\n\nexport interface AgentSurface {\n /** Tool ids this repo has an agent surface for. Empty is legal (`init --no-commands`). */\n tools: string[];\n /** Workflow subset managed for those tools. */\n profile: \"full\" | \"service\" | \"docs\";\n /** Whether `tools` came from loam.json's own record or from what is on disk. */\n toolsSource: \"config\" | \"disk\";\n /** How many files the running binary would lay down for `tools`. */\n plannedFiles: number;\n /** Repo-relative paths of those files that are not there. */\n missingFiles: string[];\n /**\n * Repo-relative paths of the ones that ARE there whose body differs from the\n * one this binary writes AND whose version stamp is missing or older than\n * this binary — see `staleAgentFiles`. A file differing only in its stamp is\n * not listed.\n */\n staleFiles: string[];\n stamp: {\n /** Where the docs repo's AGENTS.md is, or null when `docsDir` did not resolve. */\n path: string | null;\n /** Whether it could be read at all — absent and unstamped are different facts. */\n present: boolean;\n /** The version the stamp names, null when there is none (or an unparseable one). */\n version: string | null;\n /** Whether `agents.stale` fired: the stamp is missing or trails this binary. */\n stale: boolean;\n };\n}\n"]}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The problem reports a repository has collected: how many there are, what
|
|
3
|
+
* state each one is in, and which ordinal the next one takes.
|
|
4
|
+
*
|
|
5
|
+
* A sub-package because `doctor/` is at its five-file limit, and a scan of its
|
|
6
|
+
* own because this is the one directory loam reads that loam never writes. The
|
|
7
|
+
* `loam-report` protocol has always asked a repository to accumulate a corpus
|
|
8
|
+
* under `loam-reports/` and then never looked at it — eleven reports landed in
|
|
9
|
+
* this repository in two days, three of them already closed, and every bit of
|
|
10
|
+
* that state lived outside the files, in whoever remembered. The directory is
|
|
11
|
+
* loam's own convention and its contents are loam's own format, so counting
|
|
12
|
+
* them is a fact loam can read as cheaply as it reads `agentFiles`. Reading is
|
|
13
|
+
* the whole of it: nothing here transmits anything, which is the constraint the
|
|
14
|
+
* protocol is built around.
|
|
15
|
+
*
|
|
16
|
+
* Everything below is best-effort on purpose. A report is a hand-written
|
|
17
|
+
* Markdown file, so an absent directory, an unreadable one, a name written
|
|
18
|
+
* before the numbering existed and a file with no `Status:` line are all
|
|
19
|
+
* ordinary states of a healthy repository. None of them is a finding, and
|
|
20
|
+
* `doctor`'s `healthy` does not move for anything in here — a repo with eleven
|
|
21
|
+
* open reports is not broken, it is one that has been paying attention.
|
|
22
|
+
*/
|
|
23
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import {} from "../report.js";
|
|
26
|
+
/**
|
|
27
|
+
* The conventional NAME of the directory the `loam-report` protocol asks a
|
|
28
|
+
* repository to accumulate, spelled once — the segment `scanReports` joins onto
|
|
29
|
+
* the resolved root to reach it.
|
|
30
|
+
*
|
|
31
|
+
* A name rather than a path, because two readers want different things out of
|
|
32
|
+
* it and both are right: `ProblemReports.dir` carries the joined ABSOLUTE
|
|
33
|
+
* location, because the protocol tells an agent to write there and a relative
|
|
34
|
+
* spelling leaves it guessing which repository "relative" meant (`../report.ts`
|
|
35
|
+
* records that), while `commands/doctor.ts`'s human row prints this constant, so
|
|
36
|
+
* a person standing in the repository reads `loam-reports/` — what the protocol,
|
|
37
|
+
* the reports themselves and every page that mentions them say.
|
|
38
|
+
*/
|
|
39
|
+
export const REPORTS_DIR = "loam-reports";
|
|
40
|
+
/**
|
|
41
|
+
* The leading ordinal of `012-2026-09-03-slug.md`. Three digits or more, so the
|
|
42
|
+
* thousandth report widens the field instead of losing its number.
|
|
43
|
+
*/
|
|
44
|
+
const ORDINAL = /^(\d{3,})-/;
|
|
45
|
+
/**
|
|
46
|
+
* A name written before the numbering existed: `2026-09-03-slug.md`, the shape
|
|
47
|
+
* eleven reports in this repository already carry.
|
|
48
|
+
*
|
|
49
|
+
* Checked FIRST, and this is the whole reason it exists as its own pattern: a
|
|
50
|
+
* four-digit year satisfies `ORDINAL` perfectly, so a legacy name read as an
|
|
51
|
+
* ordinal is report number 2026 — and the next report loam hands out is then
|
|
52
|
+
* 2027, in a directory whose highest real ordinal is 3. A legacy name is still
|
|
53
|
+
* a report; it just has no number, and contributes none.
|
|
54
|
+
*/
|
|
55
|
+
const LEGACY_DATE_NAME = /^\d{4}-\d{2}-\d{2}-/;
|
|
56
|
+
/** The ordinal a report's file name claims, or null when the name predates numbering. */
|
|
57
|
+
function ordinalOf(file) {
|
|
58
|
+
if (LEGACY_DATE_NAME.test(file))
|
|
59
|
+
return null;
|
|
60
|
+
return ORDINAL.exec(file)?.[1] ?? null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The template's `- Status:` line, matched in the header FIELD BLOCK alone (see
|
|
64
|
+
* `headerFields`). Only the first word is captured: the vocabulary's two
|
|
65
|
+
* composite values carry prose after it (`fixed in 0.2.0-alpha.5`,
|
|
66
|
+
* `superseded by 010`) that an author writes for a reader, and parsing further
|
|
67
|
+
* would make loam an authority on a sentence it does not own.
|
|
68
|
+
*/
|
|
69
|
+
const STATUS_LINE = /^[ \t]*-[ \t]*Status:[ \t]*(\S+)/m;
|
|
70
|
+
/** A fenced block's delimiter line, either spelling. */
|
|
71
|
+
const FENCE = /^[ \t]*(`{3,}|~{3,})/;
|
|
72
|
+
/**
|
|
73
|
+
* An indented code block's line — the OTHER way markdown quotes code, and the
|
|
74
|
+
* way `loam instructions loam-report` prints its own template (the four-space
|
|
75
|
+
* block in ../../agent/support/report.ts). A fence-only reader counted such a
|
|
76
|
+
* report open off the template's `- Status: open`.
|
|
77
|
+
*/
|
|
78
|
+
const INDENTED = /^(?: {4}|\t)/;
|
|
79
|
+
/** A section heading — the line that ends the header field block. */
|
|
80
|
+
const SECTION = /^##[ \t]/;
|
|
81
|
+
/**
|
|
82
|
+
* The report's header field block: every line above the first `## ` heading,
|
|
83
|
+
* with quoted code — fenced or indented — dropped.
|
|
84
|
+
*
|
|
85
|
+
* Matched anywhere in the file, `- Status:` convicted the wrong reports twice
|
|
86
|
+
* over. A report that QUOTES the protocol template — which several do, because
|
|
87
|
+
* an agent reporting that the protocol is wrong pastes the protocol — carries a
|
|
88
|
+
* `- Status: open` inside a fence and was counted open however its own header
|
|
89
|
+
* read; and a report whose body discusses a status line later on was read the
|
|
90
|
+
* same way. The status a report claims is a header FIELD, written beside
|
|
91
|
+
* `- Classification:` above the first section, so that is the only place it is
|
|
92
|
+
* read from. An unterminated fence swallows the rest of the file rather than
|
|
93
|
+
* flipping back — the safe direction, since the alternative is reading code as
|
|
94
|
+
* a field.
|
|
95
|
+
*
|
|
96
|
+
* Two shapes defeated the first version of that, and both are the same quoting
|
|
97
|
+
* case: a NESTED fence, where the outer ```` opens and the inner ``` was taken
|
|
98
|
+
* for its close, leaving the quoted block read as fields; and an INDENTED
|
|
99
|
+
* block, which the protocol itself uses and whose indented `## Summary` never
|
|
100
|
+
* ends the header either. So a fence is closed only by a run of the same
|
|
101
|
+
* character at least as long as the one that opened it, and an indented block
|
|
102
|
+
* (four spaces after a blank line, through the blank lines inside it) is
|
|
103
|
+
* dropped whole.
|
|
104
|
+
*/
|
|
105
|
+
function headerFields(text) {
|
|
106
|
+
const kept = [];
|
|
107
|
+
let fence = null;
|
|
108
|
+
let indented = false;
|
|
109
|
+
let afterBlank = true;
|
|
110
|
+
for (const line of text.split(/\r?\n/)) {
|
|
111
|
+
if (fence !== null) {
|
|
112
|
+
const close = FENCE.exec(line)?.[1];
|
|
113
|
+
if (close !== undefined && close[0] === fence[0] && close.length >= fence.length)
|
|
114
|
+
fence = null;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const blank = line.trim() === "";
|
|
118
|
+
if (indented) {
|
|
119
|
+
if (blank || INDENTED.test(line))
|
|
120
|
+
continue;
|
|
121
|
+
indented = false;
|
|
122
|
+
}
|
|
123
|
+
const open = FENCE.exec(line)?.[1];
|
|
124
|
+
if (open !== undefined) {
|
|
125
|
+
fence = open;
|
|
126
|
+
afterBlank = false;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (afterBlank && INDENTED.test(line)) {
|
|
130
|
+
indented = true;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (SECTION.test(line))
|
|
134
|
+
break;
|
|
135
|
+
kept.push(line);
|
|
136
|
+
afterBlank = blank;
|
|
137
|
+
}
|
|
138
|
+
return kept.join("\n");
|
|
139
|
+
}
|
|
140
|
+
/** The words a `Status:` line may say that loam can count. */
|
|
141
|
+
const STATED = ["open", "sent", "fixed", "superseded"];
|
|
142
|
+
/**
|
|
143
|
+
* The ordinal the next report takes: one past the highest present, never one
|
|
144
|
+
* past the count.
|
|
145
|
+
*
|
|
146
|
+
* Those two differ exactly when the directory has a gap or a legacy name in it
|
|
147
|
+
* — which this directory does, since the numbering arrived after eleven
|
|
148
|
+
* reports had already been written — and counting instead of maximising is how
|
|
149
|
+
* a next ordinal starts handing out numbers that are already taken.
|
|
150
|
+
*/
|
|
151
|
+
function nextOrdinal(entries) {
|
|
152
|
+
let highest = 0;
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
if (entry.ordinal === null)
|
|
155
|
+
continue;
|
|
156
|
+
const value = Number.parseInt(entry.ordinal, 10);
|
|
157
|
+
if (value > highest)
|
|
158
|
+
highest = value;
|
|
159
|
+
}
|
|
160
|
+
return String(highest + 1).padStart(3, "0");
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* What one report's own `Status:` line says, or `unstated`.
|
|
164
|
+
*
|
|
165
|
+
* Unreadable reads as unstated rather than throwing: a report saved as UTF-16,
|
|
166
|
+
* a file the process cannot open, or a directory somebody named `notes.md` are
|
|
167
|
+
* all things a diagnostic must survive. `doctor` is the command that has to be
|
|
168
|
+
* able to describe a broken repository without becoming the next thing that
|
|
169
|
+
* breaks in it.
|
|
170
|
+
*/
|
|
171
|
+
async function statusOf(path) {
|
|
172
|
+
let text;
|
|
173
|
+
try {
|
|
174
|
+
text = await readFile(path, "utf8");
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Unreadable is not a state the file claims — it is a state of the read.
|
|
178
|
+
return "unstated";
|
|
179
|
+
}
|
|
180
|
+
const word = STATUS_LINE.exec(headerFields(text))?.[1]?.toLowerCase();
|
|
181
|
+
return STATED.find((known) => known === word) ?? "unstated";
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Read `<root>/loam-reports/`, where `root` is the directory holding the
|
|
185
|
+
* `loam.json` that resolved — the repository the reports were written at the
|
|
186
|
+
* root of — falling back, when nothing parsed, to the directory of the
|
|
187
|
+
* `loam.json` loam FOUND (`doctor.ts` says why that and not the cwd), and to the
|
|
188
|
+
* run directory only when there is no config file anywhere above it.
|
|
189
|
+
*
|
|
190
|
+
* Every `*.md` in the directory is an entry, including a hand-written index
|
|
191
|
+
* page: which files in there are "really" reports is a second naming
|
|
192
|
+
* convention, and inventing one would make loam disagree with a directory
|
|
193
|
+
* listing about what the directory holds. An unnumbered entry contributes
|
|
194
|
+
* nothing to `next`, so an index costs the corpus nothing either.
|
|
195
|
+
*/
|
|
196
|
+
export async function scanReports(root) {
|
|
197
|
+
const dir = join(root, REPORTS_DIR);
|
|
198
|
+
let names;
|
|
199
|
+
try {
|
|
200
|
+
names = (await readdir(dir)).filter((name) => name.toLowerCase().endsWith(".md"));
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
// Absent is the state of every repository that has never had to write a
|
|
204
|
+
// report, and it is the common one — `present: false`, nothing to say. Any
|
|
205
|
+
// other errno means the directory IS there and this process could not list
|
|
206
|
+
// it, which is a different sentence: the corpus exists and loam cannot see
|
|
207
|
+
// into it, so `present` stays true and the counts stay honestly empty.
|
|
208
|
+
const code = error.code;
|
|
209
|
+
const present = code !== "ENOENT" && code !== "ENOTDIR";
|
|
210
|
+
return { dir, present, total: 0, next: "001", entries: [] };
|
|
211
|
+
}
|
|
212
|
+
// Sorted here rather than left in readdir order, which is the filesystem's and
|
|
213
|
+
// differs between one machine and another — and sorted by the ORDINAL the name
|
|
214
|
+
// claims, numerically, because report order is what a reader scanning the list
|
|
215
|
+
// expects and byte order stops being it at exactly the point the widening rule
|
|
216
|
+
// was written for: `1000-…` sorts before `998-…`, so a directory that has
|
|
217
|
+
// passed its thousandth report reads 1000, 1001, 998, 999 (verification
|
|
218
|
+
// 2026-09-04, second pass). Unnumbered names — the shape reports carried before
|
|
219
|
+
// the numbering existed — contribute no ordinal, so they sort last among
|
|
220
|
+
// themselves by name, where a reader looking for "the next one" is not looking.
|
|
221
|
+
const rank = (file) => {
|
|
222
|
+
const ordinal = ordinalOf(file);
|
|
223
|
+
return ordinal === null ? Number.POSITIVE_INFINITY : Number.parseInt(ordinal, 10);
|
|
224
|
+
};
|
|
225
|
+
names.sort((a, b) => rank(a) - rank(b) || (a < b ? -1 : a > b ? 1 : 0));
|
|
226
|
+
// Read together, like `staleAgentFiles` does: a report corpus is a flat
|
|
227
|
+
// directory of small hand-written files, so there is nothing here worth a
|
|
228
|
+
// concurrency cap, and `Promise.all` preserves the order just established.
|
|
229
|
+
const entries = await Promise.all(names.map(async (file) => ({
|
|
230
|
+
file,
|
|
231
|
+
ordinal: ordinalOf(file),
|
|
232
|
+
status: await statusOf(join(dir, file)),
|
|
233
|
+
})));
|
|
234
|
+
return {
|
|
235
|
+
dir,
|
|
236
|
+
present: true,
|
|
237
|
+
total: entries.length,
|
|
238
|
+
next: nextOrdinal(entries),
|
|
239
|
+
entries,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=scan.js.map
|