perttool 0.1.0-alpha.2 → 0.2.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/CHANGELOG.md +78 -28
- package/README.md +173 -95
- package/dist/application/agent-help.d.ts +3 -0
- package/dist/application/agent-help.d.ts.map +1 -0
- package/dist/application/agent-help.js +5 -0
- package/dist/application/agent-help.js.map +1 -0
- package/dist/application/analyze.js +5 -5
- package/dist/application/analyze.js.map +1 -1
- package/dist/application/init.d.ts +42 -0
- package/dist/application/init.d.ts.map +1 -0
- package/dist/application/init.js +217 -0
- package/dist/application/init.js.map +1 -0
- package/dist/application/mutate.d.ts.map +1 -1
- package/dist/application/mutate.js +28 -15
- package/dist/application/mutate.js.map +1 -1
- package/dist/application/project.d.ts +25 -0
- package/dist/application/project.d.ts.map +1 -0
- package/dist/application/project.js +54 -0
- package/dist/application/project.js.map +1 -0
- package/dist/cli.js +419 -331
- package/dist/cli.js.map +1 -1
- package/dist/command/discovery.d.ts +30 -0
- package/dist/command/discovery.d.ts.map +1 -0
- package/dist/command/discovery.js +625 -0
- package/dist/command/discovery.js.map +1 -0
- package/dist/command/registry.d.ts +84 -0
- package/dist/command/registry.d.ts.map +1 -0
- package/dist/command/registry.js +951 -0
- package/dist/command/registry.js.map +1 -0
- package/dist/command/usage.d.ts +42 -0
- package/dist/command/usage.d.ts.map +1 -0
- package/dist/command/usage.js +337 -0
- package/dist/command/usage.js.map +1 -0
- package/dist/conversion/mermaid-import.js +56 -56
- package/dist/conversion/mermaid-import.js.map +1 -1
- package/dist/conversion/mermaid.js +1 -1
- package/dist/conversion/mermaid.js.map +1 -1
- package/dist/guidance/profile.d.ts +6 -0
- package/dist/guidance/profile.d.ts.map +1 -0
- package/dist/guidance/profile.js +1158 -0
- package/dist/guidance/profile.js.map +1 -0
- package/dist/guidance/projection.d.ts +16 -0
- package/dist/guidance/projection.d.ts.map +1 -0
- package/dist/guidance/projection.js +276 -0
- package/dist/guidance/projection.js.map +1 -0
- package/dist/guidance/query.d.ts +4 -0
- package/dist/guidance/query.d.ts.map +1 -0
- package/dist/guidance/query.js +178 -0
- package/dist/guidance/query.js.map +1 -0
- package/dist/guidance/text.d.ts +4 -0
- package/dist/guidance/text.d.ts.map +1 -0
- package/dist/guidance/text.js +118 -0
- package/dist/guidance/text.js.map +1 -0
- package/dist/guidance/types.d.ts +227 -0
- package/dist/guidance/types.d.ts.map +1 -0
- package/dist/guidance/types.js +54 -0
- package/dist/guidance/types.js.map +1 -0
- package/dist/guidance/validator.d.ts +8 -0
- package/dist/guidance/validator.d.ts.map +1 -0
- package/dist/guidance/validator.js +383 -0
- package/dist/guidance/validator.js.map +1 -0
- package/dist/help/guide.d.ts +12 -0
- package/dist/help/guide.d.ts.map +1 -0
- package/dist/help/guide.js +103 -0
- package/dist/help/guide.js.map +1 -0
- package/dist/help/registry.d.ts.map +1 -1
- package/dist/help/registry.js +76 -67
- package/dist/help/registry.js.map +1 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/io/safe-write.js +17 -17
- package/dist/io/safe-write.js.map +1 -1
- package/dist/mutation/gate.d.ts +5 -0
- package/dist/mutation/gate.d.ts.map +1 -0
- package/dist/mutation/gate.js +156 -0
- package/dist/mutation/gate.js.map +1 -0
- package/dist/mutation/milestone.js +26 -26
- package/dist/mutation/milestone.js.map +1 -1
- package/dist/mutation/project.d.ts +5 -0
- package/dist/mutation/project.d.ts.map +1 -0
- package/dist/mutation/project.js +148 -0
- package/dist/mutation/project.js.map +1 -0
- package/dist/mutation/resource.js +22 -22
- package/dist/mutation/resource.js.map +1 -1
- package/dist/mutation/task.js +40 -40
- package/dist/mutation/task.js.map +1 -1
- package/dist/mutation/types.d.ts +46 -1
- package/dist/mutation/types.d.ts.map +1 -1
- package/dist/parser/document-parser.js +17 -17
- package/dist/parser/document-parser.js.map +1 -1
- package/dist/semantic/validator.js +41 -41
- package/dist/semantic/validator.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -5,11 +5,19 @@ import { TextDecoder } from "node:util";
|
|
|
5
5
|
import { analyzeDocument } from "./application/analyze.js";
|
|
6
6
|
import { checkDocument } from "./application/check.js";
|
|
7
7
|
import { planFormat } from "./application/format.js";
|
|
8
|
+
import { planProjectInit, projectInitResultToJson, renderProjectInitResult, withProjectInitOutput, } from "./application/init.js";
|
|
8
9
|
import { planBatchMutation, planMutation } from "./application/mutate.js";
|
|
9
10
|
import { selectNextTasks } from "./application/next.js";
|
|
11
|
+
import { getProjectMetadata, } from "./application/project.js";
|
|
12
|
+
import { getAgentHelp } from "./application/agent-help.js";
|
|
10
13
|
import { exportMermaid, } from "./conversion/mermaid.js";
|
|
11
14
|
import { importMermaid } from "./conversion/mermaid-import.js";
|
|
12
|
-
import {
|
|
15
|
+
import { getGuide, renderGuideResult, serializeGuideResult, } from "./help/guide.js";
|
|
16
|
+
import { commandOptionSets, } from "./command/registry.js";
|
|
17
|
+
import { CONTRACT3_COMMAND_REGISTRY, getCommandDiscovery, renderCommandHelpResult, serializeCommandHelpResult, } from "./command/discovery.js";
|
|
18
|
+
import { handlerCommandUsageError, renderCommandUsageError, serializeCommandUsageError, validateCommandInvocation, } from "./command/usage.js";
|
|
19
|
+
import { agentGuidanceResultToJson } from "./guidance/projection.js";
|
|
20
|
+
import { agentGuidanceExitCode, renderAgentGuidanceText, } from "./guidance/text.js";
|
|
13
21
|
import { documentContentFromBytes, readDocumentFile, } from "./io/document-file.js";
|
|
14
22
|
import { createArtifactFile, createDocumentFile, replaceDocumentFile, SafeWriteConflictError, SafeWriteVerificationError, } from "./io/safe-write.js";
|
|
15
23
|
import { formatDecimal } from "./model/rational.js";
|
|
@@ -21,159 +29,6 @@ import { TOOL_VERSION } from "./version.js";
|
|
|
21
29
|
class UsageError extends Error {
|
|
22
30
|
code = "PTCLI-001";
|
|
23
31
|
}
|
|
24
|
-
function topLevelHelp() {
|
|
25
|
-
return [
|
|
26
|
-
"perttool - document-based PERT/CPM task management",
|
|
27
|
-
"",
|
|
28
|
-
"Usage:",
|
|
29
|
-
" perttool --version",
|
|
30
|
-
" perttool --help",
|
|
31
|
-
" perttool dsl check <file> [--format text|json]",
|
|
32
|
-
" perttool dsl format <file> [--check] [--diff] [--format text|json]",
|
|
33
|
-
" perttool dsl help [topic [subtopic]] [--level index|quick|detail] [--format text|json]",
|
|
34
|
-
" perttool dag analyze <file> [--schedule precedence|resource|both] [--format text|json]",
|
|
35
|
-
" perttool dag next <file> [--capacity <resource-id>=<integer>] [--format text|json]",
|
|
36
|
-
" perttool dag advance <file> [--diff] [--write | --out <path>] [--format text|json]",
|
|
37
|
-
" perttool dag render <file> --to mermaid [--profile perttool|plain] [--format text|json]",
|
|
38
|
-
" perttool dag import <file> --from mermaid [--strict-loss] [--out <path>] [--format text|json]",
|
|
39
|
-
" perttool task add|set|remove|finish ...",
|
|
40
|
-
" perttool milestone add|set|remove ...",
|
|
41
|
-
" perttool resource add|set|remove ...",
|
|
42
|
-
" perttool mutation apply <file> --request <json-file|-> [--diff] [--format text|json]",
|
|
43
|
-
"",
|
|
44
|
-
"Format and mutation commands preview by default; use --write or --out for explicit writes.",
|
|
45
|
-
].join("\n");
|
|
46
|
-
}
|
|
47
|
-
function commandHelp(resource, action) {
|
|
48
|
-
if (resource === "dsl" && action === "check") {
|
|
49
|
-
return [
|
|
50
|
-
"Usage: perttool dsl check <file>",
|
|
51
|
-
" [--warnings-as-errors]",
|
|
52
|
-
" [--max-diagnostics <integer>]",
|
|
53
|
-
" [--format text|json]",
|
|
54
|
-
" [--color auto|always|never]",
|
|
55
|
-
].join("\n");
|
|
56
|
-
}
|
|
57
|
-
if (resource === "dsl" && action === "format")
|
|
58
|
-
return [
|
|
59
|
-
"Usage: perttool dsl format <file>",
|
|
60
|
-
" [--check] [--diff]",
|
|
61
|
-
" [--write [--expect-digest <digest>] | --out <path>]",
|
|
62
|
-
" [--max-diagnostics <integer>] [--warnings-as-errors]",
|
|
63
|
-
" [--format text|json] [--color auto|always|never]",
|
|
64
|
-
].join("\n");
|
|
65
|
-
if (resource === "dsl" && action === "help")
|
|
66
|
-
return [
|
|
67
|
-
"Usage: perttool dsl help [topic [subtopic]]",
|
|
68
|
-
" [--level index|quick|detail]",
|
|
69
|
-
" [--format text|json]",
|
|
70
|
-
" [--color auto|always|never]",
|
|
71
|
-
].join("\n");
|
|
72
|
-
if (resource === "dag" && action === "analyze")
|
|
73
|
-
return [
|
|
74
|
-
"Usage: perttool dag analyze <file>",
|
|
75
|
-
" [--schedule precedence|resource|both]",
|
|
76
|
-
" [--capacity <resource-id>=<integer>]...",
|
|
77
|
-
" [--max-paths <integer>] [--precision <integer>]",
|
|
78
|
-
" [--max-diagnostics <integer>]",
|
|
79
|
-
" [--warnings-as-errors]",
|
|
80
|
-
" [--format text|json] [--color auto|always|never]",
|
|
81
|
-
].join("\n");
|
|
82
|
-
if (resource === "dag" && action === "next")
|
|
83
|
-
return [
|
|
84
|
-
"Usage: perttool dag next <file>",
|
|
85
|
-
" [--capacity <resource-id>=<integer>]...",
|
|
86
|
-
" [--explain-depth <integer>] [--precision <integer>]",
|
|
87
|
-
" [--max-diagnostics <integer>]",
|
|
88
|
-
" [--warnings-as-errors]",
|
|
89
|
-
" [--format text|json] [--color auto|always|never]",
|
|
90
|
-
"Output: Perttool.NextResult.v3 with a complete recommendation graph in JSON.",
|
|
91
|
-
"Consumers must inspect schema_version before using recommendation authority.",
|
|
92
|
-
].join("\n");
|
|
93
|
-
if (resource === "dag" && action === "advance")
|
|
94
|
-
return [
|
|
95
|
-
"Usage: perttool dag advance <file>",
|
|
96
|
-
" [--diff] [--write [--expect-digest <digest>] | --out <path>]",
|
|
97
|
-
" [--max-diagnostics <integer>] [--warnings-as-errors]",
|
|
98
|
-
" [--format text|json] [--color auto|always|never]",
|
|
99
|
-
].join("\n");
|
|
100
|
-
if (resource === "dag" && action === "render")
|
|
101
|
-
return [
|
|
102
|
-
"Usage: perttool dag render <file> --to mermaid",
|
|
103
|
-
" [--profile perttool|plain] [--analysis none|precedence|resource|both]",
|
|
104
|
-
" [--capacity <resource-id>=<integer>]... [--strict-loss] [--out <path>]",
|
|
105
|
-
" [--max-diagnostics <integer>] [--warnings-as-errors]",
|
|
106
|
-
" [--format text|json] [--color auto|always|never]",
|
|
107
|
-
].join("\n");
|
|
108
|
-
if (resource === "dag" && action === "import")
|
|
109
|
-
return [
|
|
110
|
-
"Usage: perttool dag import <file> --from mermaid",
|
|
111
|
-
" [--strict-loss] [--out <path>]",
|
|
112
|
-
" [--max-diagnostics <integer>] [--warnings-as-errors]",
|
|
113
|
-
" [--format text|json] [--color auto|always|never]",
|
|
114
|
-
].join("\n");
|
|
115
|
-
const preview = " [--diff] [--write [--expect-digest <digest>] | --out <path>] [--max-diagnostics <integer>] [--warnings-as-errors] [--format text|json] [--color auto|always|never]";
|
|
116
|
-
if (resource === "task" && action === "add")
|
|
117
|
-
return [
|
|
118
|
-
"Usage: perttool task add <file> <id> <from> <to>",
|
|
119
|
-
" --title <text> (--duration <duration> | --optimistic <duration> --most-likely <duration> --pessimistic <duration>)",
|
|
120
|
-
" [--description <text>] [--status planned|active|blocked|done] [--priority <integer>]",
|
|
121
|
-
" [--owner <text>] [--blocked-reason <text>] [--source <text>] [--tag <tag>]... [--require <resource-id>=<integer>]...",
|
|
122
|
-
preview,
|
|
123
|
-
].join("\n");
|
|
124
|
-
if (resource === "task" && action === "set")
|
|
125
|
-
return [
|
|
126
|
-
"Usage: perttool task set <file> <id> [field options]",
|
|
127
|
-
" [--from <id>] [--to <id>] [--title <text>] [--description <text>] [--duration <duration>]",
|
|
128
|
-
" [--optimistic <duration> --most-likely <duration> --pessimistic <duration>]",
|
|
129
|
-
" [--status planned|active|blocked|done] [--priority <integer>] [--owner <text>]",
|
|
130
|
-
" [--blocked-reason <text>] [--source <text>] [--require <resource-id>=<integer>]...",
|
|
131
|
-
" [--add-tag <tag>]... [--remove-tag <tag>]... [--remove-require <resource-id>]... [--clear <field>]...",
|
|
132
|
-
preview,
|
|
133
|
-
].join("\n");
|
|
134
|
-
if (resource === "task")
|
|
135
|
-
return [
|
|
136
|
-
`Usage: perttool task ${action} <file> <id>`,
|
|
137
|
-
preview,
|
|
138
|
-
].join("\n");
|
|
139
|
-
if (resource === "milestone" && action === "add")
|
|
140
|
-
return [
|
|
141
|
-
"Usage: perttool milestone add <file> <id> --title <text>",
|
|
142
|
-
" [--description <text>] [--state planned|reached] [--tag <tag>]...",
|
|
143
|
-
preview,
|
|
144
|
-
].join("\n");
|
|
145
|
-
if (resource === "milestone" && action === "set")
|
|
146
|
-
return [
|
|
147
|
-
"Usage: perttool milestone set <file> <id> [--title <text>] [--description <text>] [--state planned|reached]",
|
|
148
|
-
" [--add-tag <tag>]... [--remove-tag <tag>]... [--clear description|state|tags]...",
|
|
149
|
-
preview,
|
|
150
|
-
].join("\n");
|
|
151
|
-
if (resource === "milestone")
|
|
152
|
-
return [
|
|
153
|
-
`Usage: perttool milestone ${action} <file> <id>`,
|
|
154
|
-
preview,
|
|
155
|
-
].join("\n");
|
|
156
|
-
if (resource === "resource" && action === "add")
|
|
157
|
-
return [
|
|
158
|
-
"Usage: perttool resource add <file> <id> --title <text> --capacity <integer>",
|
|
159
|
-
" [--description <text>]",
|
|
160
|
-
preview,
|
|
161
|
-
].join("\n");
|
|
162
|
-
if (resource === "resource" && action === "set")
|
|
163
|
-
return [
|
|
164
|
-
"Usage: perttool resource set <file> <id> [--title <text>] [--description <text>] [--capacity <integer>] [--clear description]",
|
|
165
|
-
preview,
|
|
166
|
-
].join("\n");
|
|
167
|
-
if (resource === "resource")
|
|
168
|
-
return [
|
|
169
|
-
`Usage: perttool resource ${action} <file> <id>`,
|
|
170
|
-
preview,
|
|
171
|
-
].join("\n");
|
|
172
|
-
return [
|
|
173
|
-
"Usage: perttool mutation apply <file> --request <json-file|->",
|
|
174
|
-
preview,
|
|
175
|
-
].join("\n");
|
|
176
|
-
}
|
|
177
32
|
function parseOptions(args, valueOptions, flagOptions, repeatableOptions = new Set()) {
|
|
178
33
|
const positionals = [];
|
|
179
34
|
const values = new Map();
|
|
@@ -223,6 +78,14 @@ function parseOptions(args, valueOptions, flagOptions, repeatableOptions = new S
|
|
|
223
78
|
}
|
|
224
79
|
return { positionals, values, repeatedValues, flags };
|
|
225
80
|
}
|
|
81
|
+
function parseCommandOptions(operation, args) {
|
|
82
|
+
const descriptor = CONTRACT3_COMMAND_REGISTRY.find((candidate) => candidate.operation === operation);
|
|
83
|
+
if (descriptor === undefined) {
|
|
84
|
+
throw new Error(`command descriptor is missing for ${operation}`);
|
|
85
|
+
}
|
|
86
|
+
const optionSets = commandOptionSets(descriptor);
|
|
87
|
+
return parseOptions(args, optionSets.values, optionSets.flags, optionSets.repeatable);
|
|
88
|
+
}
|
|
226
89
|
function outputFormat(value) {
|
|
227
90
|
if (value === undefined || value === "text")
|
|
228
91
|
return "text";
|
|
@@ -272,7 +135,8 @@ function jsonDiagnostic(diagnostic) {
|
|
|
272
135
|
message: related.message,
|
|
273
136
|
span: jsonSpan(related.span),
|
|
274
137
|
})),
|
|
275
|
-
help_topic:
|
|
138
|
+
help_topic: null,
|
|
139
|
+
guide_topic: diagnostic.helpTopic ?? null,
|
|
276
140
|
expected_syntax: diagnostic.expectedSyntax ?? null,
|
|
277
141
|
fixes: [],
|
|
278
142
|
data: diagnostic.data ?? {},
|
|
@@ -298,7 +162,7 @@ function renderDiagnostic(diagnostic, source, mode) {
|
|
|
298
162
|
}
|
|
299
163
|
if (diagnostic.helpTopic !== undefined) {
|
|
300
164
|
const [topic, subtopic] = diagnostic.helpTopic.split(".", 2);
|
|
301
|
-
lines.push(`
|
|
165
|
+
lines.push(` guide: perttool guide ${topic}${subtopic === undefined ? "" : ` ${subtopic}`} --level quick`);
|
|
302
166
|
}
|
|
303
167
|
return lines.join("\n");
|
|
304
168
|
}
|
|
@@ -341,6 +205,7 @@ function cliError(error, exitCode, operation, json) {
|
|
|
341
205
|
if (json) {
|
|
342
206
|
writeJson({
|
|
343
207
|
schema_version: "Perttool.CliError.v1",
|
|
208
|
+
cli_contract_version: 3,
|
|
344
209
|
tool_version: TOOL_VERSION,
|
|
345
210
|
operation,
|
|
346
211
|
ok: false,
|
|
@@ -353,9 +218,9 @@ function cliError(error, exitCode, operation, json) {
|
|
|
353
218
|
return exitCode;
|
|
354
219
|
}
|
|
355
220
|
async function runCheck(args) {
|
|
356
|
-
const parsed =
|
|
221
|
+
const parsed = parseCommandOptions("document.check", args);
|
|
357
222
|
if (parsed.positionals.length !== 1) {
|
|
358
|
-
throw new UsageError("
|
|
223
|
+
throw new UsageError("document check requires exactly one <file>");
|
|
359
224
|
}
|
|
360
225
|
const format = outputFormat(parsed.values.get("format"));
|
|
361
226
|
const color = colorMode(parsed.values.get("color"), format);
|
|
@@ -367,7 +232,7 @@ async function runCheck(args) {
|
|
|
367
232
|
input = await readDocument(sourceOperand);
|
|
368
233
|
}
|
|
369
234
|
catch (error) {
|
|
370
|
-
return cliError(error instanceof Error ? error : new Error(String(error)), 3, "
|
|
235
|
+
return cliError(error instanceof Error ? error : new Error(String(error)), 3, "document.check", format === "json");
|
|
371
236
|
}
|
|
372
237
|
const result = checkDocument(input.text, { maxDiagnostics });
|
|
373
238
|
const warningsAsErrors = parsed.flags.has("warnings-as-errors");
|
|
@@ -376,8 +241,9 @@ async function runCheck(args) {
|
|
|
376
241
|
if (format === "json") {
|
|
377
242
|
writeJson({
|
|
378
243
|
schema_version: "Perttool.CheckResult.v1",
|
|
244
|
+
cli_contract_version: 3,
|
|
379
245
|
tool_version: TOOL_VERSION,
|
|
380
|
-
operation: "
|
|
246
|
+
operation: "document.check",
|
|
381
247
|
ok,
|
|
382
248
|
document_id: result.documentId,
|
|
383
249
|
source,
|
|
@@ -401,6 +267,137 @@ async function runCheck(args) {
|
|
|
401
267
|
}
|
|
402
268
|
return ok ? 0 : 1;
|
|
403
269
|
}
|
|
270
|
+
function projectJson(project) {
|
|
271
|
+
return {
|
|
272
|
+
id: project.id,
|
|
273
|
+
version: project.version,
|
|
274
|
+
title: project.title,
|
|
275
|
+
description: project.description,
|
|
276
|
+
as_of: project.asOf,
|
|
277
|
+
duration_unit: project.durationUnit,
|
|
278
|
+
velocity: project.velocity,
|
|
279
|
+
finish: project.finish,
|
|
280
|
+
critical_epsilon: project.criticalEpsilon,
|
|
281
|
+
target_duration: project.targetDuration,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function renderProjectText(project) {
|
|
285
|
+
const optional = (value) => value ?? "-";
|
|
286
|
+
return [
|
|
287
|
+
`PROJECT ${project.id}`,
|
|
288
|
+
`VERSION ${project.version}`,
|
|
289
|
+
`TITLE ${JSON.stringify(project.title)}`,
|
|
290
|
+
`DESCRIPTION ${project.description === null ? "-" : JSON.stringify(project.description)}`,
|
|
291
|
+
`AS_OF ${optional(project.asOf)}`,
|
|
292
|
+
`DURATION_UNIT ${project.durationUnit}`,
|
|
293
|
+
`VELOCITY ${optional(project.velocity)}`,
|
|
294
|
+
`FINISH ${project.finish}`,
|
|
295
|
+
`CRITICAL_EPSILON ${optional(project.criticalEpsilon)}`,
|
|
296
|
+
`TARGET_DURATION ${optional(project.targetDuration)}`,
|
|
297
|
+
"",
|
|
298
|
+
].join("\n");
|
|
299
|
+
}
|
|
300
|
+
async function runProjectShow(args) {
|
|
301
|
+
const parsed = parseCommandOptions("project.show", args);
|
|
302
|
+
if (parsed.positionals.length !== 1) {
|
|
303
|
+
throw new UsageError("project show requires exactly one <file>");
|
|
304
|
+
}
|
|
305
|
+
const format = outputFormat(parsed.values.get("format"));
|
|
306
|
+
const color = colorMode(parsed.values.get("color"), format);
|
|
307
|
+
const maxDiagnostics = boundedInteger(parsed.values.get("max-diagnostics"), "max-diagnostics", 100, 1, 1000);
|
|
308
|
+
const sourceOperand = parsed.positionals[0];
|
|
309
|
+
const source = sourceOperand === "-" ? "<stdin>" : sourceOperand;
|
|
310
|
+
let input;
|
|
311
|
+
try {
|
|
312
|
+
input = await readDocument(sourceOperand);
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
return cliError(error instanceof Error ? error : new Error(String(error)), 3, "project.show", format === "json");
|
|
316
|
+
}
|
|
317
|
+
const result = getProjectMetadata(input.text, { maxDiagnostics });
|
|
318
|
+
const warningFailure = parsed.flags.has("warnings-as-errors") &&
|
|
319
|
+
(result.diagnosticsTruncated ||
|
|
320
|
+
result.diagnostics.some((diagnostic) => diagnostic.severity === "warning"));
|
|
321
|
+
const ok = result.ok && !warningFailure;
|
|
322
|
+
if (format === "json") {
|
|
323
|
+
writeJson({
|
|
324
|
+
schema_version: "Perttool.ProjectResult.v1",
|
|
325
|
+
cli_contract_version: 3,
|
|
326
|
+
tool_version: TOOL_VERSION,
|
|
327
|
+
operation: "project.show",
|
|
328
|
+
ok,
|
|
329
|
+
document_id: result.documentId,
|
|
330
|
+
source,
|
|
331
|
+
source_digest: input.digest,
|
|
332
|
+
diagnostics: result.diagnostics.map(jsonDiagnostic),
|
|
333
|
+
diagnostics_truncated: result.diagnosticsTruncated,
|
|
334
|
+
grammar_version: result.grammarVersion,
|
|
335
|
+
project: result.project === null ? null : projectJson(result.project),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
if (ok && result.project !== null) {
|
|
340
|
+
process.stdout.write(renderProjectText(result.project));
|
|
341
|
+
}
|
|
342
|
+
for (const diagnostic of result.diagnostics) {
|
|
343
|
+
process.stderr.write(`${renderDiagnostic(diagnostic, source, color)}\n`);
|
|
344
|
+
}
|
|
345
|
+
if (result.diagnosticsTruncated) {
|
|
346
|
+
process.stderr.write(`DIAGNOSTICS_TRUNCATED true limit=${maxDiagnostics}\n`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return ok ? 0 : 1;
|
|
350
|
+
}
|
|
351
|
+
async function runProjectInit(args) {
|
|
352
|
+
const parsed = parseCommandOptions("project.init", args);
|
|
353
|
+
const format = outputFormat(parsed.values.get("format"));
|
|
354
|
+
const color = colorMode(parsed.values.get("color"), format);
|
|
355
|
+
const durationUnit = enumOption(requiredOption(parsed, "duration-unit"), "duration-unit", new Set(["day", "hour", "point"]));
|
|
356
|
+
const version = optionalInteger(parsed, "version", 0, 2_147_483_647);
|
|
357
|
+
let result = planProjectInit({
|
|
358
|
+
projectId: parsed.positionals[0],
|
|
359
|
+
title: requiredOption(parsed, "title"),
|
|
360
|
+
durationUnit,
|
|
361
|
+
initialMilestone: requiredOption(parsed, "initial-milestone"),
|
|
362
|
+
initialMilestoneTitle: requiredOption(parsed, "initial-milestone-title"),
|
|
363
|
+
finish: requiredOption(parsed, "finish"),
|
|
364
|
+
...(version === undefined ? {} : { version }),
|
|
365
|
+
...(parsed.values.get("as-of") === undefined
|
|
366
|
+
? {}
|
|
367
|
+
: { asOf: parsed.values.get("as-of") }),
|
|
368
|
+
...(parsed.values.get("velocity") === undefined
|
|
369
|
+
? {}
|
|
370
|
+
: { velocity: parsed.values.get("velocity") }),
|
|
371
|
+
});
|
|
372
|
+
let writeResult = null;
|
|
373
|
+
const output = parsed.values.get("out");
|
|
374
|
+
if (result.ok && output !== undefined) {
|
|
375
|
+
try {
|
|
376
|
+
writeResult = await createDocumentFile(output, result.candidateText);
|
|
377
|
+
result = withProjectInitOutput(result, writeResult);
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
return writeFailureExit(error, "project.init", format === "json");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (format === "json") {
|
|
384
|
+
writeJson(projectInitResultToJson(result));
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
if (result.ok) {
|
|
388
|
+
if (writeResult === null) {
|
|
389
|
+
process.stdout.write(renderProjectInitResult(result));
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
process.stderr.write(renderWriteSummary("project.init", writeResult));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
for (const diagnostic of result.diagnostics) {
|
|
396
|
+
process.stderr.write(`${renderDiagnostic(diagnostic, "<project-init>", color)}\n`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return result.ok ? 0 : 1;
|
|
400
|
+
}
|
|
404
401
|
function boundedInteger(raw, option, defaultValue, minimum, maximum) {
|
|
405
402
|
if (raw === undefined)
|
|
406
403
|
return defaultValue;
|
|
@@ -438,14 +435,6 @@ function capacityOverrides(values) {
|
|
|
438
435
|
}
|
|
439
436
|
return overrides;
|
|
440
437
|
}
|
|
441
|
-
const mutationCommonValueOptions = [
|
|
442
|
-
"format",
|
|
443
|
-
"color",
|
|
444
|
-
"max-diagnostics",
|
|
445
|
-
"out",
|
|
446
|
-
"expect-digest",
|
|
447
|
-
];
|
|
448
|
-
const mutationCommonFlagOptions = ["diff", "write", "warnings-as-errors"];
|
|
449
438
|
function editingWriteRequest(parsed, sourceOperand, check = false) {
|
|
450
439
|
const write = parsed.flags.has("write");
|
|
451
440
|
const out = parsed.values.get("out");
|
|
@@ -486,12 +475,12 @@ function assertExpectedDigest(request, initialDigest) {
|
|
|
486
475
|
if (request.mode === "in_place" &&
|
|
487
476
|
request.expectedDigest !== undefined &&
|
|
488
477
|
request.expectedDigest !== initialDigest) {
|
|
489
|
-
throw new SafeWriteConflictError("expected_digest_mismatch", "--expect-digest
|
|
478
|
+
throw new SafeWriteConflictError("expected_digest_mismatch", "--expect-digest does not match the initial document digest");
|
|
490
479
|
}
|
|
491
480
|
}
|
|
492
481
|
async function commitCandidate(request, candidateText, initialDigest) {
|
|
493
482
|
if (candidateText === null) {
|
|
494
|
-
throw new SafeWriteVerificationError("invalid_candidate", "successful editing result
|
|
483
|
+
throw new SafeWriteVerificationError("invalid_candidate", "successful editing result has no candidate text");
|
|
495
484
|
}
|
|
496
485
|
return request.mode === "in_place"
|
|
497
486
|
? replaceDocumentFile(request.target, candidateText, {
|
|
@@ -515,9 +504,9 @@ function renderWriteSummary(operation, result) {
|
|
|
515
504
|
return `WRITE ${operation} mode=${result.mode} target=${result.target} digest=${result.digest} written=${result.written}\n`;
|
|
516
505
|
}
|
|
517
506
|
async function runFormat(args) {
|
|
518
|
-
const parsed =
|
|
507
|
+
const parsed = parseCommandOptions("document.format", args);
|
|
519
508
|
if (parsed.positionals.length !== 1) {
|
|
520
|
-
throw new UsageError("
|
|
509
|
+
throw new UsageError("document format requires exactly one <file>");
|
|
521
510
|
}
|
|
522
511
|
const format = outputFormat(parsed.values.get("format"));
|
|
523
512
|
const color = colorMode(parsed.values.get("color"), format);
|
|
@@ -530,7 +519,7 @@ async function runFormat(args) {
|
|
|
530
519
|
input = await readDocument(sourceOperand);
|
|
531
520
|
}
|
|
532
521
|
catch (error) {
|
|
533
|
-
return cliError(error instanceof Error ? error : new Error(String(error)), 3, "
|
|
522
|
+
return cliError(error instanceof Error ? error : new Error(String(error)), 3, "document.format", format === "json");
|
|
534
523
|
}
|
|
535
524
|
const result = planFormat(input.text, {
|
|
536
525
|
maxDiagnostics,
|
|
@@ -551,14 +540,15 @@ async function runFormat(args) {
|
|
|
551
540
|
}
|
|
552
541
|
}
|
|
553
542
|
catch (error) {
|
|
554
|
-
return writeFailureExit(error, "
|
|
543
|
+
return writeFailureExit(error, "document.format", format === "json");
|
|
555
544
|
}
|
|
556
545
|
}
|
|
557
546
|
if (format === "json") {
|
|
558
547
|
writeJson({
|
|
559
548
|
schema_version: "Perttool.FormatResult.v1",
|
|
549
|
+
cli_contract_version: 3,
|
|
560
550
|
tool_version: TOOL_VERSION,
|
|
561
|
-
operation: "
|
|
551
|
+
operation: "document.format",
|
|
562
552
|
ok,
|
|
563
553
|
document_id: result.documentId,
|
|
564
554
|
source,
|
|
@@ -572,7 +562,7 @@ async function runFormat(args) {
|
|
|
572
562
|
const candidateAllowed = result.ok && !warningFailure;
|
|
573
563
|
if (candidateAllowed) {
|
|
574
564
|
if (writeResult !== null) {
|
|
575
|
-
process.stderr.write(renderWriteSummary("
|
|
565
|
+
process.stderr.write(renderWriteSummary("document.format", writeResult));
|
|
576
566
|
}
|
|
577
567
|
else if (parsed.flags.has("check")) {
|
|
578
568
|
if (parsed.flags.has("diff"))
|
|
@@ -581,7 +571,7 @@ async function runFormat(args) {
|
|
|
581
571
|
else {
|
|
582
572
|
process.stdout.write(parsed.flags.has("diff") ? (result.diff ?? "") : (result.updatedText ?? ""));
|
|
583
573
|
if (!parsed.flags.has("diff")) {
|
|
584
|
-
process.stderr.write(`PREVIEW
|
|
574
|
+
process.stderr.write(`PREVIEW document.format changed=${result.changed} original_digest=${result.originalDigest} updated_digest=${result.updatedDigest}\n`);
|
|
585
575
|
}
|
|
586
576
|
}
|
|
587
577
|
}
|
|
@@ -594,46 +584,6 @@ async function runFormat(args) {
|
|
|
594
584
|
}
|
|
595
585
|
return ok ? 0 : 1;
|
|
596
586
|
}
|
|
597
|
-
function mutationOptionSets(resource, action) {
|
|
598
|
-
const values = new Set(mutationCommonValueOptions);
|
|
599
|
-
const flags = new Set(mutationCommonFlagOptions);
|
|
600
|
-
const repeatable = new Set();
|
|
601
|
-
const addValues = (...names) => {
|
|
602
|
-
for (const name of names)
|
|
603
|
-
values.add(name);
|
|
604
|
-
};
|
|
605
|
-
const addRepeatable = (...names) => {
|
|
606
|
-
for (const name of names)
|
|
607
|
-
repeatable.add(name);
|
|
608
|
-
};
|
|
609
|
-
if (resource === "task" && action === "add") {
|
|
610
|
-
addValues("title", "description", "duration", "optimistic", "most-likely", "pessimistic", "status", "priority", "owner", "blocked-reason", "source");
|
|
611
|
-
addRepeatable("tag", "require");
|
|
612
|
-
}
|
|
613
|
-
else if (resource === "task" && action === "set") {
|
|
614
|
-
addValues("from", "to", "title", "description", "duration", "optimistic", "most-likely", "pessimistic", "status", "priority", "owner", "blocked-reason", "source");
|
|
615
|
-
addRepeatable("require", "add-tag", "remove-tag", "remove-require", "clear");
|
|
616
|
-
}
|
|
617
|
-
else if (resource === "milestone" && action === "add") {
|
|
618
|
-
addValues("title", "description", "state");
|
|
619
|
-
addRepeatable("tag");
|
|
620
|
-
}
|
|
621
|
-
else if (resource === "milestone" && action === "set") {
|
|
622
|
-
addValues("title", "description", "state");
|
|
623
|
-
addRepeatable("add-tag", "remove-tag", "clear");
|
|
624
|
-
}
|
|
625
|
-
else if (resource === "resource" && action === "add") {
|
|
626
|
-
addValues("title", "description", "capacity");
|
|
627
|
-
}
|
|
628
|
-
else if (resource === "resource" && action === "set") {
|
|
629
|
-
addValues("title", "description", "capacity");
|
|
630
|
-
addRepeatable("clear");
|
|
631
|
-
}
|
|
632
|
-
else if (resource === "mutation" && action === "apply") {
|
|
633
|
-
addValues("request");
|
|
634
|
-
}
|
|
635
|
-
return { values, flags, repeatable };
|
|
636
|
-
}
|
|
637
587
|
function requiredOption(parsed, name) {
|
|
638
588
|
const value = parsed.values.get(name);
|
|
639
589
|
if (value === undefined)
|
|
@@ -824,6 +774,62 @@ function taskMutationFromOptions(action, parsed) {
|
|
|
824
774
|
...(removeRequirements.length === 0 ? {} : { removeRequirements }),
|
|
825
775
|
};
|
|
826
776
|
}
|
|
777
|
+
function projectMutationFromOptions(parsed) {
|
|
778
|
+
if (parsed.positionals.length !== 1) {
|
|
779
|
+
throw new UsageError("project set requires exactly one <file>");
|
|
780
|
+
}
|
|
781
|
+
const version = optionalInteger(parsed, "version", 0, 2_147_483_647);
|
|
782
|
+
const durationUnit = enumOption(parsed.values.get("duration-unit"), "duration-unit", new Set(["day", "hour", "point"]));
|
|
783
|
+
const clear = enumRepeated(parsed, "clear", new Set([
|
|
784
|
+
"description",
|
|
785
|
+
"as_of",
|
|
786
|
+
"velocity",
|
|
787
|
+
"critical_epsilon",
|
|
788
|
+
"target_duration",
|
|
789
|
+
]));
|
|
790
|
+
const conflicts = new Map([
|
|
791
|
+
["description", parsed.values.has("description")],
|
|
792
|
+
["as_of", parsed.values.has("as-of")],
|
|
793
|
+
["velocity", parsed.values.has("velocity")],
|
|
794
|
+
["critical_epsilon", parsed.values.has("critical-epsilon")],
|
|
795
|
+
["target_duration", parsed.values.has("target-duration")],
|
|
796
|
+
]);
|
|
797
|
+
const conflict = clear.find((field) => conflicts.get(field) === true);
|
|
798
|
+
if (conflict !== undefined) {
|
|
799
|
+
throw new UsageError(`--clear ${conflict} conflicts with another project field option`);
|
|
800
|
+
}
|
|
801
|
+
const set = {
|
|
802
|
+
...(parsed.values.get("id") === undefined ? {} : { id: parsed.values.get("id") }),
|
|
803
|
+
...(version === undefined ? {} : { version }),
|
|
804
|
+
...(parsed.values.get("title") === undefined
|
|
805
|
+
? {}
|
|
806
|
+
: { title: parsed.values.get("title") }),
|
|
807
|
+
...(parsed.values.get("description") === undefined
|
|
808
|
+
? {}
|
|
809
|
+
: { description: parsed.values.get("description") }),
|
|
810
|
+
...(parsed.values.get("as-of") === undefined
|
|
811
|
+
? {}
|
|
812
|
+
: { asOf: parsed.values.get("as-of") }),
|
|
813
|
+
...(durationUnit === undefined ? {} : { durationUnit }),
|
|
814
|
+
...(parsed.values.get("velocity") === undefined
|
|
815
|
+
? {}
|
|
816
|
+
: { velocity: parsed.values.get("velocity") }),
|
|
817
|
+
...(parsed.values.get("finish") === undefined
|
|
818
|
+
? {}
|
|
819
|
+
: { finish: parsed.values.get("finish") }),
|
|
820
|
+
...(parsed.values.get("critical-epsilon") === undefined
|
|
821
|
+
? {}
|
|
822
|
+
: { criticalEpsilon: parsed.values.get("critical-epsilon") }),
|
|
823
|
+
...(parsed.values.get("target-duration") === undefined
|
|
824
|
+
? {}
|
|
825
|
+
: { targetDuration: parsed.values.get("target-duration") }),
|
|
826
|
+
};
|
|
827
|
+
return {
|
|
828
|
+
kind: "project.set",
|
|
829
|
+
...(Object.keys(set).length === 0 ? {} : { set }),
|
|
830
|
+
...(clear.length === 0 ? {} : { clear }),
|
|
831
|
+
};
|
|
832
|
+
}
|
|
827
833
|
function milestoneMutationFromOptions(action, parsed) {
|
|
828
834
|
if (parsed.positionals.length !== 2) {
|
|
829
835
|
throw new UsageError(`milestone ${action} requires <file> <id>`);
|
|
@@ -918,6 +924,37 @@ function resourceMutationFromOptions(action, parsed) {
|
|
|
918
924
|
...(clear.length === 0 ? {} : { clear }),
|
|
919
925
|
};
|
|
920
926
|
}
|
|
927
|
+
function gateMutationFromOptions(action, parsed) {
|
|
928
|
+
const expectedPositionals = action === "add" ? 4 : 2;
|
|
929
|
+
if (parsed.positionals.length !== expectedPositionals) {
|
|
930
|
+
throw new UsageError(`gate ${action} requires ${action === "add" ? "<file> <id> <from> <to>" : "<file> <id>"}`);
|
|
931
|
+
}
|
|
932
|
+
const id = parsed.positionals[1];
|
|
933
|
+
if (action === "remove")
|
|
934
|
+
return { kind: "gate.remove", id };
|
|
935
|
+
if (action === "add") {
|
|
936
|
+
return {
|
|
937
|
+
kind: "gate.add",
|
|
938
|
+
id,
|
|
939
|
+
from: parsed.positionals[2],
|
|
940
|
+
to: parsed.positionals[3],
|
|
941
|
+
gate: { reason: requiredOption(parsed, "reason") },
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
const from = parsed.values.get("from");
|
|
945
|
+
const to = parsed.values.get("to");
|
|
946
|
+
const reason = parsed.values.get("reason");
|
|
947
|
+
if (from === undefined && to === undefined && reason === undefined) {
|
|
948
|
+
throw new UsageError("gate set requires --from, --to, or --reason");
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
kind: "gate.set",
|
|
952
|
+
id,
|
|
953
|
+
...(from === undefined ? {} : { from }),
|
|
954
|
+
...(to === undefined ? {} : { to }),
|
|
955
|
+
...(reason === undefined ? {} : { set: { reason } }),
|
|
956
|
+
};
|
|
957
|
+
}
|
|
921
958
|
function previewResultJson(result, exposeCandidate, writeRequest = { mode: "preview", target: null }, writeResult = null) {
|
|
922
959
|
return {
|
|
923
960
|
changed: exposeCandidate ? result.changed : false,
|
|
@@ -948,8 +985,7 @@ async function readMutationRequest(source) {
|
|
|
948
985
|
}
|
|
949
986
|
}
|
|
950
987
|
async function runMutation(resource, action, args) {
|
|
951
|
-
const
|
|
952
|
-
const parsed = parseOptions(args, config.values, config.flags, config.repeatable);
|
|
988
|
+
const parsed = parseCommandOptions(`${resource}.${action}`, args);
|
|
953
989
|
const format = outputFormat(parsed.values.get("format"));
|
|
954
990
|
const color = colorMode(parsed.values.get("color"), format);
|
|
955
991
|
const maxDiagnostics = boundedInteger(parsed.values.get("max-diagnostics"), "max-diagnostics", 100, 1, 1000);
|
|
@@ -957,9 +993,9 @@ async function runMutation(resource, action, args) {
|
|
|
957
993
|
let sourceOperand;
|
|
958
994
|
let writeRequest;
|
|
959
995
|
let mutation;
|
|
960
|
-
if (resource === "
|
|
996
|
+
if (resource === "batch") {
|
|
961
997
|
if (parsed.positionals.length !== 1) {
|
|
962
|
-
throw new UsageError("
|
|
998
|
+
throw new UsageError("batch apply requires exactly one <file>");
|
|
963
999
|
}
|
|
964
1000
|
sourceOperand = parsed.positionals[0];
|
|
965
1001
|
writeRequest = editingWriteRequest(parsed, sourceOperand);
|
|
@@ -980,11 +1016,15 @@ async function runMutation(resource, action, args) {
|
|
|
980
1016
|
}
|
|
981
1017
|
else {
|
|
982
1018
|
mutation =
|
|
983
|
-
resource === "
|
|
984
|
-
?
|
|
985
|
-
: resource === "
|
|
986
|
-
?
|
|
987
|
-
:
|
|
1019
|
+
resource === "project"
|
|
1020
|
+
? projectMutationFromOptions(parsed)
|
|
1021
|
+
: resource === "task"
|
|
1022
|
+
? taskMutationFromOptions(action, parsed)
|
|
1023
|
+
: resource === "gate"
|
|
1024
|
+
? gateMutationFromOptions(action, parsed)
|
|
1025
|
+
: resource === "milestone"
|
|
1026
|
+
? milestoneMutationFromOptions(action, parsed)
|
|
1027
|
+
: resourceMutationFromOptions(action, parsed);
|
|
988
1028
|
sourceOperand = parsed.positionals[0];
|
|
989
1029
|
writeRequest = editingWriteRequest(parsed, sourceOperand);
|
|
990
1030
|
}
|
|
@@ -1001,7 +1041,7 @@ async function runMutation(resource, action, args) {
|
|
|
1001
1041
|
originalLabel: source,
|
|
1002
1042
|
updatedLabel: "candidate",
|
|
1003
1043
|
};
|
|
1004
|
-
const result = resource === "
|
|
1044
|
+
const result = resource === "batch"
|
|
1005
1045
|
? planBatchMutation(input.text, mutation, mutationOptions)
|
|
1006
1046
|
: planMutation(input.text, mutation, mutationOptions);
|
|
1007
1047
|
const warningFailure = parsed.flags.has("warnings-as-errors") &&
|
|
@@ -1023,6 +1063,7 @@ async function runMutation(resource, action, args) {
|
|
|
1023
1063
|
if (format === "json") {
|
|
1024
1064
|
writeJson({
|
|
1025
1065
|
schema_version: "Perttool.MutationResult.v1",
|
|
1066
|
+
cli_contract_version: 3,
|
|
1026
1067
|
tool_version: TOOL_VERSION,
|
|
1027
1068
|
operation,
|
|
1028
1069
|
ok,
|
|
@@ -1077,7 +1118,7 @@ function renderAdvanceSummary(details) {
|
|
|
1077
1118
|
].join("\n");
|
|
1078
1119
|
}
|
|
1079
1120
|
async function runAdvance(args) {
|
|
1080
|
-
const parsed =
|
|
1121
|
+
const parsed = parseCommandOptions("dag.advance", args);
|
|
1081
1122
|
if (parsed.positionals.length !== 1) {
|
|
1082
1123
|
throw new UsageError("dag advance requires exactly one <file>");
|
|
1083
1124
|
}
|
|
@@ -1118,6 +1159,7 @@ async function runAdvance(args) {
|
|
|
1118
1159
|
if (format === "json") {
|
|
1119
1160
|
writeJson({
|
|
1120
1161
|
schema_version: "Perttool.MutationResult.v1",
|
|
1162
|
+
cli_contract_version: 3,
|
|
1121
1163
|
tool_version: TOOL_VERSION,
|
|
1122
1164
|
operation: "dag.advance",
|
|
1123
1165
|
ok,
|
|
@@ -1398,14 +1440,7 @@ function renderAnalysisText(result) {
|
|
|
1398
1440
|
return `${lines.join("\n")}\n`;
|
|
1399
1441
|
}
|
|
1400
1442
|
async function runAnalyze(args) {
|
|
1401
|
-
const parsed =
|
|
1402
|
-
"schedule",
|
|
1403
|
-
"max-paths",
|
|
1404
|
-
"precision",
|
|
1405
|
-
"max-diagnostics",
|
|
1406
|
-
"format",
|
|
1407
|
-
"color",
|
|
1408
|
-
]), new Set(["warnings-as-errors"]), new Set(["capacity"]));
|
|
1443
|
+
const parsed = parseCommandOptions("dag.analyze", args);
|
|
1409
1444
|
if (parsed.positionals.length !== 1) {
|
|
1410
1445
|
throw new UsageError("dag analyze requires exactly one <file>");
|
|
1411
1446
|
}
|
|
@@ -1439,6 +1474,7 @@ async function runAnalyze(args) {
|
|
|
1439
1474
|
if (format === "json") {
|
|
1440
1475
|
writeJson({
|
|
1441
1476
|
schema_version: "Perttool.AnalysisResult.v2",
|
|
1477
|
+
cli_contract_version: 3,
|
|
1442
1478
|
tool_version: TOOL_VERSION,
|
|
1443
1479
|
operation: "dag.analyze",
|
|
1444
1480
|
ok,
|
|
@@ -1498,15 +1534,7 @@ function renderConversionLoss(loss, source, color) {
|
|
|
1498
1534
|
}, source, color);
|
|
1499
1535
|
}
|
|
1500
1536
|
async function runRender(args) {
|
|
1501
|
-
const parsed =
|
|
1502
|
-
"to",
|
|
1503
|
-
"profile",
|
|
1504
|
-
"analysis",
|
|
1505
|
-
"out",
|
|
1506
|
-
"max-diagnostics",
|
|
1507
|
-
"format",
|
|
1508
|
-
"color",
|
|
1509
|
-
]), new Set(["strict-loss", "warnings-as-errors"]), new Set(["capacity"]));
|
|
1537
|
+
const parsed = parseCommandOptions("dag.render", args);
|
|
1510
1538
|
if (parsed.positionals.length !== 1) {
|
|
1511
1539
|
throw new UsageError("dag render requires exactly one <file>");
|
|
1512
1540
|
}
|
|
@@ -1558,6 +1586,7 @@ async function runRender(args) {
|
|
|
1558
1586
|
if (format === "json") {
|
|
1559
1587
|
writeJson({
|
|
1560
1588
|
schema_version: "Perttool.ExportResult.v1",
|
|
1589
|
+
cli_contract_version: 3,
|
|
1561
1590
|
tool_version: TOOL_VERSION,
|
|
1562
1591
|
operation: "dag.render",
|
|
1563
1592
|
ok,
|
|
@@ -1608,7 +1637,7 @@ async function runRender(args) {
|
|
|
1608
1637
|
return strictFailure ? 4 : ok ? 0 : 1;
|
|
1609
1638
|
}
|
|
1610
1639
|
async function runImport(args) {
|
|
1611
|
-
const parsed =
|
|
1640
|
+
const parsed = parseCommandOptions("dag.import", args);
|
|
1612
1641
|
if (parsed.positionals.length !== 1) {
|
|
1613
1642
|
throw new UsageError("dag import requires exactly one <file>");
|
|
1614
1643
|
}
|
|
@@ -1649,6 +1678,7 @@ async function runImport(args) {
|
|
|
1649
1678
|
if (format === "json") {
|
|
1650
1679
|
writeJson({
|
|
1651
1680
|
schema_version: "Perttool.ImportResult.v1",
|
|
1681
|
+
cli_contract_version: 3,
|
|
1652
1682
|
tool_version: TOOL_VERSION,
|
|
1653
1683
|
operation: "dag.import",
|
|
1654
1684
|
ok,
|
|
@@ -1907,7 +1937,7 @@ function renderNextText(result) {
|
|
|
1907
1937
|
return `${lines.join("\n")}\n`;
|
|
1908
1938
|
}
|
|
1909
1939
|
async function runNext(args) {
|
|
1910
|
-
const parsed =
|
|
1940
|
+
const parsed = parseCommandOptions("dag.next", args);
|
|
1911
1941
|
if (parsed.positionals.length !== 1) {
|
|
1912
1942
|
throw new UsageError("dag next requires exactly one <file>");
|
|
1913
1943
|
}
|
|
@@ -1938,6 +1968,7 @@ async function runNext(args) {
|
|
|
1938
1968
|
if (format === "json") {
|
|
1939
1969
|
writeJson({
|
|
1940
1970
|
schema_version: "Perttool.CliError.v1",
|
|
1971
|
+
cli_contract_version: 3,
|
|
1941
1972
|
tool_version: TOOL_VERSION,
|
|
1942
1973
|
operation: "dag.next",
|
|
1943
1974
|
ok: false,
|
|
@@ -1958,6 +1989,7 @@ async function runNext(args) {
|
|
|
1958
1989
|
if (format === "json") {
|
|
1959
1990
|
writeJson({
|
|
1960
1991
|
schema_version: "Perttool.NextResult.v3",
|
|
1992
|
+
cli_contract_version: 3,
|
|
1961
1993
|
recommendation_interface_version: 1,
|
|
1962
1994
|
tool_version: TOOL_VERSION,
|
|
1963
1995
|
operation: "dag.next",
|
|
@@ -1982,62 +2014,140 @@ async function runNext(args) {
|
|
|
1982
2014
|
}
|
|
1983
2015
|
return ok ? 0 : 1;
|
|
1984
2016
|
}
|
|
1985
|
-
function
|
|
1986
|
-
const
|
|
1987
|
-
if (
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
2017
|
+
function runGuide(args) {
|
|
2018
|
+
const parsed = parseCommandOptions("guide", args);
|
|
2019
|
+
if (parsed.positionals.length > 2) {
|
|
2020
|
+
throw new UsageError("guide accepts at most <topic> <subtopic>");
|
|
2021
|
+
}
|
|
2022
|
+
const format = outputFormat(parsed.values.get("format"));
|
|
2023
|
+
colorMode(parsed.values.get("color"), format);
|
|
2024
|
+
const topicId = parsed.positionals.length === 0 ? null : parsed.positionals.join(".");
|
|
2025
|
+
const level = helpLevel(parsed.values.get("level"), topicId !== null);
|
|
2026
|
+
const result = getGuide(topicId, level);
|
|
2027
|
+
if (format === "json") {
|
|
2028
|
+
process.stdout.write(serializeGuideResult(result));
|
|
2029
|
+
}
|
|
2030
|
+
else {
|
|
2031
|
+
const rendered = renderGuideResult(result);
|
|
2032
|
+
if (result.ok) {
|
|
2033
|
+
process.stdout.write(rendered);
|
|
2034
|
+
}
|
|
2035
|
+
else {
|
|
2036
|
+
process.stderr.write(rendered);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
return result.ok ? 0 : 1;
|
|
2040
|
+
}
|
|
2041
|
+
function runCommandHelp(args) {
|
|
2042
|
+
const parsed = parseCommandOptions("help", args);
|
|
2043
|
+
if (parsed.positionals.length > 2) {
|
|
2044
|
+
throw new UsageError("help accepts at most <resource> <action>");
|
|
1991
2045
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
2046
|
+
const format = outputFormat(parsed.values.get("format"));
|
|
2047
|
+
const result = getCommandDiscovery({
|
|
2048
|
+
resource: parsed.positionals[0] ?? null,
|
|
2049
|
+
action: parsed.positionals[1] ?? null,
|
|
2050
|
+
});
|
|
2051
|
+
if (format === "json") {
|
|
2052
|
+
process.stdout.write(serializeCommandHelpResult(result));
|
|
1994
2053
|
}
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2054
|
+
else {
|
|
2055
|
+
const rendered = renderCommandHelpResult(result);
|
|
2056
|
+
if (result.ok) {
|
|
2057
|
+
process.stdout.write(rendered);
|
|
2058
|
+
}
|
|
2059
|
+
else {
|
|
2060
|
+
process.stderr.write(rendered);
|
|
2061
|
+
}
|
|
1999
2062
|
}
|
|
2000
|
-
|
|
2001
|
-
lines.push("", `Related: ${result.related.join(", ")}`);
|
|
2002
|
-
return `${lines.join("\n")}\n`;
|
|
2063
|
+
return result.ok ? 0 : 1;
|
|
2003
2064
|
}
|
|
2004
|
-
function
|
|
2005
|
-
const parsed =
|
|
2065
|
+
function runAgentHelp(args) {
|
|
2066
|
+
const parsed = parseCommandOptions("agent.help", args);
|
|
2006
2067
|
if (parsed.positionals.length > 2) {
|
|
2007
|
-
throw new UsageError("
|
|
2068
|
+
throw new UsageError("agent help accepts at most <provider> <surface>");
|
|
2008
2069
|
}
|
|
2009
2070
|
const format = outputFormat(parsed.values.get("format"));
|
|
2010
|
-
colorMode(parsed.values.get("color"), format);
|
|
2011
|
-
const
|
|
2012
|
-
const
|
|
2013
|
-
const
|
|
2071
|
+
const color = colorMode(parsed.values.get("color"), format);
|
|
2072
|
+
const providerId = parsed.positionals[0] ?? null;
|
|
2073
|
+
const surfaceId = parsed.positionals[1] ?? null;
|
|
2074
|
+
const level = helpLevel(parsed.values.get("level"), providerId !== null);
|
|
2075
|
+
const result = getAgentHelp({
|
|
2076
|
+
providerId,
|
|
2077
|
+
surfaceId,
|
|
2078
|
+
level,
|
|
2079
|
+
});
|
|
2014
2080
|
if (format === "json") {
|
|
2081
|
+
const projected = agentGuidanceResultToJson(result);
|
|
2082
|
+
const { schema_version: schemaVersion, ...payload } = projected;
|
|
2015
2083
|
writeJson({
|
|
2016
|
-
schema_version:
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
ok: result.ok,
|
|
2020
|
-
diagnostics: result.diagnostics.map(jsonDiagnostic),
|
|
2021
|
-
topic_id: result.topicId,
|
|
2022
|
-
level: result.level,
|
|
2023
|
-
title: result.title,
|
|
2024
|
-
summary: result.summary,
|
|
2025
|
-
sections: result.sections,
|
|
2026
|
-
syntax: result.syntax,
|
|
2027
|
-
examples: result.examples,
|
|
2028
|
-
related: result.related,
|
|
2029
|
-
topics: result.topics,
|
|
2084
|
+
schema_version: schemaVersion,
|
|
2085
|
+
cli_contract_version: 3,
|
|
2086
|
+
...payload,
|
|
2030
2087
|
});
|
|
2031
2088
|
}
|
|
2032
|
-
else if (result.ok) {
|
|
2033
|
-
process.stdout.write(renderHelpText(result));
|
|
2034
|
-
}
|
|
2035
2089
|
else {
|
|
2090
|
+
if (result.ok) {
|
|
2091
|
+
process.stdout.write(renderAgentGuidanceText(result));
|
|
2092
|
+
}
|
|
2036
2093
|
for (const diagnostic of result.diagnostics) {
|
|
2037
|
-
process.stderr.write(`${renderDiagnostic(
|
|
2094
|
+
process.stderr.write(`${renderDiagnostic({
|
|
2095
|
+
code: diagnostic.code,
|
|
2096
|
+
severity: diagnostic.severity,
|
|
2097
|
+
message: diagnostic.message,
|
|
2098
|
+
data: {
|
|
2099
|
+
provider_id: diagnostic.providerId,
|
|
2100
|
+
surface_id: diagnostic.surfaceId,
|
|
2101
|
+
},
|
|
2102
|
+
}, "<agent-guidance>", color)}\n`);
|
|
2038
2103
|
}
|
|
2039
2104
|
}
|
|
2040
|
-
return result.
|
|
2105
|
+
return agentGuidanceExitCode(result.diagnostics);
|
|
2106
|
+
}
|
|
2107
|
+
async function dispatchCommand(descriptor, args) {
|
|
2108
|
+
switch (descriptor.operation) {
|
|
2109
|
+
case "help":
|
|
2110
|
+
return runCommandHelp(args);
|
|
2111
|
+
case "guide":
|
|
2112
|
+
return runGuide(args);
|
|
2113
|
+
case "document.check":
|
|
2114
|
+
return runCheck(args);
|
|
2115
|
+
case "document.format":
|
|
2116
|
+
return runFormat(args);
|
|
2117
|
+
case "agent.help":
|
|
2118
|
+
return runAgentHelp(args);
|
|
2119
|
+
case "project.init":
|
|
2120
|
+
return runProjectInit(args);
|
|
2121
|
+
case "project.show":
|
|
2122
|
+
return runProjectShow(args);
|
|
2123
|
+
case "dag.analyze":
|
|
2124
|
+
return runAnalyze(args);
|
|
2125
|
+
case "dag.next":
|
|
2126
|
+
return runNext(args);
|
|
2127
|
+
case "dag.advance":
|
|
2128
|
+
return runAdvance(args);
|
|
2129
|
+
case "dag.render":
|
|
2130
|
+
return runRender(args);
|
|
2131
|
+
case "dag.import":
|
|
2132
|
+
return runImport(args);
|
|
2133
|
+
}
|
|
2134
|
+
if (descriptor.path.length === 2
|
|
2135
|
+
&& (descriptor.operation === "project.set"
|
|
2136
|
+
|| descriptor.operation === "batch.apply"
|
|
2137
|
+
|| /^(?:task|gate|milestone|resource)\.(?:add|set|remove|finish)$/.test(descriptor.operation))) {
|
|
2138
|
+
const [resource, action] = descriptor.path;
|
|
2139
|
+
return runMutation(resource, action, args);
|
|
2140
|
+
}
|
|
2141
|
+
throw new Error(`no Contract 3 handler for ${descriptor.operation}`);
|
|
2142
|
+
}
|
|
2143
|
+
function emitCommandUsage(error, json) {
|
|
2144
|
+
if (json) {
|
|
2145
|
+
process.stdout.write(serializeCommandUsageError(error));
|
|
2146
|
+
}
|
|
2147
|
+
else {
|
|
2148
|
+
process.stderr.write(renderCommandUsageError(error));
|
|
2149
|
+
}
|
|
2150
|
+
return 2;
|
|
2041
2151
|
}
|
|
2042
2152
|
async function main(argv) {
|
|
2043
2153
|
if (argv.length === 1 && argv[0] === "--version") {
|
|
@@ -2045,59 +2155,37 @@ async function main(argv) {
|
|
|
2045
2155
|
return 0;
|
|
2046
2156
|
}
|
|
2047
2157
|
if (argv.length === 1 && argv[0] === "--help") {
|
|
2048
|
-
|
|
2049
|
-
return 0;
|
|
2158
|
+
return runCommandHelp([]);
|
|
2050
2159
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
]);
|
|
2061
|
-
const isMutationCommand = entityActions.get(resource)?.has(action) === true;
|
|
2062
|
-
if (argv.length === 3 &&
|
|
2063
|
-
argv[2] === "--help" &&
|
|
2064
|
-
((resource === "dsl" && ["check", "format", "help"].includes(action)) ||
|
|
2065
|
-
(resource === "dag" && ["analyze", "next", "advance", "render", "import"].includes(action)) ||
|
|
2066
|
-
isMutationCommand)) {
|
|
2067
|
-
process.stdout.write(`${commandHelp(resource, action)}\n`);
|
|
2068
|
-
return 0;
|
|
2069
|
-
}
|
|
2070
|
-
if (resource === "dag" && action === "analyze") {
|
|
2071
|
-
return runAnalyze(argv.slice(2));
|
|
2072
|
-
}
|
|
2073
|
-
if (resource === "dag" && action === "next") {
|
|
2074
|
-
return runNext(argv.slice(2));
|
|
2075
|
-
}
|
|
2076
|
-
if (resource === "dag" && action === "advance") {
|
|
2077
|
-
return runAdvance(argv.slice(2));
|
|
2078
|
-
}
|
|
2079
|
-
if (resource === "dag" && action === "render") {
|
|
2080
|
-
return runRender(argv.slice(2));
|
|
2081
|
-
}
|
|
2082
|
-
if (resource === "dag" && action === "import") {
|
|
2083
|
-
return runImport(argv.slice(2));
|
|
2160
|
+
const validation = validateCommandInvocation(argv);
|
|
2161
|
+
if (!validation.ok) {
|
|
2162
|
+
if (jsonRequested(argv)) {
|
|
2163
|
+
process.stdout.write(serializeCommandUsageError(validation.error));
|
|
2164
|
+
}
|
|
2165
|
+
else {
|
|
2166
|
+
process.stderr.write(renderCommandUsageError(validation.error));
|
|
2167
|
+
}
|
|
2168
|
+
return 2;
|
|
2084
2169
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2170
|
+
const descriptor = validation.descriptor;
|
|
2171
|
+
if (validation.helpAlias) {
|
|
2172
|
+
return runCommandHelp([...descriptor.path]);
|
|
2087
2173
|
}
|
|
2088
|
-
|
|
2089
|
-
return
|
|
2174
|
+
try {
|
|
2175
|
+
return await dispatchCommand(descriptor, argv.slice(descriptor.path.length));
|
|
2090
2176
|
}
|
|
2091
|
-
|
|
2092
|
-
|
|
2177
|
+
catch (error) {
|
|
2178
|
+
if (error instanceof UsageError) {
|
|
2179
|
+
return emitCommandUsage(handlerCommandUsageError(descriptor, error.message), jsonRequested(argv));
|
|
2180
|
+
}
|
|
2181
|
+
throw error;
|
|
2093
2182
|
}
|
|
2094
|
-
return action === "check" ? runCheck(argv.slice(2)) : runHelp(argv.slice(2));
|
|
2095
2183
|
}
|
|
2096
2184
|
const args = process.argv.slice(2);
|
|
2097
2185
|
try {
|
|
2098
2186
|
process.exitCode = await main(args);
|
|
2099
2187
|
}
|
|
2100
2188
|
catch (error) {
|
|
2101
|
-
process.exitCode = cliError(error instanceof Error ? error : new Error(String(error)),
|
|
2189
|
+
process.exitCode = cliError(error instanceof Error ? error : new Error(String(error)), 70, null, jsonRequested(args));
|
|
2102
2190
|
}
|
|
2103
2191
|
//# sourceMappingURL=cli.js.map
|