perttool 0.3.0 → 0.5.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 (286) hide show
  1. package/CHANGELOG.md +78 -1
  2. package/README.md +134 -24
  3. package/dist/actuals/lifecycle.d.ts +101 -0
  4. package/dist/actuals/lifecycle.d.ts.map +1 -0
  5. package/dist/actuals/lifecycle.js +329 -0
  6. package/dist/actuals/lifecycle.js.map +1 -0
  7. package/dist/actuals/source.d.ts +36 -0
  8. package/dist/actuals/source.d.ts.map +1 -0
  9. package/dist/actuals/source.js +119 -0
  10. package/dist/actuals/source.js.map +1 -0
  11. package/dist/analysis/temporal-deadline.d.ts +2 -2
  12. package/dist/analysis/temporal-deadline.d.ts.map +1 -1
  13. package/dist/analysis/temporal-deadline.js.map +1 -1
  14. package/dist/analysis/temporal-precedence.d.ts +2 -2
  15. package/dist/analysis/temporal-precedence.d.ts.map +1 -1
  16. package/dist/analysis/temporal-precedence.js +2 -0
  17. package/dist/analysis/temporal-precedence.js.map +1 -1
  18. package/dist/analysis/temporal-resource.d.ts +2 -2
  19. package/dist/analysis/temporal-resource.d.ts.map +1 -1
  20. package/dist/analysis/temporal-resource.js +2 -0
  21. package/dist/analysis/temporal-resource.js.map +1 -1
  22. package/dist/application/analyze.d.ts +1 -0
  23. package/dist/application/analyze.d.ts.map +1 -1
  24. package/dist/application/analyze.js +38 -29
  25. package/dist/application/analyze.js.map +1 -1
  26. package/dist/application/check.d.ts +3 -0
  27. package/dist/application/check.d.ts.map +1 -1
  28. package/dist/application/check.js +17 -1
  29. package/dist/application/check.js.map +1 -1
  30. package/dist/application/contract4.js +3 -3
  31. package/dist/application/contract5-governance.d.ts +8 -0
  32. package/dist/application/contract5-governance.d.ts.map +1 -0
  33. package/dist/application/contract5-governance.js +12 -0
  34. package/dist/application/contract5-governance.js.map +1 -0
  35. package/dist/application/contract6-actuals.d.ts +24 -0
  36. package/dist/application/contract6-actuals.d.ts.map +1 -0
  37. package/dist/application/contract6-actuals.js +51 -0
  38. package/dist/application/contract6-actuals.js.map +1 -0
  39. package/dist/application/contract6-mutation.d.ts +18 -0
  40. package/dist/application/contract6-mutation.d.ts.map +1 -0
  41. package/dist/application/contract6-mutation.js +173 -0
  42. package/dist/application/contract6-mutation.js.map +1 -0
  43. package/dist/application/contract6-projection.d.ts +9 -0
  44. package/dist/application/contract6-projection.d.ts.map +1 -0
  45. package/dist/application/contract6-projection.js +105 -0
  46. package/dist/application/contract6-projection.js.map +1 -0
  47. package/dist/application/init.d.ts +3 -12
  48. package/dist/application/init.d.ts.map +1 -1
  49. package/dist/application/init.js +16 -138
  50. package/dist/application/init.js.map +1 -1
  51. package/dist/application/project.d.ts +4 -24
  52. package/dist/application/project.d.ts.map +1 -1
  53. package/dist/application/project.js +3 -52
  54. package/dist/application/project.js.map +1 -1
  55. package/dist/application/target-actuals-advance.d.ts +12 -0
  56. package/dist/application/target-actuals-advance.d.ts.map +1 -0
  57. package/dist/application/target-actuals-advance.js +113 -0
  58. package/dist/application/target-actuals-advance.js.map +1 -0
  59. package/dist/application/target-actuals-analysis.d.ts +76 -0
  60. package/dist/application/target-actuals-analysis.d.ts.map +1 -0
  61. package/dist/application/target-actuals-analysis.js +250 -0
  62. package/dist/application/target-actuals-analysis.js.map +1 -0
  63. package/dist/application/target-actuals-mutation.d.ts +28 -0
  64. package/dist/application/target-actuals-mutation.d.ts.map +1 -0
  65. package/dist/application/target-actuals-mutation.js +404 -0
  66. package/dist/application/target-actuals-mutation.js.map +1 -0
  67. package/dist/application/target-actuals-write.d.ts +7 -0
  68. package/dist/application/target-actuals-write.d.ts.map +1 -0
  69. package/dist/application/target-actuals-write.js +37 -0
  70. package/dist/application/target-actuals-write.js.map +1 -0
  71. package/dist/application/target-governance-init.d.ts +36 -0
  72. package/dist/application/target-governance-init.d.ts.map +1 -0
  73. package/dist/application/target-governance-init.js +195 -0
  74. package/dist/application/target-governance-init.js.map +1 -0
  75. package/dist/application/target-governance-mutation.d.ts +20 -0
  76. package/dist/application/target-governance-mutation.d.ts.map +1 -0
  77. package/dist/application/target-governance-mutation.js +175 -0
  78. package/dist/application/target-governance-mutation.js.map +1 -0
  79. package/dist/application/target-governance-project.d.ts +34 -0
  80. package/dist/application/target-governance-project.d.ts.map +1 -0
  81. package/dist/application/target-governance-project.js +82 -0
  82. package/dist/application/target-governance-project.js.map +1 -0
  83. package/dist/application/target-governance-projection.d.ts +18 -0
  84. package/dist/application/target-governance-projection.d.ts.map +1 -0
  85. package/dist/application/target-governance-projection.js +248 -0
  86. package/dist/application/target-governance-projection.js.map +1 -0
  87. package/dist/application/target-governance-write.d.ts +25 -0
  88. package/dist/application/target-governance-write.d.ts.map +1 -0
  89. package/dist/application/target-governance-write.js +55 -0
  90. package/dist/application/target-governance-write.js.map +1 -0
  91. package/dist/application/target-mutate.d.ts +6 -2
  92. package/dist/application/target-mutate.d.ts.map +1 -1
  93. package/dist/application/target-mutate.js +59 -2
  94. package/dist/application/target-mutate.js.map +1 -1
  95. package/dist/application/target-project-history.d.ts +18 -0
  96. package/dist/application/target-project-history.d.ts.map +1 -0
  97. package/dist/application/target-project-history.js +279 -0
  98. package/dist/application/target-project-history.js.map +1 -0
  99. package/dist/application/target-temporal-analysis.d.ts +13 -5
  100. package/dist/application/target-temporal-analysis.d.ts.map +1 -1
  101. package/dist/application/target-temporal-analysis.js +18 -11
  102. package/dist/application/target-temporal-analysis.js.map +1 -1
  103. package/dist/application/target-temporal-input.d.ts +8 -6
  104. package/dist/application/target-temporal-input.d.ts.map +1 -1
  105. package/dist/application/target-temporal-input.js +14 -3
  106. package/dist/application/target-temporal-input.js.map +1 -1
  107. package/dist/application/target-unit-migration-candidate.d.ts +2 -2
  108. package/dist/application/target-unit-migration-candidate.d.ts.map +1 -1
  109. package/dist/application/target-unit-migration-candidate.js +21 -6
  110. package/dist/application/target-unit-migration-candidate.js.map +1 -1
  111. package/dist/application/target-unit-migration-request.d.ts +4 -4
  112. package/dist/application/target-unit-migration-request.d.ts.map +1 -1
  113. package/dist/application/target-unit-migration-request.js +6 -2
  114. package/dist/application/target-unit-migration-request.js.map +1 -1
  115. package/dist/application/target-unit-migration-result.d.ts +5 -5
  116. package/dist/application/target-unit-migration-result.d.ts.map +1 -1
  117. package/dist/application/target-unit-migration-result.js +1 -1
  118. package/dist/application/target-unit-migration-result.js.map +1 -1
  119. package/dist/application/target-velocity-observation.d.ts +11 -0
  120. package/dist/application/target-velocity-observation.d.ts.map +1 -0
  121. package/dist/application/target-velocity-observation.js +201 -0
  122. package/dist/application/target-velocity-observation.js.map +1 -0
  123. package/dist/application/unit-migration.d.ts +4 -1
  124. package/dist/application/unit-migration.d.ts.map +1 -1
  125. package/dist/application/unit-migration.js +5 -3
  126. package/dist/application/unit-migration.js.map +1 -1
  127. package/dist/cli.js +390 -137
  128. package/dist/cli.js.map +1 -1
  129. package/dist/command/actuals-discovery.d.ts +26 -0
  130. package/dist/command/actuals-discovery.d.ts.map +1 -0
  131. package/dist/command/actuals-discovery.js +271 -0
  132. package/dist/command/actuals-discovery.js.map +1 -0
  133. package/dist/command/actuals-usage.d.ts +22 -0
  134. package/dist/command/actuals-usage.d.ts.map +1 -0
  135. package/dist/command/actuals-usage.js +122 -0
  136. package/dist/command/actuals-usage.js.map +1 -0
  137. package/dist/command/registry.d.ts +7 -1
  138. package/dist/command/registry.d.ts.map +1 -1
  139. package/dist/command/registry.js.map +1 -1
  140. package/dist/command/target-governance-discovery.d.ts +31 -0
  141. package/dist/command/target-governance-discovery.d.ts.map +1 -0
  142. package/dist/command/target-governance-discovery.js +390 -0
  143. package/dist/command/target-governance-discovery.js.map +1 -0
  144. package/dist/command/target-governance-usage.d.ts +22 -0
  145. package/dist/command/target-governance-usage.d.ts.map +1 -0
  146. package/dist/command/target-governance-usage.js +116 -0
  147. package/dist/command/target-governance-usage.js.map +1 -0
  148. package/dist/command/usage.d.ts +2 -0
  149. package/dist/command/usage.d.ts.map +1 -1
  150. package/dist/command/usage.js +43 -25
  151. package/dist/command/usage.js.map +1 -1
  152. package/dist/conversion/mermaid-import.d.ts.map +1 -1
  153. package/dist/conversion/mermaid-import.js +3 -2
  154. package/dist/conversion/mermaid-import.js.map +1 -1
  155. package/dist/conversion/mermaid.d.ts.map +1 -1
  156. package/dist/conversion/mermaid.js +27 -0
  157. package/dist/conversion/mermaid.js.map +1 -1
  158. package/dist/formatter/source-formatter.d.ts +3 -3
  159. package/dist/formatter/source-formatter.d.ts.map +1 -1
  160. package/dist/formatter/source-formatter.js +64 -5
  161. package/dist/formatter/source-formatter.js.map +1 -1
  162. package/dist/formatter/target-source-formatter.d.ts +3 -1
  163. package/dist/formatter/target-source-formatter.d.ts.map +1 -1
  164. package/dist/formatter/target-source-formatter.js +34 -2
  165. package/dist/formatter/target-source-formatter.js.map +1 -1
  166. package/dist/governance/authority.d.ts +25 -0
  167. package/dist/governance/authority.d.ts.map +1 -0
  168. package/dist/governance/authority.js +206 -0
  169. package/dist/governance/authority.js.map +1 -0
  170. package/dist/governance/guidance.d.ts +2 -0
  171. package/dist/governance/guidance.d.ts.map +1 -0
  172. package/dist/governance/guidance.js +2 -0
  173. package/dist/governance/guidance.js.map +1 -0
  174. package/dist/governance/source.d.ts +37 -0
  175. package/dist/governance/source.d.ts.map +1 -0
  176. package/dist/governance/source.js +103 -0
  177. package/dist/governance/source.js.map +1 -0
  178. package/dist/governance/types.d.ts +41 -0
  179. package/dist/governance/types.d.ts.map +1 -0
  180. package/dist/governance/types.js +2 -0
  181. package/dist/governance/types.js.map +1 -0
  182. package/dist/help/actuals-guide.d.ts +10 -0
  183. package/dist/help/actuals-guide.d.ts.map +1 -0
  184. package/dist/help/actuals-guide.js +23 -0
  185. package/dist/help/actuals-guide.js.map +1 -0
  186. package/dist/help/guide.d.ts +8 -5
  187. package/dist/help/guide.d.ts.map +1 -1
  188. package/dist/help/guide.js.map +1 -1
  189. package/dist/help/registry.d.ts.map +1 -1
  190. package/dist/help/registry.js +98 -8
  191. package/dist/help/registry.js.map +1 -1
  192. package/dist/help/target-governance-guide.d.ts +10 -0
  193. package/dist/help/target-governance-guide.d.ts.map +1 -0
  194. package/dist/help/target-governance-guide.js +57 -0
  195. package/dist/help/target-governance-guide.js.map +1 -0
  196. package/dist/history/git-probe.d.ts +52 -0
  197. package/dist/history/git-probe.d.ts.map +1 -0
  198. package/dist/history/git-probe.js +565 -0
  199. package/dist/history/git-probe.js.map +1 -0
  200. package/dist/history/project-history.d.ts +112 -0
  201. package/dist/history/project-history.d.ts.map +1 -0
  202. package/dist/history/project-history.js +786 -0
  203. package/dist/history/project-history.js.map +1 -0
  204. package/dist/history/velocity-observation.d.ts +69 -0
  205. package/dist/history/velocity-observation.d.ts.map +1 -0
  206. package/dist/history/velocity-observation.js +604 -0
  207. package/dist/history/velocity-observation.js.map +1 -0
  208. package/dist/index.d.ts +28 -14
  209. package/dist/index.d.ts.map +1 -1
  210. package/dist/index.js +11 -7
  211. package/dist/index.js.map +1 -1
  212. package/dist/io/safe-write.d.ts +4 -0
  213. package/dist/io/safe-write.d.ts.map +1 -1
  214. package/dist/io/safe-write.js +22 -1
  215. package/dist/io/safe-write.js.map +1 -1
  216. package/dist/io/target-safe-write.d.ts +8 -2
  217. package/dist/io/target-safe-write.d.ts.map +1 -1
  218. package/dist/io/target-safe-write.js +38 -2
  219. package/dist/io/target-safe-write.js.map +1 -1
  220. package/dist/migration/conversion.d.ts +1 -1
  221. package/dist/migration/conversion.d.ts.map +1 -1
  222. package/dist/migration/grammar-boundary.d.ts +1 -1
  223. package/dist/migration/grammar-boundary.d.ts.map +1 -1
  224. package/dist/migration/grammar-boundary.js +15 -4
  225. package/dist/migration/grammar-boundary.js.map +1 -1
  226. package/dist/migration/request.d.ts +5 -5
  227. package/dist/migration/request.d.ts.map +1 -1
  228. package/dist/migration/request.js +15 -2
  229. package/dist/migration/request.js.map +1 -1
  230. package/dist/model/calendar.d.ts +2 -0
  231. package/dist/model/calendar.d.ts.map +1 -1
  232. package/dist/model/calendar.js +19 -0
  233. package/dist/model/calendar.js.map +1 -1
  234. package/dist/model/declaration-fields.d.ts +15 -0
  235. package/dist/model/declaration-fields.d.ts.map +1 -1
  236. package/dist/model/declaration-fields.js +31 -0
  237. package/dist/model/declaration-fields.js.map +1 -1
  238. package/dist/model/exact-person-hours-source.d.ts +2 -0
  239. package/dist/model/exact-person-hours-source.d.ts.map +1 -0
  240. package/dist/model/exact-person-hours-source.js +10 -0
  241. package/dist/model/exact-person-hours-source.js.map +1 -0
  242. package/dist/model/syntax.d.ts +23 -8
  243. package/dist/model/syntax.d.ts.map +1 -1
  244. package/dist/model/syntax.js.map +1 -1
  245. package/dist/mutation/actuals.d.ts +17 -0
  246. package/dist/mutation/actuals.d.ts.map +1 -0
  247. package/dist/mutation/actuals.js +90 -0
  248. package/dist/mutation/actuals.js.map +1 -0
  249. package/dist/mutation/advance.d.ts +17 -0
  250. package/dist/mutation/advance.d.ts.map +1 -1
  251. package/dist/mutation/advance.js +56 -14
  252. package/dist/mutation/advance.js.map +1 -1
  253. package/dist/mutation/entity-editor.d.ts +2 -2
  254. package/dist/mutation/entity-editor.d.ts.map +1 -1
  255. package/dist/mutation/entity-editor.js.map +1 -1
  256. package/dist/mutation/project.d.ts +7 -1
  257. package/dist/mutation/project.d.ts.map +1 -1
  258. package/dist/mutation/project.js +108 -6
  259. package/dist/mutation/project.js.map +1 -1
  260. package/dist/mutation/source.d.ts +3 -3
  261. package/dist/mutation/source.d.ts.map +1 -1
  262. package/dist/mutation/source.js.map +1 -1
  263. package/dist/mutation/target-types.d.ts +18 -1
  264. package/dist/mutation/target-types.d.ts.map +1 -1
  265. package/dist/mutation/types.d.ts +8 -1
  266. package/dist/mutation/types.d.ts.map +1 -1
  267. package/dist/parser/document-parser.d.ts +15 -1
  268. package/dist/parser/document-parser.d.ts.map +1 -1
  269. package/dist/parser/document-parser.js +205 -22
  270. package/dist/parser/document-parser.js.map +1 -1
  271. package/dist/recommendation/override-types.d.ts +2 -2
  272. package/dist/recommendation/override.d.ts +2 -2
  273. package/dist/recommendation/override.d.ts.map +1 -1
  274. package/dist/recommendation/override.js +2 -2
  275. package/dist/recommendation/override.js.map +1 -1
  276. package/dist/semantic/target-validator.d.ts +23 -2
  277. package/dist/semantic/target-validator.d.ts.map +1 -1
  278. package/dist/semantic/target-validator.js +89 -2
  279. package/dist/semantic/target-validator.js.map +1 -1
  280. package/dist/semantic/validator.d.ts +5 -1
  281. package/dist/semantic/validator.d.ts.map +1 -1
  282. package/dist/semantic/validator.js +180 -6
  283. package/dist/semantic/validator.js.map +1 -1
  284. package/dist/version.d.ts +1 -1
  285. package/dist/version.js +1 -1
  286. package/package.json +3 -2
