@intentius/chant 0.89.0 → 0.91.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/cli/handlers/components.d.ts +8 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +14 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/serve.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +10 -5
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +13 -5
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +55 -0
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -0
- package/dist/cli/registry.d.ts +31 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/verbs/vuln-scan.d.ts +72 -0
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +40 -5
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +72 -16
- package/dist/lifecycle/lease.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts +3 -2
- package/dist/lifecycle/member-ledger.d.ts.map +1 -1
- package/dist/lifecycle/plan-ledger.d.ts +114 -0
- package/dist/lifecycle/plan-ledger.d.ts.map +1 -0
- package/dist/lifecycle/work-lease.d.ts +140 -0
- package/dist/lifecycle/work-lease.d.ts.map +1 -0
- package/dist/op/activities/activity-contracts.d.ts +2 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/discover.d.ts +25 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/index.d.ts +9 -3
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/lifecycle-receipt-store.d.ts +34 -0
- package/dist/op/lifecycle-receipt-store.d.ts.map +1 -0
- package/dist/op/local-executor.d.ts +19 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-output.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +10 -1
- package/dist/op/op-ir.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/operator.d.ts +29 -0
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +9 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/step-output-ref.d.ts +2 -2
- package/dist/op/step-output-ref.d.ts.map +1 -1
- package/dist/op/steward.d.ts +140 -0
- package/dist/op/steward.d.ts.map +1 -0
- package/dist/op/types.d.ts +51 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-decl.d.ts +18 -0
- package/dist/op/work-lease-decl.d.ts.map +1 -0
- package/dist/op/work-lease-run.d.ts +173 -0
- package/dist/op/work-lease-run.d.ts.map +1 -0
- package/dist/workspace/box-isolation.d.ts +99 -0
- package/dist/workspace/box-isolation.d.ts.map +1 -0
- package/dist/workspace/checks/box-isolation.d.ts +18 -0
- package/dist/workspace/checks/box-isolation.d.ts.map +1 -0
- package/dist/workspace/checks/boxes.d.ts +71 -0
- package/dist/workspace/checks/boxes.d.ts.map +1 -0
- package/dist/workspace/checks/records.d.ts +1 -0
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +10 -1
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/conformance/index.d.ts +42 -2
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +184 -0
- package/dist/workspace/decide.d.ts.map +1 -0
- package/dist/workspace/decision-points.schema.json +137 -0
- package/dist/workspace/declaration.d.ts +51 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +172 -0
- package/dist/workspace/declared-kinds.d.ts +12 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -1
- package/dist/workspace/points-cli.d.ts +113 -0
- package/dist/workspace/points-cli.d.ts.map +1 -0
- package/dist/workspace/points.d.ts +320 -0
- package/dist/workspace/points.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +29 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-assets.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +12 -0
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +35 -3
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +12 -3
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/source-block.d.ts +85 -0
- package/dist/workspace/source-block.d.ts.map +1 -0
- package/dist/workspace/status-stewards.d.ts +121 -0
- package/dist/workspace/status-stewards.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +52 -1
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +78 -0
- package/dist/workspace/work-cli.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/cli/handlers/components.test.ts +93 -0
- package/src/cli/handlers/components.ts +44 -3
- package/src/cli/handlers/operator.ts +107 -2
- package/src/cli/handlers/run.test.ts +19 -0
- package/src/cli/handlers/run.ts +53 -1
- package/src/cli/handlers/serve.ts +2 -1
- package/src/cli/main.test.ts +40 -0
- package/src/cli/main.ts +78 -2
- package/src/cli/mcp/docs-parity.test.ts +20 -2
- package/src/cli/mcp/server.ts +23 -6
- package/src/cli/mcp/types.ts +15 -2
- package/src/cli/mcp/workspace-tools.test.ts +211 -0
- package/src/cli/mcp/workspace-tools.ts +449 -0
- package/src/cli/registry.ts +31 -0
- package/src/components/verbs/vuln-scan.test.ts +124 -1
- package/src/components/verbs/vuln-scan.ts +142 -1
- package/src/lifecycle/git.ts +65 -15
- package/src/lifecycle/lease.test.ts +22 -0
- package/src/lifecycle/lease.ts +133 -29
- package/src/lifecycle/member-ledger.ts +3 -2
- package/src/lifecycle/plan-ledger.test.ts +148 -0
- package/src/lifecycle/plan-ledger.ts +158 -0
- package/src/lifecycle/work-lease.test.ts +236 -0
- package/src/lifecycle/work-lease.ts +426 -0
- package/src/op/builders.ts +5 -0
- package/src/op/discover.ts +71 -0
- package/src/op/index.ts +16 -3
- package/src/op/lifecycle-receipt-store.test.ts +60 -0
- package/src/op/lifecycle-receipt-store.ts +61 -0
- package/src/op/local-executor.ts +216 -18
- package/src/op/local-output.ts +13 -0
- package/src/op/op-ir.ts +14 -0
- package/src/op/op-verb-class.ts +6 -0
- package/src/op/operator.ts +75 -4
- package/src/op/runtime.ts +6 -0
- package/src/op/runtimes/local.ts +3 -0
- package/src/op/step-output-ref.ts +6 -2
- package/src/op/steward.test.ts +212 -0
- package/src/op/steward.ts +253 -0
- package/src/op/types.ts +53 -0
- package/src/op/work-lease-decl.ts +80 -0
- package/src/op/work-lease-run.test.ts +326 -0
- package/src/op/work-lease-run.ts +395 -0
- package/src/workspace/box-isolation.test.ts +261 -0
- package/src/workspace/box-isolation.ts +205 -0
- package/src/workspace/check-contract.test.ts +3 -1
- package/src/workspace/check.schema.json +15 -7
- package/src/workspace/checks/box-isolation.ts +68 -0
- package/src/workspace/checks/boxes.test.ts +197 -0
- package/src/workspace/checks/boxes.ts +307 -0
- package/src/workspace/checks/records.ts +25 -0
- package/src/workspace/checks.test.ts +7 -0
- package/src/workspace/checks.ts +19 -2
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +4 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +100 -6
- package/src/workspace/conformance/index.mjs +3 -0
- package/src/workspace/conformance/index.ts +185 -7
- package/src/workspace/conformance/vitest.ts +17 -8
- package/src/workspace/decide.test.ts +224 -0
- package/src/workspace/decide.ts +576 -0
- package/src/workspace/decision-points.schema.json +137 -0
- package/src/workspace/declaration.schema.json +172 -0
- package/src/workspace/declaration.ts +137 -0
- package/src/workspace/declared-kinds.ts +25 -2
- package/src/workspace/intent.schema.json +4 -1
- package/src/workspace/point-answer.schema.json +95 -0
- package/src/workspace/points-cli.ts +273 -0
- package/src/workspace/points-write.schema.json +489 -0
- package/src/workspace/points.schema.json +710 -0
- package/src/workspace/points.test.ts +264 -0
- package/src/workspace/points.ts +564 -0
- package/src/workspace/read-contract.test.ts +18 -1
- package/src/workspace/reason-codes.test.ts +14 -1
- package/src/workspace/reason-codes.ts +36 -0
- package/src/workspace/record-assets.test.ts +3 -2
- package/src/workspace/record-assets.ts +4 -1
- package/src/workspace/records-amend.schema.json +2 -1
- package/src/workspace/records-cli.ts +15 -2
- package/src/workspace/records-close.schema.json +2 -1
- package/src/workspace/records-contract.test.ts +3 -2
- package/src/workspace/records-new.schema.json +4 -1
- package/src/workspace/records-review.schema.json +2 -1
- package/src/workspace/records-write.ts +85 -7
- package/src/workspace/records.schema.json +18 -0
- package/src/workspace/records.ts +61 -3
- package/src/workspace/source-block.test.ts +167 -0
- package/src/workspace/source-block.ts +129 -0
- package/src/workspace/status-contract.test.ts +178 -0
- package/src/workspace/status-stewards.ts +225 -0
- package/src/workspace/status.schema.json +230 -4
- package/src/workspace/status.ts +106 -5
- package/src/workspace/work-cli.test.ts +180 -0
- package/src/workspace/work-cli.ts +246 -0
- package/src/workspace/work-lease.schema.json +233 -0
- package/src/workspace/work-readiness-chud.test.ts +145 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "urn:intentius:chant:point-answer:1",
|
|
4
|
+
"title": "Decision point answer front matter",
|
|
5
|
+
"description": "The front matter of one answer to a decision point (ws-058, #2739), as `chant workspace points ask` and `points answer` write it into the records directory of a record kind with an `answers` block. One record per point, declaration and inputs: its id is the point's name and the first 12 hex digits of the inputs hash, so the same question is answered once. A table's answer is `answered`; a model's answer at or above its threshold is `proposed` until a person confirms it; a question no decider before the quorum answered is `escalated`, open for people. A workspace copies this file next to its answer kind, as reference-workspace/answers/ does. Values stay within what YAML and JSON share. Fields other than these must start with x-.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"patternProperties": { "^x-": {} },
|
|
9
|
+
"required": ["id", "title", "point", "point_version", "question_type", "candidates", "inputs", "inputs_hash", "state", "decider", "asked_on"],
|
|
10
|
+
"properties": {
|
|
11
|
+
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*-[0-9a-f]{12}$", "description": "The point's name, a hyphen, and the first 12 hex digits of inputs_hash." },
|
|
12
|
+
"title": { "type": "string", "minLength": 1 },
|
|
13
|
+
"point": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$", "description": "The point in the points file this answers." },
|
|
14
|
+
"point_version": { "$ref": "#/definitions/sha256", "description": "sha256 of the point's declaration when it was asked. Editing the question or any decider changes it." },
|
|
15
|
+
"question_type": { "enum": ["noul", "choice", "score"] },
|
|
16
|
+
"candidates": { "type": "array", "minItems": 2, "items": { "type": ["string", "boolean"] }, "description": "The answers the question allowed: true and false, a choice's options, or a score's levels." },
|
|
17
|
+
"inputs": { "type": "object", "description": "The state the deciders saw, input name to value." },
|
|
18
|
+
"inputs_hash": { "$ref": "#/definitions/sha256", "description": "sha256 of the point, its version and its inputs. The same hash is answered once." },
|
|
19
|
+
"constrains": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "What the question is about, such as a work item's id." },
|
|
20
|
+
"state": { "enum": ["escalated", "proposed", "answered"] },
|
|
21
|
+
"answer": { "type": ["string", "boolean"] },
|
|
22
|
+
"decider": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"required": ["kind"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"kind": { "enum": ["table", "model", "quorum"] },
|
|
27
|
+
"row": { "type": "integer", "minimum": 0, "description": "table: the row that matched." },
|
|
28
|
+
"backend": { "type": "string", "minLength": 1 },
|
|
29
|
+
"model": { "type": "string", "minLength": 1, "description": "model: the model id that answered, as the backend reported it." },
|
|
30
|
+
"count": { "type": "integer", "minimum": 1 },
|
|
31
|
+
"roles": { "type": "array", "items": { "type": "string" } },
|
|
32
|
+
"by": { "type": "array", "items": { "type": "string" }, "description": "quorum: who answered." }
|
|
33
|
+
},
|
|
34
|
+
"additionalProperties": false
|
|
35
|
+
},
|
|
36
|
+
"probabilities": { "$ref": "#/definitions/probabilities" },
|
|
37
|
+
"confidence": { "$ref": "#/definitions/unit" },
|
|
38
|
+
"threshold": { "$ref": "#/definitions/unit" },
|
|
39
|
+
"escalations": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"description": "The deciders asked before the one that answered, or before the quorum, and why each did not answer.",
|
|
42
|
+
"items": { "$ref": "#/definitions/escalation" }
|
|
43
|
+
},
|
|
44
|
+
"answered_by": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "The people who answered or confirmed it." },
|
|
45
|
+
"asked_on": { "$ref": "#/definitions/date" },
|
|
46
|
+
"answered_on": { "$ref": "#/definitions/date" },
|
|
47
|
+
"source": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"description": "Where the answer came from (#2708): via, client and model, the model id that answered when a model decider did."
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"allOf": [
|
|
53
|
+
{
|
|
54
|
+
"if": { "properties": { "state": { "const": "proposed" } }, "required": ["state"] },
|
|
55
|
+
"then": { "required": ["answer"], "properties": { "decider": { "properties": { "kind": { "const": "model" } } } } }
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"if": { "properties": { "state": { "const": "answered" } }, "required": ["state"] },
|
|
59
|
+
"then": { "required": ["answer", "answered_on"] }
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"if": { "properties": { "state": { "const": "escalated" } }, "required": ["state"] },
|
|
63
|
+
"then": { "not": { "required": ["answer"] }, "properties": { "decider": { "properties": { "kind": { "const": "quorum" } } } } }
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"if": { "properties": { "decider": { "properties": { "kind": { "const": "model" } }, "required": ["kind"] } }, "required": ["decider"] },
|
|
67
|
+
"then": { "required": ["probabilities", "confidence", "threshold"], "properties": { "decider": { "required": ["backend", "model"] } } }
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"if": { "properties": { "state": { "const": "answered" }, "decider": { "properties": { "kind": { "const": "quorum" } }, "required": ["kind"] } }, "required": ["state", "decider"] },
|
|
71
|
+
"then": { "required": ["answered_by"], "properties": { "decider": { "required": ["by"] } } }
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"definitions": {
|
|
75
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
76
|
+
"unit": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
77
|
+
"date": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
|
|
78
|
+
"probabilities": { "type": "object", "additionalProperties": { "$ref": "#/definitions/unit" } },
|
|
79
|
+
"escalation": {
|
|
80
|
+
"type": "object",
|
|
81
|
+
"required": ["kind", "reason"],
|
|
82
|
+
"properties": {
|
|
83
|
+
"kind": { "enum": ["table", "model"] },
|
|
84
|
+
"reason": { "type": "string", "minLength": 1 },
|
|
85
|
+
"backend": { "type": "string" },
|
|
86
|
+
"model": { "type": "string" },
|
|
87
|
+
"answer": { "type": ["string", "boolean", "null"] },
|
|
88
|
+
"probabilities": { "$ref": "#/definitions/probabilities" },
|
|
89
|
+
"confidence": { "$ref": "#/definitions/unit" },
|
|
90
|
+
"threshold": { "$ref": "#/definitions/unit" }
|
|
91
|
+
},
|
|
92
|
+
"additionalProperties": false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant workspace points [--open] [--kind <kind file>] [--at <rev>] [--json]`
|
|
3
|
+
* (ws-058, #2739): the workspace's decision points and the questions asked of
|
|
4
|
+
* them, a read-contract output (`points.schema.json`).
|
|
5
|
+
*
|
|
6
|
+
* The points come from each record kind the declaration names with an
|
|
7
|
+
* `answers` block, or from `--kind`, and the questions are that kind's
|
|
8
|
+
* records. A question is open while it is escalated to people or proposed by a
|
|
9
|
+
* model and not yet confirmed, and it lists any model's answer with its
|
|
10
|
+
* confidence, so a reader such as hud can prompt a person and an MCP client
|
|
11
|
+
* can see it. With `--open`, only open questions are listed. It never writes
|
|
12
|
+
* and never calls a model.
|
|
13
|
+
*
|
|
14
|
+
* `points ask` and `points answer` are the writes, in `decide.ts`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { resolve } from "node:path";
|
|
19
|
+
import { formatError } from "../cli/format";
|
|
20
|
+
import type { CommandContext } from "../cli/registry";
|
|
21
|
+
import { declaredRecordKinds, readDeclaration, readerVersion, WorkspaceReadError, type ErrorLocation, type WorkspaceErrorCode } from "./declaration";
|
|
22
|
+
import { declaredKindFile } from "./declared-kinds";
|
|
23
|
+
import { answerPoint, askPoint, questionView, POINTS_WRITE_CONTRACT_VERSION, POINTS_WRITE_SCHEMA_ID, type PointsWriteDocument, type QuestionView } from "./decide";
|
|
24
|
+
import { candidates, inputOutput, pointsFileOf, pointVersion, quorumOf, readPointsThrough, type Decider, type ModelAsk, type WireAnswer } from "./points";
|
|
25
|
+
import type { ReasonCode } from "./reason-codes";
|
|
26
|
+
import { gitRevisionSource, workingTreeSource } from "./record-source";
|
|
27
|
+
import { loadRecordKind, RecordReadError, type ReadErrorCode } from "./records";
|
|
28
|
+
import { readRecordsFor } from "./records-cli";
|
|
29
|
+
import { locateWorkspace } from "./which-chant";
|
|
30
|
+
|
|
31
|
+
export const POINTS_CONTRACT_VERSION = 1;
|
|
32
|
+
export const POINTS_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/points/v1/points.schema.json";
|
|
33
|
+
|
|
34
|
+
/** Why `points` read nothing. Closed. */
|
|
35
|
+
export const POINTS_ERROR_CODES = [
|
|
36
|
+
"declaration-missing",
|
|
37
|
+
"declaration-ambiguous",
|
|
38
|
+
"declaration-unparseable",
|
|
39
|
+
"declaration-invalid",
|
|
40
|
+
"placement-invalid",
|
|
41
|
+
"reader-too-old",
|
|
42
|
+
"root-chant-required",
|
|
43
|
+
"not-a-git-repository",
|
|
44
|
+
"revision-unknown",
|
|
45
|
+
] as const satisfies readonly ReasonCode[];
|
|
46
|
+
|
|
47
|
+
/** Why one answer kind's points or questions could not be read. The document is still printed. Closed. */
|
|
48
|
+
export const POINTS_SOURCE_REASON_CODES = [
|
|
49
|
+
"kind-unreadable",
|
|
50
|
+
"kind-invalid",
|
|
51
|
+
"schema-unreadable",
|
|
52
|
+
"schema-id-mismatch",
|
|
53
|
+
"schema-invalid",
|
|
54
|
+
"location-missing",
|
|
55
|
+
/** The points file the kind names can't be read, or is not valid. */
|
|
56
|
+
"points-invalid",
|
|
57
|
+
] as const satisfies readonly ReasonCode[];
|
|
58
|
+
type SourceReasonCode = (typeof POINTS_SOURCE_REASON_CODES)[number];
|
|
59
|
+
|
|
60
|
+
/** One point as `points` lists it. */
|
|
61
|
+
export interface PointView {
|
|
62
|
+
name: string;
|
|
63
|
+
title: string;
|
|
64
|
+
/** The answer kind file, from the workspace root, whose records answer it. */
|
|
65
|
+
kind: string;
|
|
66
|
+
/** The points file, from the repository root. */
|
|
67
|
+
file: string;
|
|
68
|
+
/** sha256 of the declaration: a question asked under another version answers an older question. */
|
|
69
|
+
version: string;
|
|
70
|
+
questionType: string;
|
|
71
|
+
instructions: string;
|
|
72
|
+
candidates: (string | boolean)[];
|
|
73
|
+
/** Each input: its name, the read-contract output it names, and its description. */
|
|
74
|
+
inputs: { name: string; output: string; description: string }[];
|
|
75
|
+
deciders: Decider[];
|
|
76
|
+
quorum: { count: number; roles?: string[] };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** One answer kind the read covered. */
|
|
80
|
+
export interface SourceView {
|
|
81
|
+
/** The kind file, as declared from the workspace root, or as given with --kind. */
|
|
82
|
+
kind: string;
|
|
83
|
+
/** The points file from the repository root, or null when the kind couldn't be loaded. */
|
|
84
|
+
points: string | null;
|
|
85
|
+
reason: { code: SourceReasonCode; message: string } | null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type PointsDocument =
|
|
89
|
+
| {
|
|
90
|
+
$schema: string;
|
|
91
|
+
contract: number;
|
|
92
|
+
chant: string;
|
|
93
|
+
at: string | null;
|
|
94
|
+
workspace: { name: string; root: string };
|
|
95
|
+
/** Whether only open questions are listed (--open). */
|
|
96
|
+
open: boolean;
|
|
97
|
+
sources: SourceView[];
|
|
98
|
+
points: PointView[];
|
|
99
|
+
questions: QuestionView[];
|
|
100
|
+
summary: { points: number; questions: number; open: number; escalated: number; proposed: number; answered: number };
|
|
101
|
+
}
|
|
102
|
+
| { $schema: string; contract: number; chant: string; error: { code: WorkspaceErrorCode; message: string; location: ErrorLocation | null } };
|
|
103
|
+
|
|
104
|
+
export interface PointsQuery {
|
|
105
|
+
cwd: string;
|
|
106
|
+
at?: string;
|
|
107
|
+
open?: boolean;
|
|
108
|
+
/** One answer kind file, in place of the declared ones. */
|
|
109
|
+
kind?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Read the points and questions. Never throws a read error: a declaration that can't be read is the failure document. */
|
|
113
|
+
export async function workspacePoints(query: PointsQuery): Promise<PointsDocument> {
|
|
114
|
+
const head = { $schema: POINTS_OUTPUT_SCHEMA_ID, contract: POINTS_CONTRACT_VERSION, chant: readerVersion() };
|
|
115
|
+
let located;
|
|
116
|
+
let declaration;
|
|
117
|
+
try {
|
|
118
|
+
located = locateWorkspace(query.cwd, query.at);
|
|
119
|
+
declaration = readDeclaration(located.tree);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (!(err instanceof WorkspaceReadError)) throw err;
|
|
122
|
+
return { ...head, error: { code: err.code, message: err.message, location: err.location ?? null } };
|
|
123
|
+
}
|
|
124
|
+
const kinds =
|
|
125
|
+
query.kind !== undefined
|
|
126
|
+
? [{ shown: query.kind, file: resolve(query.cwd, query.kind), given: true }]
|
|
127
|
+
: declaredRecordKinds(declaration).map((d) => ({ shown: d.path, file: declaredKindFile(d, located.rootOnDisk), given: false }));
|
|
128
|
+
|
|
129
|
+
const sources: SourceView[] = [];
|
|
130
|
+
const points: PointView[] = [];
|
|
131
|
+
const questions: QuestionView[] = [];
|
|
132
|
+
for (const k of kinds) {
|
|
133
|
+
try {
|
|
134
|
+
const loaded = await loadRecordKind(k.file);
|
|
135
|
+
if (!loaded.kind.answers) {
|
|
136
|
+
if (k.given) sources.push({ kind: k.shown, points: null, reason: { code: "kind-invalid", message: `the ${loaded.kind.name} kind has no answers block, so it holds no answers to decision points` } });
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const read = await readRecordsFor({ kind: k.file, cwd: query.cwd, at: query.at });
|
|
140
|
+
const source = read.at !== null && read.top ? gitRevisionSource(read.top, read.at) : workingTreeSource(read.root);
|
|
141
|
+
const file = pointsFileOf(read.loaded, read.root);
|
|
142
|
+
const parsed = readPointsThrough(source, file);
|
|
143
|
+
const declared = "points" in parsed ? parsed.points : {};
|
|
144
|
+
sources.push({ kind: k.shown, points: file, reason: "error" in parsed ? { code: "points-invalid", message: parsed.error } : null });
|
|
145
|
+
for (const [name, p] of Object.entries(declared)) {
|
|
146
|
+
points.push({
|
|
147
|
+
name,
|
|
148
|
+
title: p.title,
|
|
149
|
+
kind: k.shown,
|
|
150
|
+
file,
|
|
151
|
+
version: pointVersion(p),
|
|
152
|
+
questionType: p.question.type,
|
|
153
|
+
instructions: p.question.instructions,
|
|
154
|
+
candidates: candidates(p.question),
|
|
155
|
+
inputs: Object.entries(p.inputs).map(([n, description]) => ({ name: n, output: inputOutput(n), description })),
|
|
156
|
+
deciders: p.deciders,
|
|
157
|
+
quorum: quorumOf(p),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
for (const e of read.result.records) {
|
|
161
|
+
const view = questionView(e, typeof e.data?.point === "string" ? declared[e.data.point] : undefined);
|
|
162
|
+
if (view && (!query.open || view.open)) questions.push(view);
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (!(err instanceof RecordReadError)) throw err;
|
|
166
|
+
sources.push({ kind: k.shown, points: null, reason: { code: err.code as Exclude<ReadErrorCode, "not-a-git-repository" | "revision-unknown">, message: err.message } });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const count = (s: string) => questions.filter((q) => q.state === s).length;
|
|
170
|
+
return {
|
|
171
|
+
...head,
|
|
172
|
+
at: located.at,
|
|
173
|
+
workspace: { name: declaration.name, root: located.root },
|
|
174
|
+
open: !!query.open,
|
|
175
|
+
sources,
|
|
176
|
+
points,
|
|
177
|
+
questions,
|
|
178
|
+
summary: { points: points.length, questions: questions.length, open: questions.filter((q) => q.open).length, escalated: count("escalated"), proposed: count("proposed"), answered: count("answered") },
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The document as lines for a person. */
|
|
183
|
+
export function formatPoints(doc: Extract<PointsDocument, { points: unknown }>): string {
|
|
184
|
+
const lines: string[] = [];
|
|
185
|
+
for (const s of doc.sources) if (s.reason) lines.push(`${s.kind}: ${s.reason.code}: ${s.reason.message}`);
|
|
186
|
+
for (const p of doc.points) lines.push(`${p.name} ${p.questionType} ${p.title} (${p.deciders.map((d) => d.kind).join(", ")})`);
|
|
187
|
+
if (doc.points.length === 0) lines.push("no decision points are declared");
|
|
188
|
+
lines.push("");
|
|
189
|
+
for (const q of doc.questions) {
|
|
190
|
+
const model = q.model && q.state !== "answered" ? ` model ${JSON.stringify(q.model.answer)} at ${q.model.confidence ?? "?"}${q.model.observed ? "" : `, below ${q.model.threshold ?? "?"}`}` : "";
|
|
191
|
+
lines.push(`${q.id} ${q.state} ${q.title}${model}`);
|
|
192
|
+
}
|
|
193
|
+
const n = doc.summary.questions;
|
|
194
|
+
lines.push(doc.open ? `${n} open ${n === 1 ? "question" : "questions"}` : `${n} ${n === 1 ? "question" : "questions"}, ${doc.summary.open} open`);
|
|
195
|
+
return lines.join("\n");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** A POST /v1/systemone response the caller already has, as the model call: its answer to the point asked. */
|
|
199
|
+
export function responseAsk(response: unknown): ModelAsk {
|
|
200
|
+
return async (request) => {
|
|
201
|
+
const r = response as { model?: unknown; answers?: Record<string, unknown> } | null;
|
|
202
|
+
if (r === null || typeof r !== "object") throw new Error("the response is not a JSON object");
|
|
203
|
+
if (typeof r.model !== "string" || r.model === "") throw new Error("the response names no model");
|
|
204
|
+
const answer = r.answers?.[request.point];
|
|
205
|
+
if (answer === null || typeof answer !== "object") throw new Error(`the response has no answer for ${request.point}`);
|
|
206
|
+
return { model: r.model, answer: answer as WireAnswer };
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const USAGE = [
|
|
211
|
+
"chant workspace points [--open] [--kind <kind file>] [--at <rev>] [--json]",
|
|
212
|
+
"chant workspace points ask <point> --inputs <file|-> [--response <file>] [--subject <id>] [--kind <kind file>] [--dry-run]",
|
|
213
|
+
"chant workspace points answer <id> --answer <value> --by <name> [--by <name>...] [--kind <kind file>] [--dry-run]",
|
|
214
|
+
].join("\n");
|
|
215
|
+
|
|
216
|
+
function usage(verb: "ask" | "answer", message: string): PointsWriteDocument {
|
|
217
|
+
return { $schema: POINTS_WRITE_SCHEMA_ID, contract: POINTS_WRITE_CONTRACT_VERSION, verb, error: { code: "write-usage-invalid", message } };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function readJson(verb: "ask" | "answer", flag: string, value: string, cwd: string): { value: unknown } | PointsWriteDocument {
|
|
221
|
+
let text: string;
|
|
222
|
+
try {
|
|
223
|
+
text = readFileSync(value === "-" ? 0 : resolve(cwd, value), "utf-8");
|
|
224
|
+
} catch (err) {
|
|
225
|
+
return { $schema: POINTS_WRITE_SCHEMA_ID, contract: POINTS_WRITE_CONTRACT_VERSION, verb, error: { code: "write-input-invalid", message: `${flag} ${value} could not be read: ${err instanceof Error ? err.message : String(err)}` } };
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
return { value: JSON.parse(text) };
|
|
229
|
+
} catch (err) {
|
|
230
|
+
return { $schema: POINTS_WRITE_SCHEMA_ID, contract: POINTS_WRITE_CONTRACT_VERSION, verb, error: { code: "write-input-invalid", message: `${flag} ${value} is not JSON: ${err instanceof Error ? err.message : String(err)}` } };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** `chant workspace points`, and its `ask` and `answer` verbs. */
|
|
235
|
+
export async function runWorkspacePoints(ctx: CommandContext): Promise<number> {
|
|
236
|
+
const { args } = ctx;
|
|
237
|
+
const cwd = process.cwd();
|
|
238
|
+
const print = (doc: object): number => {
|
|
239
|
+
console.log(JSON.stringify(doc, null, 2));
|
|
240
|
+
return "error" in doc ? 1 : 0;
|
|
241
|
+
};
|
|
242
|
+
const verb = args.extraPositional;
|
|
243
|
+
if (verb === "ask") {
|
|
244
|
+
const point = args.extraPositional2;
|
|
245
|
+
if (!point) return print(usage("ask", "ask needs the point's name"));
|
|
246
|
+
if (args.inputs === undefined) return print(usage("ask", "--inputs <file|-> is required"));
|
|
247
|
+
const inputs = readJson("ask", "--inputs", args.inputs, cwd);
|
|
248
|
+
if (!("value" in inputs)) return print(inputs);
|
|
249
|
+
let ask: ModelAsk | undefined;
|
|
250
|
+
if (args.response !== undefined) {
|
|
251
|
+
const response = readJson("ask", "--response", args.response, cwd);
|
|
252
|
+
if (!("value" in response)) return print(response);
|
|
253
|
+
ask = responseAsk(response.value);
|
|
254
|
+
}
|
|
255
|
+
return print(await askPoint({ cwd, point, inputs: inputs.value, subject: args.subject, kind: args.kind, ask, dryRun: args.dryRun }));
|
|
256
|
+
}
|
|
257
|
+
if (verb === "answer") {
|
|
258
|
+
const id = args.extraPositional2;
|
|
259
|
+
if (!id) return print(usage("answer", "answer needs the question's id"));
|
|
260
|
+
if (args.answer === undefined) return print(usage("answer", "--answer <value> is required"));
|
|
261
|
+
if (!args.bys || args.bys.length === 0) return print(usage("answer", "--by <name> is required, once for each person who answered"));
|
|
262
|
+
return print(await answerPoint({ cwd, id, answer: args.answer, by: args.bys, kind: args.kind, dryRun: args.dryRun }));
|
|
263
|
+
}
|
|
264
|
+
if (verb !== undefined) {
|
|
265
|
+
console.error(formatError({ message: `chant workspace points takes no argument but ask or answer (got ${verb})`, hint: USAGE }));
|
|
266
|
+
return 1;
|
|
267
|
+
}
|
|
268
|
+
const doc = await workspacePoints({ cwd, at: args.at, open: args.open, kind: args.kind });
|
|
269
|
+
if (args.json) console.log(JSON.stringify(doc, null, 2));
|
|
270
|
+
else if ("error" in doc) console.error(formatError({ message: `${doc.error.code}: ${doc.error.message}`, hint: USAGE }));
|
|
271
|
+
else console.log(formatPoints(doc));
|
|
272
|
+
return "error" in doc ? 1 : 0;
|
|
273
|
+
}
|