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.
Files changed (99) hide show
  1. package/CHANGELOG.md +78 -28
  2. package/README.md +173 -95
  3. package/dist/application/agent-help.d.ts +3 -0
  4. package/dist/application/agent-help.d.ts.map +1 -0
  5. package/dist/application/agent-help.js +5 -0
  6. package/dist/application/agent-help.js.map +1 -0
  7. package/dist/application/analyze.js +5 -5
  8. package/dist/application/analyze.js.map +1 -1
  9. package/dist/application/init.d.ts +42 -0
  10. package/dist/application/init.d.ts.map +1 -0
  11. package/dist/application/init.js +217 -0
  12. package/dist/application/init.js.map +1 -0
  13. package/dist/application/mutate.d.ts.map +1 -1
  14. package/dist/application/mutate.js +28 -15
  15. package/dist/application/mutate.js.map +1 -1
  16. package/dist/application/project.d.ts +25 -0
  17. package/dist/application/project.d.ts.map +1 -0
  18. package/dist/application/project.js +54 -0
  19. package/dist/application/project.js.map +1 -0
  20. package/dist/cli.js +419 -331
  21. package/dist/cli.js.map +1 -1
  22. package/dist/command/discovery.d.ts +30 -0
  23. package/dist/command/discovery.d.ts.map +1 -0
  24. package/dist/command/discovery.js +625 -0
  25. package/dist/command/discovery.js.map +1 -0
  26. package/dist/command/registry.d.ts +84 -0
  27. package/dist/command/registry.d.ts.map +1 -0
  28. package/dist/command/registry.js +951 -0
  29. package/dist/command/registry.js.map +1 -0
  30. package/dist/command/usage.d.ts +42 -0
  31. package/dist/command/usage.d.ts.map +1 -0
  32. package/dist/command/usage.js +337 -0
  33. package/dist/command/usage.js.map +1 -0
  34. package/dist/conversion/mermaid-import.js +56 -56
  35. package/dist/conversion/mermaid-import.js.map +1 -1
  36. package/dist/conversion/mermaid.js +1 -1
  37. package/dist/conversion/mermaid.js.map +1 -1
  38. package/dist/guidance/profile.d.ts +6 -0
  39. package/dist/guidance/profile.d.ts.map +1 -0
  40. package/dist/guidance/profile.js +1158 -0
  41. package/dist/guidance/profile.js.map +1 -0
  42. package/dist/guidance/projection.d.ts +16 -0
  43. package/dist/guidance/projection.d.ts.map +1 -0
  44. package/dist/guidance/projection.js +276 -0
  45. package/dist/guidance/projection.js.map +1 -0
  46. package/dist/guidance/query.d.ts +4 -0
  47. package/dist/guidance/query.d.ts.map +1 -0
  48. package/dist/guidance/query.js +178 -0
  49. package/dist/guidance/query.js.map +1 -0
  50. package/dist/guidance/text.d.ts +4 -0
  51. package/dist/guidance/text.d.ts.map +1 -0
  52. package/dist/guidance/text.js +118 -0
  53. package/dist/guidance/text.js.map +1 -0
  54. package/dist/guidance/types.d.ts +227 -0
  55. package/dist/guidance/types.d.ts.map +1 -0
  56. package/dist/guidance/types.js +54 -0
  57. package/dist/guidance/types.js.map +1 -0
  58. package/dist/guidance/validator.d.ts +8 -0
  59. package/dist/guidance/validator.d.ts.map +1 -0
  60. package/dist/guidance/validator.js +383 -0
  61. package/dist/guidance/validator.js.map +1 -0
  62. package/dist/help/guide.d.ts +12 -0
  63. package/dist/help/guide.d.ts.map +1 -0
  64. package/dist/help/guide.js +103 -0
  65. package/dist/help/guide.js.map +1 -0
  66. package/dist/help/registry.d.ts.map +1 -1
  67. package/dist/help/registry.js +76 -67
  68. package/dist/help/registry.js.map +1 -1
  69. package/dist/index.d.ts +21 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +13 -0
  72. package/dist/index.js.map +1 -1
  73. package/dist/io/safe-write.js +17 -17
  74. package/dist/io/safe-write.js.map +1 -1
  75. package/dist/mutation/gate.d.ts +5 -0
  76. package/dist/mutation/gate.d.ts.map +1 -0
  77. package/dist/mutation/gate.js +156 -0
  78. package/dist/mutation/gate.js.map +1 -0
  79. package/dist/mutation/milestone.js +26 -26
  80. package/dist/mutation/milestone.js.map +1 -1
  81. package/dist/mutation/project.d.ts +5 -0
  82. package/dist/mutation/project.d.ts.map +1 -0
  83. package/dist/mutation/project.js +148 -0
  84. package/dist/mutation/project.js.map +1 -0
  85. package/dist/mutation/resource.js +22 -22
  86. package/dist/mutation/resource.js.map +1 -1
  87. package/dist/mutation/task.js +40 -40
  88. package/dist/mutation/task.js.map +1 -1
  89. package/dist/mutation/types.d.ts +46 -1
  90. package/dist/mutation/types.d.ts.map +1 -1
  91. package/dist/parser/document-parser.js +17 -17
  92. package/dist/parser/document-parser.js.map +1 -1
  93. package/dist/semantic/validator.js +41 -41
  94. package/dist/semantic/validator.js.map +1 -1
  95. package/dist/version.d.ts +1 -1
  96. package/dist/version.d.ts.map +1 -1
  97. package/dist/version.js +1 -1
  98. package/dist/version.js.map +1 -1
  99. package/package.json +4 -4