package/dist/cli.js CHANGED
@@ -2,20 +2,26 @@
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, selectNextTasks, } from "./application/contract4.js";
5
+ import { analyzeDocument, selectNextTasks, } from "./application/contract6-actuals.js";
6
6
  import { checkDocument } from "./application/check.js";
7
7
  import { planFormat } from "./application/format.js";
8
8
  import { planProjectInit, projectInitResultToJson, renderProjectInitResult, withProjectInitOutput, } from "./application/init.js";
9
- import { planBatchMutation, planMutation } from "./application/mutate.js";
10
9
  import { planUnitMigration, withUnitMigrationWrite, } from "./application/unit-migration.js";
11
10
  import { getProjectMetadata, } from "./application/project.js";
11
+ import { planAdvance, planBatchMutation, planFinishActuals, planLifecycle, planMutation, } from "./application/contract6-mutation.js";
12
+ import { renderTargetGovernanceDecision, renderTargetGovernanceProjectText, } from "./application/target-governance-projection.js";
13
+ import { contract6MutationResultToJson, contract6ProjectResultToJson, contract6WorkEventToJson, } from "./application/contract6-projection.js";
14
+ import { persistTargetActualsResult, } from "./application/target-actuals-write.js";
15
+ import { inspectTargetProjectHistoryFile, renderTargetProjectHistoryText, targetProjectHistoryResultToJson, } from "./application/target-project-history.js";
16
+ import { observeTargetProjectVelocity, renderTargetVelocityObservationText, targetVelocityObservationResultToJson, } from "./application/target-velocity-observation.js";
12
17
  import { getAgentHelp } from "./application/agent-help.js";
