perttool 0.1.0-alpha.2 → 0.1.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 (58) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/README.md +23 -7
  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/mutate.d.ts.map +1 -1
  8. package/dist/application/mutate.js +16 -7
  9. package/dist/application/mutate.js.map +1 -1
  10. package/dist/application/project.d.ts +25 -0
  11. package/dist/application/project.d.ts.map +1 -0
  12. package/dist/application/project.js +54 -0
  13. package/dist/application/project.js.map +1 -0
  14. package/dist/cli.js +223 -8
  15. package/dist/cli.js.map +1 -1
  16. package/dist/guidance/profile.d.ts +6 -0
  17. package/dist/guidance/profile.d.ts.map +1 -0
  18. package/dist/guidance/profile.js +1158 -0
  19. package/dist/guidance/profile.js.map +1 -0
  20. package/dist/guidance/projection.d.ts +16 -0
  21. package/dist/guidance/projection.d.ts.map +1 -0
  22. package/dist/guidance/projection.js +276 -0
  23. package/dist/guidance/projection.js.map +1 -0
  24. package/dist/guidance/query.d.ts +4 -0
  25. package/dist/guidance/query.d.ts.map +1 -0
  26. package/dist/guidance/query.js +178 -0
  27. package/dist/guidance/query.js.map +1 -0
  28. package/dist/guidance/text.d.ts +4 -0
  29. package/dist/guidance/text.d.ts.map +1 -0
  30. package/dist/guidance/text.js +118 -0
  31. package/dist/guidance/text.js.map +1 -0
  32. package/dist/guidance/types.d.ts +227 -0
  33. package/dist/guidance/types.d.ts.map +1 -0
  34. package/dist/guidance/types.js +54 -0
  35. package/dist/guidance/types.js.map +1 -0
  36. package/dist/guidance/validator.d.ts +8 -0
  37. package/dist/guidance/validator.d.ts.map +1 -0
  38. package/dist/guidance/validator.js +383 -0
  39. package/dist/guidance/validator.js.map +1 -0
  40. package/dist/help/registry.d.ts +6 -0
  41. package/dist/help/registry.d.ts.map +1 -1
  42. package/dist/help/registry.js +21 -1
  43. package/dist/help/registry.js.map +1 -1
  44. package/dist/index.d.ts +12 -2
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +9 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/mutation/project.d.ts +5 -0
  49. package/dist/mutation/project.d.ts.map +1 -0
  50. package/dist/mutation/project.js +148 -0
  51. package/dist/mutation/project.js.map +1 -0
  52. package/dist/mutation/types.d.ts +21 -1
  53. package/dist/mutation/types.d.ts.map +1 -1
  54. package/dist/version.d.ts +1 -1
  55. package/dist/version.d.ts.map +1 -1
  56. package/dist/version.js +1 -1
  57. package/dist/version.js.map +1 -1
  58. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -7,9 +7,13 @@ import { checkDocument } from "./application/check.js";
7
7
  import { planFormat } from "./application/format.js";
8
8
  import { planBatchMutation, planMutation } from "./application/mutate.js";
9
9
  import { selectNextTasks } from "./application/next.js";
10
+ import { getProjectMetadata, } from "./application/project.js";
11
+ import { getAgentHelp } from "./application/agent-help.js";
10
12
  import { exportMermaid, } from "./conversion/mermaid.js";
11
13
  import { importMermaid } from "./conversion/mermaid-import.js";
12
- import { getHelp } from "./help/registry.js";
14
+ import { getAgentHelpCommandHelp, getHelp, } from "./help/registry.js";
15
+ import { serializeAgentGuidanceResult } from "./guidance/projection.js";
16
+ import { agentGuidanceExitCode, renderAgentGuidanceText, } from "./guidance/text.js";
13
17
  import { documentContentFromBytes, readDocumentFile, } from "./io/document-file.js";
14
18
  import { createArtifactFile, createDocumentFile, replaceDocumentFile, SafeWriteConflictError, SafeWriteVerificationError, } from "./io/safe-write.js";
15
19
  import { formatDecimal } from "./model/rational.js";
@@ -31,6 +35,9 @@ function topLevelHelp() {
31
35
  " perttool dsl check <file> [--format text|json]",
32
36
  " perttool dsl format <file> [--check] [--diff] [--format text|json]",
33
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>]",
34
41
  " perttool dag analyze <file> [--schedule precedence|resource|both] [--format text|json]",
35
42
  " perttool dag next <file> [--capacity <resource-id>=<integer>] [--format text|json]",
