perttool 0.1.0 → 0.3.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 +112 -33
- package/README.md +206 -111
- package/dist/analysis/temporal-deadline.d.ts +59 -0
- package/dist/analysis/temporal-deadline.d.ts.map +1 -0
- package/dist/analysis/temporal-deadline.js +387 -0
- package/dist/analysis/temporal-deadline.js.map +1 -0
- package/dist/analysis/temporal-precedence.d.ts +45 -0
- package/dist/analysis/temporal-precedence.d.ts.map +1 -0
- package/dist/analysis/temporal-precedence.js +123 -0
- package/dist/analysis/temporal-precedence.js.map +1 -0
- package/dist/analysis/temporal-resource.d.ts +66 -0
- package/dist/analysis/temporal-resource.d.ts.map +1 -0
- package/dist/analysis/temporal-resource.js +292 -0
- package/dist/analysis/temporal-resource.js.map +1 -0
- package/dist/application/analyze.js +5 -5
- package/dist/application/analyze.js.map +1 -1
- package/dist/application/check.d.ts +16 -0
- package/dist/application/check.d.ts.map +1 -1
- package/dist/application/check.js +51 -0
- package/dist/application/check.js.map +1 -1
- package/dist/application/contract4.d.ts +17 -0
- package/dist/application/contract4.d.ts.map +1 -0
- package/dist/application/contract4.js +27 -0
- package/dist/application/contract4.js.map +1 -0
- package/dist/application/init.d.ts +43 -0
- package/dist/application/init.d.ts.map +1 -0
- package/dist/application/init.js +226 -0
- package/dist/application/init.js.map +1 -0
- package/dist/application/mutate.d.ts +19 -0
- package/dist/application/mutate.d.ts.map +1 -1
- package/dist/application/mutate.js +47 -25
- package/dist/application/mutate.js.map +1 -1
- package/dist/application/next.d.ts +2 -0
- package/dist/application/next.d.ts.map +1 -1
- package/dist/application/next.js +7 -1
- package/dist/application/next.js.map +1 -1
- package/dist/application/project.d.ts +3 -1
- package/dist/application/project.d.ts.map +1 -1
- package/dist/application/project.js +2 -1
- package/dist/application/project.js.map +1 -1
- package/dist/application/target-check.d.ts +34 -0
- package/dist/application/target-check.d.ts.map +1 -0
- package/dist/application/target-check.js +90 -0
- package/dist/application/target-check.js.map +1 -0
- package/dist/application/target-grammar-boundary.d.ts +22 -0
- package/dist/application/target-grammar-boundary.d.ts.map +1 -0
- package/dist/application/target-grammar-boundary.js +91 -0
- package/dist/application/target-grammar-boundary.js.map +1 -0
- package/dist/application/target-mutate.d.ts +8 -0
- package/dist/application/target-mutate.d.ts.map +1 -0
- package/dist/application/target-mutate.js +56 -0
- package/dist/application/target-mutate.js.map +1 -0
- package/dist/application/target-project.d.ts +28 -0
- package/dist/application/target-project.d.ts.map +1 -0
- package/dist/application/target-project.js +60 -0
- package/dist/application/target-project.js.map +1 -0
- package/dist/application/target-temporal-analysis.d.ts +138 -0
- package/dist/application/target-temporal-analysis.d.ts.map +1 -0
- package/dist/application/target-temporal-analysis.js +409 -0
- package/dist/application/target-temporal-analysis.js.map +1 -0
- package/dist/application/target-temporal-input.d.ts +86 -0
- package/dist/application/target-temporal-input.d.ts.map +1 -0
- package/dist/application/target-temporal-input.js +238 -0
- package/dist/application/target-temporal-input.js.map +1 -0
- package/dist/application/target-unit-migration-candidate.d.ts +60 -0
- package/dist/application/target-unit-migration-candidate.d.ts.map +1 -0
- package/dist/application/target-unit-migration-candidate.js +290 -0
- package/dist/application/target-unit-migration-candidate.js.map +1 -0
- package/dist/application/target-unit-migration-request.d.ts +35 -0
- package/dist/application/target-unit-migration-request.d.ts.map +1 -0
- package/dist/application/target-unit-migration-request.js +68 -0
- package/dist/application/target-unit-migration-request.js.map +1 -0
- package/dist/application/target-unit-migration-result.d.ts +66 -0
- package/dist/application/target-unit-migration-result.d.ts.map +1 -0
- package/dist/application/target-unit-migration-result.js +86 -0
- package/dist/application/target-unit-migration-result.js.map +1 -0
- package/dist/application/unit-migration.d.ts +13 -0
- package/dist/application/unit-migration.d.ts.map +1 -0
- package/dist/application/unit-migration.js +9 -0
- package/dist/application/unit-migration.js.map +1 -0
- package/dist/cli.js +494 -383
- 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 +752 -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 +964 -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/formatter/source-formatter.d.ts +12 -0
- package/dist/formatter/source-formatter.d.ts.map +1 -1
- package/dist/formatter/source-formatter.js +48 -6
- package/dist/formatter/source-formatter.js.map +1 -1
- package/dist/formatter/target-source-formatter.d.ts +8 -0
- package/dist/formatter/target-source-formatter.d.ts.map +1 -0
- package/dist/formatter/target-source-formatter.js +36 -0
- package/dist/formatter/target-source-formatter.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 +0 -6
- package/dist/help/registry.d.ts.map +1 -1
- package/dist/help/registry.js +186 -86
- package/dist/help/registry.js.map +1 -1
- package/dist/index.d.ts +20 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/io/safe-write.d.ts +7 -0
- package/dist/io/safe-write.d.ts.map +1 -1
- package/dist/io/safe-write.js +36 -27
- package/dist/io/safe-write.js.map +1 -1
- package/dist/io/target-safe-write.d.ts +7 -0
- package/dist/io/target-safe-write.d.ts.map +1 -0
- package/dist/io/target-safe-write.js +33 -0
- package/dist/io/target-safe-write.js.map +1 -0
- package/dist/migration/conversion.d.ts +40 -0
- package/dist/migration/conversion.d.ts.map +1 -0
- package/dist/migration/conversion.js +106 -0
- package/dist/migration/conversion.js.map +1 -0
- package/dist/migration/grammar-boundary.d.ts +20 -0
- package/dist/migration/grammar-boundary.d.ts.map +1 -0
- package/dist/migration/grammar-boundary.js +92 -0
- package/dist/migration/grammar-boundary.js.map +1 -0
- package/dist/migration/request.d.ts +79 -0
- package/dist/migration/request.d.ts.map +1 -0
- package/dist/migration/request.js +308 -0
- package/dist/migration/request.js.map +1 -0
- package/dist/model/calendar-arithmetic.d.ts +46 -0
- package/dist/model/calendar-arithmetic.d.ts.map +1 -0
- package/dist/model/calendar-arithmetic.js +236 -0
- package/dist/model/calendar-arithmetic.js.map +1 -0
- package/dist/model/calendar.d.ts +22 -0
- package/dist/model/calendar.d.ts.map +1 -0
- package/dist/model/calendar.js +83 -0
- package/dist/model/calendar.js.map +1 -0
- package/dist/model/declaration-fields.d.ts +15 -0
- package/dist/model/declaration-fields.d.ts.map +1 -0
- package/dist/model/declaration-fields.js +54 -0
- package/dist/model/declaration-fields.js.map +1 -0
- package/dist/model/exact-duration-source.d.ts +10 -0
- package/dist/model/exact-duration-source.d.ts.map +1 -0
- package/dist/model/exact-duration-source.js +69 -0
- package/dist/model/exact-duration-source.js.map +1 -0
- package/dist/model/rational.d.ts +2 -2
- package/dist/model/rational.d.ts.map +1 -1
- package/dist/model/rational.js +3 -1
- package/dist/model/rational.js.map +1 -1
- package/dist/model/syntax.d.ts +9 -2
- package/dist/model/syntax.d.ts.map +1 -1
- package/dist/model/syntax.js +20 -4
- package/dist/model/syntax.js.map +1 -1
- package/dist/model/target-calendar.d.ts +25 -0
- package/dist/model/target-calendar.d.ts.map +1 -0
- package/dist/model/target-calendar.js +56 -0
- package/dist/model/target-calendar.js.map +1 -0
- package/dist/mutation/advance.d.ts.map +1 -1
- package/dist/mutation/advance.js +21 -4
- package/dist/mutation/advance.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.d.ts +10 -1
- package/dist/mutation/milestone.d.ts.map +1 -1
- package/dist/mutation/milestone.js +104 -48
- package/dist/mutation/milestone.js.map +1 -1
- package/dist/mutation/project.d.ts +7 -1
- package/dist/mutation/project.d.ts.map +1 -1
- package/dist/mutation/project.js +32 -19
- package/dist/mutation/project.js.map +1 -1
- package/dist/mutation/resource.js +22 -22
- package/dist/mutation/resource.js.map +1 -1
- package/dist/mutation/target-types.d.ts +42 -0
- package/dist/mutation/target-types.d.ts.map +1 -0
- package/dist/mutation/target-types.js +2 -0
- package/dist/mutation/target-types.js.map +1 -0
- package/dist/mutation/task.d.ts +12 -1
- package/dist/mutation/task.d.ts.map +1 -1
- package/dist/mutation/task.js +148 -89
- package/dist/mutation/task.js.map +1 -1
- package/dist/mutation/types.d.ts +34 -3
- package/dist/mutation/types.d.ts.map +1 -1
- package/dist/parser/document-parser.d.ts +14 -0
- package/dist/parser/document-parser.d.ts.map +1 -1
- package/dist/parser/document-parser.js +159 -55
- package/dist/parser/document-parser.js.map +1 -1
- package/dist/recommendation/override-types.d.ts +2 -2
- package/dist/recommendation/override.d.ts +2 -2
- package/dist/recommendation/override.d.ts.map +1 -1
- package/dist/recommendation/override.js +49 -12
- package/dist/recommendation/override.js.map +1 -1
- package/dist/semantic/target-validator.d.ts +36 -0
- package/dist/semantic/target-validator.d.ts.map +1 -0
- package/dist/semantic/target-validator.js +87 -0
- package/dist/semantic/target-validator.js.map +1 -0
- package/dist/semantic/validator.d.ts +2 -0
- package/dist/semantic/validator.d.ts.map +1 -1
- package/dist/semantic/validator.js +90 -45
- package/dist/semantic/validator.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { TextDecoder } from "node:util";
|
|
5
|
-
import { analyzeDocument } from "./application/
|
|
5
|
+
import { analyzeDocument, selectNextTasks, } from "./application/contract4.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
|
-
import {
|
|
10
|
+
import { planUnitMigration, withUnitMigrationWrite, } from "./application/unit-migration.js";
|
|
10
11
|
import { getProjectMetadata, } from "./application/project.js";
|
|
11
12
|
import { getAgentHelp } from "./application/agent-help.js";
|
|
12
13
|
import { exportMermaid, } from "./conversion/mermaid.js";
|
|
13
14
|
import { importMermaid } from "./conversion/mermaid-import.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
15
|
+
import { getGuide, renderGuideResult, serializeGuideResult, } from "./help/guide.js";
|
|
16
|
+
import { commandOptionSets, } from "./command/registry.js";
|
|
17
|
+
import { CONTRACT4_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";
|
|
16
20
|
import { agentGuidanceExitCode, renderAgentGuidanceText, } from "./guidance/text.js";
|
|
17
21
|
import { documentContentFromBytes, readDocumentFile, } from "./io/document-file.js";
|
|
18
22
|
import { createArtifactFile, createDocumentFile, replaceDocumentFile, SafeWriteConflictError, SafeWriteVerificationError, } from "./io/safe-write.js";
|
|
@@ -25,183 +29,6 @@ import { TOOL_VERSION } from "./version.js";
|
|
|
25
29
|
class UsageError extends Error {
|
|
26
30
|
code = "PTCLI-001";
|
|
27
31
|
}
|
|
28
|
-
function topLevelHelp() {
|
|
29
|
-
return [
|
|
30
|
-
"perttool - document-based PERT/CPM task management",
|
|
31
|
-
"",
|
|
32
|
-
"Usage:",
|
|
33
|
-
" perttool --version",
|
|
34
|
-
" perttool --help",
|
|
35
|
-
" perttool dsl check <file> [--format text|json]",
|
|
36
|
-
" perttool dsl format <file> [--check] [--diff] [--format text|json]",
|
|
37
|
-
" perttool dsl help [topic [subtopic]] [--level index|quick|detail] [--format text|json]",
|
|
38
|
-
" perttool agent help [provider [surface]] [--level index|quick|detail] [--format text|json]",
|
|
39
|
-
" perttool project show <file> [--format text|json]",
|
|
40
|
-
" perttool project set <file> [field options] [--diff] [--write | --out <path>]",
|
|
41
|
-
" perttool dag analyze <file> [--schedule precedence|resource|both] [--format text|json]",
|
|
42
|
-
" perttool dag next <file> [--capacity <resource-id>=<integer>] [--format text|json]",
|
|
43
|
-
" perttool dag advance <file> [--diff] [--write | --out <path>] [--format text|json]",
|
|
44
|
-
" perttool dag render <file> --to mermaid [--profile perttool|plain] [--format text|json]",
|
|
45
|
-
" perttool dag import <file> --from mermaid [--strict-loss] [--out <path>] [--format text|json]",
|
|
46
|
-
" perttool task add|set|remove|finish ...",
|
|
47
|
-
" perttool milestone add|set|remove ...",
|
|
48
|
-
" perttool resource add|set|remove ...",
|
|
49
|
-
" perttool mutation apply <file> --request <json-file|-> [--diff] [--format text|json]",
|
|
50
|
-
"",
|
|
51
|
-
"Format and mutation commands preview by default; use --write or --out for explicit writes.",
|
|
52
|
-
].join("\n");
|
|
53
|
-
}
|
|
54
|
-
function commandHelp(resource, action) {
|
|
55
|
-
if (resource === "agent" && action === "help") {
|
|
56
|
-
return getAgentHelpCommandHelp().syntax.join("\n");
|
|
57
|
-
}
|
|
58
|
-
if (resource === "dsl" && action === "check") {
|
|
59
|
-
return [
|
|
60
|
-
"Usage: perttool dsl check <file>",
|
|
61
|
-
" [--warnings-as-errors]",
|
|
62
|
-
" [--max-diagnostics <integer>]",
|
|
63
|
-
" [--format text|json]",
|
|
64
|
-
" [--color auto|always|never]",
|
|
65
|
-
].join("\n");
|
|
66
|
-
}
|
|
67
|
-
if (resource === "dsl" && action === "format")
|
|
68
|
-
return [
|
|
69
|
-
"Usage: perttool dsl format <file>",
|
|
70
|
-
" [--check] [--diff]",
|
|
71
|
-
" [--write [--expect-digest <digest>] | --out <path>]",
|
|
72
|
-
" [--max-diagnostics <integer>] [--warnings-as-errors]",
|
|
73
|
-
" [--format text|json] [--color auto|always|never]",
|
|
74
|
-
].join("\n");
|
|
75
|
-
if (resource === "dsl" && action === "help")
|
|
76
|
-
return [
|
|
77
|
-
"Usage: perttool dsl help [topic [subtopic]]",
|
|
78
|
-
" [--level index|quick|detail]",
|
|
79
|
-
" [--format text|json]",
|
|
80
|
-
" [--color auto|always|never]",
|
|
81
|
-
].join("\n");
|
|
82
|
-
if (resource === "project" && action === "show")
|
|
83
|
-
return [
|
|
84
|
-
"Usage: perttool project show <file>",
|
|
85
|
-
" [--max-diagnostics <integer>] [--warnings-as-errors]",
|
|
86
|
-
" [--format text|json] [--color auto|always|never]",
|
|
87
|
-
].join("\n");
|
|
88
|
-
if (resource === "project" && action === "set")
|
|
89
|
-
return [
|
|
90
|
-
"Usage: perttool project set <file> [field options]",
|
|
91
|
-
" [--id <id>] [--version <integer>] [--title <text>] [--description <text>]",
|
|
92
|
-
" [--as-of <date-or-date-time>] [--duration-unit day|hour|point]",
|
|
93
|
-
" [--velocity <velocity>] [--finish <milestone-id>]",
|
|
94
|
-
" [--critical-epsilon <duration>] [--target-duration <duration>]",
|
|
95
|
-
" [--clear description|as_of|velocity|critical_epsilon|target_duration]...",
|
|
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 === "analyze")
|
|
101
|
-
return [
|
|
102
|
-
"Usage: perttool dag analyze <file>",
|
|
103
|
-
" [--schedule precedence|resource|both]",
|
|
104
|
-
" [--capacity <resource-id>=<integer>]...",
|
|
105
|
-
" [--max-paths <integer>] [--precision <integer>]",
|
|
106
|
-
" [--max-diagnostics <integer>]",
|
|
107
|
-
" [--warnings-as-errors]",
|
|
108
|
-
" [--format text|json] [--color auto|always|never]",
|
|
109
|
-
].join("\n");
|
|
110
|
-
if (resource === "dag" && action === "next")
|
|
111
|
-
return [
|
|
112
|
-
"Usage: perttool dag next <file>",
|
|
113
|
-
" [--capacity <resource-id>=<integer>]...",
|
|
114
|
-
" [--explain-depth <integer>] [--precision <integer>]",
|
|
115
|
-
" [--max-diagnostics <integer>]",
|
|
116
|
-
" [--warnings-as-errors]",
|
|
117
|
-
" [--format text|json] [--color auto|always|never]",
|
|
118
|
-
"Output: Perttool.NextResult.v3 with a complete recommendation graph in JSON.",
|
|
119
|
-
"Consumers must inspect schema_version before using recommendation authority.",
|
|
120
|
-
].join("\n");
|
|
121
|
-
if (resource === "dag" && action === "advance")
|
|
122
|
-
return [
|
|
123
|
-
"Usage: perttool dag advance <file>",
|
|
124
|
-
" [--diff] [--write [--expect-digest <digest>] | --out <path>]",
|
|
125
|
-
" [--max-diagnostics <integer>] [--warnings-as-errors]",
|
|
126
|
-
" [--format text|json] [--color auto|always|never]",
|
|
127
|
-
].join("\n");
|
|
128
|
-
if (resource === "dag" && action === "render")
|
|
129
|
-
return [
|
|
130
|
-
"Usage: perttool dag render <file> --to mermaid",
|
|
131
|
-
" [--profile perttool|plain] [--analysis none|precedence|resource|both]",
|
|
132
|
-
" [--capacity <resource-id>=<integer>]... [--strict-loss] [--out <path>]",
|
|
133
|
-
" [--max-diagnostics <integer>] [--warnings-as-errors]",
|
|
134
|
-
" [--format text|json] [--color auto|always|never]",
|
|
135
|
-
].join("\n");
|
|
136
|
-
if (resource === "dag" && action === "import")
|
|
137
|
-
return [
|
|
138
|
-
"Usage: perttool dag import <file> --from mermaid",
|
|
139
|
-
" [--strict-loss] [--out <path>]",
|
|
140
|
-
" [--max-diagnostics <integer>] [--warnings-as-errors]",
|
|
141
|
-
" [--format text|json] [--color auto|always|never]",
|
|
142
|
-
].join("\n");
|
|
143
|
-
const preview = " [--diff] [--write [--expect-digest <digest>] | --out <path>] [--max-diagnostics <integer>] [--warnings-as-errors] [--format text|json] [--color auto|always|never]";
|
|
144
|
-
if (resource === "task" && action === "add")
|
|
145
|
-
return [
|
|
146
|
-
"Usage: perttool task add <file> <id> <from> <to>",
|
|
147
|
-
" --title <text> (--duration <duration> | --optimistic <duration> --most-likely <duration> --pessimistic <duration>)",
|
|
148
|
-
" [--description <text>] [--status planned|active|blocked|done] [--priority <integer>]",
|
|
149
|
-
" [--owner <text>] [--blocked-reason <text>] [--source <text>] [--tag <tag>]... [--require <resource-id>=<integer>]...",
|
|
150
|
-
preview,
|
|
151
|
-
].join("\n");
|
|
152
|
-
if (resource === "task" && action === "set")
|
|
153
|
-
return [
|
|
154
|
-
"Usage: perttool task set <file> <id> [field options]",
|
|
155
|
-
" [--from <id>] [--to <id>] [--title <text>] [--description <text>] [--duration <duration>]",
|
|
156
|
-
" [--optimistic <duration> --most-likely <duration> --pessimistic <duration>]",
|
|
157
|
-
" [--status planned|active|blocked|done] [--priority <integer>] [--owner <text>]",
|
|
158
|
-
" [--blocked-reason <text>] [--source <text>] [--require <resource-id>=<integer>]...",
|
|
159
|
-
" [--add-tag <tag>]... [--remove-tag <tag>]... [--remove-require <resource-id>]... [--clear <field>]...",
|
|
160
|
-
preview,
|
|
161
|
-
].join("\n");
|
|
162
|
-
if (resource === "task")
|
|
163
|
-
return [
|
|
164
|
-
`Usage: perttool task ${action} <file> <id>`,
|
|
165
|
-
preview,
|
|
166
|
-
].join("\n");
|
|
167
|
-
if (resource === "milestone" && action === "add")
|
|
168
|
-
return [
|
|
169
|
-
"Usage: perttool milestone add <file> <id> --title <text>",
|
|
170
|
-
" [--description <text>] [--state planned|reached] [--tag <tag>]...",
|
|
171
|
-
preview,
|
|
172
|
-
].join("\n");
|
|
173
|
-
if (resource === "milestone" && action === "set")
|
|
174
|
-
return [
|
|
175
|
-
"Usage: perttool milestone set <file> <id> [--title <text>] [--description <text>] [--state planned|reached]",
|
|
176
|
-
" [--add-tag <tag>]... [--remove-tag <tag>]... [--clear description|state|tags]...",
|
|
177
|
-
preview,
|
|
178
|
-
].join("\n");
|
|
179
|
-
if (resource === "milestone")
|
|
180
|
-
return [
|
|
181
|
-
`Usage: perttool milestone ${action} <file> <id>`,
|
|
182
|
-
preview,
|
|
183
|
-
].join("\n");
|
|
184
|
-
if (resource === "resource" && action === "add")
|
|
185
|
-
return [
|
|
186
|
-
"Usage: perttool resource add <file> <id> --title <text> --capacity <integer>",
|
|
187
|
-
" [--description <text>]",
|
|
188
|
-
preview,
|
|
189
|
-
].join("\n");
|
|
190
|
-
if (resource === "resource" && action === "set")
|
|
191
|
-
return [
|
|
192
|
-
"Usage: perttool resource set <file> <id> [--title <text>] [--description <text>] [--capacity <integer>] [--clear description]",
|
|
193
|
-
preview,
|
|
194
|
-
].join("\n");
|
|
195
|
-
if (resource === "resource")
|
|
196
|
-
return [
|
|
197
|
-
`Usage: perttool resource ${action} <file> <id>`,
|
|
198
|
-
preview,
|
|
199
|
-
].join("\n");
|
|
200
|
-
return [
|
|
201
|
-
"Usage: perttool mutation apply <file> --request <json-file|->",
|
|
202
|
-
preview,
|
|
203
|
-
].join("\n");
|
|
204
|
-
}
|
|
205
32
|
function parseOptions(args, valueOptions, flagOptions, repeatableOptions = new Set()) {
|
|
206
33
|
const positionals = [];
|
|
207
34
|
const values = new Map();
|
|
@@ -251,6 +78,14 @@ function parseOptions(args, valueOptions, flagOptions, repeatableOptions = new S
|
|
|
251
78
|
}
|
|
252
79
|
return { positionals, values, repeatedValues, flags };
|
|
253
80
|
}
|
|
81
|
+
function parseCommandOptions(operation, args) {
|
|
82
|
+
const descriptor = CONTRACT4_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
|
+
}
|
|
254
89
|
function outputFormat(value) {
|
|
255
90
|
if (value === undefined || value === "text")
|
|
256
91
|
return "text";
|
|
@@ -300,7 +135,8 @@ function jsonDiagnostic(diagnostic) {
|
|
|
300
135
|
message: related.message,
|
|
301
136
|
span: jsonSpan(related.span),
|
|
302
137
|
})),
|
|
303
|
-
help_topic:
|
|
138
|
+
help_topic: null,
|
|
139
|
+
guide_topic: diagnostic.helpTopic ?? null,
|
|
304
140
|
expected_syntax: diagnostic.expectedSyntax ?? null,
|
|
305
141
|
fixes: [],
|
|
306
142
|
data: diagnostic.data ?? {},
|
|
@@ -326,7 +162,7 @@ function renderDiagnostic(diagnostic, source, mode) {
|
|
|
326
162
|
}
|
|
327
163
|
if (diagnostic.helpTopic !== undefined) {
|
|
328
164
|
const [topic, subtopic] = diagnostic.helpTopic.split(".", 2);
|
|
329
|
-
lines.push(`
|
|
165
|
+
lines.push(` guide: perttool guide ${topic}${subtopic === undefined ? "" : ` ${subtopic}`} --level quick`);
|
|
330
166
|
}
|
|
331
167
|
return lines.join("\n");
|
|
332
168
|
}
|
|
@@ -346,6 +182,18 @@ async function readStdin() {
|
|
|
346
182
|
function writeJson(value) {
|
|
347
183
|
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
348
184
|
}
|
|
185
|
+
function snakeJson(value) {
|
|
186
|
+
if (typeof value === "bigint")
|
|
187
|
+
return value.toString();
|
|
188
|
+
if (Array.isArray(value))
|
|
189
|
+
return value.map(snakeJson);
|
|
190
|
+
if (value === null || typeof value !== "object")
|
|
191
|
+
return value;
|
|
192
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
193
|
+
key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase(),
|
|
194
|
+
snakeJson(item),
|
|
195
|
+
]));
|
|
196
|
+
}
|
|
349
197
|
function cliError(error, exitCode, operation, json) {
|
|
350
198
|
const code = error instanceof UsageError
|
|
351
199
|
? error.code
|
|
@@ -369,6 +217,7 @@ function cliError(error, exitCode, operation, json) {
|
|
|
369
217
|
if (json) {
|
|
370
218
|
writeJson({
|
|
371
219
|
schema_version: "Perttool.CliError.v1",
|
|
220
|
+
cli_contract_version: 4,
|
|
372
221
|
tool_version: TOOL_VERSION,
|
|
373
222
|
operation,
|
|
374
223
|
ok: false,
|
|
@@ -381,9 +230,9 @@ function cliError(error, exitCode, operation, json) {
|
|
|
381
230
|
return exitCode;
|
|
382
231
|
}
|
|
383
232
|
async function runCheck(args) {
|
|
384
|
-
const parsed =
|
|
233
|
+
const parsed = parseCommandOptions("document.check", args);
|
|
385
234
|
if (parsed.positionals.length !== 1) {
|
|
386
|
-
throw new UsageError("
|
|
235
|
+
throw new UsageError("document check requires exactly one <file>");
|
|
387
236
|
}
|
|
388
237
|
const format = outputFormat(parsed.values.get("format"));
|
|
389
238
|
const color = colorMode(parsed.values.get("color"), format);
|
|
@@ -395,7 +244,7 @@ async function runCheck(args) {
|
|
|
395
244
|
input = await readDocument(sourceOperand);
|
|
396
245
|
}
|
|
397
246
|
catch (error) {
|
|
398
|
-
return cliError(error instanceof Error ? error : new Error(String(error)), 3, "
|
|
247
|
+
return cliError(error instanceof Error ? error : new Error(String(error)), 3, "document.check", format === "json");
|
|
399
248
|
}
|
|
400
249
|
const result = checkDocument(input.text, { maxDiagnostics });
|
|
401
250
|
const warningsAsErrors = parsed.flags.has("warnings-as-errors");
|
|
@@ -403,9 +252,10 @@ async function runCheck(args) {
|
|
|
403
252
|
const ok = result.ok && !warningFailure;
|
|
404
253
|
if (format === "json") {
|
|
405
254
|
writeJson({
|
|
406
|
-
schema_version: "Perttool.CheckResult.
|
|
255
|
+
schema_version: "Perttool.CheckResult.v2",
|
|
256
|
+
cli_contract_version: 4,
|
|
407
257
|
tool_version: TOOL_VERSION,
|
|
408
|
-
operation: "
|
|
258
|
+
operation: "document.check",
|
|
409
259
|
ok,
|
|
410
260
|
document_id: result.documentId,
|
|
411
261
|
source,
|
|
@@ -414,11 +264,12 @@ async function runCheck(args) {
|
|
|
414
264
|
diagnostics_truncated: result.diagnosticsTruncated,
|
|
415
265
|
grammar_version: result.grammarVersion,
|
|
416
266
|
summary: result.summary,
|
|
267
|
+
temporal_inputs: snakeJson(result.temporalInputs),
|
|
417
268
|
});
|
|
418
269
|
}
|
|
419
270
|
else {
|
|
420
271
|
if (ok) {
|
|
421
|
-
process.stdout.write(`OK ${source} project=${result.documentId ?? "-"} milestones=${result.summary.milestones} tasks=${result.summary.tasks} gates=${result.summary.gates} resources=${result.summary.resources}\n`);
|
|
272
|
+
process.stdout.write(`OK ${source} project=${result.documentId ?? "-"} milestones=${result.summary.milestones} tasks=${result.summary.tasks} gates=${result.summary.gates} resources=${result.summary.resources} temporal=milestone_deadlines:${result.temporalInputs?.milestoneDeadlines.length ?? 0},task_not_before:${result.temporalInputs?.taskConstraints.filter(({ notBefore }) => notBefore !== null).length ?? 0},task_deadlines:${result.temporalInputs?.taskConstraints.filter(({ deadline }) => deadline !== null).length ?? 0}\n`);
|
|
422
273
|
}
|
|
423
274
|
for (const diagnostic of result.diagnostics) {
|
|
424
275
|
process.stderr.write(`${renderDiagnostic(diagnostic, source, color)}\n`);
|
|
@@ -435,32 +286,37 @@ function projectJson(project) {
|
|
|
435
286
|
version: project.version,
|
|
436
287
|
title: project.title,
|
|
437
288
|
description: project.description,
|
|
438
|
-
as_of: project.asOf,
|
|
289
|
+
as_of: snakeJson(project.asOf),
|
|
439
290
|
duration_unit: project.durationUnit,
|
|
440
291
|
velocity: project.velocity,
|
|
441
292
|
finish: project.finish,
|
|
293
|
+
finish_deadline: snakeJson(project.finishDeadline),
|
|
442
294
|
critical_epsilon: project.criticalEpsilon,
|
|
443
295
|
target_duration: project.targetDuration,
|
|
444
296
|
};
|
|
445
297
|
}
|
|
446
298
|
function renderProjectText(project) {
|
|
447
299
|
const optional = (value) => value ?? "-";
|
|
300
|
+
const calendar = (value) => value === null
|
|
301
|
+
? "-"
|
|
302
|
+
: `${value.kind.toUpperCase()} ${value.sourceText ?? "-"}`;
|
|
448
303
|
return [
|
|
449
304
|
`PROJECT ${project.id}`,
|
|
450
305
|
`VERSION ${project.version}`,
|
|
451
306
|
`TITLE ${JSON.stringify(project.title)}`,
|
|
452
307
|
`DESCRIPTION ${project.description === null ? "-" : JSON.stringify(project.description)}`,
|
|
453
|
-
`AS_OF ${
|
|
308
|
+
`AS_OF ${calendar(project.asOf)}`,
|
|
454
309
|
`DURATION_UNIT ${project.durationUnit}`,
|
|
455
310
|
`VELOCITY ${optional(project.velocity)}`,
|
|
456
311
|
`FINISH ${project.finish}`,
|
|
312
|
+
`FINISH_DEADLINE ${calendar(project.finishDeadline)}`,
|
|
457
313
|
`CRITICAL_EPSILON ${optional(project.criticalEpsilon)}`,
|
|
458
314
|
`TARGET_DURATION ${optional(project.targetDuration)}`,
|
|
459
315
|
"",
|
|
460
316
|
].join("\n");
|
|
461
317
|
}
|
|
462
318
|
async function runProjectShow(args) {
|
|
463
|
-
const parsed =
|
|
319
|
+
const parsed = parseCommandOptions("project.show", args);
|
|
464
320
|
if (parsed.positionals.length !== 1) {
|
|
465
321
|
throw new UsageError("project show requires exactly one <file>");
|
|
466
322
|
}
|
|
@@ -483,7 +339,8 @@ async function runProjectShow(args) {
|
|
|
483
339
|
const ok = result.ok && !warningFailure;
|
|
484
340
|
if (format === "json") {
|
|
485
341
|
writeJson({
|
|
486
|
-
schema_version: "Perttool.ProjectResult.
|
|
342
|
+
schema_version: "Perttool.ProjectResult.v2",
|
|
343
|
+
cli_contract_version: 4,
|
|
487
344
|
tool_version: TOOL_VERSION,
|
|
488
345
|
operation: "project.show",
|
|
489
346
|
ok,
|
|
@@ -509,6 +366,61 @@ async function runProjectShow(args) {
|
|
|
509
366
|
}
|
|
510
367
|
return ok ? 0 : 1;
|
|
511
368
|
}
|
|
369
|
+
async function runProjectInit(args) {
|
|
370
|
+
const parsed = parseCommandOptions("project.init", args);
|
|
371
|
+
const format = outputFormat(parsed.values.get("format"));
|
|
372
|
+
const color = colorMode(parsed.values.get("color"), format);
|
|
373
|
+
const durationUnit = enumOption(requiredOption(parsed, "duration-unit"), "duration-unit", new Set(["day", "hour", "point"]));
|
|
374
|
+
const version = optionalInteger(parsed, "version", 0, 2_147_483_647);
|
|
375
|
+
let result = planProjectInit({
|
|
376
|
+
projectId: parsed.positionals[0],
|
|
377
|
+
title: requiredOption(parsed, "title"),
|
|
378
|
+
durationUnit,
|
|
379
|
+
initialMilestone: requiredOption(parsed, "initial-milestone"),
|
|
380
|
+
initialMilestoneTitle: requiredOption(parsed, "initial-milestone-title"),
|
|
381
|
+
finish: requiredOption(parsed, "finish"),
|
|
382
|
+
...(version === undefined ? {} : { version }),
|
|
383
|
+
...(parsed.values.get("as-of") === undefined
|
|
384
|
+
? {}
|
|
385
|
+
: { asOf: parsed.values.get("as-of") }),
|
|
386
|
+
...(parsed.values.get("velocity") === undefined
|
|
387
|
+
? {}
|
|
388
|
+
: { velocity: parsed.values.get("velocity") }),
|
|
389
|
+
...(parsed.values.get("initial-milestone-deadline") === undefined
|
|
390
|
+
? {}
|
|
391
|
+
: {
|
|
392
|
+
initialMilestoneDeadline: parsed.values.get("initial-milestone-deadline"),
|
|
393
|
+
}),
|
|
394
|
+
});
|
|
395
|
+
let writeResult = null;
|
|
396
|
+
const output = parsed.values.get("out");
|
|
397
|
+
if (result.ok && output !== undefined) {
|
|
398
|
+
try {
|
|
399
|
+
writeResult = await createDocumentFile(output, result.candidateText);
|
|
400
|
+
result = withProjectInitOutput(result, writeResult);
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
return writeFailureExit(error, "project.init", format === "json");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (format === "json") {
|
|
407
|
+
writeJson(projectInitResultToJson(result));
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
if (result.ok) {
|
|
411
|
+
if (writeResult === null) {
|
|
412
|
+
process.stdout.write(renderProjectInitResult(result));
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
process.stderr.write(renderWriteSummary("project.init", writeResult));
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
for (const diagnostic of result.diagnostics) {
|
|
419
|
+
process.stderr.write(`${renderDiagnostic(diagnostic, "<project-init>", color)}\n`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return result.ok ? 0 : 1;
|
|
423
|
+
}
|
|
512
424
|
function boundedInteger(raw, option, defaultValue, minimum, maximum) {
|
|
513
425
|
if (raw === undefined)
|
|
514
426
|
return defaultValue;
|
|
@@ -546,14 +458,6 @@ function capacityOverrides(values) {
|
|
|
546
458
|
}
|
|
547
459
|
return overrides;
|
|
548
460
|
}
|
|
549
|
-
const mutationCommonValueOptions = [
|
|
550
|
-
"format",
|
|
551
|
-
"color",
|
|
552
|
-
"max-diagnostics",
|
|
553
|
-
"out",
|
|
554
|
-
"expect-digest",
|
|
555
|
-
];
|
|
556
|
-
const mutationCommonFlagOptions = ["diff", "write", "warnings-as-errors"];
|
|
557
461
|
function editingWriteRequest(parsed, sourceOperand, check = false) {
|
|
558
462
|
const write = parsed.flags.has("write");
|
|
559
463
|
const out = parsed.values.get("out");
|
|
@@ -594,12 +498,12 @@ function assertExpectedDigest(request, initialDigest) {
|
|
|
594
498
|
if (request.mode === "in_place" &&
|
|
595
499
|
request.expectedDigest !== undefined &&
|
|
596
500
|
request.expectedDigest !== initialDigest) {
|
|
597
|
-
throw new SafeWriteConflictError("expected_digest_mismatch", "--expect-digest
|
|
501
|
+
throw new SafeWriteConflictError("expected_digest_mismatch", "--expect-digest does not match the initial document digest");
|
|
598
502
|
}
|
|
599
503
|
}
|
|
600
504
|
async function commitCandidate(request, candidateText, initialDigest) {
|
|
601
505
|
if (candidateText === null) {
|
|
602
|
-
throw new SafeWriteVerificationError("invalid_candidate", "successful editing result
|
|
506
|
+
throw new SafeWriteVerificationError("invalid_candidate", "successful editing result has no candidate text");
|
|
603
507
|
}
|
|
604
508
|
return request.mode === "in_place"
|
|
605
509
|
? replaceDocumentFile(request.target, candidateText, {
|
|
@@ -623,9 +527,9 @@ function renderWriteSummary(operation, result) {
|
|
|
623
527
|
return `WRITE ${operation} mode=${result.mode} target=${result.target} digest=${result.digest} written=${result.written}\n`;
|
|
624
528
|
}
|
|
625
529
|
async function runFormat(args) {
|
|
626
|
-
const parsed =
|
|
530
|
+
const parsed = parseCommandOptions("document.format", args);
|
|
627
531
|
if (parsed.positionals.length !== 1) {
|
|
628
|
-
throw new UsageError("
|
|
532
|
+
throw new UsageError("document format requires exactly one <file>");
|
|
629
533
|
}
|
|
630
534
|
const format = outputFormat(parsed.values.get("format"));
|
|
631
535
|
const color = colorMode(parsed.values.get("color"), format);
|
|
@@ -638,7 +542,7 @@ async function runFormat(args) {
|
|
|
638
542
|
input = await readDocument(sourceOperand);
|
|
639
543
|
}
|
|
640
544
|
catch (error) {
|
|
641
|
-
return cliError(error instanceof Error ? error : new Error(String(error)), 3, "
|
|
545
|
+
return cliError(error instanceof Error ? error : new Error(String(error)), 3, "document.format", format === "json");
|
|
642
546
|
}
|
|
643
547
|
const result = planFormat(input.text, {
|
|
644
548
|
maxDiagnostics,
|
|
@@ -659,14 +563,15 @@ async function runFormat(args) {
|
|
|
659
563
|
}
|
|
660
564
|
}
|
|
661
565
|
catch (error) {
|
|
662
|
-
return writeFailureExit(error, "
|
|
566
|
+
return writeFailureExit(error, "document.format", format === "json");
|
|
663
567
|
}
|
|
664
568
|
}
|
|
665
569
|
if (format === "json") {
|
|
666
570
|
writeJson({
|
|
667
571
|
schema_version: "Perttool.FormatResult.v1",
|
|
572
|
+
cli_contract_version: 4,
|
|
668
573
|
tool_version: TOOL_VERSION,
|
|
669
|
-
operation: "
|
|
574
|
+
operation: "document.format",
|
|
670
575
|
ok,
|
|
671
576
|
document_id: result.documentId,
|
|
672
577
|
source,
|
|
@@ -680,7 +585,7 @@ async function runFormat(args) {
|
|
|
680
585
|
const candidateAllowed = result.ok && !warningFailure;
|
|
681
586
|
if (candidateAllowed) {
|
|
682
587
|
if (writeResult !== null) {
|
|
683
|
-
process.stderr.write(renderWriteSummary("
|
|
588
|
+
process.stderr.write(renderWriteSummary("document.format", writeResult));
|
|
684
589
|
}
|
|
685
590
|
else if (parsed.flags.has("check")) {
|
|
686
591
|
if (parsed.flags.has("diff"))
|
|
@@ -689,7 +594,7 @@ async function runFormat(args) {
|
|
|
689
594
|
else {
|
|
690
595
|
process.stdout.write(parsed.flags.has("diff") ? (result.diff ?? "") : (result.updatedText ?? ""));
|
|
691
596
|
if (!parsed.flags.has("diff")) {
|
|
692
|
-
process.stderr.write(`PREVIEW
|
|
597
|
+
process.stderr.write(`PREVIEW document.format changed=${result.changed} original_digest=${result.originalDigest} updated_digest=${result.updatedDigest}\n`);
|
|
693
598
|
}
|
|
694
599
|
}
|
|
695
600
|
}
|
|
@@ -702,50 +607,6 @@ async function runFormat(args) {
|
|
|
702
607
|
}
|
|
703
608
|
return ok ? 0 : 1;
|
|
704
609
|
}
|
|
705
|
-
function mutationOptionSets(resource, action) {
|
|
706
|
-
const values = new Set(mutationCommonValueOptions);
|
|
707
|
-
const flags = new Set(mutationCommonFlagOptions);
|
|
708
|
-
const repeatable = new Set();
|
|
709
|
-
const addValues = (...names) => {
|
|
710
|
-
for (const name of names)
|
|
711
|
-
values.add(name);
|
|
712
|
-
};
|
|
713
|
-
const addRepeatable = (...names) => {
|
|
714
|
-
for (const name of names)
|
|
715
|
-
repeatable.add(name);
|
|
716
|
-
};
|
|
717
|
-
if (resource === "project" && action === "set") {
|
|
718
|
-
addValues("id", "version", "title", "description", "as-of", "duration-unit", "velocity", "finish", "critical-epsilon", "target-duration");
|
|
719
|
-
addRepeatable("clear");
|
|
720
|
-
}
|
|
721
|
-
else if (resource === "task" && action === "add") {
|
|
722
|
-
addValues("title", "description", "duration", "optimistic", "most-likely", "pessimistic", "status", "priority", "owner", "blocked-reason", "source");
|
|
723
|
-
addRepeatable("tag", "require");
|
|
724
|
-
}
|
|
725
|
-
else if (resource === "task" && action === "set") {
|
|
726
|
-
addValues("from", "to", "title", "description", "duration", "optimistic", "most-likely", "pessimistic", "status", "priority", "owner", "blocked-reason", "source");
|
|
727
|
-
addRepeatable("require", "add-tag", "remove-tag", "remove-require", "clear");
|
|
728
|
-
}
|
|
729
|
-
else if (resource === "milestone" && action === "add") {
|
|
730
|
-
addValues("title", "description", "state");
|
|
731
|
-
addRepeatable("tag");
|
|
732
|
-
}
|
|
733
|
-
else if (resource === "milestone" && action === "set") {
|
|
734
|
-
addValues("title", "description", "state");
|
|
735
|
-
addRepeatable("add-tag", "remove-tag", "clear");
|
|
736
|
-
}
|
|
737
|
-
else if (resource === "resource" && action === "add") {
|
|
738
|
-
addValues("title", "description", "capacity");
|
|
739
|
-
}
|
|
740
|
-
else if (resource === "resource" && action === "set") {
|
|
741
|
-
addValues("title", "description", "capacity");
|
|
742
|
-
addRepeatable("clear");
|
|
743
|
-
}
|
|
744
|
-
else if (resource === "mutation" && action === "apply") {
|
|
745
|
-
addValues("request");
|
|
746
|
-
}
|
|
747
|
-
return { values, flags, repeatable };
|
|
748
|
-
}
|
|
749
610
|
function requiredOption(parsed, name) {
|
|
750
611
|
const value = parsed.values.get(name);
|
|
751
612
|
if (value === undefined)
|
|
@@ -862,6 +723,12 @@ function taskMutationFromOptions(action, parsed) {
|
|
|
862
723
|
...(parsed.values.get("description") === undefined
|
|
863
724
|
? {}
|
|
864
725
|
: { description: parsed.values.get("description") }),
|
|
726
|
+
...(parsed.values.get("not-before") === undefined
|
|
727
|
+
? {}
|
|
728
|
+
: { notBefore: parsed.values.get("not-before") }),
|
|
729
|
+
...(parsed.values.get("deadline") === undefined
|
|
730
|
+
? {}
|
|
731
|
+
: { deadline: parsed.values.get("deadline") }),
|
|
865
732
|
...(status === undefined ? {} : { status }),
|
|
866
733
|
...(priority === undefined ? {} : { priority }),
|
|
867
734
|
...(requirements.length === 0 ? {} : { requirements }),
|
|
@@ -882,7 +749,7 @@ function taskMutationFromOptions(action, parsed) {
|
|
|
882
749
|
}
|
|
883
750
|
const clear = enumRepeated(parsed, "clear", new Set([
|
|
884
751
|
"description", "status", "priority", "owner", "blocked_reason", "source",
|
|
885
|
-
"tags", "requires",
|
|
752
|
+
"tags", "requires", "not_before", "deadline",
|
|
886
753
|
]));
|
|
887
754
|
const addTags = uniqueRepeated(parsed, "add-tag");
|
|
888
755
|
const removeTags = uniqueRepeated(parsed, "remove-tag");
|
|
@@ -897,6 +764,12 @@ function taskMutationFromOptions(action, parsed) {
|
|
|
897
764
|
? {}
|
|
898
765
|
: { description: parsed.values.get("description") }),
|
|
899
766
|
...timing,
|
|
767
|
+
...(parsed.values.get("not-before") === undefined
|
|
768
|
+
? {}
|
|
769
|
+
: { notBefore: parsed.values.get("not-before") }),
|
|
770
|
+
...(parsed.values.get("deadline") === undefined
|
|
771
|
+
? {}
|
|
772
|
+
: { deadline: parsed.values.get("deadline") }),
|
|
900
773
|
...(status === undefined ? {} : { status }),
|
|
901
774
|
...(priority === undefined ? {} : { priority }),
|
|
902
775
|
...(parsed.values.get("owner") === undefined
|
|
@@ -911,6 +784,8 @@ function taskMutationFromOptions(action, parsed) {
|
|
|
911
784
|
};
|
|
912
785
|
const clearConflicts = new Map([
|
|
913
786
|
["description", parsed.values.has("description")],
|
|
787
|
+
["not_before", parsed.values.has("not-before")],
|
|
788
|
+
["deadline", parsed.values.has("deadline")],
|
|
914
789
|
["status", status !== undefined],
|
|
915
790
|
["priority", priority !== undefined],
|
|
916
791
|
["owner", parsed.values.has("owner")],
|
|
@@ -1010,18 +885,22 @@ function milestoneMutationFromOptions(action, parsed) {
|
|
|
1010
885
|
? {}
|
|
1011
886
|
: { description: parsed.values.get("description") }),
|
|
1012
887
|
...(state === undefined ? {} : { state }),
|
|
888
|
+
...(parsed.values.get("deadline") === undefined
|
|
889
|
+
? {}
|
|
890
|
+
: { deadline: parsed.values.get("deadline") }),
|
|
1013
891
|
...((parsed.repeatedValues.get("tag") ?? []).length === 0
|
|
1014
892
|
? {}
|
|
1015
893
|
: { tags: parsed.repeatedValues.get("tag") }),
|
|
1016
894
|
},
|
|
1017
895
|
};
|
|
1018
896
|
}
|
|
1019
|
-
const clear = enumRepeated(parsed, "clear", new Set(["description", "state", "tags"]));
|
|
897
|
+
const clear = enumRepeated(parsed, "clear", new Set(["description", "state", "deadline", "tags"]));
|
|
1020
898
|
const addTags = uniqueRepeated(parsed, "add-tag");
|
|
1021
899
|
const removeTags = uniqueRepeated(parsed, "remove-tag");
|
|
1022
900
|
ensureDisjoint(addTags, "add-tag", removeTags, "remove-tag");
|
|
1023
901
|
if (clear.some((field) => (field === "description" && parsed.values.has("description")) ||
|
|
1024
902
|
(field === "state" && state !== undefined) ||
|
|
903
|
+
(field === "deadline" && parsed.values.has("deadline")) ||
|
|
1025
904
|
(field === "tags" && (addTags.length > 0 || removeTags.length > 0)))) {
|
|
1026
905
|
throw new UsageError("--clear conflicts with another milestone field option");
|
|
1027
906
|
}
|
|
@@ -1033,6 +912,9 @@ function milestoneMutationFromOptions(action, parsed) {
|
|
|
1033
912
|
? {}
|
|
1034
913
|
: { description: parsed.values.get("description") }),
|
|
1035
914
|
...(state === undefined ? {} : { state }),
|
|
915
|
+
...(parsed.values.get("deadline") === undefined
|
|
916
|
+
? {}
|
|
917
|
+
: { deadline: parsed.values.get("deadline") }),
|
|
1036
918
|
};
|
|
1037
919
|
return {
|
|
1038
920
|
kind: "milestone.set",
|
|
@@ -1086,6 +968,37 @@ function resourceMutationFromOptions(action, parsed) {
|
|
|
1086
968
|
...(clear.length === 0 ? {} : { clear }),
|
|
1087
969
|
};
|
|
1088
970
|
}
|
|
971
|
+
function gateMutationFromOptions(action, parsed) {
|
|
972
|
+
const expectedPositionals = action === "add" ? 4 : 2;
|
|
973
|
+
if (parsed.positionals.length !== expectedPositionals) {
|
|
974
|
+
throw new UsageError(`gate ${action} requires ${action === "add" ? "<file> <id> <from> <to>" : "<file> <id>"}`);
|
|
975
|
+
}
|
|
976
|
+
const id = parsed.positionals[1];
|
|
977
|
+
if (action === "remove")
|
|
978
|
+
return { kind: "gate.remove", id };
|
|
979
|
+
if (action === "add") {
|
|
980
|
+
return {
|
|
981
|
+
kind: "gate.add",
|
|
982
|
+
id,
|
|
983
|
+
from: parsed.positionals[2],
|
|
984
|
+
to: parsed.positionals[3],
|
|
985
|
+
gate: { reason: requiredOption(parsed, "reason") },
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
const from = parsed.values.get("from");
|
|
989
|
+
const to = parsed.values.get("to");
|
|
990
|
+
const reason = parsed.values.get("reason");
|
|
991
|
+
if (from === undefined && to === undefined && reason === undefined) {
|
|
992
|
+
throw new UsageError("gate set requires --from, --to, or --reason");
|
|
993
|
+
}
|
|
994
|
+
return {
|
|
995
|
+
kind: "gate.set",
|
|
996
|
+
id,
|
|
997
|
+
...(from === undefined ? {} : { from }),
|
|
998
|
+
...(to === undefined ? {} : { to }),
|
|
999
|
+
...(reason === undefined ? {} : { set: { reason } }),
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1089
1002
|
function previewResultJson(result, exposeCandidate, writeRequest = { mode: "preview", target: null }, writeResult = null) {
|
|
1090
1003
|
return {
|
|
1091
1004
|
changed: exposeCandidate ? result.changed : false,
|
|
@@ -1116,8 +1029,7 @@ async function readMutationRequest(source) {
|
|
|
1116
1029
|
}
|
|
1117
1030
|
}
|
|
1118
1031
|
async function runMutation(resource, action, args) {
|
|
1119
|
-
const
|
|
1120
|
-
const parsed = parseOptions(args, config.values, config.flags, config.repeatable);
|
|
1032
|
+
const parsed = parseCommandOptions(`${resource}.${action}`, args);
|
|
1121
1033
|
const format = outputFormat(parsed.values.get("format"));
|
|
1122
1034
|
const color = colorMode(parsed.values.get("color"), format);
|
|
1123
1035
|
const maxDiagnostics = boundedInteger(parsed.values.get("max-diagnostics"), "max-diagnostics", 100, 1, 1000);
|
|
@@ -1125,9 +1037,9 @@ async function runMutation(resource, action, args) {
|
|
|
1125
1037
|
let sourceOperand;
|
|
1126
1038
|
let writeRequest;
|
|
1127
1039
|
let mutation;
|
|
1128
|
-
if (resource === "
|
|
1040
|
+
if (resource === "batch") {
|
|
1129
1041
|
if (parsed.positionals.length !== 1) {
|
|
1130
|
-
throw new UsageError("
|
|
1042
|
+
throw new UsageError("batch apply requires exactly one <file>");
|
|
1131
1043
|
}
|
|
1132
1044
|
sourceOperand = parsed.positionals[0];
|
|
1133
1045
|
writeRequest = editingWriteRequest(parsed, sourceOperand);
|
|
@@ -1152,9 +1064,11 @@ async function runMutation(resource, action, args) {
|
|
|
1152
1064
|
? projectMutationFromOptions(parsed)
|
|
1153
1065
|
: resource === "task"
|
|
1154
1066
|
? taskMutationFromOptions(action, parsed)
|
|
1155
|
-
: resource === "
|
|
1156
|
-
?
|
|
1157
|
-
:
|
|
1067
|
+
: resource === "gate"
|
|
1068
|
+
? gateMutationFromOptions(action, parsed)
|
|
1069
|
+
: resource === "milestone"
|
|
1070
|
+
? milestoneMutationFromOptions(action, parsed)
|
|
1071
|
+
: resourceMutationFromOptions(action, parsed);
|
|
1158
1072
|
sourceOperand = parsed.positionals[0];
|
|
1159
1073
|
writeRequest = editingWriteRequest(parsed, sourceOperand);
|
|
1160
1074
|
}
|
|
@@ -1171,7 +1085,7 @@ async function runMutation(resource, action, args) {
|
|
|
1171
1085
|
originalLabel: source,
|
|
1172
1086
|
updatedLabel: "candidate",
|
|
1173
1087
|
};
|
|
1174
|
-
const result = resource === "
|
|
1088
|
+
const result = resource === "batch"
|
|
1175
1089
|
? planBatchMutation(input.text, mutation, mutationOptions)
|
|
1176
1090
|
: planMutation(input.text, mutation, mutationOptions);
|
|
1177
1091
|
const warningFailure = parsed.flags.has("warnings-as-errors") &&
|
|
@@ -1193,6 +1107,7 @@ async function runMutation(resource, action, args) {
|
|
|
1193
1107
|
if (format === "json") {
|
|
1194
1108
|
writeJson({
|
|
1195
1109
|
schema_version: "Perttool.MutationResult.v1",
|
|
1110
|
+
cli_contract_version: 4,
|
|
1196
1111
|
tool_version: TOOL_VERSION,
|
|
1197
1112
|
operation,
|
|
1198
1113
|
ok,
|
|
@@ -1225,6 +1140,142 @@ async function runMutation(resource, action, args) {
|
|
|
1225
1140
|
}
|
|
1226
1141
|
return ok ? 0 : 1;
|
|
1227
1142
|
}
|
|
1143
|
+
function unitMigrationJson(result, source, sourceDigest, ok) {
|
|
1144
|
+
const exact = (value) => ({
|
|
1145
|
+
numerator: value.numerator.toString(),
|
|
1146
|
+
denominator: value.denominator.toString(),
|
|
1147
|
+
unit: value.unit,
|
|
1148
|
+
});
|
|
1149
|
+
return {
|
|
1150
|
+
schema_version: result.schemaVersion,
|
|
1151
|
+
cli_contract_version: 4,
|
|
1152
|
+
tool_version: TOOL_VERSION,
|
|
1153
|
+
operation: "project.migrate-unit",
|
|
1154
|
+
ok,
|
|
1155
|
+
document_id: result.documentId,
|
|
1156
|
+
source,
|
|
1157
|
+
source_digest: sourceDigest,
|
|
1158
|
+
diagnostics: result.diagnostics.map(jsonDiagnostic),
|
|
1159
|
+
diagnostics_truncated: result.diagnosticsTruncated,
|
|
1160
|
+
unit_migration: result.unitMigration,
|
|
1161
|
+
source_grammar_version: result.sourceGrammarVersion,
|
|
1162
|
+
target_grammar_version: result.targetGrammarVersion,
|
|
1163
|
+
grammar_disposition: result.grammarDisposition,
|
|
1164
|
+
source_unit: result.sourceUnit,
|
|
1165
|
+
target_unit: result.targetUnit,
|
|
1166
|
+
effective_velocity: result.effectiveVelocity === null
|
|
1167
|
+
? null
|
|
1168
|
+
: {
|
|
1169
|
+
points: exact(result.effectiveVelocity.points),
|
|
1170
|
+
period: exact(result.effectiveVelocity.period),
|
|
1171
|
+
},
|
|
1172
|
+
velocity_disposition: result.velocityDisposition,
|
|
1173
|
+
changed: result.changed,
|
|
1174
|
+
converted_fields: result.convertedFields.map((field) => ({
|
|
1175
|
+
entity_kind: field.entityKind,
|
|
1176
|
+
entity_id: field.entityId,
|
|
1177
|
+
field_path: field.fieldPath,
|
|
1178
|
+
original: exact(field.original),
|
|
1179
|
+
converted: exact(field.converted),
|
|
1180
|
+
canonical_token: field.canonicalToken,
|
|
1181
|
+
})),
|
|
1182
|
+
reversibility: result.reversibility,
|
|
1183
|
+
qualifications: result.qualifications,
|
|
1184
|
+
unavailable_causes: result.unavailableCauses.map((cause) => ({
|
|
1185
|
+
cause: cause.cause,
|
|
1186
|
+
diagnostic_code: cause.diagnosticCode,
|
|
1187
|
+
field_paths: cause.fieldPaths,
|
|
1188
|
+
})),
|
|
1189
|
+
original_digest: result.originalDigest,
|
|
1190
|
+
updated_digest: result.updatedDigest,
|
|
1191
|
+
updated_text: result.updatedText,
|
|
1192
|
+
diff: result.diff,
|
|
1193
|
+
edits: result.edits.map((edit) => ({
|
|
1194
|
+
start_offset: edit.startOffset,
|
|
1195
|
+
end_offset: edit.endOffset,
|
|
1196
|
+
replacement: edit.replacement,
|
|
1197
|
+
})),
|
|
1198
|
+
write: {
|
|
1199
|
+
mode: result.write.mode,
|
|
1200
|
+
target: result.write.target,
|
|
1201
|
+
written: result.write.written,
|
|
1202
|
+
},
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
async function runUnitMigration(args) {
|
|
1206
|
+
const parsed = parseCommandOptions("project.migrate-unit", args);
|
|
1207
|
+
if (parsed.positionals.length !== 1) {
|
|
1208
|
+
throw new UsageError("project migrate-unit requires exactly one <file>");
|
|
1209
|
+
}
|
|
1210
|
+
const format = outputFormat(parsed.values.get("format"));
|
|
1211
|
+
const color = colorMode(parsed.values.get("color"), format);
|
|
1212
|
+
const targetUnit = enumOption(requiredOption(parsed, "to-unit"), "to-unit", new Set(["day", "hour", "point"]));
|
|
1213
|
+
const maxDiagnostics = boundedInteger(parsed.values.get("max-diagnostics"), "max-diagnostics", 100, 1, 1000);
|
|
1214
|
+
const sourceOperand = parsed.positionals[0];
|
|
1215
|
+
const writeRequest = editingWriteRequest(parsed, sourceOperand);
|
|
1216
|
+
const source = sourceOperand === "-" ? "<stdin>" : sourceOperand;
|
|
1217
|
+
let input;
|
|
1218
|
+
try {
|
|
1219
|
+
input = await readDocument(sourceOperand);
|
|
1220
|
+
}
|
|
1221
|
+
catch (error) {
|
|
1222
|
+
return cliError(error instanceof Error ? error : new Error(String(error)), 3, "project.migrate-unit", format === "json");
|
|
1223
|
+
}
|
|
1224
|
+
let result = planUnitMigration(input.text, {
|
|
1225
|
+
targetUnit,
|
|
1226
|
+
...(parsed.values.get("replacement-velocity") === undefined
|
|
1227
|
+
? {}
|
|
1228
|
+
: {
|
|
1229
|
+
replacementVelocity: parsed.values.get("replacement-velocity"),
|
|
1230
|
+
}),
|
|
1231
|
+
}, {
|
|
1232
|
+
maxDiagnostics,
|
|
1233
|
+
originalLabel: source,
|
|
1234
|
+
updatedLabel: "candidate",
|
|
1235
|
+
});
|
|
1236
|
+
const warningFailure = parsed.flags.has("warnings-as-errors") &&
|
|
1237
|
+
(result.diagnosticsTruncated ||
|
|
1238
|
+
result.diagnostics.some(({ severity }) => severity === "warning"));
|
|
1239
|
+
const ok = result.ok && !warningFailure;
|
|
1240
|
+
let writeResult = null;
|
|
1241
|
+
if (result.ok) {
|
|
1242
|
+
try {
|
|
1243
|
+
assertExpectedDigest(writeRequest, input.digest);
|
|
1244
|
+
if (ok && writeRequest.mode !== "preview") {
|
|
1245
|
+
writeResult = await commitCandidate(writeRequest, result.updatedText, input.digest);
|
|
1246
|
+
result = withUnitMigrationWrite(result, writeResult);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
catch (error) {
|
|
1250
|
+
return writeFailureExit(error, "project.migrate-unit", format === "json");
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
if (format === "json") {
|
|
1254
|
+
writeJson(unitMigrationJson(result, source, input.digest, ok));
|
|
1255
|
+
}
|
|
1256
|
+
else {
|
|
1257
|
+
if (ok) {
|
|
1258
|
+
if (writeResult !== null) {
|
|
1259
|
+
process.stderr.write(renderWriteSummary("project.migrate-unit", writeResult));
|
|
1260
|
+
}
|
|
1261
|
+
else {
|
|
1262
|
+
process.stdout.write(parsed.flags.has("diff")
|
|
1263
|
+
? (result.diff ?? "")
|
|
1264
|
+
: (result.updatedText ?? ""));
|
|
1265
|
+
if (!parsed.flags.has("diff")) {
|
|
1266
|
+
process.stderr.write(`PREVIEW project.migrate-unit changed=${result.changed} source_unit=${result.sourceUnit ?? "-"} target_unit=${result.targetUnit} original_digest=${result.originalDigest} updated_digest=${result.updatedDigest ?? "-"}\n`);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
for (const diagnostic of result.diagnostics) {
|
|
1271
|
+
process.stderr.write(`${renderDiagnostic(diagnostic, source, color)}\n`);
|
|
1272
|
+
}
|
|
1273
|
+
if (result.diagnosticsTruncated) {
|
|
1274
|
+
process.stderr.write(`DIAGNOSTICS_TRUNCATED true limit=${maxDiagnostics}\n`);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return ok ? 0 : 1;
|
|
1278
|
+
}
|
|
1228
1279
|
function advanceResultJson(details) {
|
|
1229
1280
|
return details === null
|
|
1230
1281
|
? null
|
|
@@ -1247,7 +1298,7 @@ function renderAdvanceSummary(details) {
|
|
|
1247
1298
|
].join("\n");
|
|
1248
1299
|
}
|
|
1249
1300
|
async function runAdvance(args) {
|
|
1250
|
-
const parsed =
|
|
1301
|
+
const parsed = parseCommandOptions("dag.advance", args);
|
|
1251
1302
|
if (parsed.positionals.length !== 1) {
|
|
1252
1303
|
throw new UsageError("dag advance requires exactly one <file>");
|
|
1253
1304
|
}
|
|
@@ -1288,6 +1339,7 @@ async function runAdvance(args) {
|
|
|
1288
1339
|
if (format === "json") {
|
|
1289
1340
|
writeJson({
|
|
1290
1341
|
schema_version: "Perttool.MutationResult.v1",
|
|
1342
|
+
cli_contract_version: 4,
|
|
1291
1343
|
tool_version: TOOL_VERSION,
|
|
1292
1344
|
operation: "dag.advance",
|
|
1293
1345
|
ok,
|
|
@@ -1565,17 +1617,45 @@ function renderAnalysisText(result) {
|
|
|
1565
1617
|
if (resource.resources.length === 0)
|
|
1566
1618
|
lines.push("-");
|
|
1567
1619
|
}
|
|
1620
|
+
if (result.temporal !== null) {
|
|
1621
|
+
const temporalPoint = (point) => {
|
|
1622
|
+
const causes = point.unavailableCauses
|
|
1623
|
+
.map(({ cause }) => cause)
|
|
1624
|
+
.join(",") || "-";
|
|
1625
|
+
return [
|
|
1626
|
+
`state=${point.state}`,
|
|
1627
|
+
`relative=${point.relative === null ? "-" : `${point.relative.numerator}/${point.relative.denominator}${durationSuffix(point.relative.unit)}`}`,
|
|
1628
|
+
`calendar=${point.calendar?.sourceText ?? "-"}`,
|
|
1629
|
+
`causes=${causes}`,
|
|
1630
|
+
].join(" ");
|
|
1631
|
+
};
|
|
1632
|
+
for (const [title, projection] of [
|
|
1633
|
+
["TEMPORAL PRECEDENCE", result.temporal.precedence],
|
|
1634
|
+
["TEMPORAL RESOURCE", result.temporal.resource],
|
|
1635
|
+
]) {
|
|
1636
|
+
lines.push("", title, `STATE ${projection.state}`, `ALGORITHM ${projection.algorithm === null ? "-" : `${projection.algorithm.id}@${projection.algorithm.version} optimal=${projection.algorithm.optimal}`}`, `CONDITIONAL_ON_BLOCKS_RESOLVED ${projection.conditionalOnBlocksResolved}`, `BLOCKED_TASKS ${projection.blockedTaskIds.join(",") || "-"}`);
|
|
1637
|
+
for (const task of projection.tasks) {
|
|
1638
|
+
lines.push(`${task.taskId} START ${temporalPoint(task.start)} FINISH ${temporalPoint(task.finish)}`);
|
|
1639
|
+
}
|
|
1640
|
+
if (projection.tasks.length === 0)
|
|
1641
|
+
lines.push("-");
|
|
1642
|
+
}
|
|
1643
|
+
lines.push("", "DEADLINES");
|
|
1644
|
+
for (const evaluation of result.temporal.deadlineEvaluations) {
|
|
1645
|
+
const causes = [
|
|
1646
|
+
...evaluation.current.unavailableCauses,
|
|
1647
|
+
...evaluation.precedence.unavailableCauses,
|
|
1648
|
+
...evaluation.resource.unavailableCauses,
|
|
1649
|
+
].map(({ cause }) => cause).join(",") || "-";
|
|
1650
|
+
lines.push(`${evaluation.subject.kind}:${evaluation.subject.id} assessment=${evaluation.combinedAssessment} precedence=${evaluation.precedence.assessment ?? evaluation.precedence.state} resource=${evaluation.resource.assessment ?? evaluation.resource.state} conditional=${evaluation.conditionalOnBlocksResolved} causes=${causes}`);
|
|
1651
|
+
}
|
|
1652
|
+
if (result.temporal.deadlineEvaluations.length === 0)
|
|
1653
|
+
lines.push("-");
|
|
1654
|
+
}
|
|
1568
1655
|
return `${lines.join("\n")}\n`;
|
|
1569
1656
|
}
|
|
1570
1657
|
async function runAnalyze(args) {
|
|
1571
|
-
const parsed =
|
|
1572
|
-
"schedule",
|
|
1573
|
-
"max-paths",
|
|
1574
|
-
"precision",
|
|
1575
|
-
"max-diagnostics",
|
|
1576
|
-
"format",
|
|
1577
|
-
"color",
|
|
1578
|
-
]), new Set(["warnings-as-errors"]), new Set(["capacity"]));
|
|
1658
|
+
const parsed = parseCommandOptions("dag.analyze", args);
|
|
1579
1659
|
if (parsed.positionals.length !== 1) {
|
|
1580
1660
|
throw new UsageError("dag analyze requires exactly one <file>");
|
|
1581
1661
|
}
|
|
@@ -1608,7 +1688,8 @@ async function runAnalyze(args) {
|
|
|
1608
1688
|
const ok = result.ok && !warningFailure;
|
|
1609
1689
|
if (format === "json") {
|
|
1610
1690
|
writeJson({
|
|
1611
|
-
schema_version: "Perttool.AnalysisResult.
|
|
1691
|
+
schema_version: "Perttool.AnalysisResult.v3",
|
|
1692
|
+
cli_contract_version: 4,
|
|
1612
1693
|
tool_version: TOOL_VERSION,
|
|
1613
1694
|
operation: "dag.analyze",
|
|
1614
1695
|
ok,
|
|
@@ -1617,6 +1698,7 @@ async function runAnalyze(args) {
|
|
|
1617
1698
|
source_digest: input.digest,
|
|
1618
1699
|
diagnostics: result.diagnostics.map(jsonDiagnostic),
|
|
1619
1700
|
diagnostics_truncated: result.diagnosticsTruncated,
|
|
1701
|
+
grammar_version: result.grammarVersion,
|
|
1620
1702
|
mode: result.mode,
|
|
1621
1703
|
precision: result.precision,
|
|
1622
1704
|
...(result.durationUnit === null
|
|
@@ -1633,6 +1715,7 @@ async function runAnalyze(args) {
|
|
|
1633
1715
|
resource: result.resource === null || result.durationUnit === null
|
|
1634
1716
|
? null
|
|
1635
1717
|
: resourceJson(result.resource, result.durationUnit, result.precision),
|
|
1718
|
+
temporal: snakeJson(result.temporal),
|
|
1636
1719
|
});
|
|
1637
1720
|
}
|
|
1638
1721
|
else {
|
|
@@ -1668,15 +1751,7 @@ function renderConversionLoss(loss, source, color) {
|
|
|
1668
1751
|
}, source, color);
|
|
1669
1752
|
}
|
|
1670
1753
|
async function runRender(args) {
|
|
1671
|
-
const parsed =
|
|
1672
|
-
"to",
|
|
1673
|
-
"profile",
|
|
1674
|
-
"analysis",
|
|
1675
|
-
"out",
|
|
1676
|
-
"max-diagnostics",
|
|
1677
|
-
"format",
|
|
1678
|
-
"color",
|
|
1679
|
-
]), new Set(["strict-loss", "warnings-as-errors"]), new Set(["capacity"]));
|
|
1754
|
+
const parsed = parseCommandOptions("dag.render", args);
|
|
1680
1755
|
if (parsed.positionals.length !== 1) {
|
|
1681
1756
|
throw new UsageError("dag render requires exactly one <file>");
|
|
1682
1757
|
}
|
|
@@ -1728,6 +1803,7 @@ async function runRender(args) {
|
|
|
1728
1803
|
if (format === "json") {
|
|
1729
1804
|
writeJson({
|
|
1730
1805
|
schema_version: "Perttool.ExportResult.v1",
|
|
1806
|
+
cli_contract_version: 4,
|
|
1731
1807
|
tool_version: TOOL_VERSION,
|
|
1732
1808
|
operation: "dag.render",
|
|
1733
1809
|
ok,
|
|
@@ -1778,7 +1854,7 @@ async function runRender(args) {
|
|
|
1778
1854
|
return strictFailure ? 4 : ok ? 0 : 1;
|
|
1779
1855
|
}
|
|
1780
1856
|
async function runImport(args) {
|
|
1781
|
-
const parsed =
|
|
1857
|
+
const parsed = parseCommandOptions("dag.import", args);
|
|
1782
1858
|
if (parsed.positionals.length !== 1) {
|
|
1783
1859
|
throw new UsageError("dag import requires exactly one <file>");
|
|
1784
1860
|
}
|
|
@@ -1819,6 +1895,7 @@ async function runImport(args) {
|
|
|
1819
1895
|
if (format === "json") {
|
|
1820
1896
|
writeJson({
|
|
1821
1897
|
schema_version: "Perttool.ImportResult.v1",
|
|
1898
|
+
cli_contract_version: 4,
|
|
1822
1899
|
tool_version: TOOL_VERSION,
|
|
1823
1900
|
operation: "dag.import",
|
|
1824
1901
|
ok,
|
|
@@ -2035,7 +2112,15 @@ function renderExplanation(node, indent) {
|
|
|
2035
2112
|
function renderNextText(result) {
|
|
2036
2113
|
const unit = result.durationUnit;
|
|
2037
2114
|
const taskById = new Map(result.tasks.map((task) => [task.id, task]));
|
|
2115
|
+
const authority = result.temporal.authority;
|
|
2038
2116
|
const lines = [
|
|
2117
|
+
"START AUTHORITY",
|
|
2118
|
+
`POLICY ${authority.policy}`,
|
|
2119
|
+
`STARTABLE RECOMMENDED ${authority.startableRecommendedTaskIds.join(",") || "-"}`,
|
|
2120
|
+
`DELAYED RECOMMENDED ${authority.delayedRecommendedTaskIds.join(",") || "-"}`,
|
|
2121
|
+
`UNAVAILABLE RECOMMENDED ${authority.unavailableRecommendedTaskIds.join(",") || "-"}`,
|
|
2122
|
+
"DEADLINE FACTS INFORMATIONAL FOR RANKING v1",
|
|
2123
|
+
"",
|
|
2039
2124
|
`PERTTOOL NEXT ${result.documentId ?? "-"}`,
|
|
2040
2125
|
`VELOCITY ${result.velocity === null ? "-" : velocityText(result.velocity, result.precision)}`,
|
|
2041
2126
|
`VELOCITY_FORECAST_UNIT ${result.velocityForecast?.targetUnit ?? "-"}`,
|
|
@@ -2074,10 +2159,19 @@ function renderNextText(result) {
|
|
|
2074
2159
|
section("READY / WAITING RESOURCE", result.groups.ready.filter((id) => !result.groups.runnableNow.includes(id)), "rejection");
|
|
2075
2160
|
section("BLOCKED NOW", result.groups.blockedNow, "blocked");
|
|
2076
2161
|
section("UPCOMING", result.groups.upcoming, "explanation");
|
|
2162
|
+
lines.push("", "TEMPORAL CONTEXT");
|
|
2163
|
+
for (const temporal of result.temporal.tasks) {
|
|
2164
|
+
const causes = temporal.timeEligibility.unavailableCauses
|
|
2165
|
+
.map(({ cause }) => cause)
|
|
2166
|
+
.join(",") || "-";
|
|
2167
|
+
lines.push(`${temporal.taskId} eligibility=${temporal.timeEligibility.state} release=${temporal.timeEligibility.releaseBound === null ? "-" : `${temporal.timeEligibility.releaseBound.numerator}/${temporal.timeEligibility.releaseBound.denominator}${durationSuffix(temporal.timeEligibility.releaseBound.unit)}`} task_deadline=${temporal.taskDeadline?.sourceText ?? "-"} destination=${temporal.destinationMilestoneId} destination_deadline=${temporal.destinationDeadline?.sourceText ?? "-"} causes=${causes}`);
|
|
2168
|
+
}
|
|
2169
|
+
if (result.temporal.tasks.length === 0)
|
|
2170
|
+
lines.push("-");
|
|
2077
2171
|
return `${lines.join("\n")}\n`;
|
|
2078
2172
|
}
|
|
2079
2173
|
async function runNext(args) {
|
|
2080
|
-
const parsed =
|
|
2174
|
+
const parsed = parseCommandOptions("dag.next", args);
|
|
2081
2175
|
if (parsed.positionals.length !== 1) {
|
|
2082
2176
|
throw new UsageError("dag next requires exactly one <file>");
|
|
2083
2177
|
}
|
|
@@ -2108,6 +2202,7 @@ async function runNext(args) {
|
|
|
2108
2202
|
if (format === "json") {
|
|
2109
2203
|
writeJson({
|
|
2110
2204
|
schema_version: "Perttool.CliError.v1",
|
|
2205
|
+
cli_contract_version: 4,
|
|
2111
2206
|
tool_version: TOOL_VERSION,
|
|
2112
2207
|
operation: "dag.next",
|
|
2113
2208
|
ok: false,
|
|
@@ -2127,7 +2222,8 @@ async function runNext(args) {
|
|
|
2127
2222
|
const ok = result.ok && !warningFailure;
|
|
2128
2223
|
if (format === "json") {
|
|
2129
2224
|
writeJson({
|
|
2130
|
-
schema_version: "Perttool.NextResult.
|
|
2225
|
+
schema_version: "Perttool.NextResult.v4",
|
|
2226
|
+
cli_contract_version: 4,
|
|
2131
2227
|
recommendation_interface_version: 1,
|
|
2132
2228
|
tool_version: TOOL_VERSION,
|
|
2133
2229
|
operation: "dag.next",
|
|
@@ -2137,7 +2233,9 @@ async function runNext(args) {
|
|
|
2137
2233
|
source_digest: input.digest,
|
|
2138
2234
|
diagnostics: result.diagnostics.map(jsonDiagnostic),
|
|
2139
2235
|
diagnostics_truncated: result.diagnosticsTruncated,
|
|
2236
|
+
grammar_version: result.grammarVersion,
|
|
2140
2237
|
...(result.durationUnit === null ? {} : nextJson(result)),
|
|
2238
|
+
temporal: snakeJson(result.temporal),
|
|
2141
2239
|
});
|
|
2142
2240
|
}
|
|
2143
2241
|
else {
|
|
@@ -2152,65 +2250,56 @@ async function runNext(args) {
|
|
|
2152
2250
|
}
|
|
2153
2251
|
return ok ? 0 : 1;
|
|
2154
2252
|
}
|
|
2155
|
-
function
|
|
2156
|
-
const
|
|
2157
|
-
if (result.topics.length > 0) {
|
|
2158
|
-
lines.push("", "Topics:");
|
|
2159
|
-
for (const topic of result.topics)
|
|
2160
|
-
lines.push(` ${topic.id.padEnd(12)} ${topic.summary}`);
|
|
2161
|
-
}
|
|
2162
|
-
for (const section of result.sections) {
|
|
2163
|
-
lines.push("", section.title, section.body);
|
|
2164
|
-
}
|
|
2165
|
-
if (result.syntax.length > 0)
|
|
2166
|
-
lines.push("", "Syntax:", ...result.syntax.map((line) => ` ${line}`));
|
|
2167
|
-
if (result.examples.length > 0) {
|
|
2168
|
-
lines.push("", "Examples:", ...result.examples.map((example) => ` ${example.id}: ${example.text}`));
|
|
2169
|
-
}
|
|
2170
|
-
if (result.related.length > 0)
|
|
2171
|
-
lines.push("", `Related: ${result.related.join(", ")}`);
|
|
2172
|
-
return `${lines.join("\n")}\n`;
|
|
2173
|
-
}
|
|
2174
|
-
function runHelp(args) {
|
|
2175
|
-
const parsed = parseOptions(args, new Set(["level", "format", "color"]), new Set());
|
|
2253
|
+
function runGuide(args) {
|
|
2254
|
+
const parsed = parseCommandOptions("guide", args);
|
|
2176
2255
|
if (parsed.positionals.length > 2) {
|
|
2177
|
-
throw new UsageError("
|
|
2256
|
+
throw new UsageError("guide accepts at most <topic> <subtopic>");
|
|
2178
2257
|
}
|
|
2179
2258
|
const format = outputFormat(parsed.values.get("format"));
|
|
2180
2259
|
colorMode(parsed.values.get("color"), format);
|
|
2181
2260
|
const topicId = parsed.positionals.length === 0 ? null : parsed.positionals.join(".");
|
|
2182
2261
|
const level = helpLevel(parsed.values.get("level"), topicId !== null);
|
|
2183
|
-
const result =
|
|
2262
|
+
const result = getGuide(topicId, level);
|
|
2184
2263
|
if (format === "json") {
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
sections: result.sections,
|
|
2196
|
-
syntax: result.syntax,
|
|
2197
|
-
examples: result.examples,
|
|
2198
|
-
related: result.related,
|
|
2199
|
-
topics: result.topics,
|
|
2200
|
-
});
|
|
2264
|
+
process.stdout.write(serializeGuideResult(result));
|
|
2265
|
+
}
|
|
2266
|
+
else {
|
|
2267
|
+
const rendered = renderGuideResult(result);
|
|
2268
|
+
if (result.ok) {
|
|
2269
|
+
process.stdout.write(rendered);
|
|
2270
|
+
}
|
|
2271
|
+
else {
|
|
2272
|
+
process.stderr.write(rendered);
|
|
2273
|
+
}
|
|
2201
2274
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
2275
|
+
return result.ok ? 0 : 1;
|
|
2276
|
+
}
|
|
2277
|
+
function runCommandHelp(args) {
|
|
2278
|
+
const parsed = parseCommandOptions("help", args);
|
|
2279
|
+
if (parsed.positionals.length > 2) {
|
|
2280
|
+
throw new UsageError("help accepts at most <resource> <action>");
|
|
2281
|
+
}
|
|
2282
|
+
const format = outputFormat(parsed.values.get("format"));
|
|
2283
|
+
const result = getCommandDiscovery({
|
|
2284
|
+
resource: parsed.positionals[0] ?? null,
|
|
2285
|
+
action: parsed.positionals[1] ?? null,
|
|
2286
|
+
});
|
|
2287
|
+
if (format === "json") {
|
|
2288
|
+
process.stdout.write(serializeCommandHelpResult(result));
|
|
2204
2289
|
}
|
|
2205
2290
|
else {
|
|
2206
|
-
|
|
2207
|
-
|
|
2291
|
+
const rendered = renderCommandHelpResult(result);
|
|
2292
|
+
if (result.ok) {
|
|
2293
|
+
process.stdout.write(rendered);
|
|
2294
|
+
}
|
|
2295
|
+
else {
|
|
2296
|
+
process.stderr.write(rendered);
|
|
2208
2297
|
}
|
|
2209
2298
|
}
|
|
2210
2299
|
return result.ok ? 0 : 1;
|
|
2211
2300
|
}
|
|
2212
2301
|
function runAgentHelp(args) {
|
|
2213
|
-
const parsed =
|
|
2302
|
+
const parsed = parseCommandOptions("agent.help", args);
|
|
2214
2303
|
if (parsed.positionals.length > 2) {
|
|
2215
2304
|
throw new UsageError("agent help accepts at most <provider> <surface>");
|
|
2216
2305
|
}
|
|
@@ -2225,7 +2314,13 @@ function runAgentHelp(args) {
|
|
|
2225
2314
|
level,
|
|
2226
2315
|
});
|
|
2227
2316
|
if (format === "json") {
|
|
2228
|
-
|
|
2317
|
+
const projected = agentGuidanceResultToJson(result);
|
|
2318
|
+
const { schema_version: schemaVersion, ...payload } = projected;
|
|
2319
|
+
writeJson({
|
|
2320
|
+
schema_version: schemaVersion,
|
|
2321
|
+
cli_contract_version: 4,
|
|
2322
|
+
...payload,
|
|
2323
|
+
});
|
|
2229
2324
|
}
|
|
2230
2325
|
else {
|
|
2231
2326
|
if (result.ok) {
|
|
@@ -2245,74 +2340,90 @@ function runAgentHelp(args) {
|
|
|
2245
2340
|
}
|
|
2246
2341
|
return agentGuidanceExitCode(result.diagnostics);
|
|
2247
2342
|
}
|
|
2343
|
+
async function dispatchCommand(descriptor, args) {
|
|
2344
|
+
switch (descriptor.operation) {
|
|
2345
|
+
case "help":
|
|
2346
|
+
return runCommandHelp(args);
|
|
2347
|
+
case "guide":
|
|
2348
|
+
return runGuide(args);
|
|
2349
|
+
case "document.check":
|
|
2350
|
+
return runCheck(args);
|
|
2351
|
+
case "document.format":
|
|
2352
|
+
return runFormat(args);
|
|
2353
|
+
case "agent.help":
|
|
2354
|
+
return runAgentHelp(args);
|
|
2355
|
+
case "project.init":
|
|
2356
|
+
return runProjectInit(args);
|
|
2357
|
+
case "project.show":
|
|
2358
|
+
return runProjectShow(args);
|
|
2359
|
+
case "project.migrate-unit":
|
|
2360
|
+
return runUnitMigration(args);
|
|
2361
|
+
case "dag.analyze":
|
|
2362
|
+
return runAnalyze(args);
|
|
2363
|
+
case "dag.next":
|
|
2364
|
+
return runNext(args);
|
|
2365
|
+
case "dag.advance":
|
|
2366
|
+
return runAdvance(args);
|
|
2367
|
+
case "dag.render":
|
|
2368
|
+
return runRender(args);
|
|
2369
|
+
case "dag.import":
|
|
2370
|
+
return runImport(args);
|
|
2371
|
+
}
|
|
2372
|
+
if (descriptor.path.length === 2
|
|
2373
|
+
&& (descriptor.operation === "project.set"
|
|
2374
|
+
|| descriptor.operation === "batch.apply"
|
|
2375
|
+
|| /^(?:task|gate|milestone|resource)\.(?:add|set|remove|finish)$/.test(descriptor.operation))) {
|
|
2376
|
+
const [resource, action] = descriptor.path;
|
|
2377
|
+
return runMutation(resource, action, args);
|
|
2378
|
+
}
|
|
2379
|
+
throw new Error(`no Contract 4 handler for ${descriptor.operation}`);
|
|
2380
|
+
}
|
|
2381
|
+
function emitCommandUsage(error, json) {
|
|
2382
|
+
if (json) {
|
|
2383
|
+
process.stdout.write(serializeCommandUsageError(error));
|
|
2384
|
+
}
|
|
2385
|
+
else {
|
|
2386
|
+
process.stderr.write(renderCommandUsageError(error));
|
|
2387
|
+
}
|
|
2388
|
+
return 2;
|
|
2389
|
+
}
|
|
2248
2390
|
async function main(argv) {
|
|
2249
2391
|
if (argv.length === 1 && argv[0] === "--version") {
|
|
2250
2392
|
process.stdout.write(`perttool ${TOOL_VERSION}\n`);
|
|
2251
2393
|
return 0;
|
|
2252
2394
|
}
|
|
2253
2395
|
if (argv.length === 1 && argv[0] === "--help") {
|
|
2254
|
-
|
|
2255
|
-
return 0;
|
|
2256
|
-
}
|
|
2257
|
-
if (argv.length < 2)
|
|
2258
|
-
throw new UsageError("expected <resource> <action>");
|
|
2259
|
-
const resource = argv[0];
|
|
2260
|
-
const action = argv[1];
|
|
2261
|
-
const entityActions = new Map([
|
|
2262
|
-
["project", new Set(["set"])],
|
|
2263
|
-
["task", new Set(["add", "set", "remove", "finish"])],
|
|
2264
|
-
["milestone", new Set(["add", "set", "remove"])],
|
|
2265
|
-
["resource", new Set(["add", "set", "remove"])],
|
|
2266
|
-
["mutation", new Set(["apply"])],
|
|
2267
|
-
]);
|
|
2268
|
-
const isMutationCommand = entityActions.get(resource)?.has(action) === true;
|
|
2269
|
-
if (argv.length === 3 &&
|
|
2270
|
-
argv[2] === "--help" &&
|
|
2271
|
-
((resource === "agent" && action === "help") ||
|
|
2272
|
-
(resource === "dsl" && ["check", "format", "help"].includes(action)) ||
|
|
2273
|
-
(resource === "project" && action === "show") ||
|
|
2274
|
-
(resource === "dag" && ["analyze", "next", "advance", "render", "import"].includes(action)) ||
|
|
2275
|
-
isMutationCommand)) {
|
|
2276
|
-
process.stdout.write(`${commandHelp(resource, action)}\n`);
|
|
2277
|
-
return 0;
|
|
2278
|
-
}
|
|
2279
|
-
if (resource === "dag" && action === "analyze") {
|
|
2280
|
-
return runAnalyze(argv.slice(2));
|
|
2281
|
-
}
|
|
2282
|
-
if (resource === "dag" && action === "next") {
|
|
2283
|
-
return runNext(argv.slice(2));
|
|
2284
|
-
}
|
|
2285
|
-
if (resource === "dag" && action === "advance") {
|
|
2286
|
-
return runAdvance(argv.slice(2));
|
|
2287
|
-
}
|
|
2288
|
-
if (resource === "dag" && action === "render") {
|
|
2289
|
-
return runRender(argv.slice(2));
|
|
2290
|
-
}
|
|
2291
|
-
if (resource === "dag" && action === "import") {
|
|
2292
|
-
return runImport(argv.slice(2));
|
|
2293
|
-
}
|
|
2294
|
-
if (resource === "agent" && action === "help") {
|
|
2295
|
-
return runAgentHelp(argv.slice(2));
|
|
2396
|
+
return runCommandHelp([]);
|
|
2296
2397
|
}
|
|
2297
|
-
|
|
2298
|
-
|
|
2398
|
+
const validation = validateCommandInvocation(argv);
|
|
2399
|
+
if (!validation.ok) {
|
|
2400
|
+
if (jsonRequested(argv)) {
|
|
2401
|
+
process.stdout.write(serializeCommandUsageError(validation.error));
|
|
2402
|
+
}
|
|
2403
|
+
else {
|
|
2404
|
+
process.stderr.write(renderCommandUsageError(validation.error));
|
|
2405
|
+
}
|
|
2406
|
+
return 2;
|
|
2299
2407
|
}
|
|
2300
|
-
|
|
2301
|
-
|
|
2408
|
+
const descriptor = validation.descriptor;
|
|
2409
|
+
if (validation.helpAlias) {
|
|
2410
|
+
return runCommandHelp([...descriptor.path]);
|
|
2302
2411
|
}
|
|
2303
|
-
|
|
2304
|
-
return
|
|
2412
|
+
try {
|
|
2413
|
+
return await dispatchCommand(descriptor, argv.slice(descriptor.path.length));
|
|
2305
2414
|
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2415
|
+
catch (error) {
|
|
2416
|
+
if (error instanceof UsageError) {
|
|
2417
|
+
return emitCommandUsage(handlerCommandUsageError(descriptor, error.message), jsonRequested(argv));
|
|
2418
|
+
}
|
|
2419
|
+
throw error;
|
|
2308
2420
|
}
|
|
2309
|
-
return action === "check" ? runCheck(argv.slice(2)) : runHelp(argv.slice(2));
|
|
2310
2421
|
}
|
|
2311
2422
|
const args = process.argv.slice(2);
|
|
2312
2423
|
try {
|
|
2313
2424
|
process.exitCode = await main(args);
|
|
2314
2425
|
}
|
|
2315
2426
|
catch (error) {
|
|
2316
|
-
process.exitCode = cliError(error instanceof Error ? error : new Error(String(error)),
|
|
2427
|
+
process.exitCode = cliError(error instanceof Error ? error : new Error(String(error)), 70, null, jsonRequested(args));
|
|
2317
2428
|
}
|
|
2318
2429
|
//# sourceMappingURL=cli.js.map
|