13
18
  import { exportMermaid, } from "./conversion/mermaid.js";
14
19
  import { importMermaid } from "./conversion/mermaid-import.js";
15
- import { getGuide, renderGuideResult, serializeGuideResult, } from "./help/guide.js";
20
+ import { getActualsGuide, renderActualsGuideResult, serializeActualsGuideResult, } from "./help/actuals-guide.js";
16
21
  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";
22
+ import { ACTUALS_COMMAND_REGISTRY, getActualsCommandDiscovery, renderActualsCommandHelpResult, serializeActualsCommandHelpResult, } from "./command/actuals-discovery.js";
23
+ import { handlerCommandUsageError, } from "./command/usage.js";
24
+ import { renderActualsCommandUsageError, serializeActualsCommandUsageError, validateActualsCommandInvocation, } from "./command/actuals-usage.js";
19
25
  import { agentGuidanceResultToJson } from "./guidance/projection.js";
20
26
  import { agentGuidanceExitCode, renderAgentGuidanceText, } from "./guidance/text.js";
21
27
  import { documentContentFromBytes, readDocumentFile, } from "./io/document-file.js";
@@ -24,7 +30,7 @@ import { formatDecimal } from "./model/rational.js";
24
30
  import { convertWithVelocity, durationSuffix } from "./model/units.js";
25
31
  import { recommendationInvariantExitCode } from "./recommendation/failure.js";
26
32
  import { recommendationAnalysisToJson } from "./recommendation/json.js";
27
- import { planAdvance, } from "./mutation/advance.js";
33
+ import { TARGET_GRAMMAR_5_CAPABILITY, } from "./parser/document-parser.js";
28
34
  import { TOOL_VERSION } from "./version.js";