36
43
  " perttool dag advance <file> [--diff] [--write | --out <path>] [--format text|json]",
@@ -45,6 +52,9 @@ function topLevelHelp() {
45
52
  ].join("\n");
46
53
  }
47
54
  function commandHelp(resource, action) {
55
+ if (resource === "agent" && action === "help") {
56
+ return getAgentHelpCommandHelp().syntax.join("\n");
57
+ }
48
58
  if (resource === "dsl" && action === "check") {
49
59
  return [
50
60
  "Usage: perttool dsl check <file>",
@@ -69,6 +79,24 @@ function commandHelp(resource, action) {
69
79
  " [--format text|json]",
70
80
  " [--color auto|always|never]",
71
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");
72
100
  if (resource === "dag" && action === "analyze")
73
101
  return [
74
102
  "Usage: perttool dag analyze <file>",
@@ -401,6 +429,86 @@ async function runCheck(args) {
401
429
  }
402
430
  return ok ? 0 : 1;
403
431
  }
432
+ function projectJson(project) {
433
+ return {
434
+ id: project.id,
435
+ version: project.version,
436
+ title: project.title,
437
+ description: project.description,
438
+ as_of: project.asOf,
439
+ duration_unit: project.durationUnit,
440
+ velocity: project.velocity,
441
+ finish: project.finish,
442
+ critical_epsilon: project.criticalEpsilon,
443
+ target_duration: project.targetDuration,
444
+ };
445
+ }
446
+ function renderProjectText(project) {
447
+ const optional = (value) => value ?? "-";
448
+ return [
449
+ `PROJECT ${project.id}`,
450
+ `VERSION ${project.version}`,
451
+ `TITLE ${JSON.stringify(project.title)}`,
452
+ `DESCRIPTION ${project.description === null ? "-" : JSON.stringify(project.description)}`,
453
+ `AS_OF ${optional(project.asOf)}`,
454
+ `DURATION_UNIT ${project.durationUnit}`,
455
+ `VELOCITY ${optional(project.velocity)}`,
456
+ `FINISH ${project.finish}`,
457
+ `CRITICAL_EPSILON ${optional(project.criticalEpsilon)}`,
458
+ `TARGET_DURATION ${optional(project.targetDuration)}`,
459
+ "",
460
+ ].join("\n");
461
+ }
462
+ async function runProjectShow(args) {
463
+ const parsed = parseOptions(args, new Set(["format", "color", "max-diagnostics"]), new Set(["warnings-as-errors"]));
464
+ if (parsed.positionals.length !== 1) {
465
+ throw new UsageError("project show requires exactly one <file>");
466
+ }
467
+ const format = outputFormat(parsed.values.get("format"));
468
+ const color = colorMode(parsed.values.get("color"), format);
469
+ const maxDiagnostics = boundedInteger(parsed.values.get("max-diagnostics"), "max-diagnostics", 100, 1, 1000);
470
+ const sourceOperand = parsed.positionals[0];
471
+ const source = sourceOperand === "-" ? "<stdin>" : sourceOperand;
472
+ let input;
473
+ try {
474
+ input = await readDocument(sourceOperand);
475
+ }
476
+ catch (error) {
477
+ return cliError(error instanceof Error ? error : new Error(String(error)), 3, "project.show", format === "json");
478
+ }
479
+ const result = getProjectMetadata(input.text, { maxDiagnostics });
480
+ const warningFailure = parsed.flags.has("warnings-as-errors") &&
481
+ (result.diagnosticsTruncated ||
482
+ result.diagnostics.some((diagnostic) => diagnostic.severity === "warning"));
483
+ const ok = result.ok && !warningFailure;
484
+ if (format === "json") {
485
+ writeJson({
486
+ schema_version: "Perttool.ProjectResult.v1",
487
+ tool_version: TOOL_VERSION,
488
+ operation: "project.show",
489
+ ok,
490
+ document_id: result.documentId,
491
+ source,
492
+ source_digest: input.digest,
493
+ diagnostics: result.diagnostics.map(jsonDiagnostic),
494
+ diagnostics_truncated: result.diagnosticsTruncated,
495
+ grammar_version: result.grammarVersion,
496
+ project: result.project === null ? null : projectJson(result.project),
497
+ });
498
+ }
499
+ else {
500
+ if (ok && result.project !== null) {
501
+ process.stdout.write(renderProjectText(result.project));
502
+ }
503
+ for (const diagnostic of result.diagnostics) {
504
+ process.stderr.write(`${renderDiagnostic(diagnostic, source, color)}\n`);
505
+ }
506
+ if (result.diagnosticsTruncated) {
507
+ process.stderr.write(`DIAGNOSTICS_TRUNCATED true limit=${maxDiagnostics}\n`);
508
+ }
509
+ }
510
+ return ok ? 0 : 1;
511
+ }
404
512
  function boundedInteger(raw, option, defaultValue, minimum, maximum) {
405
513
  if (raw === undefined)
406
514
  return defaultValue;
@@ -606,7 +714,11 @@ function mutationOptionSets(resource, action) {
606
714
  for (const name of names)
607
715
  repeatable.add(name);
608
716
  };
609
- if (resource === "task" && action === "add") {
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") {
610
722
  addValues("title", "description", "duration", "optimistic", "most-likely", "pessimistic", "status", "priority", "owner", "blocked-reason", "source");
611
723
  addRepeatable("tag", "require");
612
724
  }
@@ -824,6 +936,62 @@ function taskMutationFromOptions(action, parsed) {
824
936
  ...(removeRequirements.length === 0 ? {} : { removeRequirements }),
825
937
  };
826
938
  }
939
+ function projectMutationFromOptions(parsed) {
940
+ if (parsed.positionals.length !== 1) {
941
+ throw new UsageError("project set requires exactly one <file>");
942
+ }
943
+ const version = optionalInteger(parsed, "version", 0, 2_147_483_647);
944
+ const durationUnit = enumOption(parsed.values.get("duration-unit"), "duration-unit", new Set(["day", "hour", "point"]));
945
+ const clear = enumRepeated(parsed, "clear", new Set([
946
+ "description",
947
+ "as_of",
948
+ "velocity",
949
+ "critical_epsilon",
950
+ "target_duration",
951
+ ]));
952
+ const conflicts = new Map([
953
+ ["description", parsed.values.has("description")],
954
+ ["as_of", parsed.values.has("as-of")],
955
+ ["velocity", parsed.values.has("velocity")],
956
+ ["critical_epsilon", parsed.values.has("critical-epsilon")],
957
+ ["target_duration", parsed.values.has("target-duration")],
958
+ ]);
959
+ const conflict = clear.find((field) => conflicts.get(field) === true);
960
+ if (conflict !== undefined) {
961
+ throw new UsageError(`--clear ${conflict} conflicts with another project field option`);
962
+ }
963
+ const set = {
964
+ ...(parsed.values.get("id") === undefined ? {} : { id: parsed.values.get("id") }),
965
+ ...(version === undefined ? {} : { version }),
966
+ ...(parsed.values.get("title") === undefined
967
+ ? {}
968
+ : { title: parsed.values.get("title") }),
969
+ ...(parsed.values.get("description") === undefined
970
+ ? {}
971
+ : { description: parsed.values.get("description") }),
972
+ ...(parsed.values.get("as-of") === undefined
973
+ ? {}
974
+ : { asOf: parsed.values.get("as-of") }),
975
+ ...(durationUnit === undefined ? {} : { durationUnit }),
976
+ ...(parsed.values.get("velocity") === undefined
977
+ ? {}
978
+ : { velocity: parsed.values.get("velocity") }),
979
+ ...(parsed.values.get("finish") === undefined
980
+ ? {}
981
+ : { finish: parsed.values.get("finish") }),
982
+ ...(parsed.values.get("critical-epsilon") === undefined
983
+ ? {}
984
+ : { criticalEpsilon: parsed.values.get("critical-epsilon") }),
985
+ ...(parsed.values.get("target-duration") === undefined
986
+ ? {}
987
+ : { targetDuration: parsed.values.get("target-duration") }),
988
+ };
989
+ return {
990
+ kind: "project.set",
991
+ ...(Object.keys(set).length === 0 ? {} : { set }),
992
+ ...(clear.length === 0 ? {} : { clear }),
993
+ };
994
+ }
827
995
  function milestoneMutationFromOptions(action, parsed) {
828
996
  if (parsed.positionals.length !== 2) {
829
997
  throw new UsageError(`milestone ${action} requires <file> <id>`);
@@ -980,11 +1148,13 @@ async function runMutation(resource, action, args) {
980
1148
  }
981
1149
  else {
982
1150
  mutation =
983
- resource === "task"
984
- ? taskMutationFromOptions(action, parsed)
985
- : resource === "milestone"
986
- ? milestoneMutationFromOptions(action, parsed)
987
- : resourceMutationFromOptions(action, parsed);
1151
+ resource === "project"
1152
+ ? projectMutationFromOptions(parsed)
1153
+ : resource === "task"
1154
+ ? taskMutationFromOptions(action, parsed)
1155
+ : resource === "milestone"
1156
+ ? milestoneMutationFromOptions(action, parsed)
1157
+ : resourceMutationFromOptions(action, parsed);
988
1158
  sourceOperand = parsed.positionals[0];
989
1159
  writeRequest = editingWriteRequest(parsed, sourceOperand);
990
1160
  }
@@ -2039,6 +2209,42 @@ function runHelp(args) {
2039
2209
  }
2040
2210
  return result.ok ? 0 : 1;
2041
2211
  }
2212
+ function runAgentHelp(args) {
2213
+ const parsed = parseOptions(args, new Set(["level", "format", "color"]), new Set());
2214
+ if (parsed.positionals.length > 2) {
2215
+ throw new UsageError("agent help accepts at most <provider> <surface>");
2216
+ }
2217
+ const format = outputFormat(parsed.values.get("format"));
2218
+ const color = colorMode(parsed.values.get("color"), format);
2219
+ const providerId = parsed.positionals[0] ?? null;
2220
+ const surfaceId = parsed.positionals[1] ?? null;
2221
+ const level = helpLevel(parsed.values.get("level"), providerId !== null);
2222
+ const result = getAgentHelp({
2223
+ providerId,
2224
+ surfaceId,
2225
+ level,
2226
+ });
2227
+ if (format === "json") {
2228
+ process.stdout.write(serializeAgentGuidanceResult(result));
2229
+ }
2230
+ else {
2231
+ if (result.ok) {
2232
+ process.stdout.write(renderAgentGuidanceText(result));
2233
+ }
2234
+ for (const diagnostic of result.diagnostics) {
2235
+ process.stderr.write(`${renderDiagnostic({
2236
+ code: diagnostic.code,
2237
+ severity: diagnostic.severity,
2238
+ message: diagnostic.message,
2239
+ data: {
2240
+ provider_id: diagnostic.providerId,
2241
+ surface_id: diagnostic.surfaceId,
2242
+ },
2243
+ }, "<agent-guidance>", color)}\n`);
2244
+ }
2245
+ }
2246
+ return agentGuidanceExitCode(result.diagnostics);
2247
+ }
2042
2248
  async function main(argv) {
2043
2249
  if (argv.length === 1 && argv[0] === "--version") {
2044
2250
  process.stdout.write(`perttool ${TOOL_VERSION}\n`);
@@ -2053,6 +2259,7 @@ async function main(argv) {
2053
2259
  const resource = argv[0];
2054
2260
  const action = argv[1];
2055
2261
  const entityActions = new Map([
2262
+ ["project", new Set(["set"])],
2056
2263
  ["task", new Set(["add", "set", "remove", "finish"])],
2057
2264
  ["milestone", new Set(["add", "set", "remove"])],
2058
2265
  ["resource", new Set(["add", "set", "remove"])],
@@ -2061,7 +2268,9 @@ async function main(argv) {
2061
2268
  const isMutationCommand = entityActions.get(resource)?.has(action) === true;
2062
2269
  if (argv.length === 3 &&
2063
2270
  argv[2] === "--help" &&
2064
- ((resource === "dsl" && ["check", "format", "help"].includes(action)) ||
2271
+ ((resource === "agent" && action === "help") ||
2272
+ (resource === "dsl" && ["check", "format", "help"].includes(action)) ||
2273
+ (resource === "project" && action === "show") ||
2065
2274
  (resource === "dag" && ["analyze", "next", "advance", "render", "import"].includes(action)) ||
2066
2275
  isMutationCommand)) {
2067
2276
  process.stdout.write(`${commandHelp(resource, action)}\n`);
@@ -2082,6 +2291,12 @@ async function main(argv) {
2082
2291
  if (resource === "dag" && action === "import") {
2083
2292
  return runImport(argv.slice(2));
2084
2293
  }
2294
+ if (resource === "agent" && action === "help") {
2295
+ return runAgentHelp(argv.slice(2));
2296
+ }
2297
+ if (resource === "project" && action === "show") {
2298
+ return runProjectShow(argv.slice(2));
2299
+ }
2085
2300
  if (resource === "dsl" && action === "format") {
2086
2301
  return runFormat(argv.slice(2));
2087
2302
  }