@@ -0,0 +1,951 @@
1
+ const statusMeanings = {
2
+ 0: "Successful operation.",
3
+ 1: "Document, analysis, validation, help lookup, or warning-policy failure.",
4
+ 2: "CLI usage error.",
5
+ 3: "Input, output, or encoding error.",
6
+ 4: "Loss detected in strict conversion.",
7
+ 5: "Optimistic-lock, symlink, or atomic-write conflict.",
8
+ 70: "Internal invariant or programmer error.",
9
+ };
10
+ function exitStatuses(...codes) {
11
+ return codes.map((code) => Object.freeze({ code, meaning: statusMeanings[code] }));
12
+ }
13
+ function spelling(name, dsl, json) {
14
+ return Object.freeze({
15
+ cli: `--${name}`,
16
+ dsl,
17
+ json: json ?? name.replaceAll("-", "_"),
18
+ });
19
+ }
20
+ function valueOption(name, config = {}) {
21
+ return Object.freeze({
22
+ name,
23
+ kind: "value",
24
+ valueType: config.valueType ?? "string",
25
+ required: config.required ?? false,
26
+ repeatable: config.repeatable ?? false,
27
+ defaultValue: config.defaultValue ?? null,
28
+ enumValues: Object.freeze([...(config.enumValues ?? [])]),
29
+ conflicts: Object.freeze([...(config.conflicts ?? [])]),
30
+ requires: Object.freeze([...(config.requires ?? [])]),
31
+ sharedGroup: config.sharedGroup ?? null,
32
+ spelling: spelling(name, config.dsl ?? null, config.json),
33
+ });
34
+ }
35
+ function flagOption(name, config = {}) {
36
+ return Object.freeze({
37
+ name,
38
+ kind: "flag",
39
+ valueType: null,
40
+ required: config.required ?? false,
41
+ repeatable: false,
42
+ defaultValue: config.defaultValue ?? false,
43
+ enumValues: Object.freeze([]),
44
+ conflicts: Object.freeze([...(config.conflicts ?? [])]),
45
+ requires: Object.freeze([...(config.requires ?? [])]),
46
+ sharedGroup: config.sharedGroup ?? null,
47
+ spelling: spelling(name, config.dsl ?? null, config.json),
48
+ });
49
+ }
50
+ const sharedOptionGroups = Object.freeze({
51
+ result: Object.freeze([
52
+ valueOption("format", {
53
+ valueType: "output-format",
54
+ defaultValue: "text",
55
+ enumValues: ["text", "json"],
56
+ sharedGroup: "result",
57
+ }),
58
+ valueOption("color", {
59
+ valueType: "color-mode",
60
+ defaultValue: "auto",
61
+ enumValues: ["auto", "always", "never"],
62
+ conflicts: ["format=json when color=always"],
63
+ sharedGroup: "result",
64
+ }),
65
+ ]),
66
+ diagnostics: Object.freeze([
67
+ valueOption("max-diagnostics", {
68
+ valueType: "integer",
69
+ defaultValue: 100,
70
+ sharedGroup: "diagnostics",
71
+ }),
72
+ flagOption("warnings-as-errors", { sharedGroup: "diagnostics" }),
73
+ ]),
74
+ preview: Object.freeze([
75
+ flagOption("diff", {
76
+ conflicts: ["write", "out"],
77
+ sharedGroup: "preview",
78
+ }),
79
+ ]),
80
+ write: Object.freeze([
81
+ flagOption("write", {
82
+ conflicts: ["out", "stdin"],
83
+ sharedGroup: "write",
84
+ }),
85
+ valueOption("out", {
86
+ valueType: "path",
87
+ conflicts: ["write", "diff"],
88
+ sharedGroup: "write",
89
+ }),
90
+ valueOption("expect-digest", {
91
+ valueType: "sha256-digest",
92
+ requires: ["write"],
93
+ sharedGroup: "write",
94
+ }),
95
+ ]),
96
+ guide: Object.freeze([
97
+ valueOption("level", {
98
+ valueType: "help-level",
99
+ defaultValue: "index without a topic; quick with a topic",
100
+ enumValues: ["index", "quick", "detail"],
101
+ sharedGroup: "guide",
102
+ }),
103
+ ]),
104
+ });
105
+ function operand(name, position, required = true, valueType = "string") {
106
+ return Object.freeze({ name, valueType, required, position });
107
+ }
108
+ const documentOperand = operand("file", 0, true, "path-or-stdin");
109
+ const mutationOperands = Object.freeze([
110
+ documentOperand,
111
+ operand("id", 1, true, "identifier"),
112
+ ]);
113
+ const editingGroups = ["result", "diagnostics", "preview", "write"];
114
+ const readDocumentGroups = ["result", "diagnostics"];
115
+ const noStdin = Object.freeze({
116
+ document: false,
117
+ artifact: false,
118
+ request: false,
119
+ mutuallyExclusive: false,
120
+ });
121
+ const documentStdin = Object.freeze({
122
+ document: true,
123
+ artifact: false,
124
+ request: false,
125
+ mutuallyExclusive: false,
126
+ });
127
+ const artifactStdin = Object.freeze({
128
+ document: false,
129
+ artifact: true,
130
+ request: false,
131
+ mutuallyExclusive: false,
132
+ });
133
+ const mutationRequestStdin = Object.freeze({
134
+ document: true,
135
+ artifact: false,
136
+ request: true,
137
+ mutuallyExclusive: true,
138
+ });
139
+ const previewHelp = " [--diff] [--write [--expect-digest <digest>] | --out <path>] [--max-diagnostics <integer>] [--warnings-as-errors] [--format text|json] [--color auto|always|never]";
140
+ function expandOptions(definition) {
141
+ const expanded = [];
142
+ const names = new Set();
143
+ for (const group of definition.optionGroups ?? []) {
144
+ for (const option of sharedOptionGroups[group]) {
145
+ if (names.has(option.name)) {
146
+ throw new Error(`duplicate command option --${option.name} in ${definition.operation}`);
147
+ }
148
+ names.add(option.name);
149
+ expanded.push(option);
150
+ }
151
+ }
152
+ for (const option of definition.options ?? []) {
153
+ if (names.has(option.name)) {
154
+ throw new Error(`duplicate command option --${option.name} in ${definition.operation}`);
155
+ }
156
+ names.add(option.name);
157
+ expanded.push(option);
158
+ }
159
+ return Object.freeze(expanded);
160
+ }
161
+ function defineCommand(definition) {
162
+ const output = definition.effect === "read"
163
+ ? Object.freeze({
164
+ formats: Object.freeze(["text", "json"]),
165
+ payload: "result",
166
+ fileEffect: "none",
167
+ })
168
+ : definition.handler === "render"
169
+ ? Object.freeze({
170
+ formats: Object.freeze(["text", "json"]),
171
+ payload: "artifact",
172
+ fileEffect: "optional-create",
173
+ })
174
+ : definition.handler === "import"
175
+ ? Object.freeze({
176
+ formats: Object.freeze(["text", "json"]),
177
+ payload: "candidate-document",
178
+ fileEffect: "optional-create",
179
+ })
180
+ : Object.freeze({
181
+ formats: Object.freeze(["text", "json"]),
182
+ payload: "candidate-document",
183
+ fileEffect: "optional-write-or-create",
184
+ });
185
+ return Object.freeze({
186
+ contractVersion: 2,
187
+ path: Object.freeze([...definition.path]),
188
+ operation: definition.operation,
189
+ handler: definition.handler,
190
+ summary: definition.summary,
191
+ operands: Object.freeze([...definition.operands]),
192
+ options: expandOptions(definition),
193
+ input: definition.input,
194
+ output,
195
+ stdin: definition.stdin,
196
+ effect: definition.effect,
197
+ resultSchemas: Object.freeze([...definition.resultSchemas]),
198
+ exitStatuses: Object.freeze([...definition.exitStatuses]),
199
+ examples: Object.freeze([...definition.examples]),
200
+ textHelp: Object.freeze([...definition.textHelp]),
201
+ topLevelUsage: definition.topLevelUsage,
202
+ });
203
+ }
204
+ function example(id, invocation, summary) {
205
+ return Object.freeze({ id, invocation, summary });
206
+ }
207
+ const taskEstimateOptions = Object.freeze([
208
+ valueOption("duration", {
209
+ valueType: "duration",
210
+ dsl: "duration",
211
+ conflicts: ["optimistic", "most-likely", "pessimistic"],
212
+ }),
213
+ valueOption("optimistic", {
214
+ valueType: "duration",
215
+ dsl: "estimate.optimistic",
216
+ conflicts: ["duration"],
217
+ requires: ["most-likely", "pessimistic"],
218
+ }),
219
+ valueOption("most-likely", {
220
+ valueType: "duration",
221
+ dsl: "estimate.most_likely",
222
+ conflicts: ["duration"],
223
+ requires: ["optimistic", "pessimistic"],
224
+ }),
225
+ valueOption("pessimistic", {
226
+ valueType: "duration",
227
+ dsl: "estimate.pessimistic",
228
+ conflicts: ["duration"],
229
+ requires: ["optimistic", "most-likely"],
230
+ }),
231
+ ]);
232
+ const taskMetadataOptions = Object.freeze([
233
+ valueOption("title", { dsl: "title" }),
234
+ valueOption("description", { dsl: "description" }),
235
+ valueOption("status", {
236
+ valueType: "task-status",
237
+ enumValues: ["planned", "active", "blocked", "done"],
238
+ dsl: "status",
239
+ }),
240
+ valueOption("priority", { valueType: "integer", dsl: "priority" }),
241
+ valueOption("owner", { dsl: "owner" }),
242
+ valueOption("blocked-reason", { dsl: "blocked_reason" }),
243
+ valueOption("source", { dsl: "source" }),
244
+ ]);
245
+ const commandDefinitions = [
246
+ {
247
+ path: ["dsl", "check"],
248
+ operation: "dsl.check",
249
+ handler: "check",
250
+ summary: "Validates one PERT document without changing it.",
251
+ operands: [documentOperand],
252
+ optionGroups: readDocumentGroups,
253
+ input: "document",
254
+ stdin: documentStdin,
255
+ effect: "read",
256
+ resultSchemas: ["Perttool.CheckResult.v1", "Perttool.CliError.v1"],
257
+ exitStatuses: exitStatuses(0, 1, 2, 3, 70),
258
+ examples: [example("check", "perttool dsl check plan.pert", "Validate a plan.")],
259
+ textHelp: [
260
+ "Usage: perttool dsl check <file>",
261
+ " [--warnings-as-errors]",
262
+ " [--max-diagnostics <integer>]",
263
+ " [--format text|json]",
264
+ " [--color auto|always|never]",
265
+ ],
266
+ topLevelUsage: " perttool dsl check <file> [--format text|json]",
267
+ },
268
+ {
269
+ path: ["dsl", "format"],
270
+ operation: "dsl.format",
271
+ handler: "format",
272
+ summary: "Formats one PERT document and previews changes by default.",
273
+ operands: [documentOperand],
274
+ optionGroups: editingGroups,
275
+ options: [
276
+ flagOption("check", { conflicts: ["write", "out"] }),
277
+ ],
278
+ input: "document",
279
+ stdin: documentStdin,
280
+ effect: "preview",
281
+ resultSchemas: ["Perttool.FormatResult.v1", "Perttool.CliError.v1"],
282
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
283
+ examples: [example("diff", "perttool dsl format plan.pert --diff", "Preview a formatting diff.")],
284
+ textHelp: [
285
+ "Usage: perttool dsl format <file>",
286
+ " [--check] [--diff]",
287
+ " [--write [--expect-digest <digest>] | --out <path>]",
288
+ " [--max-diagnostics <integer>] [--warnings-as-errors]",
289
+ " [--format text|json] [--color auto|always|never]",
290
+ ],
291
+ topLevelUsage: " perttool dsl format <file> [--check] [--diff] [--format text|json]",
292
+ },
293
+ {
294
+ path: ["dsl", "help"],
295
+ operation: "dsl.help",
296
+ handler: "domain-help",
297
+ summary: "Displays bundled DSL and workflow guidance.",
298
+ operands: [
299
+ operand("topic", 0, false, "help-topic"),
300
+ operand("subtopic", 1, false, "help-topic"),
301
+ ],
302
+ optionGroups: ["guide", "result"],
303
+ input: "none",
304
+ stdin: noStdin,
305
+ effect: "read",
306
+ resultSchemas: ["Perttool.HelpResult.v1", "Perttool.CliError.v1"],
307
+ exitStatuses: exitStatuses(0, 1, 2, 70),
308
+ examples: [example("syntax", "perttool dsl help syntax --level quick", "Show quick syntax guidance.")],
309
+ textHelp: [
310
+ "Usage: perttool dsl help [topic [subtopic]]",
311
+ " [--level index|quick|detail]",
312
+ " [--format text|json]",
313
+ " [--color auto|always|never]",
314
+ ],
315
+ topLevelUsage: " perttool dsl help [topic [subtopic]] [--level index|quick|detail] [--format text|json]",
316
+ },
317
+ {
318
+ path: ["agent", "help"],
319
+ operation: "agent.help",
320
+ handler: "agent-help",
321
+ summary: "Displays read-only AI agent guidance for each provider from bundled offline profiles.",
322
+ operands: [
323
+ operand("provider", 0, false, "provider-id"),
324
+ operand("surface", 1, false, "surface-id"),
325
+ ],
326
+ optionGroups: ["guide", "result"],
327
+ input: "none",
328
+ stdin: noStdin,
329
+ effect: "read",
330
+ resultSchemas: ["Perttool.AgentGuidanceResult.v1", "Perttool.CliError.v1"],
331
+ exitStatuses: exitStatuses(0, 1, 2, 70),
332
+ examples: [example("index", "perttool agent help", "List bundled provider guidance.")],
333
+ textHelp: [
334
+ "Usage: perttool agent help [<provider> [<surface>]]",
335
+ " [--level index|quick|detail]",
336
+ " [--format text|json]",
337
+ " [--color auto|always|never]",
338
+ ],
339
+ topLevelUsage: " perttool agent help [provider [surface]] [--level index|quick|detail] [--format text|json]",
340
+ },
341
+ {
342
+ path: ["project", "show"],
343
+ operation: "project.show",
344
+ handler: "project-show",
345
+ summary: "Displays effective project metadata.",
346
+ operands: [documentOperand],
347
+ optionGroups: readDocumentGroups,
348
+ input: "document",
349
+ stdin: documentStdin,
350
+ effect: "read",
351
+ resultSchemas: ["Perttool.ProjectResult.v1", "Perttool.CliError.v1"],
352
+ exitStatuses: exitStatuses(0, 1, 2, 3, 70),
353
+ examples: [example("show", "perttool project show plan.pert", "Read project metadata.")],
354
+ textHelp: [
355
+ "Usage: perttool project show <file>",
356
+ " [--max-diagnostics <integer>] [--warnings-as-errors]",
357
+ " [--format text|json] [--color auto|always|never]",
358
+ ],
359
+ topLevelUsage: " perttool project show <file> [--format text|json]",
360
+ },
361
+ {
362
+ path: ["project", "set"],
363
+ operation: "project.set",
364
+ handler: "mutation",
365
+ summary: "Previews source-preserving project metadata changes.",
366
+ operands: [documentOperand],
367
+ optionGroups: editingGroups,
368
+ options: [
369
+ valueOption("id", { dsl: "project.id" }),
370
+ valueOption("version", { valueType: "integer", dsl: "version" }),
371
+ valueOption("title", { dsl: "title" }),
372
+ valueOption("description", { dsl: "description" }),
373
+ valueOption("as-of", { valueType: "date-or-date-time", dsl: "as_of" }),
374
+ valueOption("duration-unit", {
375
+ valueType: "duration-unit",
376
+ enumValues: ["day", "hour", "point"],
377
+ dsl: "duration_unit",
378
+ }),
379
+ valueOption("velocity", { valueType: "velocity", dsl: "velocity" }),
380
+ valueOption("finish", { valueType: "milestone-id", dsl: "finish" }),
381
+ valueOption("critical-epsilon", { valueType: "duration", dsl: "critical_epsilon" }),
382
+ valueOption("target-duration", { valueType: "duration", dsl: "target_duration" }),
383
+ valueOption("clear", {
384
+ valueType: "project-field",
385
+ repeatable: true,
386
+ enumValues: ["description", "as_of", "velocity", "critical_epsilon", "target_duration"],
387
+ }),
388
+ ],
389
+ input: "document",
390
+ stdin: documentStdin,
391
+ effect: "preview",
392
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
393
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
394
+ examples: [example("velocity", "perttool project set plan.pert --velocity 29p/2d --diff", "Preview a velocity update.")],
395
+ textHelp: [
396
+ "Usage: perttool project set <file> [field options]",
397
+ " [--id <id>] [--version <integer>] [--title <text>] [--description <text>]",
398
+ " [--as-of <date-or-date-time>] [--duration-unit day|hour|point]",
399
+ " [--velocity <velocity>] [--finish <milestone-id>]",
400
+ " [--critical-epsilon <duration>] [--target-duration <duration>]",
401
+ " [--clear description|as_of|velocity|critical_epsilon|target_duration]...",
402
+ " [--diff] [--write [--expect-digest <digest>] | --out <path>]",
403
+ " [--max-diagnostics <integer>] [--warnings-as-errors]",
404
+ " [--format text|json] [--color auto|always|never]",
405
+ ],
406
+ topLevelUsage: " perttool project set <file> [field options] [--diff] [--write | --out <path>]",
407
+ },
408
+ {
409
+ path: ["dag", "analyze"],
410
+ operation: "dag.analyze",
411
+ handler: "analyze",
412
+ summary: "Analyzes precedence and resource-constrained schedules.",
413
+ operands: [documentOperand],
414
+ optionGroups: readDocumentGroups,
415
+ options: [
416
+ valueOption("schedule", {
417
+ valueType: "analysis-mode",
418
+ defaultValue: "both",
419
+ enumValues: ["precedence", "resource", "both"],
420
+ }),
421
+ valueOption("capacity", {
422
+ valueType: "resource-capacity",
423
+ repeatable: true,
424
+ }),
425
+ valueOption("max-paths", { valueType: "integer", defaultValue: 1 }),
426
+ valueOption("precision", { valueType: "integer", defaultValue: 3 }),
427
+ ],
428
+ input: "document",
429
+ stdin: documentStdin,
430
+ effect: "read",
431
+ resultSchemas: ["Perttool.AnalysisResult.v2", "Perttool.CliError.v1"],
432
+ exitStatuses: exitStatuses(0, 1, 2, 3, 70),
433
+ examples: [example("both", "perttool dag analyze plan.pert --schedule both", "Analyze both schedule views.")],
434
+ textHelp: [
435
+ "Usage: perttool dag analyze <file>",
436
+ " [--schedule precedence|resource|both]",
437
+ " [--capacity <resource-id>=<integer>]...",
438
+ " [--max-paths <integer>] [--precision <integer>]",
439
+ " [--max-diagnostics <integer>]",
440
+ " [--warnings-as-errors]",
441
+ " [--format text|json] [--color auto|always|never]",
442
+ ],
443
+ topLevelUsage: " perttool dag analyze <file> [--schedule precedence|resource|both] [--format text|json]",
444
+ },
445
+ {
446
+ path: ["dag", "next"],
447
+ operation: "dag.next",
448
+ handler: "next",
449
+ summary: "Selects the next resource-feasible task set with a complete explanation.",
450
+ operands: [documentOperand],
451
+ optionGroups: readDocumentGroups,
452
+ options: [
453
+ valueOption("capacity", { valueType: "resource-capacity", repeatable: true }),
454
+ valueOption("explain-depth", { valueType: "integer", defaultValue: 1 }),
455
+ valueOption("precision", { valueType: "integer", defaultValue: 3 }),
456
+ ],
457
+ input: "document",
458
+ stdin: documentStdin,
459
+ effect: "read",
460
+ resultSchemas: ["Perttool.NextResult.v3", "Perttool.CliError.v1"],
461
+ exitStatuses: exitStatuses(0, 1, 2, 3, 70),
462
+ examples: [example("json", "perttool dag next plan.pert --format json", "Return recommendation authority as JSON.")],
463
+ textHelp: [
464
+ "Usage: perttool dag next <file>",
465
+ " [--capacity <resource-id>=<integer>]...",
466
+ " [--explain-depth <integer>] [--precision <integer>]",
467
+ " [--max-diagnostics <integer>]",
468
+ " [--warnings-as-errors]",
469
+ " [--format text|json] [--color auto|always|never]",
470
+ "Output: Perttool.NextResult.v3 with a complete recommendation graph in JSON.",
471
+ "Consumers must inspect schema_version before using recommendation authority.",
472
+ ],
473
+ topLevelUsage: " perttool dag next <file> [--capacity <resource-id>=<integer>] [--format text|json]",
474
+ },
475
+ {
476
+ path: ["dag", "advance"],
477
+ operation: "dag.advance",
478
+ handler: "advance",
479
+ summary: "Previews removal of completed history from a PERT document.",
480
+ operands: [documentOperand],
481
+ optionGroups: editingGroups,
482
+ input: "document",
483
+ stdin: documentStdin,
484
+ effect: "preview",
485
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
486
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
487
+ examples: [example("diff", "perttool dag advance plan.pert --diff", "Preview an advance diff.")],
488
+ textHelp: [
489
+ "Usage: perttool dag advance <file>",
490
+ " [--diff] [--write [--expect-digest <digest>] | --out <path>]",
491
+ " [--max-diagnostics <integer>] [--warnings-as-errors]",
492
+ " [--format text|json] [--color auto|always|never]",
493
+ ],
494
+ topLevelUsage: " perttool dag advance <file> [--diff] [--write | --out <path>] [--format text|json]",
495
+ },
496
+ {
497
+ path: ["dag", "render"],
498
+ operation: "dag.render",
499
+ handler: "render",
500
+ summary: "Exports a PERT document as a Mermaid artifact.",
501
+ operands: [documentOperand],
502
+ optionGroups: readDocumentGroups,
503
+ options: [
504
+ valueOption("to", {
505
+ valueType: "artifact-format",
506
+ required: true,
507
+ enumValues: ["mermaid"],
508
+ }),
509
+ valueOption("profile", {
510
+ valueType: "mermaid-profile",
511
+ defaultValue: "perttool",
512
+ enumValues: ["perttool", "plain"],
513
+ }),
514
+ valueOption("analysis", {
515
+ valueType: "mermaid-analysis-mode",
516
+ defaultValue: "none",
517
+ enumValues: ["none", "precedence", "resource", "both"],
518
+ }),
519
+ valueOption("capacity", {
520
+ valueType: "resource-capacity",
521
+ repeatable: true,
522
+ requires: ["analysis=resource|both"],
523
+ }),
524
+ flagOption("strict-loss"),
525
+ valueOption("out", { valueType: "path" }),
526
+ ],
527
+ input: "document",
528
+ stdin: documentStdin,
529
+ effect: "write-or-create",
530
+ resultSchemas: ["Perttool.ExportResult.v1", "Perttool.CliError.v1"],
531
+ exitStatuses: exitStatuses(0, 1, 2, 3, 4, 5, 70),
532
+ examples: [example("mermaid", "perttool dag render plan.pert --to mermaid", "Preview Mermaid output.")],
533
+ textHelp: [
534
+ "Usage: perttool dag render <file> --to mermaid",
535
+ " [--profile perttool|plain] [--analysis none|precedence|resource|both]",
536
+ " [--capacity <resource-id>=<integer>]... [--strict-loss] [--out <path>]",
537
+ " [--max-diagnostics <integer>] [--warnings-as-errors]",
538
+ " [--format text|json] [--color auto|always|never]",
539
+ ],
540
+ topLevelUsage: " perttool dag render <file> --to mermaid [--profile perttool|plain] [--format text|json]",
541
+ },
542
+ {
543
+ path: ["dag", "import"],
544
+ operation: "dag.import",
545
+ handler: "import",
546
+ summary: "Imports a Mermaid artifact as a candidate PERT document.",
547
+ operands: [documentOperand],
548
+ optionGroups: readDocumentGroups,
549
+ options: [
550
+ valueOption("from", {
551
+ valueType: "artifact-format",
552
+ required: true,
553
+ enumValues: ["mermaid"],
554
+ }),
555
+ flagOption("strict-loss"),
556
+ valueOption("out", { valueType: "path" }),
557
+ ],
558
+ input: "artifact",
559
+ stdin: artifactStdin,
560
+ effect: "write-or-create",
561
+ resultSchemas: ["Perttool.ImportResult.v1", "Perttool.CliError.v1"],
562
+ exitStatuses: exitStatuses(0, 1, 2, 3, 4, 5, 70),
563
+ examples: [example("mermaid", "perttool dag import graph.mmd --from mermaid", "Preview imported DSL.")],
564
+ textHelp: [
565
+ "Usage: perttool dag import <file> --from mermaid",
566
+ " [--strict-loss] [--out <path>]",
567
+ " [--max-diagnostics <integer>] [--warnings-as-errors]",
568
+ " [--format text|json] [--color auto|always|never]",
569
+ ],
570
+ topLevelUsage: " perttool dag import <file> --from mermaid [--strict-loss] [--out <path>] [--format text|json]",
571
+ },
572
+ {
573
+ path: ["task", "add"],
574
+ operation: "task.add",
575
+ handler: "mutation",
576
+ summary: "Previews adding one task.",
577
+ operands: [
578
+ documentOperand,
579
+ operand("id", 1, true, "task-id"),
580
+ operand("from", 2, true, "milestone-id"),
581
+ operand("to", 3, true, "milestone-id"),
582
+ ],
583
+ optionGroups: editingGroups,
584
+ options: [
585
+ ...taskMetadataOptions.map((option) => option.name === "title"
586
+ ? valueOption("title", { required: true, dsl: "title" })
587
+ : option),
588
+ ...taskEstimateOptions,
589
+ valueOption("tag", { valueType: "tag", repeatable: true, dsl: "tags" }),
590
+ valueOption("require", {
591
+ valueType: "resource-requirement",
592
+ repeatable: true,
593
+ dsl: "requires",
594
+ }),
595
+ ],
596
+ input: "document",
597
+ stdin: documentStdin,
598
+ effect: "preview",
599
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
600
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
601
+ examples: [example("add", "perttool task add plan.pert BUILD M1 M2 --title Build --duration 2p --diff", "Preview adding a task.")],
602
+ textHelp: [
603
+ "Usage: perttool task add <file> <id> <from> <to>",
604
+ " --title <text> (--duration <duration> | --optimistic <duration> --most-likely <duration> --pessimistic <duration>)",
605
+ " [--description <text>] [--status planned|active|blocked|done] [--priority <integer>]",
606
+ " [--owner <text>] [--blocked-reason <text>] [--source <text>] [--tag <tag>]... [--require <resource-id>=<integer>]...",
607
+ previewHelp,
608
+ ],
609
+ topLevelUsage: " perttool task add|set|remove|finish ...",
610
+ },
611
+ {
612
+ path: ["task", "set"],
613
+ operation: "task.set",
614
+ handler: "mutation",
615
+ summary: "Previews source-preserving task field changes.",
616
+ operands: mutationOperands,
617
+ optionGroups: editingGroups,
618
+ options: [
619
+ valueOption("from", { valueType: "milestone-id" }),
620
+ valueOption("to", { valueType: "milestone-id" }),
621
+ ...taskMetadataOptions,
622
+ ...taskEstimateOptions,
623
+ valueOption("require", { valueType: "resource-requirement", repeatable: true, dsl: "requires" }),
624
+ valueOption("add-tag", { valueType: "tag", repeatable: true, dsl: "tags" }),
625
+ valueOption("remove-tag", { valueType: "tag", repeatable: true, dsl: "tags" }),
626
+ valueOption("remove-require", { valueType: "resource-id", repeatable: true, dsl: "requires" }),
627
+ valueOption("clear", { valueType: "task-field", repeatable: true }),
628
+ ],
629
+ input: "document",
630
+ stdin: documentStdin,
631
+ effect: "preview",
632
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
633
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
634
+ examples: [example("set", "perttool task set plan.pert BUILD --status active --diff", "Preview changing task status.")],
635
+ textHelp: [
636
+ "Usage: perttool task set <file> <id> [field options]",
637
+ " [--from <id>] [--to <id>] [--title <text>] [--description <text>] [--duration <duration>]",
638
+ " [--optimistic <duration> --most-likely <duration> --pessimistic <duration>]",
639
+ " [--status planned|active|blocked|done] [--priority <integer>] [--owner <text>]",
640
+ " [--blocked-reason <text>] [--source <text>] [--require <resource-id>=<integer>]...",
641
+ " [--add-tag <tag>]... [--remove-tag <tag>]... [--remove-require <resource-id>]... [--clear <field>]...",
642
+ previewHelp,
643
+ ],
644
+ topLevelUsage: null,
645
+ },
646
+ ...["remove", "finish"].map((action) => ({
647
+ path: ["task", action],
648
+ operation: `task.${action}`,
649
+ handler: "mutation",
650
+ summary: action === "remove" ? "Previews removing one task." : "Previews marking one task done.",
651
+ operands: mutationOperands,
652
+ optionGroups: editingGroups,
653
+ input: "document",
654
+ stdin: documentStdin,
655
+ effect: "preview",
656
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
657
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
658
+ examples: [example(action, `perttool task ${action} plan.pert BUILD --diff`, `Preview task ${action}.`)],
659
+ textHelp: [
660
+ `Usage: perttool task ${action} <file> <id>`,
661
+ previewHelp,
662
+ ],
663
+ topLevelUsage: null,
664
+ })),
665
+ {
666
+ path: ["milestone", "add"],
667
+ operation: "milestone.add",
668
+ handler: "mutation",
669
+ summary: "Previews adding one milestone.",
670
+ operands: mutationOperands,
671
+ optionGroups: editingGroups,
672
+ options: [
673
+ valueOption("title", { required: true, dsl: "title" }),
674
+ valueOption("description", { dsl: "description" }),
675
+ valueOption("state", {
676
+ valueType: "milestone-state",
677
+ enumValues: ["planned", "reached"],
678
+ dsl: "state",
679
+ }),
680
+ valueOption("tag", { valueType: "tag", repeatable: true, dsl: "tags" }),
681
+ ],
682
+ input: "document",
683
+ stdin: documentStdin,
684
+ effect: "preview",
685
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
686
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
687
+ examples: [example("add", "perttool milestone add plan.pert M2 --title Done --diff", "Preview adding a milestone.")],
688
+ textHelp: [
689
+ "Usage: perttool milestone add <file> <id> --title <text>",
690
+ " [--description <text>] [--state planned|reached] [--tag <tag>]...",
691
+ previewHelp,
692
+ ],
693
+ topLevelUsage: " perttool milestone add|set|remove ...",
694
+ },
695
+ {
696
+ path: ["milestone", "set"],
697
+ operation: "milestone.set",
698
+ handler: "mutation",
699
+ summary: "Previews source-preserving milestone field changes.",
700
+ operands: mutationOperands,
701
+ optionGroups: editingGroups,
702
+ options: [
703
+ valueOption("title", { dsl: "title" }),
704
+ valueOption("description", { dsl: "description" }),
705
+ valueOption("state", {
706
+ valueType: "milestone-state",
707
+ enumValues: ["planned", "reached"],
708
+ dsl: "state",
709
+ }),
710
+ valueOption("add-tag", { valueType: "tag", repeatable: true, dsl: "tags" }),
711
+ valueOption("remove-tag", { valueType: "tag", repeatable: true, dsl: "tags" }),
712
+ valueOption("clear", {
713
+ valueType: "milestone-field",
714
+ repeatable: true,
715
+ enumValues: ["description", "state", "tags"],
716
+ }),
717
+ ],
718
+ input: "document",
719
+ stdin: documentStdin,
720
+ effect: "preview",
721
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
722
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
723
+ examples: [example("set", "perttool milestone set plan.pert M2 --state reached --diff", "Preview changing milestone state.")],
724
+ textHelp: [
725
+ "Usage: perttool milestone set <file> <id> [--title <text>] [--description <text>] [--state planned|reached]",
726
+ " [--add-tag <tag>]... [--remove-tag <tag>]... [--clear description|state|tags]...",
727
+ previewHelp,
728
+ ],
729
+ topLevelUsage: null,
730
+ },
731
+ {
732
+ path: ["milestone", "remove"],
733
+ operation: "milestone.remove",
734
+ handler: "mutation",
735
+ summary: "Previews removing one milestone.",
736
+ operands: mutationOperands,
737
+ optionGroups: editingGroups,
738
+ input: "document",
739
+ stdin: documentStdin,
740
+ effect: "preview",
741
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
742
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
743
+ examples: [example("remove", "perttool milestone remove plan.pert M2 --diff", "Preview removing a milestone.")],
744
+ textHelp: ["Usage: perttool milestone remove <file> <id>", previewHelp],
745
+ topLevelUsage: null,
746
+ },
747
+ {
748
+ path: ["resource", "add"],
749
+ operation: "resource.add",
750
+ handler: "mutation",
751
+ summary: "Previews adding one resource.",
752
+ operands: mutationOperands,
753
+ optionGroups: editingGroups,
754
+ options: [
755
+ valueOption("title", { required: true, dsl: "title" }),
756
+ valueOption("description", { dsl: "description" }),
757
+ valueOption("capacity", { valueType: "integer", required: true, dsl: "capacity" }),
758
+ ],
759
+ input: "document",
760
+ stdin: documentStdin,
761
+ effect: "preview",
762
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
763
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
764
+ examples: [example("add", "perttool resource add plan.pert DEV --title Developers --capacity 2 --diff", "Preview adding a resource.")],
765
+ textHelp: [
766
+ "Usage: perttool resource add <file> <id> --title <text> --capacity <integer>",
767
+ " [--description <text>]",
768
+ previewHelp,
769
+ ],
770
+ topLevelUsage: " perttool resource add|set|remove ...",
771
+ },
772
+ {
773
+ path: ["resource", "set"],
774
+ operation: "resource.set",
775
+ handler: "mutation",
776
+ summary: "Previews source-preserving resource field changes.",
777
+ operands: mutationOperands,
778
+ optionGroups: editingGroups,
779
+ options: [
780
+ valueOption("title", { dsl: "title" }),
781
+ valueOption("description", { dsl: "description" }),
782
+ valueOption("capacity", { valueType: "integer", dsl: "capacity" }),
783
+ valueOption("clear", {
784
+ valueType: "resource-field",
785
+ repeatable: true,
786
+ enumValues: ["description"],
787
+ }),
788
+ ],
789
+ input: "document",
790
+ stdin: documentStdin,
791
+ effect: "preview",
792
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
793
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
794
+ examples: [example("set", "perttool resource set plan.pert DEV --capacity 3 --diff", "Preview changing capacity.")],
795
+ textHelp: [
796
+ "Usage: perttool resource set <file> <id> [--title <text>] [--description <text>] [--capacity <integer>] [--clear description]",
797
+ previewHelp,
798
+ ],
799
+ topLevelUsage: null,
800
+ },
801
+ {
802
+ path: ["resource", "remove"],
803
+ operation: "resource.remove",
804
+ handler: "mutation",
805
+ summary: "Previews removing one resource.",
806
+ operands: mutationOperands,
807
+ optionGroups: editingGroups,
808
+ input: "document",
809
+ stdin: documentStdin,
810
+ effect: "preview",
811
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
812
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
813
+ examples: [example("remove", "perttool resource remove plan.pert DEV --diff", "Preview removing a resource.")],
814
+ textHelp: ["Usage: perttool resource remove <file> <id>", previewHelp],
815
+ topLevelUsage: null,
816
+ },
817
+ {
818
+ path: ["mutation", "apply"],
819
+ operation: "mutation.apply",
820
+ handler: "mutation",
821
+ summary: "Previews one typed atomic mutation request.",
822
+ operands: [documentOperand],
823
+ optionGroups: editingGroups,
824
+ options: [
825
+ valueOption("request", {
826
+ valueType: "json-path-or-stdin",
827
+ required: true,
828
+ }),
829
+ ],
830
+ input: "document",
831
+ stdin: mutationRequestStdin,
832
+ effect: "preview",
833
+ resultSchemas: ["Perttool.MutationResult.v1", "Perttool.CliError.v1"],
834
+ exitStatuses: exitStatuses(0, 1, 2, 3, 5, 70),
835
+ examples: [example("apply", "perttool mutation apply plan.pert --request request.json --diff", "Preview an atomic mutation request.")],
836
+ textHelp: [
837
+ "Usage: perttool mutation apply <file> --request <json-file|->",
838
+ previewHelp,
839
+ ],
840
+ topLevelUsage: " perttool mutation apply <file> --request <json-file|-> [--diff] [--format text|json]",
841
+ },
842
+ ];
843
+ export const COMMAND_REGISTRY = Object.freeze(commandDefinitions.map(defineCommand));
844
+ const commandsByPath = new Map();
845
+ const commandsByOperation = new Map();
846
+ for (const descriptor of COMMAND_REGISTRY) {
847
+ const pathKey = descriptor.path.join("\0");
848
+ if (commandsByPath.has(pathKey)) {
849
+ throw new Error(`duplicate command path: ${descriptor.path.join(" ")}`);
850
+ }
851
+ if (commandsByOperation.has(descriptor.operation)) {
852
+ throw new Error(`duplicate command operation: ${descriptor.operation}`);
853
+ }
854
+ commandsByPath.set(pathKey, descriptor);
855
+ commandsByOperation.set(descriptor.operation, descriptor);
856
+ }
857
+ export function getCommandDescriptor(resource, action) {
858
+ return commandsByPath.get(`${resource}\0${action}`) ?? null;
859
+ }
860
+ export function getCommandDescriptorByOperation(operation) {
861
+ return commandsByOperation.get(operation) ?? null;
862
+ }
863
+ export function renderCommandHelp(descriptor) {
864
+ return descriptor.textHelp.join("\n");
865
+ }
866
+ export function commandDescriptorToJson(descriptor) {
867
+ return {
868
+ cli_contract_version: descriptor.contractVersion,
869
+ path: descriptor.path,
870
+ operation: descriptor.operation,
871
+ summary: descriptor.summary,
872
+ operands: descriptor.operands.map((item) => ({
873
+ name: item.name,
874
+ value_type: item.valueType,
875
+ required: item.required,
876
+ position: item.position,
877
+ })),
878
+ options: descriptor.options.map((item) => ({
879
+ name: item.name,
880
+ kind: item.kind,
881
+ value_type: item.valueType,
882
+ required: item.required,
883
+ repeatable: item.repeatable,
884
+ default: item.defaultValue,
885
+ enum_values: item.enumValues,
886
+ conflicts: item.conflicts,
887
+ requires: item.requires,
888
+ shared_group: item.sharedGroup,
889
+ spelling: item.spelling,
890
+ })),
891
+ input: descriptor.input,
892
+ output: {
893
+ formats: descriptor.output.formats,
894
+ payload: descriptor.output.payload,
895
+ file_effect: descriptor.output.fileEffect,
896
+ },
897
+ stdin: {
898
+ document: descriptor.stdin.document,
899
+ artifact: descriptor.stdin.artifact,
900
+ request: descriptor.stdin.request,
901
+ mutually_exclusive: descriptor.stdin.mutuallyExclusive,
902
+ },
903
+ effect: descriptor.effect,
904
+ result_schemas: descriptor.resultSchemas,
905
+ exit_statuses: descriptor.exitStatuses,
906
+ examples: descriptor.examples,
907
+ };
908
+ }
909
+ export function commandRegistryToJson() {
910
+ return COMMAND_REGISTRY.map(commandDescriptorToJson);
911
+ }
912
+ export function renderTopLevelHelp() {
913
+ return [
914
+ "perttool - document-based PERT/CPM task management",
915
+ "",
916
+ "Usage:",
917
+ " perttool --version",
918
+ " perttool --help",
919
+ ...COMMAND_REGISTRY.flatMap((descriptor) => descriptor.topLevelUsage === null ? [] : [descriptor.topLevelUsage]),
920
+ "",
921
+ "Format and mutation commands preview by default; use --write or --out for explicit writes.",
922
+ ].join("\n");
923
+ }
924
+ export function commandOptionSets(descriptor) {
925
+ const values = new Set();
926
+ const flags = new Set();
927
+ const repeatable = new Set();
928
+ for (const option of descriptor.options) {
929
+ if (option.kind === "flag") {
930
+ flags.add(option.name);
931
+ }
932
+ else if (option.repeatable) {
933
+ repeatable.add(option.name);
934
+ }
935
+ else {
936
+ values.add(option.name);
937
+ }
938
+ }
939
+ return { values, flags, repeatable };
940
+ }
941
+ export function getAgentHelpCommandHelp() {
942
+ const descriptor = getCommandDescriptor("agent", "help");
943
+ if (descriptor === null)
944
+ throw new Error("agent help command descriptor is missing");
945
+ return {
946
+ operation: descriptor.operation,
947
+ summary: descriptor.summary,
948
+ syntax: descriptor.textHelp,
949
+ };
950
+ }
951
+ //# sourceMappingURL=registry.js.map