29
35
  class UsageError extends Error {
30
36
  code = "PTCLI-001";
@@ -79,7 +85,7 @@ function parseOptions(args, valueOptions, flagOptions, repeatableOptions = new S
79
85
  return { positionals, values, repeatedValues, flags };
80
86
  }
81
87
  function parseCommandOptions(operation, args) {
82
- const descriptor = CONTRACT4_COMMAND_REGISTRY.find((candidate) => candidate.operation === operation);
88
+ const descriptor = ACTUALS_COMMAND_REGISTRY.find((candidate) => candidate.operation === operation);
83
89
  if (descriptor === undefined) {
84
90
  throw new Error(`command descriptor is missing for ${operation}`);
85
91
  }
@@ -217,7 +223,7 @@ function cliError(error, exitCode, operation, json) {
217
223
  if (json) {
218
224
  writeJson({
219
225
  schema_version: "Perttool.CliError.v1",
220
- cli_contract_version: 4,
226
+ cli_contract_version: 6,
221
227
  tool_version: TOOL_VERSION,
222
228
  operation,
223
229
  ok: false,
@@ -252,8 +258,8 @@ async function runCheck(args) {
252
258
  const ok = result.ok && !warningFailure;
253
259
  if (format === "json") {
254
260
  writeJson({
255
- schema_version: "Perttool.CheckResult.v2",
256
- cli_contract_version: 4,
261
+ schema_version: result.schemaVersion,
262
+ cli_contract_version: 6,
257
263
  tool_version: TOOL_VERSION,
258
264
  operation: "document.check",
259
265
  ok,
@@ -265,6 +271,12 @@ async function runCheck(args) {
265
271
  grammar_version: result.grammarVersion,
266
272
  summary: result.summary,
267
273
  temporal_inputs: snakeJson(result.temporalInputs),
274
+ actuals_inputs: result.actualsInputs === null
275
+ ? null
276
+ : {
277
+ model_version: result.actualsInputs.modelVersion,
278
+ events: result.actualsInputs.events.map(contract6WorkEventToJson),
279
+ },
268
280
  });
269
281
  }
270
282
  else {
@@ -280,41 +292,6 @@ async function runCheck(args) {
280
292
  }
281
293
  return ok ? 0 : 1;
282
294
  }
283
- function projectJson(project) {
284
- return {
285
- id: project.id,
286
- version: project.version,
287
- title: project.title,
288
- description: project.description,
289
- as_of: snakeJson(project.asOf),
290
- duration_unit: project.durationUnit,
291
- velocity: project.velocity,
292
- finish: project.finish,
293
- finish_deadline: snakeJson(project.finishDeadline),
294
- critical_epsilon: project.criticalEpsilon,
295
- target_duration: project.targetDuration,
296
- };
297
- }
298
- function renderProjectText(project) {
299
- const optional = (value) => value ?? "-";
300
- const calendar = (value) => value === null
301
- ? "-"
302
- : `${value.kind.toUpperCase()} ${value.sourceText ?? "-"}`;
303
- return [
304
- `PROJECT ${project.id}`,
305
- `VERSION ${project.version}`,
306
- `TITLE ${JSON.stringify(project.title)}`,
307
- `DESCRIPTION ${project.description === null ? "-" : JSON.stringify(project.description)}`,
308
- `AS_OF ${calendar(project.asOf)}`,
309
- `DURATION_UNIT ${project.durationUnit}`,
310
- `VELOCITY ${optional(project.velocity)}`,
311
- `FINISH ${project.finish}`,
312
- `FINISH_DEADLINE ${calendar(project.finishDeadline)}`,
313
- `CRITICAL_EPSILON ${optional(project.criticalEpsilon)}`,
314
- `TARGET_DURATION ${optional(project.targetDuration)}`,
315
- "",
316
- ].join("\n");
317
- }
318
295
  async function runProjectShow(args) {
319
296
  const parsed = parseCommandOptions("project.show", args);
320
297
  if (parsed.positionals.length !== 1) {
@@ -338,24 +315,11 @@ async function runProjectShow(args) {
338
315
  result.diagnostics.some((diagnostic) => diagnostic.severity === "warning"));
339
316
  const ok = result.ok && !warningFailure;
340
317
  if (format === "json") {
341
- writeJson({
342
- schema_version: "Perttool.ProjectResult.v2",
343
- cli_contract_version: 4,
344
- tool_version: TOOL_VERSION,
345
- operation: "project.show",
346
- ok,
347
- document_id: result.documentId,
348
- source,
349
- source_digest: input.digest,
350
- diagnostics: result.diagnostics.map(jsonDiagnostic),
351
- diagnostics_truncated: result.diagnosticsTruncated,
352
- grammar_version: result.grammarVersion,
353
- project: result.project === null ? null : projectJson(result.project),
354
- });
318
+ writeJson(contract6ProjectResultToJson(result, source, input.digest, ok));
355
319
  }
356
320
  else {
357
321
  if (ok && result.project !== null) {
358
- process.stdout.write(renderProjectText(result.project));
322
+ process.stdout.write(renderTargetGovernanceProjectText(result.project));
359
323
  }
360
324
  for (const diagnostic of result.diagnostics) {
361
325
  process.stderr.write(`${renderDiagnostic(diagnostic, source, color)}\n`);
@@ -391,6 +355,22 @@ async function runProjectInit(args) {
391
355
  : {
392
356
  initialMilestoneDeadline: parsed.values.get("initial-milestone-deadline"),
393
357
  }),
358
+ ...(parsed.values.get("goal-owner") === undefined
359
+ ? {}
360
+ : { goalOwner: parsed.values.get("goal-owner") }),
361
+ ...(parsed.values.get("goal-delegates") === undefined
362
+ ? {}
363
+ : {
364
+ goalDelegates: principalListOption(parsed.values.get("goal-delegates")),
365
+ }),
366
+ ...(parsed.values.get("dag-owner") === undefined
367
+ ? {}
368
+ : { dagOwner: parsed.values.get("dag-owner") }),
369
+ ...(parsed.values.get("dag-delegates") === undefined
370
+ ? {}
371
+ : {
372
+ dagDelegates: principalListOption(parsed.values.get("dag-delegates")),
373
+ }),
394
374
  });
395
375
  let writeResult = null;
396
376
  const output = parsed.values.get("out");
@@ -494,6 +474,39 @@ function editingWriteRequest(parsed, sourceOperand, check = false) {
494
474
  return { mode: "out", target: out };
495
475
  return { mode: "preview", target: null };
496
476
  }
477
+ function governanceRequest(parsed, writeRequest) {
478
+ return Object.freeze({
479
+ intent: writeRequest.mode === "preview"
480
+ ? "preview"
481
+ : "persist",
482
+ actor: parsed.values.get("actor") ?? null,
483
+ acceptedByOwner: Object.freeze([
484
+ ...(parsed.repeatedValues.get("accepted-by-owner") ?? []),
485
+ ]),
486
+ });
487
+ }
488
+ async function persistGovernedResult(result, request, sourceOperand) {
489
+ if (request.mode === "preview") {
490
+ return Object.freeze({
491
+ mode: "preview",
492
+ target: null,
493
+ written: false,
494
+ });
495
+ }
496
+ return persistTargetActualsResult(result, TARGET_GRAMMAR_5_CAPABILITY, request.mode === "in_place"
497
+ ? {
498
+ mode: "in_place",
499
+ target: request.target,
500
+ ...(request.expectedDigest === undefined
501
+ ? {}
502
+ : { expectedDigest: request.expectedDigest }),
503
+ }
504
+ : {
505
+ mode: "out",
506
+ source: sourceOperand,
507
+ target: request.target,
508
+ });
509
+ }
497
510
  function assertExpectedDigest(request, initialDigest) {
498
511
  if (request.mode === "in_place" &&
499
512
  request.expectedDigest !== undefined &&
@@ -526,6 +539,9 @@ function writeFailureExit(error, operation, json) {
526
539
  function renderWriteSummary(operation, result) {
527
540
  return `WRITE ${operation} mode=${result.mode} target=${result.target} digest=${result.digest} written=${result.written}\n`;
528
541
  }
542
+ function renderGovernanceWriteSummary(operation, result, digest) {
543
+ return `WRITE ${operation} mode=${result.mode} target=${result.target ?? "-"} digest=${digest ?? "-"} written=${result.written}\n`;
544
+ }
529
545
  async function runFormat(args) {
530
546
  const parsed = parseCommandOptions("document.format", args);
531
547
  if (parsed.positionals.length !== 1) {
@@ -569,7 +585,7 @@ async function runFormat(args) {
569
585
  if (format === "json") {
570
586
  writeJson({
571
587
  schema_version: "Perttool.FormatResult.v1",
572
- cli_contract_version: 4,
588
+ cli_contract_version: 6,
573
589
  tool_version: TOOL_VERSION,
574
590
  operation: "document.format",
575
591
  ok,
@@ -613,6 +629,11 @@ function requiredOption(parsed, name) {
613
629
  throw new UsageError(`option --${name} is required`);
614
630
  return value;
615
631
  }
632
+ function exactMeasurementToken(value, suffix) {
633
+ return /^(?:[0-9]+(?:\.[0-9]+)?|[0-9]+\/[1-9][0-9]*)$/.test(value)
634
+ ? `${value}${suffix}`
635
+ : value;
636
+ }
616
637
  function optionalInteger(parsed, name, minimum, maximum) {
617
638
  const raw = parsed.values.get(name);
618
639
  return raw === undefined
@@ -637,6 +658,12 @@ function uniqueRepeated(parsed, option) {
637
658
  }
638
659
  return values;
639
660
  }
661
+ function principalListOption(value) {
662
+ const interior = value.slice(1, -1).trim();
663
+ return interior === ""
664
+ ? Object.freeze([])
665
+ : Object.freeze(interior.split(",").map((item) => item.trim()));
666
+ }
640
667
  function enumRepeated(parsed, option, allowed) {
641
668
  return uniqueRepeated(parsed, option).map((value) => {
642
669
  if (!allowed.has(value)) {
@@ -823,6 +850,10 @@ function projectMutationFromOptions(parsed) {
823
850
  "velocity",
824
851
  "critical_epsilon",
825
852
  "target_duration",
853
+ "goal_owner",
854
+ "goal_delegates",
855
+ "dag_owner",
856
+ "dag_delegates",
826
857
  ]));
827
858
  const conflicts = new Map([
828
859
  ["description", parsed.values.has("description")],
@@ -830,6 +861,10 @@ function projectMutationFromOptions(parsed) {
830
861
  ["velocity", parsed.values.has("velocity")],
831
862
  ["critical_epsilon", parsed.values.has("critical-epsilon")],
832
863
  ["target_duration", parsed.values.has("target-duration")],
864
+ ["goal_owner", parsed.values.has("goal-owner")],
865
+ ["goal_delegates", parsed.values.has("goal-delegates")],
866
+ ["dag_owner", parsed.values.has("dag-owner")],
867
+ ["dag_delegates", parsed.values.has("dag-delegates")],
833
868
  ]);
834
869
  const conflict = clear.find((field) => conflicts.get(field) === true);
835
870
  if (conflict !== undefined) {
@@ -860,6 +895,22 @@ function projectMutationFromOptions(parsed) {
860
895
  ...(parsed.values.get("target-duration") === undefined
861
896
  ? {}
862
897
  : { targetDuration: parsed.values.get("target-duration") }),
898
+ ...(parsed.values.get("goal-owner") === undefined
899
+ ? {}
900
+ : { goalOwner: parsed.values.get("goal-owner") }),
901
+ ...(parsed.values.get("goal-delegates") === undefined
902
+ ? {}
903
+ : {
904
+ goalDelegates: principalListOption(parsed.values.get("goal-delegates")),
905
+ }),
906
+ ...(parsed.values.get("dag-owner") === undefined
907
+ ? {}
908
+ : { dagOwner: parsed.values.get("dag-owner") }),
909
+ ...(parsed.values.get("dag-delegates") === undefined
910
+ ? {}
911
+ : {
912
+ dagDelegates: principalListOption(parsed.values.get("dag-delegates")),
913
+ }),
863
914
  };
864
915
  return {
865
916
  kind: "project.set",
@@ -1037,6 +1088,7 @@ async function runMutation(resource, action, args) {
1037
1088
  let sourceOperand;
1038
1089
  let writeRequest;
1039
1090
  let mutation;
1091
+ let lifecycleMutation = null;
1040
1092
  if (resource === "batch") {
1041
1093
  if (parsed.positionals.length !== 1) {
1042
1094
  throw new UsageError("batch apply requires exactly one <file>");
@@ -1071,6 +1123,37 @@ async function runMutation(resource, action, args) {
1071
1123
  : resourceMutationFromOptions(action, parsed);
1072
1124
  sourceOperand = parsed.positionals[0];
1073
1125
  writeRequest = editingWriteRequest(parsed, sourceOperand);
1126
+ if (resource === "task" &&
1127
+ action === "finish" &&
1128
+ parsed.values.has("at")) {
1129
+ lifecycleMutation = {
1130
+ kind: "task.finish.actual",
1131
+ taskId: parsed.positionals[1],
1132
+ event: {
1133
+ occurredAt: parsed.values.get("at"),
1134
+ ...(parsed.values.get("event-id") === undefined
1135
+ ? {}
1136
+ : { id: parsed.values.get("event-id") }),
1137
+ ...(parsed.values.get("active-time") === undefined
1138
+ ? {}
1139
+ : {
1140
+ activeTime: exactMeasurementToken(parsed.values.get("active-time"), "h"),
1141
+ }),
1142
+ ...(parsed.values.get("effort") === undefined
1143
+ ? {}
1144
+ : {
1145
+ effort: exactMeasurementToken(parsed.values.get("effort"), "ph"),
1146
+ }),
1147
+ },
1148
+ };
1149
+ }
1150
+ else if (resource === "task" &&
1151
+ action === "finish" &&
1152
+ (parsed.values.has("event-id") ||
1153
+ parsed.values.has("active-time") ||
1154
+ parsed.values.has("effort"))) {
1155
+ throw new UsageError("--event-id, --active-time, and --effort require --at");
1156
+ }
1074
1157
  }
1075
1158
  const source = sourceOperand === "-" ? "<stdin>" : sourceOperand;
1076
1159
  let input;
@@ -1084,20 +1167,26 @@ async function runMutation(resource, action, args) {
1084
1167
  maxDiagnostics,
1085
1168
  originalLabel: source,
1086
1169
  updatedLabel: "candidate",
1170
+ governance: governanceRequest(parsed, writeRequest),
1087
1171
  };
1088
- const result = resource === "batch"
1089
- ? planBatchMutation(input.text, mutation, mutationOptions)
1090
- : planMutation(input.text, mutation, mutationOptions);
1172
+ const result = lifecycleMutation !== null
1173
+ ? planFinishActuals(input.text, lifecycleMutation, mutationOptions)
1174
+ : resource === "batch"
1175
+ ? planBatchMutation(input.text, mutation, mutationOptions)
1176
+ : planMutation(input.text, mutation, mutationOptions);
1091
1177
  const warningFailure = parsed.flags.has("warnings-as-errors") &&
1092
1178
  (result.diagnosticsTruncated ||
1093
1179
  result.diagnostics.some((diagnostic) => diagnostic.severity === "warning"));
1094
1180
  const ok = result.ok && !warningFailure;
1095
- let writeResult = null;
1181
+ let writeResult = Object.freeze({
1182
+ mode: writeRequest.mode,
1183
+ target: writeRequest.target,
1184
+ written: false,
1185
+ });
1096
1186
  if (result.ok) {
1097
1187
  try {
1098
- assertExpectedDigest(writeRequest, input.digest);
1099
1188
  if (ok && writeRequest.mode !== "preview") {
1100
- writeResult = await commitCandidate(writeRequest, result.updatedText, input.digest);
1189
+ writeResult = await persistGovernedResult(result, writeRequest, sourceOperand);
1101
1190
  }
1102
1191
  }
1103
1192
  catch (error) {
@@ -1105,24 +1194,12 @@ async function runMutation(resource, action, args) {
1105
1194
  }
1106
1195
  }
1107
1196
  if (format === "json") {
1108
- writeJson({
1109
- schema_version: "Perttool.MutationResult.v1",
1110
- cli_contract_version: 4,
1111
- tool_version: TOOL_VERSION,
1112
- operation,
1113
- ok,
1114
- document_id: result.documentId,
1115
- source,
1116
- source_digest: input.digest,
1117
- diagnostics: result.diagnostics.map(jsonDiagnostic),
1118
- diagnostics_truncated: result.diagnosticsTruncated,
1119
- ...previewResultJson(result, result.ok, writeRequest, writeResult),
1120
- });
1197
+ writeJson(contract6MutationResultToJson(ok ? result : Object.freeze({ ...result, ok: false }), operation, source, writeResult));
1121
1198
  }
1122
1199
  else {
1123
1200
  if (ok) {
1124
- if (writeResult !== null) {
1125
- process.stderr.write(renderWriteSummary(operation, writeResult));
1201
+ if (writeRequest.mode !== "preview") {
1202
+ process.stderr.write(renderGovernanceWriteSummary(operation, writeResult, result.updatedDigest));
1126
1203
  }
1127
1204
  else {
1128
1205
  process.stdout.write(parsed.flags.has("diff") ? (result.diff ?? "") : (result.updatedText ?? ""));
@@ -1131,6 +1208,97 @@ async function runMutation(resource, action, args) {
1131
1208
  }
1132
1209
  }
1133
1210
  }
1211
+ if (result.governance !== null) {
1212
+ process.stderr.write(renderTargetGovernanceDecision(result.governance));
1213
+ }
1214
+ for (const diagnostic of result.diagnostics) {
1215
+ process.stderr.write(`${renderDiagnostic(diagnostic, source, color)}\n`);
1216
+ }
1217
+ if (result.diagnosticsTruncated) {
1218
+ process.stderr.write(`DIAGNOSTICS_TRUNCATED true limit=${maxDiagnostics}\n`);
1219
+ }
1220
+ }
1221
+ return ok ? 0 : 1;
1222
+ }
1223
+ async function runLifecycleMutation(action, args) {
1224
+ const operation = `task.${action}`;
1225
+ const parsed = parseCommandOptions(operation, args);
1226
+ if (parsed.positionals.length !== 2) {
1227
+ throw new UsageError(`task ${action} requires <file> <task-id>`);
1228
+ }
1229
+ const format = outputFormat(parsed.values.get("format"));
1230
+ const color = colorMode(parsed.values.get("color"), format);
1231
+ const maxDiagnostics = boundedInteger(parsed.values.get("max-diagnostics"), "max-diagnostics", 100, 1, 1000);
1232
+ const sourceOperand = parsed.positionals[0];
1233
+ const source = sourceOperand === "-" ? "<stdin>" : sourceOperand;
1234
+ const writeRequest = editingWriteRequest(parsed, sourceOperand);
1235
+ const event = {
1236
+ occurredAt: requiredOption(parsed, "at"),
1237
+ ...(parsed.values.get("event-id") === undefined
1238
+ ? {}
1239
+ : { id: parsed.values.get("event-id") }),
1240
+ ...(action !== "suspend" || parsed.values.get("reason") === undefined
1241
+ ? {}
1242
+ : { reason: parsed.values.get("reason") }),
1243
+ };
1244
+ const mutation = action === "start"
1245
+ ? { kind: "task.start", taskId: parsed.positionals[1], event }
1246
+ : action === "suspend"
1247
+ ? { kind: "task.suspend", taskId: parsed.positionals[1], event }
1248
+ : { kind: "task.resume", taskId: parsed.positionals[1], event };
1249
+ let input;
1250
+ try {
1251
+ input = await readDocument(sourceOperand);
1252
+ }
1253
+ catch (error) {
1254
+ return cliError(error instanceof Error ? error : new Error(String(error)), 3, operation, format === "json");
1255
+ }
1256
+ const result = planLifecycle(input.text, mutation, {
1257
+ maxDiagnostics,
1258
+ originalLabel: source,
1259
+ updatedLabel: "candidate",
1260
+ governance: governanceRequest(parsed, writeRequest),
1261
+ });
1262
+ const warningFailure = parsed.flags.has("warnings-as-errors") &&
1263
+ (result.diagnosticsTruncated ||
1264
+ result.diagnostics.some((diagnostic) => diagnostic.severity === "warning"));
1265
+ const ok = result.ok && !warningFailure;
1266
+ let writeResult = Object.freeze({
1267
+ mode: writeRequest.mode,
1268
+ target: writeRequest.target,
1269
+ written: false,
1270
+ });
1271
+ if (result.ok && ok && writeRequest.mode !== "preview") {
1272
+ try {
1273
+ writeResult = await persistGovernedResult(result, writeRequest, sourceOperand);
1274
+ }
1275
+ catch (error) {
1276
+ return writeFailureExit(error, operation, format === "json");
1277
+ }
1278
+ }
1279
+ if (format === "json") {
1280
+ writeJson(contract6MutationResultToJson(ok ? result : Object.freeze({ ...result, ok: false }), operation, source, writeResult));
1281
+ }
1282
+ else {
1283
+ if (ok) {
1284
+ if (writeRequest.mode !== "preview") {
1285
+ process.stderr.write(renderGovernanceWriteSummary(operation, writeResult, result.updatedDigest));
1286
+ }
1287
+ else {
1288
+ process.stdout.write(parsed.flags.has("diff")
1289
+ ? (result.diff ?? "")
1290
+ : (result.updatedText ?? ""));
1291
+ if (!parsed.flags.has("diff")) {
1292
+ process.stderr.write(`PREVIEW ${operation} changed=${result.changed} original_digest=${result.originalDigest} updated_digest=${result.updatedDigest}\n`);
1293
+ }
1294
+ }
1295
+ }
1296
+ if (result.lifecycle !== null) {
1297
+ process.stderr.write(`LIFECYCLE task=${result.lifecycle.taskId} from=${result.lifecycle.fromState} to=${result.lifecycle.toState} event=${result.lifecycle.event.id} coverage=${result.lifecycle.coverage}\n`);
1298
+ }
1299
+ if (result.governance !== null) {
1300
+ process.stderr.write(renderTargetGovernanceDecision(result.governance));
1301
+ }
1134
1302
  for (const diagnostic of result.diagnostics) {
1135
1303
  process.stderr.write(`${renderDiagnostic(diagnostic, source, color)}\n`);
1136
1304
  }
@@ -1148,7 +1316,7 @@ function unitMigrationJson(result, source, sourceDigest, ok) {
1148
1316
  });
1149
1317
  return {
1150
1318
  schema_version: result.schemaVersion,
1151
- cli_contract_version: 4,
1319
+ cli_contract_version: 6,
1152
1320
  tool_version: TOOL_VERSION,
1153
1321
  operation: "project.migrate-unit",
1154
1322
  ok,
@@ -1276,23 +1444,90 @@ async function runUnitMigration(args) {
1276
1444
  }
1277
1445
  return ok ? 0 : 1;
1278
1446
  }
1279
- function advanceResultJson(details) {
1280
- return details === null
1281
- ? null
1282
- : {
1283
- removed_task_ids: details.removedTaskIds,
1284
- removed_gate_ids: details.removedGateIds,
1285
- removed_milestone_ids: details.removedMilestoneIds,
1286
- frontier_before: details.frontierBefore,
1287
- frontier_after: details.frontierAfter,
1288
- ready_before: details.readyBefore,
1289
- ready_after: details.readyAfter,
1290
- };
1447
+ async function readHistoryForCommand(operation, parsed, format) {
1448
+ if (parsed.positionals.length !== 1) {
1449
+ throw new UsageError(`${operation.replace(".", " ")} requires exactly one <file>`);
1450
+ }
1451
+ const source = parsed.positionals[0];
1452
+ if (source === "-") {
1453
+ throw new UsageError(`${operation.replace(".", " ")} requires an on-disk file`);
1454
+ }
1455
+ try {
1456
+ return await inspectTargetProjectHistoryFile({
1457
+ targetPath: source,
1458
+ ...(parsed.values.get("rev") === undefined
1459
+ ? {}
1460
+ : { revision: parsed.values.get("rev") }),
1461
+ ...(parsed.repeatedValues.get("task") === undefined
1462
+ ? {}
1463
+ : { taskIds: parsed.repeatedValues.get("task") }),
1464
+ }, TARGET_GRAMMAR_5_CAPABILITY);
1465
+ }
1466
+ catch (error) {
1467
+ return error instanceof Error ? error : new Error(String(error));
1468
+ }
1469
+ }
1470
+ async function runProjectHistory(args) {
1471
+ const parsed = parseCommandOptions("project.history", args);
1472
+ const format = outputFormat(parsed.values.get("format"));
1473
+ const color = colorMode(parsed.values.get("color"), format);
1474
+ const result = await readHistoryForCommand("project.history", parsed, format);
1475
+ if (result instanceof Error) {
1476
+ return cliError(result, 3, "project.history", format === "json");
1477
+ }
1478
+ const warningFailure = parsed.flags.has("warnings-as-errors") &&
1479
+ result.diagnostics.some((diagnostic) => diagnostic.severity === "warning");
1480
+ const ok = result.ok && !warningFailure;
1481
+ if (format === "json") {
1482
+ writeJson(targetProjectHistoryResultToJson(ok ? result : Object.freeze({ ...result, ok: false }), parsed.positionals[0]));
1483
+ }
1484
+ else {
1485
+ if (ok)
1486
+ process.stdout.write(renderTargetProjectHistoryText(result));
1487
+ for (const diagnostic of result.diagnostics) {
1488
+ process.stderr.write(`${renderDiagnostic(diagnostic, parsed.positionals[0], color)}\n`);
1489
+ }
1490
+ }
1491
+ return ok ? 0 : 1;
1492
+ }
1493
+ async function runProjectVelocityObservation(args) {
1494
+ const parsed = parseCommandOptions("project.observe-velocity", args);
1495
+ const format = outputFormat(parsed.values.get("format"));
1496
+ const color = colorMode(parsed.values.get("color"), format);
1497
+ const history = await readHistoryForCommand("project.observe-velocity", parsed, format);
1498
+ if (history instanceof Error) {
1499
+ return cliError(history, 3, "project.observe-velocity", format === "json");
1500
+ }
1501
+ const evidenceValue = parsed.values.get("evidence") ?? "declared";
1502
+ const evidence = evidenceValue === "git-recorded"
1503
+ ? "git_recorded"
1504
+ : evidenceValue;
1505
+ const result = observeTargetProjectVelocity(history, {
1506
+ ...(parsed.repeatedValues.get("task") === undefined
1507
+ ? {}
1508
+ : { taskIds: parsed.repeatedValues.get("task") }),
1509
+ evidence,
1510
+ });
1511
+ const warningFailure = parsed.flags.has("warnings-as-errors") &&
1512
+ result.diagnostics.some((diagnostic) => diagnostic.severity === "warning");
1513
+ const ok = result.ok && !warningFailure;
1514
+ if (format === "json") {
1515
+ writeJson(targetVelocityObservationResultToJson(ok ? result : Object.freeze({ ...result, ok: false }), parsed.positionals[0]));
1516
+ }
1517
+ else {
1518
+ if (ok) {
1519
+ process.stdout.write(renderTargetVelocityObservationText(result));
1520
+ }
1521
+ for (const diagnostic of result.diagnostics) {
1522
+ process.stderr.write(`${renderDiagnostic(diagnostic, parsed.positionals[0], color)}\n`);
1523
+ }
1524
+ }
1525
+ return ok ? 0 : 1;
1291
1526
  }
1292
1527
  function renderAdvanceSummary(details) {
1293
1528
  const list = (ids) => ids.join(",") || "-";
1294
1529
  return [
1295
- `ADVANCE removed_tasks=${list(details.removedTaskIds)} removed_gates=${list(details.removedGateIds)} removed_milestones=${list(details.removedMilestoneIds)}`,
1530
+ `ADVANCE removed_tasks=${list(details.removedTaskIds)} removed_gates=${list(details.removedGateIds)} removed_milestones=${list(details.removedMilestoneIds)} removed_work_events=${list("removedWorkEventIds" in details ? details.removedWorkEventIds : [])}`,
1296
1531
  `ADVANCE frontier_before=${list(details.frontierBefore)} frontier_after=${list(details.frontierAfter)} ready_before=${list(details.readyBefore)} ready_after=${list(details.readyAfter)}`,
1297
1532
  "",
1298
1533
  ].join("\n");
@@ -1319,17 +1554,21 @@ async function runAdvance(args) {
1319
1554
  maxDiagnostics,
1320
1555
  originalLabel: source,
1321
1556
  updatedLabel: "candidate",
1557
+ governance: governanceRequest(parsed, writeRequest),
1322
1558
  });
1323
1559
  const warningFailure = parsed.flags.has("warnings-as-errors") &&
1324
1560
  (result.diagnosticsTruncated ||
1325
1561
  result.diagnostics.some((diagnostic) => diagnostic.severity === "warning"));
1326
1562
  const ok = result.ok && !warningFailure;
1327
- let writeResult = null;
1563
+ let writeResult = Object.freeze({
1564
+ mode: writeRequest.mode,
1565
+ target: writeRequest.target,
1566
+ written: false,
1567
+ });
1328
1568
  if (result.ok) {
1329
1569
  try {
1330
- assertExpectedDigest(writeRequest, input.digest);
1331
1570
  if (ok && writeRequest.mode !== "preview") {
1332
- writeResult = await commitCandidate(writeRequest, result.updatedText, input.digest);
1571
+ writeResult = await persistGovernedResult(result, writeRequest, sourceOperand);
1333
1572
  }
1334
1573
  }
1335
1574
  catch (error) {
@@ -1337,25 +1576,12 @@ async function runAdvance(args) {
1337
1576
  }
1338
1577
  }
1339
1578
  if (format === "json") {
1340
- writeJson({
1341
- schema_version: "Perttool.MutationResult.v1",
1342
- cli_contract_version: 4,
1343
- tool_version: TOOL_VERSION,
1344
- operation: "dag.advance",
1345
- ok,
1346
- document_id: result.documentId,
1347
- source,
1348
- source_digest: input.digest,
1349
- diagnostics: result.diagnostics.map(jsonDiagnostic),
1350
- diagnostics_truncated: result.diagnosticsTruncated,
1351
- ...previewResultJson(result, result.ok, writeRequest, writeResult),
1352
- advance: advanceResultJson(result.advance),
1353
- });
1579
+ writeJson(contract6MutationResultToJson(ok ? result : Object.freeze({ ...result, ok: false }), "dag.advance", source, writeResult));
1354
1580
  }
1355
1581
  else {
1356
1582
  if (ok && result.advance !== null) {
1357
- if (writeResult !== null) {
1358
- process.stderr.write(renderWriteSummary("dag.advance", writeResult));
1583
+ if (writeRequest.mode !== "preview") {
1584
+ process.stderr.write(renderGovernanceWriteSummary("dag.advance", writeResult, result.updatedDigest));
1359
1585
  }
1360
1586
  else {
1361
1587
  process.stdout.write(parsed.flags.has("diff") ? (result.diff ?? "") : (result.updatedText ?? ""));
@@ -1365,6 +1591,9 @@ async function runAdvance(args) {
1365
1591
  }
1366
1592
  process.stderr.write(renderAdvanceSummary(result.advance));
1367
1593
  }
1594
+ if (result.governance !== null) {
1595
+ process.stderr.write(renderTargetGovernanceDecision(result.governance));
1596
+ }
1368
1597
  for (const diagnostic of result.diagnostics) {
1369
1598
  process.stderr.write(`${renderDiagnostic(diagnostic, source, color)}\n`);
1370
1599
  }
@@ -1394,6 +1623,8 @@ function precedenceJson(result, unit, precision) {
1394
1623
  makespan: rationalJson(result.makespan, unit, precision),
1395
1624
  conditional_on_blocks_resolved: result.conditionalOnBlocksResolved,
1396
1625
  blocked_task_ids: result.blockedTaskIds,
1626
+ conditional_on_suspensions_resumed: result.conditionalOnSuspensionsResumed,
1627
+ suspended_task_ids: result.suspendedTaskIds,
1397
1628
  milestones: result.milestones.map((milestone) => ({
1398
1629
  id: milestone.id,
1399
1630
  earliest: rationalJson(milestone.earliest, unit, precision),
@@ -1445,6 +1676,8 @@ function resourceJson(result, unit, precision) {
1445
1676
  algorithm: result.algorithm,
1446
1677
  conditional_on_blocks_resolved: result.conditionalOnBlocksResolved,
1447
1678
  blocked_task_ids: result.blockedTaskIds,
1679
+ conditional_on_suspensions_resumed: result.conditionalOnSuspensionsResumed,
1680
+ suspended_task_ids: result.suspendedTaskIds,
1448
1681
  capacities: result.capacities.map((capacity) => ({
1449
1682
  id: capacity.id,
1450
1683
  declared: capacity.declared,
@@ -1553,11 +1786,14 @@ function renderAnalysisText(result) {
1553
1786
  const conditional = result.precedence?.conditionalOnBlocksResolved === true ||
1554
1787
  result.resource?.conditionalOnBlocksResolved === true;
1555
1788
  const blockedTaskIds = result.precedence?.blockedTaskIds ?? result.resource?.blockedTaskIds ?? [];
1789
+ const suspendedTaskIds = result.precedence?.suspendedTaskIds ??
1790
+ result.resource?.suspendedTaskIds ??
1791
+ [];
1556
1792
  const pathsTruncated = result.precedence?.critical.pathsTruncated === true ||
1557
1793
  result.resource?.scheduleCritical.pathsTruncated === true;
1558
1794
  const overrides = [...result.capacityOverrides]
1559
1795
  .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));
1560
- lines.push(`CONDITIONAL_ON_BLOCKS_RESOLVED ${conditional}`, `BLOCKED_TASKS ${blockedTaskIds.join(", ") || "-"}`, `PATHS_TRUNCATED ${pathsTruncated}`, `CAPACITY_OVERRIDES ${overrides.map(([id, capacity]) => `${id}=${capacity}`).join(", ") || "-"}`, `VELOCITY ${result.velocity === null ? "-" : velocityText(result.velocity, result.precision)}`, `VELOCITY_FORECAST_UNIT ${result.velocityForecast?.targetUnit ?? "-"}`);
1796
+ lines.push(`CONDITIONAL_ON_BLOCKS_RESOLVED ${conditional}`, `BLOCKED_TASKS ${blockedTaskIds.join(", ") || "-"}`, `CONDITIONAL_ON_SUSPENSIONS_RESUMED ${suspendedTaskIds.length > 0}`, `SUSPENDED_TASKS ${suspendedTaskIds.join(", ") || "-"}`, `PATHS_TRUNCATED ${pathsTruncated}`, `CAPACITY_OVERRIDES ${overrides.map(([id, capacity]) => `${id}=${capacity}`).join(", ") || "-"}`, `VELOCITY ${result.velocity === null ? "-" : velocityText(result.velocity, result.precision)}`, `VELOCITY_FORECAST_UNIT ${result.velocityForecast?.targetUnit ?? "-"}`);
1561
1797
  if (result.precedence !== null) {
1562
1798
  const precedence = result.precedence;
1563
1799
  lines.push("", "PRECEDENCE", `MAKESPAN ${durationText(precedence.makespan, unit, result.precision)}`, ...(result.velocityForecast === null
@@ -1633,7 +1869,7 @@ function renderAnalysisText(result) {
1633
1869
  ["TEMPORAL PRECEDENCE", result.temporal.precedence],
1634
1870
  ["TEMPORAL RESOURCE", result.temporal.resource],
1635
1871
  ]) {
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(",") || "-"}`);
1872
+ 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(",") || "-"}`, `CONDITIONAL_ON_SUSPENSIONS_RESUMED ${projection.conditionalOnSuspensionsResumed}`, `SUSPENDED_TASKS ${projection.suspendedTaskIds.join(",") || "-"}`);
1637
1873
  for (const task of projection.tasks) {
1638
1874
  lines.push(`${task.taskId} START ${temporalPoint(task.start)} FINISH ${temporalPoint(task.finish)}`);
1639
1875
  }
@@ -1688,8 +1924,8 @@ async function runAnalyze(args) {
1688
1924
  const ok = result.ok && !warningFailure;
1689
1925
  if (format === "json") {
1690
1926
  writeJson({
1691
- schema_version: "Perttool.AnalysisResult.v3",
1692
- cli_contract_version: 4,
1927
+ schema_version: "Perttool.AnalysisResult.v4",
1928
+ cli_contract_version: 6,
1693
1929
  tool_version: TOOL_VERSION,
1694
1930
  operation: "dag.analyze",
1695
1931
  ok,
@@ -1699,6 +1935,11 @@ async function runAnalyze(args) {
1699
1935
  diagnostics: result.diagnostics.map(jsonDiagnostic),
1700
1936
  diagnostics_truncated: result.diagnosticsTruncated,
1701
1937
  grammar_version: result.grammarVersion,
1938
+ task_actuals: result.taskActuals.map((actuals) => ({
1939
+ task_id: actuals.taskId,
1940
+ status: actuals.status,
1941
+ coverage: actuals.coverage,
1942
+ })),
1702
1943
  mode: result.mode,
1703
1944
  precision: result.precision,
1704
1945
  ...(result.durationUnit === null
@@ -1803,7 +2044,7 @@ async function runRender(args) {
1803
2044
  if (format === "json") {
1804
2045
  writeJson({
1805
2046
  schema_version: "Perttool.ExportResult.v1",
1806
- cli_contract_version: 4,
2047
+ cli_contract_version: 6,
1807
2048
  tool_version: TOOL_VERSION,
1808
2049
  operation: "dag.render",
1809
2050
  ok,
@@ -1895,7 +2136,7 @@ async function runImport(args) {
1895
2136
  if (format === "json") {
1896
2137
  writeJson({
1897
2138
  schema_version: "Perttool.ImportResult.v1",
1898
- cli_contract_version: 4,
2139
+ cli_contract_version: 6,
1899
2140
  tool_version: TOOL_VERSION,
1900
2141
  operation: "dag.import",
1901
2142
  ok,
@@ -1986,6 +2227,7 @@ function nextJson(result) {
1986
2227
  runnable_now: result.groups.runnableNow,
1987
2228
  blocked_now: result.groups.blockedNow,
1988
2229
  upcoming: result.groups.upcoming,
2230
+ suspended: result.groups.suspended,
1989
2231
  },
1990
2232
  tasks: result.tasks.map((task) => ({
1991
2233
  id: task.id,
@@ -2158,6 +2400,7 @@ function renderNextText(result) {
2158
2400
  section("RUNNABLE NOW", result.groups.runnableNow);
2159
2401
  section("READY / WAITING RESOURCE", result.groups.ready.filter((id) => !result.groups.runnableNow.includes(id)), "rejection");
2160
2402
  section("BLOCKED NOW", result.groups.blockedNow, "blocked");
2403
+ section("SUSPENDED", result.groups.suspended);
2161
2404
  section("UPCOMING", result.groups.upcoming, "explanation");
2162
2405
  lines.push("", "TEMPORAL CONTEXT");
2163
2406
  for (const temporal of result.temporal.tasks) {
@@ -2202,7 +2445,7 @@ async function runNext(args) {
2202
2445
  if (format === "json") {
2203
2446
  writeJson({
2204
2447
  schema_version: "Perttool.CliError.v1",
2205
- cli_contract_version: 4,
2448
+ cli_contract_version: 6,
2206
2449
  tool_version: TOOL_VERSION,
2207
2450
  operation: "dag.next",
2208
2451
  ok: false,
@@ -2222,8 +2465,8 @@ async function runNext(args) {
2222
2465
  const ok = result.ok && !warningFailure;
2223
2466
  if (format === "json") {
2224
2467
  writeJson({
2225
- schema_version: "Perttool.NextResult.v4",
2226
- cli_contract_version: 4,
2468
+ schema_version: "Perttool.NextResult.v5",
2469
+ cli_contract_version: 6,
2227
2470
  recommendation_interface_version: 1,
2228
2471
  tool_version: TOOL_VERSION,
2229
2472
  operation: "dag.next",
@@ -2259,12 +2502,12 @@ function runGuide(args) {
2259
2502
  colorMode(parsed.values.get("color"), format);
2260
2503
  const topicId = parsed.positionals.length === 0 ? null : parsed.positionals.join(".");
2261
2504
  const level = helpLevel(parsed.values.get("level"), topicId !== null);
2262
- const result = getGuide(topicId, level);
2505
+ const result = getActualsGuide(topicId, level);
2263
2506
  if (format === "json") {
2264
- process.stdout.write(serializeGuideResult(result));
2507
+ process.stdout.write(serializeActualsGuideResult(result));
2265
2508
  }
2266
2509
  else {
2267
- const rendered = renderGuideResult(result);
2510
+ const rendered = renderActualsGuideResult(result);
2268
2511
  if (result.ok) {
2269
2512
  process.stdout.write(rendered);
2270
2513
  }
@@ -2280,15 +2523,15 @@ function runCommandHelp(args) {
2280
2523
  throw new UsageError("help accepts at most <resource> <action>");
2281
2524
  }
2282
2525
  const format = outputFormat(parsed.values.get("format"));
2283
- const result = getCommandDiscovery({
2526
+ const result = getActualsCommandDiscovery({
2284
2527
  resource: parsed.positionals[0] ?? null,
2285
2528
  action: parsed.positionals[1] ?? null,
2286
2529
  });
2287
2530
  if (format === "json") {
2288
- process.stdout.write(serializeCommandHelpResult(result));
2531
+ process.stdout.write(serializeActualsCommandHelpResult(result));
2289
2532
  }
2290
2533
  else {
2291
- const rendered = renderCommandHelpResult(result);
2534
+ const rendered = renderActualsCommandHelpResult(result);
2292
2535
  if (result.ok) {
2293
2536
  process.stdout.write(rendered);
2294
2537
  }
@@ -2318,7 +2561,7 @@ function runAgentHelp(args) {
2318
2561
  const { schema_version: schemaVersion, ...payload } = projected;
2319
2562
  writeJson({
2320
2563
  schema_version: schemaVersion,
2321
- cli_contract_version: 4,
2564
+ cli_contract_version: 6,
2322
2565
  ...payload,
2323
2566
  });
2324
2567
  }
@@ -2356,6 +2599,10 @@ async function dispatchCommand(descriptor, args) {
2356
2599
  return runProjectInit(args);
2357
2600
  case "project.show":
2358
2601
  return runProjectShow(args);
2602
+ case "project.history":
2603
+ return runProjectHistory(args);
2604
+ case "project.observe-velocity":
2605
+ return runProjectVelocityObservation(args);
2359
2606
  case "project.migrate-unit":
2360
2607
  return runUnitMigration(args);
2361
2608
  case "dag.analyze":
@@ -2368,6 +2615,12 @@ async function dispatchCommand(descriptor, args) {
2368
2615
  return runRender(args);
2369
2616
  case "dag.import":
2370
2617
  return runImport(args);
2618
+ case "task.start":
2619
+ return runLifecycleMutation("start", args);
2620
+ case "task.suspend":
2621
+ return runLifecycleMutation("suspend", args);
2622
+ case "task.resume":
2623
+ return runLifecycleMutation("resume", args);
2371
2624
  }
2372
2625
  if (descriptor.path.length === 2
2373
2626
  && (descriptor.operation === "project.set"
@@ -2376,14 +2629,14 @@ async function dispatchCommand(descriptor, args) {
2376
2629
  const [resource, action] = descriptor.path;
2377
2630
  return runMutation(resource, action, args);
2378
2631
  }
2379
- throw new Error(`no Contract 4 handler for ${descriptor.operation}`);
2632
+ throw new Error(`no Contract 6 handler for ${descriptor.operation}`);
2380
2633
  }
2381
2634
  function emitCommandUsage(error, json) {
2382
2635
  if (json) {
2383
- process.stdout.write(serializeCommandUsageError(error));
2636
+ process.stdout.write(serializeActualsCommandUsageError(error));
2384
2637
  }
2385
2638
  else {
2386
- process.stderr.write(renderCommandUsageError(error));
2639
+ process.stderr.write(renderActualsCommandUsageError(error));
2387
2640
  }
2388
2641
  return 2;
2389
2642
  }
@@ -2395,13 +2648,13 @@ async function main(argv) {
2395
2648
  if (argv.length === 1 && argv[0] === "--help") {
2396
2649
  return runCommandHelp([]);
2397
2650
  }
2398
- const validation = validateCommandInvocation(argv);
2651
+ const validation = validateActualsCommandInvocation(argv);
2399
2652
  if (!validation.ok) {
2400
2653
  if (jsonRequested(argv)) {
2401
- process.stdout.write(serializeCommandUsageError(validation.error));
2654
+ process.stdout.write(serializeActualsCommandUsageError(validation.error));
2402
2655
  }
2403
2656
  else {
2404
- process.stderr.write(renderCommandUsageError(validation.error));
2657
+ process.stderr.write(renderActualsCommandUsageError(validation.error));
2405
2658
  }
2406
2659
  return 2;
2407
2660
  }