@intentius/chant 0.101.0 → 0.102.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/checks.d.ts.map +1 -1
- package/dist/attrref.d.ts.map +1 -1
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/nginx.d.ts.map +1 -1
- package/dist/audit/prior-art.d.ts +48 -0
- package/dist/audit/prior-art.d.ts.map +1 -1
- package/dist/behaviour-kinds.d.ts.map +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/cli/commands/carve-emit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +16 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/import.d.ts +8 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +9 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
- package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +9 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/lsp/server.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/tools/build.d.ts.map +1 -1
- package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
- package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
- package/dist/cli/mcp/tools/import.d.ts.map +1 -1
- package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
- package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
- package/dist/cli/mcp/tools/search.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +11 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/codegen/naming.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts +67 -3
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/verbs/process-runner.d.ts.map +1 -1
- package/dist/components/verbs/r2-sync.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/components/verbs/verify.d.ts.map +1 -1
- package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
- package/dist/components/verbs/wrangler.d.ts.map +1 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +19 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/declarable.d.ts +16 -0
- package/dist/declarable.d.ts.map +1 -1
- package/dist/discovery/cache.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -5
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +16 -0
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.d.ts +37 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +3 -1
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-layout.d.ts.map +1 -1
- package/dist/import/base-parser.d.ts.map +1 -1
- package/dist/import/embedded.d.ts +7 -0
- package/dist/import/embedded.d.ts.map +1 -1
- package/dist/import/parser-options.d.ts +14 -0
- package/dist/import/parser-options.d.ts.map +1 -0
- package/dist/import/parser.d.ts +17 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts.map +1 -1
- package/dist/lexicon-output.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +3 -3
- package/dist/lexicon.d.ts +55 -3
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +15 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/lifecycle/work-lease.d.ts +61 -2
- package/dist/lifecycle/work-lease.d.ts.map +1 -1
- package/dist/lint/config.d.ts +8 -8
- package/dist/lint/engine.d.ts +6 -0
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +11 -11
- package/dist/op/activities/apply.d.ts +79 -9
- package/dist/op/activities/apply.d.ts.map +1 -1
- package/dist/op/activities/decide.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +1 -1
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
- package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +1 -1
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-profiles.d.ts.map +1 -1
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/apply-op.d.ts +12 -5
- package/dist/op/composites/apply-op.d.ts.map +1 -1
- package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
- package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts.map +1 -1
- package/dist/op/decide-read-inputs.d.ts.map +1 -1
- package/dist/op/emulator-lifecycle.d.ts +21 -2
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/gate-approval.d.ts +5 -1
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-wait.d.ts.map +1 -1
- package/dist/op/gate.d.ts +19 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/index.d.ts +2 -0
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +6 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/run-context.d.ts +39 -0
- package/dist/op/run-context.d.ts.map +1 -0
- package/dist/op/run-live.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/source-archive.d.ts.map +1 -1
- package/dist/op/steward-beside.d.ts.map +1 -1
- package/dist/op/steward-points.d.ts.map +1 -1
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/types.d.ts +8 -3
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts +24 -4
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/project-root.d.ts.map +1 -1
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/pseudo-parameter.d.ts.map +1 -1
- package/dist/reconcile.d.ts.map +1 -1
- package/dist/secret-materialization.d.ts.map +1 -1
- package/dist/terraform/emit-source.d.ts.map +1 -1
- package/dist/terraform/parse.d.ts.map +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
- package/dist/workspace/agent-cli.d.ts +5 -0
- package/dist/workspace/agent-cli.d.ts.map +1 -1
- package/dist/workspace/box-factory.d.ts +73 -0
- package/dist/workspace/box-factory.d.ts.map +1 -0
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
- package/dist/workspace/checks/boxes.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +5 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +10 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/conformance/index.d.ts +67 -6
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts +12 -3
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/conformance/writer.d.ts +368 -0
- package/dist/workspace/conformance/writer.d.ts.map +1 -0
- package/dist/workspace/decide.d.ts +1 -1
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/declaration.d.ts +91 -4
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +234 -5
- package/dist/workspace/generated-files.d.ts.map +1 -1
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/identity.d.ts +111 -0
- package/dist/workspace/identity.d.ts.map +1 -0
- package/dist/workspace/intent-cli.d.ts +7 -4
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-record.d.ts +8 -1
- package/dist/workspace/intent-record.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +149 -5
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/kinds.d.ts +18 -4
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
- package/dist/workspace/lineage-lock.d.ts +2 -2
- package/dist/workspace/lineage-lock.d.ts.map +1 -1
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/principal-classes.d.ts +114 -0
- package/dist/workspace/principal-classes.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +21 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-assets.d.ts.map +1 -1
- package/dist/workspace/records-checkout.d.ts +54 -0
- package/dist/workspace/records-checkout.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +27 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +1 -1
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-since.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +5 -4
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +15 -0
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runs-cli.d.ts +164 -0
- package/dist/workspace/runs-cli.d.ts.map +1 -0
- package/dist/workspace/runs.d.ts +423 -0
- package/dist/workspace/runs.d.ts.map +1 -0
- package/dist/workspace/source-block.d.ts +1 -1
- package/dist/workspace/source-block.d.ts.map +1 -1
- package/dist/workspace/status-gates.d.ts +15 -3
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +10 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/symbols.d.ts +52 -0
- package/dist/workspace/symbols.d.ts.map +1 -0
- package/dist/workspace/trailer-joins.d.ts +46 -0
- package/dist/workspace/trailer-joins.d.ts.map +1 -0
- package/dist/workspace/trailers.d.ts +89 -0
- package/dist/workspace/trailers.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.d.ts.map +1 -1
- package/dist/workspace/trust/seal.d.ts +33 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/test-repo.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +39 -3
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work.d.ts +26 -1
- package/dist/workspace/work.d.ts.map +1 -1
- package/dist/workspace/workspace-principals.schema.json +60 -0
- package/dist/workspace/write-scope.d.ts +79 -19
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/yaml.d.ts +21 -39
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/audit/prior-art.ts +15 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
- package/src/cli/commands/carve-emit-state.test.ts +4 -0
- package/src/cli/commands/carve-emit.ts +5 -19
- package/src/cli/commands/check-lexicon.test.ts +62 -1
- package/src/cli/commands/check-lexicon.ts +36 -6
- package/src/cli/commands/import-live.test.ts +18 -0
- package/src/cli/commands/import-parser-options.test.ts +133 -0
- package/src/cli/commands/import.ts +21 -3
- package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
- package/src/cli/commands/init.test.ts +8 -1
- package/src/cli/commands/init.ts +23 -8
- package/src/cli/commands/lint.test.ts +37 -0
- package/src/cli/commands/onboard.test.ts +53 -3
- package/src/cli/commands/onboard.ts +52 -4
- package/src/cli/commands/pinned-upgrade.ts +73 -33
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
- package/src/cli/handlers/components.ts +2 -2
- package/src/cli/handlers/dev.ts +8 -6
- package/src/cli/handlers/lifecycle.test.ts +67 -0
- package/src/cli/handlers/lifecycle.ts +6 -5
- package/src/cli/handlers/misc.ts +2 -0
- package/src/cli/handlers/operator.ts +87 -3
- package/src/cli/handlers/run.test.ts +111 -0
- package/src/cli/handlers/run.ts +85 -24
- package/src/cli/handlers/scenario.ts +2 -2
- package/src/cli/main.test.ts +1 -0
- package/src/cli/main.ts +65 -11
- package/src/cli/mcp/workspace-tools.test.ts +5 -2
- package/src/cli/mcp/workspace-tools.ts +29 -2
- package/src/cli/registry.ts +11 -2
- package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
- package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
- package/src/codegen/pinned-upgrade.test.ts +104 -0
- package/src/codegen/pinned-upgrade.ts +250 -56
- package/src/codegen/testdata/postgres-tags.json +242 -0
- package/src/config.test.ts +21 -0
- package/src/config.ts +15 -0
- package/src/declarable.test.ts +21 -0
- package/src/declarable.ts +19 -0
- package/src/discovery/fold-composite.test.ts +41 -5
- package/src/discovery/fold-import.test.ts +129 -0
- package/src/discovery/fold-import.ts +643 -131
- package/src/discovery/fold-package-composite.test.ts +264 -0
- package/src/discovery/fold-specifiers.test.ts +191 -0
- package/src/discovery/param-deps.test.ts +66 -1
- package/src/discovery/param-deps.ts +45 -0
- package/src/discovery/specifier-resolve.ts +344 -0
- package/src/fold/fold.test.ts +39 -1
- package/src/fold/fold.ts +83 -9
- package/src/fold-provenance.test.ts +140 -0
- package/src/graph-ir.test.ts +83 -0
- package/src/graph-ir.ts +29 -0
- package/src/import/embedded.test.ts +20 -0
- package/src/import/embedded.ts +13 -0
- package/src/import/parser-options.ts +46 -0
- package/src/import/parser.ts +20 -0
- package/src/lexicon.ts +55 -3
- package/src/lifecycle/gate-ledger.ts +16 -0
- package/src/lifecycle/observe.test.ts +18 -0
- package/src/lifecycle/observe.ts +6 -4
- package/src/lifecycle/snapshot.ts +2 -2
- package/src/lifecycle/work-lease.test.ts +1 -1
- package/src/lifecycle/work-lease.ts +93 -2
- package/src/lint/engine.test.ts +55 -0
- package/src/lint/engine.ts +30 -4
- package/src/lint/output-docs.test.ts +59 -56
- package/src/op/activities/apply.test.ts +189 -5
- package/src/op/activities/apply.ts +230 -8
- package/src/op/activities/index.ts +3 -0
- package/src/op/activities/lexicon-upgrade.test.ts +160 -6
- package/src/op/activities/lexicon-upgrade.ts +154 -51
- package/src/op/composites/apply-op.ts +12 -5
- package/src/op/composites/composites.test.ts +30 -3
- package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
- package/src/op/composites/lexicon-upgrade-op.ts +29 -8
- package/src/op/emulator-lifecycle.test.ts +23 -0
- package/src/op/emulator-lifecycle.ts +52 -14
- package/src/op/gate-approval.test.ts +74 -0
- package/src/op/gate-approval.ts +5 -1
- package/src/op/gate.ts +31 -5
- package/src/op/index.ts +2 -0
- package/src/op/local-executor.test.ts +15 -0
- package/src/op/local-executor.ts +40 -12
- package/src/op/local-output.ts +1 -1
- package/src/op/operator.ts +4 -0
- package/src/op/run-context.test.ts +141 -0
- package/src/op/run-context.ts +51 -0
- package/src/op/runtimes/local.test.ts +14 -0
- package/src/op/runtimes/local.ts +2 -0
- package/src/op/steward-beside.ts +2 -0
- package/src/op/types.ts +8 -3
- package/src/op/work-lease-run.test.ts +47 -0
- package/src/op/work-lease-run.ts +73 -11
- package/src/provenance.ts +35 -0
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
- package/src/workspace/agent-cli.ts +7 -3
- package/src/workspace/agent.schema.json +22 -0
- package/src/workspace/box-factory.test.ts +320 -0
- package/src/workspace/box-factory.ts +103 -0
- package/src/workspace/changes.schema.json +12 -20
- package/src/workspace/changes.ts +4 -1
- package/src/workspace/checks/boxes.test.ts +2 -0
- package/src/workspace/checks.ts +29 -5
- package/src/workspace/compose-graph.ts +7 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
- package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
- package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
- package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
- package/src/workspace/conformance/index.mjs +30 -3
- package/src/workspace/conformance/index.ts +103 -15
- package/src/workspace/conformance/vitest.ts +44 -2
- package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
- package/src/workspace/conformance/writer.test.ts +156 -0
- package/src/workspace/conformance/writer.ts +880 -0
- package/src/workspace/decide.ts +7 -1
- package/src/workspace/declaration.schema.json +234 -5
- package/src/workspace/declaration.ts +174 -10
- package/src/workspace/graph-cli.ts +5 -2
- package/src/workspace/graph.schema.json +247 -0
- package/src/workspace/identity.test.ts +196 -0
- package/src/workspace/identity.ts +186 -0
- package/src/workspace/intent-cli.ts +64 -10
- package/src/workspace/intent-record.schema.json +208 -0
- package/src/workspace/intent-record.ts +35 -1
- package/src/workspace/intent-why.test.ts +274 -0
- package/src/workspace/intent.schema.json +718 -7
- package/src/workspace/intent.test.ts +11 -2
- package/src/workspace/intent.ts +495 -32
- package/src/workspace/kinds.ts +44 -37
- package/src/workspace/points-write.schema.json +1 -0
- package/src/workspace/principal-classes.test.ts +175 -0
- package/src/workspace/principal-classes.ts +307 -0
- package/src/workspace/reason-codes.test.ts +11 -3
- package/src/workspace/reason-codes.ts +24 -1
- package/src/workspace/records-amend.schema.json +2 -0
- package/src/workspace/records-checkout.ts +106 -0
- package/src/workspace/records-cli.ts +103 -11
- package/src/workspace/records-close.schema.json +1 -0
- package/src/workspace/records-new.schema.json +2 -0
- package/src/workspace/records-review.schema.json +2 -0
- package/src/workspace/records-uncommitted.test.ts +171 -0
- package/src/workspace/records-write.ts +28 -6
- package/src/workspace/records.schema.json +72 -1
- package/src/workspace/records.ts +27 -0
- package/src/workspace/runs-cli.ts +381 -0
- package/src/workspace/runs-write.schema.json +583 -0
- package/src/workspace/runs.schema.json +810 -0
- package/src/workspace/runs.test.ts +242 -0
- package/src/workspace/runs.ts +709 -0
- package/src/workspace/status-contract.test.ts +5 -0
- package/src/workspace/status-gates.ts +22 -8
- package/src/workspace/status.schema.json +127 -2
- package/src/workspace/status.ts +21 -1
- package/src/workspace/symbols.ts +180 -0
- package/src/workspace/trailer-joins.test.ts +171 -0
- package/src/workspace/trailer-joins.ts +82 -0
- package/src/workspace/trailers.ts +148 -0
- package/src/workspace/trust/seal.ts +52 -1
- package/src/workspace/work-cli.e2e.test.ts +28 -1
- package/src/workspace/work-cli.ts +133 -8
- package/src/workspace/work-evidence.schema.json +2 -0
- package/src/workspace/work-history.schema.json +73 -1
- package/src/workspace/work-history.test.ts +48 -5
- package/src/workspace/work-kind-fields.test.ts +154 -0
- package/src/workspace/work-lease.schema.json +14 -1
- package/src/workspace/work.ts +82 -0
- package/src/workspace/workspace-principals.schema.json +60 -0
- package/src/workspace/write-scope.test.ts +128 -2
- package/src/workspace/write-scope.ts +170 -45
- package/src/yaml.test.ts +111 -1
- package/src/yaml.ts +141 -549
|
@@ -50,7 +50,8 @@ import { policyAtBase, resolveBase } from "./trust/provenance";
|
|
|
50
50
|
import { WorkspaceReadError } from "./declaration";
|
|
51
51
|
import { findSessionKinds, headCommit, sessionKindsFor } from "./session-kinds";
|
|
52
52
|
import { workingTree } from "./tree";
|
|
53
|
-
import { AGENT_ENV, refuseRecordWrite, WRITE_SCOPE_CODES, WriteScopeError } from "./write-scope";
|
|
53
|
+
import { AGENT_ENV, refuseRecordWrite, WRITE_SCOPE_CODES, WriteScopeError, type ScopeSource } from "./write-scope";
|
|
54
|
+
import { IDENTITY_CODES, IdentityError, refuseUnidentified } from "./identity";
|
|
54
55
|
import type { WriteVerb } from "./declaration";
|
|
55
56
|
|
|
56
57
|
// ── Contract ─────────────────────────────────────────────────────────────────
|
|
@@ -78,6 +79,7 @@ export const NEW_ERROR_CODES = [
|
|
|
78
79
|
"record-state-not-initial",
|
|
79
80
|
"ratify-quorum-not-met",
|
|
80
81
|
...WRITE_SCOPE_CODES,
|
|
82
|
+
...IDENTITY_CODES,
|
|
81
83
|
...RECORD_REASON_CODES,
|
|
82
84
|
] as const satisfies readonly ReasonCode[];
|
|
83
85
|
|
|
@@ -93,6 +95,7 @@ export const AMEND_ERROR_CODES = [
|
|
|
93
95
|
"record-sign-failed",
|
|
94
96
|
"ratify-quorum-not-met",
|
|
95
97
|
...WRITE_SCOPE_CODES,
|
|
98
|
+
...IDENTITY_CODES,
|
|
96
99
|
...RECORD_REASON_CODES,
|
|
97
100
|
] as const satisfies readonly ReasonCode[];
|
|
98
101
|
|
|
@@ -108,6 +111,7 @@ export const REVIEW_ERROR_CODES = [
|
|
|
108
111
|
"session-unknown",
|
|
109
112
|
"session-not-open",
|
|
110
113
|
...WRITE_SCOPE_CODES,
|
|
114
|
+
...IDENTITY_CODES,
|
|
111
115
|
...RECORD_REASON_CODES,
|
|
112
116
|
] as const satisfies readonly ReasonCode[];
|
|
113
117
|
|
|
@@ -361,8 +365,24 @@ export async function open(kind: string, cwd: string): Promise<Opened> {
|
|
|
361
365
|
* written with `verb`, by the session `agent` names (`CHANT_AGENT`) or the
|
|
362
366
|
* principal `by` names. Throws a {@link WriteScopeError}.
|
|
363
367
|
*/
|
|
364
|
-
export function refuseOutOfScope(o: Opened, verb: WriteVerb, cwd: string, writer: { agent?: string; by?: string }):
|
|
365
|
-
refuseRecordWrite(cwd, { kindName: o.loaded.kind.name, kindFile: o.loaded.file, recordsDir: o.loaded.dir, verb, agent: writer.agent, principal: writer.by });
|
|
368
|
+
export function refuseOutOfScope(o: Opened, verb: WriteVerb, cwd: string, writer: { agent?: string; by?: string }): ScopeSource {
|
|
369
|
+
const source = refuseRecordWrite(cwd, { kindName: o.loaded.kind.name, kindFile: o.loaded.file, recordsDir: o.loaded.dir, verb, agent: writer.agent, principal: writer.by });
|
|
370
|
+
// #3163: under identity.attribution "identified" at base, --by names a forge identity or a signer.
|
|
371
|
+
if (writer.by !== undefined) refuseUnidentified(source, [writer.by], "--by", { agent: writer.agent });
|
|
372
|
+
return source;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Refuse author fields (the kind's reviews.decider and proposedBy fields)
|
|
377
|
+
* that name a person by a bare name, under identity.attribution
|
|
378
|
+
* "identified" at base (#3163). `flag` is where the fields came from.
|
|
379
|
+
*/
|
|
380
|
+
function refuseUnidentifiedAuthors(source: ScopeSource, kind: Opened["loaded"]["kind"], fields: Record<string, unknown>, flag: string, agent?: string): void {
|
|
381
|
+
const names = [kind.reviews?.decider, kind.proposedBy?.field]
|
|
382
|
+
.filter((f): f is string => f !== undefined)
|
|
383
|
+
.map((f) => fields[f])
|
|
384
|
+
.filter((v): v is string => typeof v === "string" && v.trim() !== "");
|
|
385
|
+
refuseUnidentified(source, names, `${flag} author`, { agent });
|
|
366
386
|
}
|
|
367
387
|
|
|
368
388
|
/**
|
|
@@ -628,7 +648,7 @@ function today(): string {
|
|
|
628
648
|
}
|
|
629
649
|
|
|
630
650
|
export function failure<C>(schema: string, err: unknown): WriteFailure<C> {
|
|
631
|
-
if (err instanceof RecordWriteError || err instanceof RecordReadError || err instanceof WriteScopeError) {
|
|
651
|
+
if (err instanceof RecordWriteError || err instanceof RecordReadError || err instanceof WriteScopeError || err instanceof IdentityError) {
|
|
632
652
|
return { $schema: schema, contract: RECORDS_WRITE_CONTRACT_VERSION, error: { code: err.code as C, message: err.message } };
|
|
633
653
|
}
|
|
634
654
|
throw err;
|
|
@@ -773,9 +793,10 @@ export async function newRecord(opts: NewRecordOptions & ChannelOptions): Promis
|
|
|
773
793
|
throw new RecordWriteError("write-usage-invalid", `--prefix takes letters and digits, starting with a letter, not ${JSON.stringify(opts.prefix)}`);
|
|
774
794
|
}
|
|
775
795
|
const o = await open(opts.kind, opts.cwd);
|
|
776
|
-
refuseOutOfScope(o, "new", opts.cwd, opts);
|
|
796
|
+
const scope = refuseOutOfScope(o, "new", opts.cwd, opts);
|
|
777
797
|
const fields = applyChannel(o, parseFields(opts.fields, "--from"), opts, true, "--from");
|
|
778
798
|
const { kind, schema } = o.loaded;
|
|
799
|
+
refuseUnidentifiedAuthors(scope, kind, fields, "--from", opts.agent);
|
|
779
800
|
refuseSealField(o, fields, "--from");
|
|
780
801
|
refuseRevisionFields(kind, fields, {}, "--from");
|
|
781
802
|
const idField = kind.idField!;
|
|
@@ -858,9 +879,10 @@ export interface AmendRecordOptions {
|
|
|
858
879
|
export async function amendRecord(opts: AmendRecordOptions & ChannelOptions): Promise<AmendDocument> {
|
|
859
880
|
try {
|
|
860
881
|
const o = await open(opts.kind, opts.cwd);
|
|
861
|
-
refuseOutOfScope(o, "amend", opts.cwd, opts);
|
|
882
|
+
const scope = refuseOutOfScope(o, "amend", opts.cwd, opts);
|
|
862
883
|
const given = applyChannel(o, parseFields(opts.fields, "--set"), opts, false, "--set");
|
|
863
884
|
const { kind } = o.loaded;
|
|
885
|
+
refuseUnidentifiedAuthors(scope, kind, given, "--set", opts.agent);
|
|
864
886
|
refuseSealField(o, given, "--set");
|
|
865
887
|
const before = await readAll(o, o.source);
|
|
866
888
|
const target = findRecord(before, opts.id, kind.name);
|
|
@@ -124,6 +124,14 @@
|
|
|
124
124
|
},
|
|
125
125
|
"error": false,
|
|
126
126
|
"current": { "type": "boolean", "description": "Whether superseded records were left out (--current)." },
|
|
127
|
+
"uncommitted": {
|
|
128
|
+
"type": "boolean",
|
|
129
|
+
"description": "Added in contract 1 by #3160. Whether only the records HEAD doesn't hold were listed (--uncommitted): each record's worktree is then modified or new, checkout is present, and summary's total, valid and invalid count the records listed. Absent from documents written before #3160, which read as false."
|
|
130
|
+
},
|
|
131
|
+
"checkout": {
|
|
132
|
+
"description": "Added in contract 1 by #3160, on a read of the working tree in a git repository; absent under --at and outside git. The branch, head and base the working tree is on, and the records of the kind it has deleted.",
|
|
133
|
+
"$ref": "#/$defs/checkout"
|
|
134
|
+
},
|
|
127
135
|
"trust": { "$ref": "#/$defs/trust" },
|
|
128
136
|
"records": {
|
|
129
137
|
"description": "One entry per file the kind locates, sorted by path.",
|
|
@@ -145,6 +153,42 @@
|
|
|
145
153
|
"type": "array",
|
|
146
154
|
"items": { "$ref": "#/$defs/decisionWork" }
|
|
147
155
|
}
|
|
156
|
+
},
|
|
157
|
+
"allOf": [
|
|
158
|
+
{
|
|
159
|
+
"if": { "properties": { "uncommitted": { "const": true } }, "required": ["uncommitted"] },
|
|
160
|
+
"then": {
|
|
161
|
+
"required": ["checkout"],
|
|
162
|
+
"properties": {
|
|
163
|
+
"checkout": true,
|
|
164
|
+
"at": { "type": "null" },
|
|
165
|
+
"records": { "type": "array", "items": { "type": "object", "required": ["worktree"], "properties": { "worktree": { "enum": ["modified", "new"] } } } }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
"checkout": {
|
|
172
|
+
"type": "object",
|
|
173
|
+
"required": ["branch", "head", "base", "baseFrom", "deleted"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"branch": { "type": ["string", "null"], "description": "The checked-out branch's short name, such as chant/work/w-12, or null when HEAD is detached." },
|
|
176
|
+
"head": {
|
|
177
|
+
"type": ["string", "null"],
|
|
178
|
+
"pattern": "^[0-9a-f]{40,64}$",
|
|
179
|
+
"description": "The full commit id HEAD names, or null before the first commit (every record is then new). Each record's worktree is judged against it."
|
|
180
|
+
},
|
|
181
|
+
"base": {
|
|
182
|
+
"type": ["string", "null"],
|
|
183
|
+
"pattern": "^[0-9a-f]{40,64}$",
|
|
184
|
+
"description": "The merge base of head and the target branch tip, the commit the branch forked from: what applying the branch would be measured against. The target is --base when given, else origin/HEAD, main or master, as for trust. Null without a head, a target, or common history."
|
|
185
|
+
},
|
|
186
|
+
"baseFrom": { "enum": ["flag", "origin/HEAD", "main", "master", null], "description": "Where the target branch was found; null when there is none." },
|
|
187
|
+
"deleted": {
|
|
188
|
+
"type": "array",
|
|
189
|
+
"items": { "type": "string" },
|
|
190
|
+
"description": "Record files of the kind that head holds and the working tree no longer has, staged or not, from the repository root with / separators, sorted. A renamed record is here under its old name and in records as new."
|
|
191
|
+
}
|
|
148
192
|
}
|
|
149
193
|
},
|
|
150
194
|
"workLink": {
|
|
@@ -155,6 +199,17 @@
|
|
|
155
199
|
"state": { "type": ["string", "null"], "description": "The linked record's state as it writes it, or null when no record has the id." }
|
|
156
200
|
}
|
|
157
201
|
},
|
|
202
|
+
"workAnswer": {
|
|
203
|
+
"type": "object",
|
|
204
|
+
"required": ["id", "point", "state", "answer", "answeredBy"],
|
|
205
|
+
"properties": {
|
|
206
|
+
"id": { "type": "string", "description": "The answer record's id: the point's name and the first 12 hex digits of its inputs hash." },
|
|
207
|
+
"point": { "type": ["string", "null"], "description": "The decision point it answers." },
|
|
208
|
+
"state": { "type": ["string", "null"], "description": "escalated, proposed or answered." },
|
|
209
|
+
"answer": { "oneOf": [{ "type": "string" }, { "type": "boolean" }, { "type": "null" }], "description": "The answer, or null while there is none." },
|
|
210
|
+
"answeredBy": { "type": "array", "items": { "type": "string" }, "description": "The people who answered or confirmed it; empty when a table or a model answered." }
|
|
211
|
+
}
|
|
212
|
+
},
|
|
158
213
|
"decisionWork": {
|
|
159
214
|
"type": "object",
|
|
160
215
|
"required": ["id", "path", "state", "supersededBy", "implementedBy"],
|
|
@@ -199,7 +254,7 @@
|
|
|
199
254
|
"items": { "$ref": "#/$defs/asset" }
|
|
200
255
|
},
|
|
201
256
|
"warnings": {
|
|
202
|
-
"description": "Added in contract 1 by #2549. Findings that leave the record valid: a pinned file that changed, went missing or did not follow a superseding decision, a supersedes link that has no effect yet, (added by #2654) an evidence list that is empty, (added by #2672) a verdict that names no digest, or (added by #2683) a work record's links and dates, or (added by #2688) a record whose author seal is not attested under a signers file at base, or (added by #2772) a work record's acceptance criteria unmet when it is done, or met by a manual verdict from its implementer. A work record never carries record-no-evidence; a done one with no evidence carries work-done-unpinned. valid and --current do not look at them.",
|
|
257
|
+
"description": "Added in contract 1 by #2549. Findings that leave the record valid: a pinned file that changed, went missing or did not follow a superseding decision, a supersedes link that has no effect yet, (added by #2654) an evidence list that is empty, (added by #2672) a verdict that names no digest, or (added by #2683) a work record's links and dates, or (added by #2688) a record whose author seal is not attested under a signers file at base, or (added by #2772) a work record's acceptance criteria unmet when it is done, or met by a manual verdict from its implementer, or (added by #3147) a work record's contract that no record has or nobody approved, or a builder tier its kind does not list. A work record never carries record-no-evidence; a done one with no evidence carries work-done-unpinned. valid and --current do not look at them.",
|
|
203
258
|
"type": "array",
|
|
204
259
|
"items": { "$ref": "#/$defs/warning" }
|
|
205
260
|
},
|
|
@@ -243,6 +298,19 @@
|
|
|
243
298
|
"description": "Added in contract 1 by #2772, on every parsed record of a work kind whose work block names acceptance criteria. How many of the record's criteria passing evidence meets: evidence naming the criterion, with result pass and the criterion's verification, and for a manual criterion a by other than the implementer. Null when the record lists no criteria, so a record without them reads as before.",
|
|
244
299
|
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/workAcceptance" }]
|
|
245
300
|
},
|
|
301
|
+
"worktree": {
|
|
302
|
+
"enum": ["committed", "modified", "new"],
|
|
303
|
+
"description": "Added in contract 1 by #3160, on every record of a read of the working tree in a git repository; absent under --at and outside git. committed when the file is as HEAD holds it, modified when it differs from HEAD, new when HEAD doesn't hold it, staged or not. modified and new are the records a person has kept and not yet committed or applied."
|
|
304
|
+
},
|
|
305
|
+
"contract": {
|
|
306
|
+
"description": "Added in contract 1 by #3147, on every parsed record of a work kind whose work block names a contract kind. The contract record the item builds, by the id in the work block's contract field, with that record's state now; state is null when no record of the contract kind has the id. Null when the item names no contract.",
|
|
307
|
+
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/workLink" }]
|
|
308
|
+
},
|
|
309
|
+
"answers": {
|
|
310
|
+
"description": "Added in contract 1 by #3147, on every record with an id of a work kind whose work block names an answer kind. The decision-point answers about the item: each answer record whose constrains names the item's id, in path order, joined by id and never copied onto the item. Read at the same revision; in the working tree, with the answers a steward keeps on the lifecycle ledger laid over it. Absent when the answer kind can't be read.",
|
|
311
|
+
"type": "array",
|
|
312
|
+
"items": { "$ref": "#/$defs/workAnswer" }
|
|
313
|
+
},
|
|
246
314
|
"lease": {
|
|
247
315
|
"description": "Added in contract 1 by #2732, on every record of a work kind with an id, read in the working tree (absent under --at). The item's active work lease (chant workspace work claim), or null when nobody holds one live. Read from the local lease refs, refs/chant/lease/[_members/<member>/]work/<id> and the remote's as last fetched, of the ledger owning the work kind file; records never fetches.",
|
|
248
316
|
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/workLease" }]
|
|
@@ -319,6 +387,9 @@
|
|
|
319
387
|
"work-done-gap-open",
|
|
320
388
|
"work-acceptance-unmet",
|
|
321
389
|
"work-acceptance-self-verified",
|
|
390
|
+
"work-contract-unknown",
|
|
391
|
+
"work-contract-undecided",
|
|
392
|
+
"work-tier-unknown",
|
|
322
393
|
"answer-points-unreadable",
|
|
323
394
|
"answer-point-unknown",
|
|
324
395
|
"answer-point-changed",
|
package/src/workspace/records.ts
CHANGED
|
@@ -419,6 +419,23 @@ export const recordKindSchema = z
|
|
|
419
419
|
* done record with a criterion that no passing evidence meets is warned
|
|
420
420
|
* `work-acceptance-unmet`, which `check` fails on (WSP117). Optional, and
|
|
421
421
|
* a record without the list is read as before.
|
|
422
|
+
*
|
|
423
|
+
* #3147 adds four, each optional:
|
|
424
|
+
*
|
|
425
|
+
* - `contract`: the front-matter field naming the contract record the
|
|
426
|
+
* item builds, and the kind file of those records (relative to this
|
|
427
|
+
* kind file). Each record gets `contract` with that record's state,
|
|
428
|
+
* warned `work-contract-unknown` or `work-contract-undecided`.
|
|
429
|
+
* - `tier`: the field holding the item's builder tier, and the tiers the
|
|
430
|
+
* workspace allows, declared once here. A tier outside them is warned
|
|
431
|
+
* `work-tier-unknown`.
|
|
432
|
+
* - `attempts`: the field holding an item's own attempt limit, and the
|
|
433
|
+
* default `max` for items without one. `work history` counts attempts
|
|
434
|
+
* against it.
|
|
435
|
+
* - `answers`: the answer kind file (relative to this kind file) whose
|
|
436
|
+
* records answer decision points about an item. Each record gets
|
|
437
|
+
* `answers`, the answers whose `constrains` names its id, joined by id
|
|
438
|
+
* rather than copied onto the item.
|
|
422
439
|
*/
|
|
423
440
|
work: z
|
|
424
441
|
.object({
|
|
@@ -429,6 +446,14 @@ export const recordKindSchema = z
|
|
|
429
446
|
done: z.string().min(1),
|
|
430
447
|
closedOn: z.string().min(1),
|
|
431
448
|
acceptance: z.object({ field: z.string().min(1), implementer: z.string().min(1) }).strict().optional(),
|
|
449
|
+
contract: z.object({ field: z.string().min(1), kind: z.string().min(1) }).strict().optional(),
|
|
450
|
+
tier: z
|
|
451
|
+
.object({ field: z.string().min(1), tiers: z.array(z.string().min(1)).min(1) })
|
|
452
|
+
.strict()
|
|
453
|
+
.refine((t) => new Set(t.tiers).size === t.tiers.length, { message: "work.tier.tiers lists each tier once", path: ["tiers"] })
|
|
454
|
+
.optional(),
|
|
455
|
+
attempts: z.object({ field: z.string().min(1), max: z.number().int().min(1) }).strict().optional(),
|
|
456
|
+
answers: z.string().min(1).optional(),
|
|
432
457
|
})
|
|
433
458
|
.strict()
|
|
434
459
|
.optional(),
|
|
@@ -1148,6 +1173,8 @@ export interface RecordEntry {
|
|
|
1148
1173
|
implements?: WorkLink[];
|
|
1149
1174
|
/** For a work kind with acceptance criteria (#2772): how many of the record's criteria passing evidence meets, or null when it lists none. */
|
|
1150
1175
|
acceptance?: WorkAcceptance | null;
|
|
1176
|
+
/** For a work kind with a contract link (#3147): the contract record the item builds, with its state, or null when it names none. */
|
|
1177
|
+
contract?: WorkLink | null;
|
|
1151
1178
|
}
|
|
1152
1179
|
|
|
1153
1180
|
export interface ReadRecordsOptions {
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant workspace runs` (#3033, ws-076): read and write the agent run
|
|
3
|
+
* record.
|
|
4
|
+
*
|
|
5
|
+
* - `runs [--unit <id>] [--decision <id>] [--by <principal>] [--since <rev>]
|
|
6
|
+
* --json` reads every run in the ledger with the commits it made, and
|
|
7
|
+
* totals tokens and cost per work item, per decision and per principal,
|
|
8
|
+
* naming the runs that report no cost instead of counting them as zero
|
|
9
|
+
* (`runs.schema.json`, a read-contract output).
|
|
10
|
+
* - `runs start --from <file|->`, `runs end <id> --from <file|->` and `runs
|
|
11
|
+
* record --from <file|->` append a run's start, its end, or both, to
|
|
12
|
+
* `_agent-runs/<id>.jsonl` on `chant/lifecycle` (`runs-write.schema.json`).
|
|
13
|
+
*
|
|
14
|
+
* chant records runs and never starts one: whatever ran the agent writes the
|
|
15
|
+
* record. Reads never fetch.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { dirname, resolve } from "node:path";
|
|
20
|
+
import { formatError } from "../cli/format";
|
|
21
|
+
import type { CommandContext } from "../cli/registry";
|
|
22
|
+
import { declaredRecordKinds, readDeclaration, readerVersion, WORKSPACE_ERROR_CODES, WorkspaceReadError } from "./declaration";
|
|
23
|
+
import { declaredKindFile } from "./declared-kinds";
|
|
24
|
+
import type { ReasonCode } from "./reason-codes";
|
|
25
|
+
import { loadRecordKind, RecordReadError } from "./records";
|
|
26
|
+
import { queryRecords } from "./records-cli";
|
|
27
|
+
import {
|
|
28
|
+
commitsByRunTrailer,
|
|
29
|
+
endRun,
|
|
30
|
+
foldRun,
|
|
31
|
+
joinTrailerCommits,
|
|
32
|
+
LEDGER_BRANCH,
|
|
33
|
+
readRuns,
|
|
34
|
+
recordRun,
|
|
35
|
+
RunWriteError,
|
|
36
|
+
startRun,
|
|
37
|
+
type RunLedgerWrite,
|
|
38
|
+
type RunView,
|
|
39
|
+
type RunWriteContext,
|
|
40
|
+
} from "./runs";
|
|
41
|
+
import { RUN_TRAILER } from "./trailers";
|
|
42
|
+
import { locateWorkspace } from "./which-chant";
|
|
43
|
+
import { idList } from "./work";
|
|
44
|
+
import { AGENT_ENV } from "./write-scope";
|
|
45
|
+
import { execFileSync } from "node:child_process";
|
|
46
|
+
|
|
47
|
+
/** The version of the `runs` documents this chant writes: read contract 1. */
|
|
48
|
+
export const RUNS_CONTRACT_VERSION = 1;
|
|
49
|
+
|
|
50
|
+
/** `$id` of the JSON Schema for `runs --json`, shipped beside this file. */
|
|
51
|
+
export const RUNS_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/runs/v1/runs.schema.json";
|
|
52
|
+
|
|
53
|
+
/** `$id` of the JSON Schema for what `runs start|end|record` print. */
|
|
54
|
+
export const RUNS_WRITE_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/runs-write/v1/runs-write.schema.json";
|
|
55
|
+
|
|
56
|
+
const USAGE = [
|
|
57
|
+
"chant workspace runs [--unit <id>] [--decision <id>] [--by <principal>] [--since <rev>] [--json]",
|
|
58
|
+
"chant workspace runs start --from <file|->",
|
|
59
|
+
"chant workspace runs end <run id> [--from <file|->]",
|
|
60
|
+
"chant workspace runs record --from <file|->",
|
|
61
|
+
].join("\n");
|
|
62
|
+
|
|
63
|
+
/** Why `runs` read nothing. Closed. */
|
|
64
|
+
export const RUNS_ERROR_CODES = [...WORKSPACE_ERROR_CODES] as const satisfies readonly ReasonCode[];
|
|
65
|
+
export type RunsErrorCode = (typeof RUNS_ERROR_CODES)[number];
|
|
66
|
+
|
|
67
|
+
/** Why part of the read is missing. The read still succeeds. Closed. */
|
|
68
|
+
export const RUNS_REASON_CODES = ["runs-no-ledger", "runs-ledger-malformed", "kind-unreadable"] as const satisfies readonly ReasonCode[];
|
|
69
|
+
export type RunsReasonCode = (typeof RUNS_REASON_CODES)[number];
|
|
70
|
+
|
|
71
|
+
/** Why a run write was refused or could not run. Closed. */
|
|
72
|
+
export const RUNS_WRITE_ERROR_CODES = [...WORKSPACE_ERROR_CODES, "write-usage-invalid", "write-input-invalid", "run-exists", "run-unknown", "run-ended"] as const satisfies readonly ReasonCode[];
|
|
73
|
+
export type RunsWriteErrorCode = (typeof RUNS_WRITE_ERROR_CODES)[number];
|
|
74
|
+
|
|
75
|
+
// ── Totals ───────────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/** Tokens and cost summed over some runs. */
|
|
78
|
+
export interface RunTotals {
|
|
79
|
+
runs: number;
|
|
80
|
+
/** Runs with no end recorded yet. */
|
|
81
|
+
running: number;
|
|
82
|
+
/** Summed over the runs that report usage; a count a run leaves out adds nothing. */
|
|
83
|
+
tokens: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
84
|
+
/** One sum per currency, since amounts in two currencies don't add. */
|
|
85
|
+
cost: { currency: string; amount: number }[];
|
|
86
|
+
/** The runs that report no cost, running ones included: left out of `cost`, never counted as zero. */
|
|
87
|
+
unpriced: string[];
|
|
88
|
+
/** The runs that report no usage: left out of `tokens`. */
|
|
89
|
+
unreported: string[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const round = (n: number) => Math.round(n * 1e6) / 1e6;
|
|
93
|
+
|
|
94
|
+
export function totalRuns(runs: RunView[]): RunTotals {
|
|
95
|
+
const t: RunTotals = { runs: runs.length, running: 0, tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, cost: [], unpriced: [], unreported: [] };
|
|
96
|
+
for (const r of runs) {
|
|
97
|
+
if (r.state === "running") t.running++;
|
|
98
|
+
if (r.usage) {
|
|
99
|
+
t.tokens.input += r.usage.inputTokens ?? 0;
|
|
100
|
+
t.tokens.output += r.usage.outputTokens ?? 0;
|
|
101
|
+
t.tokens.cacheRead += r.usage.cacheReadTokens ?? 0;
|
|
102
|
+
t.tokens.cacheWrite += r.usage.cacheWriteTokens ?? 0;
|
|
103
|
+
} else t.unreported.push(r.id);
|
|
104
|
+
if (r.cost) {
|
|
105
|
+
const sum = t.cost.find((c) => c.currency === r.cost!.currency);
|
|
106
|
+
if (sum) sum.amount = round(sum.amount + r.cost.amount);
|
|
107
|
+
else t.cost.push({ currency: r.cost.currency, amount: round(r.cost.amount) });
|
|
108
|
+
} else t.unpriced.push(r.id);
|
|
109
|
+
}
|
|
110
|
+
t.cost.sort((a, b) => (a.currency < b.currency ? -1 : a.currency > b.currency ? 1 : 0));
|
|
111
|
+
return t;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Group runs by a key each run gives zero or more of, in first-seen order. */
|
|
115
|
+
function groupBy<K extends string | null>(runs: RunView[], keys: (r: RunView) => K[]): Map<K, RunView[]> {
|
|
116
|
+
const out = new Map<K, RunView[]>();
|
|
117
|
+
for (const r of runs) for (const k of keys(r)) (out.get(k) ?? out.set(k, []).get(k)!).push(r);
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── The read ─────────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
export interface RunsQuery {
|
|
124
|
+
cwd: string;
|
|
125
|
+
/** Only the runs on this work item. */
|
|
126
|
+
unit?: string;
|
|
127
|
+
/** Only the runs that carried out this decision: its id, or `<kind>/<id>`. */
|
|
128
|
+
decision?: string;
|
|
129
|
+
/** Only the runs made for this principal. */
|
|
130
|
+
by?: string;
|
|
131
|
+
/** Only the runs that made a commit after `since` on HEAD, or started after its commit date. */
|
|
132
|
+
since?: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface RunsReason {
|
|
136
|
+
code: RunsReasonCode;
|
|
137
|
+
message: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** What `runs --json` prints. */
|
|
141
|
+
export type RunsDocument =
|
|
142
|
+
| {
|
|
143
|
+
$schema: string;
|
|
144
|
+
contract: number;
|
|
145
|
+
chant: string;
|
|
146
|
+
workspace: { name: string; root: string };
|
|
147
|
+
ledger: { branch: string; dir: string; commit: string | null };
|
|
148
|
+
filter: { unit: string | null; decision: string | null; by: string | null; since: string | null };
|
|
149
|
+
/** Newest first, by when each started. */
|
|
150
|
+
runs: RunView[];
|
|
151
|
+
totals: {
|
|
152
|
+
all: RunTotals;
|
|
153
|
+
byUnit: (RunTotals & { unit: string | null })[];
|
|
154
|
+
byDecision: (RunTotals & { decision: string })[];
|
|
155
|
+
byPrincipal: (RunTotals & { principal: string | null })[];
|
|
156
|
+
};
|
|
157
|
+
malformed: number;
|
|
158
|
+
reasons: RunsReason[];
|
|
159
|
+
}
|
|
160
|
+
| { $schema: string; contract: number; chant: string; error: { code: RunsErrorCode; message: string } };
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The decisions each run carried out, as `<kind>/<id>`: the ones its work
|
|
164
|
+
* item implements, read through the declared work kinds, and the records it
|
|
165
|
+
* names of a kind a work kind calls its decision kind. Fills `decisions` in
|
|
166
|
+
* place.
|
|
167
|
+
*/
|
|
168
|
+
export async function resolveRunDecisions(runs: RunView[], rootOnDisk: string, declared: string[], reasons: RunsReason[]): Promise<void> {
|
|
169
|
+
const implementsOf = new Map<string, Set<string>>();
|
|
170
|
+
const decisionKinds = new Set<string>();
|
|
171
|
+
for (const file of declared) {
|
|
172
|
+
try {
|
|
173
|
+
const loaded = await loadRecordKind(file, rootOnDisk);
|
|
174
|
+
const work = loaded.kind.work;
|
|
175
|
+
if (!work) continue;
|
|
176
|
+
const decisionKind = (await loadRecordKind(resolve(dirname(loaded.file), work.decisions), rootOnDisk)).kind.name;
|
|
177
|
+
decisionKinds.add(decisionKind);
|
|
178
|
+
const doc = await queryRecords({ kind: loaded.file, cwd: rootOnDisk, workGaps: false });
|
|
179
|
+
if ("error" in doc) throw new RecordReadError(doc.error.code as never, doc.error.message);
|
|
180
|
+
for (const v of doc.records) {
|
|
181
|
+
if (v.id === null) continue;
|
|
182
|
+
const set = implementsOf.get(v.id) ?? implementsOf.set(v.id, new Set()).get(v.id)!;
|
|
183
|
+
for (const d of idList(v.data, work.implements)) set.add(`${decisionKind}/${d}`);
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
if (!(err instanceof RecordReadError)) throw err;
|
|
187
|
+
reasons.push({ code: "kind-unreadable", message: `${file}: ${err.message}; the decisions its work items implement are left out` });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (decisionKinds.size === 0) decisionKinds.add("decision");
|
|
191
|
+
for (const r of runs) {
|
|
192
|
+
const out = new Set<string>(r.unit ? (implementsOf.get(r.unit.id) ?? []) : []);
|
|
193
|
+
for (const ref of r.records) if (decisionKinds.has(ref.kind)) out.add(`${ref.kind}/${ref.id}`);
|
|
194
|
+
r.decisions = [...out].sort();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function tryGit(top: string, args: string[]): string | undefined {
|
|
199
|
+
try {
|
|
200
|
+
return execFileSync("git", args, { cwd: top, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 256 * 1024 * 1024 });
|
|
201
|
+
} catch {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Read the run ledger and build the document. Never throws a {@link WorkspaceReadError}. */
|
|
207
|
+
export async function workspaceRuns(query: RunsQuery): Promise<RunsDocument> {
|
|
208
|
+
const head = { $schema: RUNS_OUTPUT_SCHEMA_ID, contract: RUNS_CONTRACT_VERSION, chant: readerVersion() };
|
|
209
|
+
try {
|
|
210
|
+
const located = locateWorkspace(query.cwd);
|
|
211
|
+
const declaration = readDeclaration(located.tree, "", { rootChant: true });
|
|
212
|
+
const top = located.top;
|
|
213
|
+
if (!top) throw new WorkspaceReadError("not-a-git-repository", "agent runs are kept on the chant/lifecycle branch, and this directory is not in a git repository");
|
|
214
|
+
const reasons: RunsReason[] = [];
|
|
215
|
+
const { tip, dir, runs, malformed } = await readRuns(top, located.rootOnDisk);
|
|
216
|
+
if (!tip) reasons.push({ code: "runs-no-ledger", message: `this checkout has no ${LEDGER_BRANCH} branch, so there are no agent runs to read; fetch it first if the remote has one` });
|
|
217
|
+
if (malformed > 0) reasons.push({ code: "runs-ledger-malformed", message: `${malformed} ${malformed === 1 ? "line" : "lines"} of ${dir} on ${LEDGER_BRANCH} ${malformed === 1 ? "is" : "are"} not a run event and ${malformed === 1 ? "was" : "were"} left out` });
|
|
218
|
+
joinTrailerCommits(runs, commitsByRunTrailer(top), top);
|
|
219
|
+
const all = [...runs.values()];
|
|
220
|
+
await resolveRunDecisions(all, located.rootOnDisk, declaredRecordKinds(declaration).map((d) => declaredKindFile(d, located.rootOnDisk)), reasons);
|
|
221
|
+
|
|
222
|
+
let since: { commits: Set<string>; date: number } | undefined;
|
|
223
|
+
if (query.since !== undefined) {
|
|
224
|
+
const sha = tryGit(top, ["rev-parse", "--verify", "--quiet", `${query.since}^{commit}`])?.trim();
|
|
225
|
+
if (!sha) throw new WorkspaceReadError("revision-unknown", `--since ${query.since} names no commit in this repository`);
|
|
226
|
+
const commits = new Set((tryGit(top, ["rev-list", `${sha}..HEAD`]) ?? "").split("\n").map((s) => s.trim()).filter(Boolean));
|
|
227
|
+
since = { commits, date: Number(tryGit(top, ["show", "-s", "--format=%ct", sha])?.trim() ?? "0") * 1000 };
|
|
228
|
+
}
|
|
229
|
+
const wantDecision = query.decision;
|
|
230
|
+
const selected = all
|
|
231
|
+
.filter((r) => query.unit === undefined || r.unit?.id === query.unit)
|
|
232
|
+
.filter((r) => wantDecision === undefined || r.decisions.some((d) => d === wantDecision || d.slice(d.indexOf("/") + 1) === wantDecision))
|
|
233
|
+
.filter((r) => query.by === undefined || r.by === query.by)
|
|
234
|
+
.filter((r) => !since || r.commits.some((c) => since!.commits.has(c.sha)) || Date.parse(r.startedAt) > since.date)
|
|
235
|
+
.sort((a, b) => (a.startedAt < b.startedAt ? 1 : a.startedAt > b.startedAt ? -1 : a.id < b.id ? 1 : -1));
|
|
236
|
+
|
|
237
|
+
const byUnit = [...groupBy(selected, (r) => [r.unit?.id ?? null])].map(([unit, rs]) => ({ unit, ...totalRuns(rs) }));
|
|
238
|
+
const byDecision = [...groupBy(selected, (r) => r.decisions)].map(([decision, rs]) => ({ decision: decision as string, ...totalRuns(rs) }));
|
|
239
|
+
const byPrincipal = [...groupBy(selected, (r) => [r.by])].map(([principal, rs]) => ({ principal, ...totalRuns(rs) }));
|
|
240
|
+
return {
|
|
241
|
+
...head,
|
|
242
|
+
workspace: { name: declaration.name, root: located.root },
|
|
243
|
+
ledger: { branch: LEDGER_BRANCH, dir, commit: tip },
|
|
244
|
+
filter: { unit: query.unit ?? null, decision: query.decision ?? null, by: query.by ?? null, since: query.since ?? null },
|
|
245
|
+
runs: selected,
|
|
246
|
+
totals: { all: totalRuns(selected), byUnit, byDecision, byPrincipal },
|
|
247
|
+
malformed,
|
|
248
|
+
reasons,
|
|
249
|
+
};
|
|
250
|
+
} catch (err) {
|
|
251
|
+
if (err instanceof WorkspaceReadError) return { ...head, error: { code: err.code as RunsErrorCode, message: err.describe() } };
|
|
252
|
+
throw err;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── The writes ───────────────────────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
export type RunsVerb = "start" | "end" | "record";
|
|
259
|
+
|
|
260
|
+
/** What `runs start|end|record` print. */
|
|
261
|
+
export type RunsWriteDocument =
|
|
262
|
+
| {
|
|
263
|
+
$schema: string;
|
|
264
|
+
contract: number;
|
|
265
|
+
verb: RunsVerb;
|
|
266
|
+
/** The run as the ledger now holds it. */
|
|
267
|
+
run: RunView;
|
|
268
|
+
/** The line a commit the run makes carries, `Chant-Run: <id>` (#3149). */
|
|
269
|
+
trailer: string;
|
|
270
|
+
ledger: RunLedgerWrite;
|
|
271
|
+
}
|
|
272
|
+
| { $schema: string; contract: number; verb: RunsVerb | null; error: { code: RunsWriteErrorCode; message: string } };
|
|
273
|
+
|
|
274
|
+
export interface RunsWriteRequest {
|
|
275
|
+
verb: RunsVerb;
|
|
276
|
+
/** The run id, for `end`. */
|
|
277
|
+
id?: string;
|
|
278
|
+
/** The JSON fields, as text. */
|
|
279
|
+
fields: string;
|
|
280
|
+
cwd: string;
|
|
281
|
+
/** The agent session in `CHANT_AGENT`, the default for `agent`. */
|
|
282
|
+
agent?: string;
|
|
283
|
+
now?: () => Date;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Run one write and build the document it prints. */
|
|
287
|
+
export async function runsWrite(req: RunsWriteRequest): Promise<RunsWriteDocument> {
|
|
288
|
+
const head = { $schema: RUNS_WRITE_SCHEMA_ID, contract: RUNS_CONTRACT_VERSION };
|
|
289
|
+
const fail = (code: RunsWriteErrorCode, message: string): RunsWriteDocument => ({ ...head, verb: req.verb, error: { code, message } });
|
|
290
|
+
let fields: unknown;
|
|
291
|
+
try {
|
|
292
|
+
fields = req.fields.trim() === "" ? {} : JSON.parse(req.fields);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
return fail("write-input-invalid", `the fields are not JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
295
|
+
}
|
|
296
|
+
if (fields === null || typeof fields !== "object" || Array.isArray(fields)) return fail("write-input-invalid", "the fields are a JSON object");
|
|
297
|
+
if (req.verb !== "end" && req.agent && (fields as Record<string, unknown>).agent === undefined) fields = { ...(fields as object), agent: req.agent };
|
|
298
|
+
try {
|
|
299
|
+
const located = locateWorkspace(req.cwd);
|
|
300
|
+
const declaration = readDeclaration(located.tree, "", { rootChant: true });
|
|
301
|
+
if (!located.top) return fail("not-a-git-repository", "agent runs are kept on the chant/lifecycle branch, and this directory is not in a git repository");
|
|
302
|
+
const ctx: RunWriteContext = { top: located.top, rootOnDisk: located.rootOnDisk, cwd: req.cwd, now: req.now };
|
|
303
|
+
const result = req.verb === "start" ? await startRun(fields, ctx) : req.verb === "end" ? await endRun(req.id ?? "", fields, ctx) : await recordRun(fields, ctx);
|
|
304
|
+
const run = foldRun(result.id, result.ledger.path, result.lines)!;
|
|
305
|
+
await resolveRunDecisions([run], located.rootOnDisk, declaredRecordKinds(declaration).map((d) => declaredKindFile(d, located.rootOnDisk)), []);
|
|
306
|
+
return { ...head, verb: req.verb, run, trailer: `${RUN_TRAILER}: ${run.id}`, ledger: result.ledger };
|
|
307
|
+
} catch (err) {
|
|
308
|
+
if (err instanceof RunWriteError) return fail(err.code, err.message);
|
|
309
|
+
if (err instanceof WorkspaceReadError) return fail(err.code as RunsWriteErrorCode, err.describe());
|
|
310
|
+
throw err;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ── Text ─────────────────────────────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
function money(t: RunTotals): string {
|
|
317
|
+
const priced = t.cost.map((c) => `${c.amount} ${c.currency}`).join(" + ") || "no cost reported";
|
|
318
|
+
return `${priced}${t.unpriced.length > 0 ? `, ${t.unpriced.length} unpriced` : ""}`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** The read as lines for a person. */
|
|
322
|
+
export function formatRuns(doc: Extract<RunsDocument, { runs: unknown }>): string {
|
|
323
|
+
const out: string[] = [];
|
|
324
|
+
for (const r of doc.runs) {
|
|
325
|
+
const model = [r.harness.name, r.model].filter(Boolean).join("/");
|
|
326
|
+
const tokens = r.usage ? `${r.usage.inputTokens ?? "?"} in, ${r.usage.outputTokens ?? "?"} out` : "no usage";
|
|
327
|
+
const cost = r.cost ? `${r.cost.amount} ${r.cost.currency} (${r.cost.source})` : "unpriced";
|
|
328
|
+
out.push(`${r.id} ${r.state}${r.outcome ? ` ${r.outcome}` : ""} ${r.startedAt} ${model}${r.by ? ` for ${r.by}` : ""}${r.unit ? ` on ${r.unit.id}` : ""} ${tokens} ${cost} ${r.commits.length} commit${r.commits.length === 1 ? "" : "s"}`);
|
|
329
|
+
}
|
|
330
|
+
if (doc.runs.length === 0) out.push("no agent runs");
|
|
331
|
+
const t = doc.totals.all;
|
|
332
|
+
out.push(`${t.runs} run${t.runs === 1 ? "" : "s"} (${t.running} running): ${t.tokens.input} tokens in, ${t.tokens.output} out; ${money(t)}`);
|
|
333
|
+
for (const r of doc.reasons) out.push(`reason ${r.code}: ${r.message}`);
|
|
334
|
+
return out.join("\n");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ── The command ──────────────────────────────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
function readFields(value: string | undefined, cwd: string): string {
|
|
340
|
+
if (value === undefined) return "";
|
|
341
|
+
return readFileSync(value === "-" ? 0 : resolve(cwd, value), "utf-8");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export async function runWorkspaceRuns(ctx: CommandContext): Promise<number> {
|
|
345
|
+
const { args } = ctx;
|
|
346
|
+
const cwd = process.cwd();
|
|
347
|
+
const verb = args.extraPositional;
|
|
348
|
+
if (verb === "start" || verb === "end" || verb === "record") {
|
|
349
|
+
const print = (doc: RunsWriteDocument): number => {
|
|
350
|
+
console.log(JSON.stringify(doc, null, 2));
|
|
351
|
+
return "error" in doc ? 1 : 0;
|
|
352
|
+
};
|
|
353
|
+
const usage = (message: string) => print({ $schema: RUNS_WRITE_SCHEMA_ID, contract: RUNS_CONTRACT_VERSION, verb, error: { code: "write-usage-invalid", message } });
|
|
354
|
+
for (const [flag, v] of [["--unit", args.unit], ["--decision", args.decision], ["--by", args.by], ["--since", args.since]] as const) {
|
|
355
|
+
if (v !== undefined) return usage(`runs ${verb} takes its fields with --from, not ${flag}`);
|
|
356
|
+
}
|
|
357
|
+
if (verb === "end" && !args.extraPositional2) return usage("runs end needs the run id: runs end <run id>");
|
|
358
|
+
if (verb !== "end" && args.extraPositional2) return usage(`runs ${verb} takes no run id; put an id in the fields to choose one`);
|
|
359
|
+
if (verb !== "end" && args.migrateFrom === undefined) return usage(`runs ${verb} needs --from <file|->: the run's fields as JSON`);
|
|
360
|
+
let fields: string;
|
|
361
|
+
try {
|
|
362
|
+
fields = readFields(args.migrateFrom, cwd);
|
|
363
|
+
} catch (err) {
|
|
364
|
+
return print({ $schema: RUNS_WRITE_SCHEMA_ID, contract: RUNS_CONTRACT_VERSION, verb, error: { code: "write-input-invalid", message: `--from ${args.migrateFrom} could not be read: ${err instanceof Error ? err.message : String(err)}` } });
|
|
365
|
+
}
|
|
366
|
+
return print(await runsWrite({ verb, id: args.extraPositional2, fields, cwd, agent: process.env[AGENT_ENV] || undefined }));
|
|
367
|
+
}
|
|
368
|
+
if (verb !== undefined) {
|
|
369
|
+
console.error(formatError({ message: `chant workspace runs takes start, end or record, or no verb to read, not ${verb}`, hint: USAGE }));
|
|
370
|
+
return 1;
|
|
371
|
+
}
|
|
372
|
+
if (args.migrateFrom !== undefined) {
|
|
373
|
+
console.error(formatError({ message: "runs is a read and takes no --from; write with runs start, end or record", hint: USAGE }));
|
|
374
|
+
return 1;
|
|
375
|
+
}
|
|
376
|
+
const doc = await workspaceRuns({ cwd, unit: args.unit, decision: args.decision, by: args.by, since: args.since });
|
|
377
|
+
if (args.json) console.log(JSON.stringify(doc, null, 2));
|
|
378
|
+
else if ("error" in doc) console.error(formatError({ message: `${doc.error.code}: ${doc.error.message}`, hint: USAGE }));
|
|
379
|
+
else console.log(formatRuns(doc));
|
|
380
|
+
return "error" in doc ? 1 : 0;
|
|
381
|
+
}
|