@pmelab/gtd 60.1.0 → 62.0.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 (61) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +16 -0
  3. package/dist/chunks/{testkit-BhTey1Do.mjs → testkit-XQeYCNMn.mjs} +1670 -1470
  4. package/dist/chunks/{wire-DZ8R1RU9.mjs → wire-CPNSm_9N.mjs} +27 -12
  5. package/dist/gtd.bundle.mjs +991 -627
  6. package/dist/gtd.core.mjs +117 -117
  7. package/dist/testkit/gherkin.mjs +1 -1
  8. package/dist/testkit.mjs +1 -1
  9. package/dist/wire.mjs +2 -2
  10. package/package.json +1 -1
  11. package/src/cli/Cli.ts +6 -1
  12. package/src/cli/History.test.ts +10 -1
  13. package/src/cli/Launch.test.ts +52 -0
  14. package/src/cli/Launch.ts +143 -0
  15. package/src/cli/Metrics.test.ts +10 -1
  16. package/src/cli/Program.test.ts +30 -20
  17. package/src/cli/Program.ts +4 -2
  18. package/src/cli/Strays.ts +3 -2
  19. package/src/cli/index.ts +1 -0
  20. package/src/edge/Edge.test.ts +79 -26
  21. package/src/edge/Edge.ts +178 -99
  22. package/src/emit/GitScript.ts +21 -3
  23. package/src/emit/index.ts +3 -0
  24. package/src/gherkin/messages.test.ts +1 -0
  25. package/src/gherkin/module-config.feature +1 -0
  26. package/src/gherkin/more-steps.feature +1 -0
  27. package/src/gherkin/testkit.feature +2 -1
  28. package/src/install/Install.ts +9 -2
  29. package/src/platform/PinFile.ts +61 -0
  30. package/src/platform/index.ts +1 -0
  31. package/src/replay/Format.test.ts +35 -8
  32. package/src/replay/Format.ts +7 -4
  33. package/src/replay/Replay.test.ts +54 -0
  34. package/src/replay/Replay.ts +61 -17
  35. package/src/replay/Trailers.test.ts +11 -48
  36. package/src/replay/Trailers.ts +36 -42
  37. package/src/replay/Tree.ts +11 -0
  38. package/src/replay/index.ts +3 -0
  39. package/src/step/LandStep.ts +1 -0
  40. package/src/step/RepoSnapshot.ts +2 -0
  41. package/src/step/planStart.test.ts +2 -3
  42. package/src/step/planStart.ts +1 -5
  43. package/src/step/planStep.test.ts +4 -4
  44. package/src/step/planStep.ts +10 -8
  45. package/src/testkit/TestRepo.test.ts +4 -1
  46. package/src/testkit/TestRepo.ts +3 -1
  47. package/src/twins/EmittedScriptRecognizer.ts +32 -1
  48. package/src/twins/GitTiers.ts +23 -0
  49. package/src/twins/InMemRepo.ts +14 -0
  50. package/src/twins/PinBeats.ts +33 -0
  51. package/src/twins/PinFile.test.ts +107 -0
  52. package/src/twins/PinFile.ts +12 -0
  53. package/src/twins/index.ts +2 -0
  54. package/src/wire/BeatDocument.ts +26 -5
  55. package/src/wire/Demand.ts +16 -6
  56. package/src/wire/constants.ts +3 -0
  57. package/src/wire/types.ts +4 -0
  58. package/src/workflow/fingerprint.ts +2 -2
  59. package/src/workflow/index.ts +2 -2
  60. package/src/workflow/load.ts +14 -1
  61. package/src/workflow/pin.ts +15 -7
package/src/wire/types.ts CHANGED
@@ -10,6 +10,9 @@ export type StateName = string
10
10
  /** The three content kinds a state can carry — exactly one per state. */
11
11
  export type ContentKind = "script" | "prompt" | "message"
12
12
 
13
+ /** What a pin or unpin beat does to the pin; a driver may do it its own way instead of running the script. */
14
+ export type PinMark = "add" | "remove"
15
+
13
16
  /** The name of a steering-file mode. Not a closed vocabulary: the valid set derives from the active definition (`BUILT_IN_MODES` plus whatever `modes:` declares). */
14
17
  export type StateMode = string
15
18
 
@@ -51,4 +54,5 @@ export interface RenderedDemandSource {
51
54
  readonly judge?: string
52
55
  readonly skills?: readonly string[]
53
56
  readonly access?: StepAccess
57
+ readonly pin?: PinMark
54
58
  }
@@ -7,12 +7,12 @@ export type ConfigFingerprint = Readonly<Record<string, string>>
7
7
 
8
8
  const HASH_LENGTH = 16
9
9
 
10
- // A path the trailer cannot carry printably is never recorded, so it can never
10
+ // A path with a control character is never recorded, so it can never
11
11
  // reach a terminal from history.
12
12
  // eslint-disable-next-line no-control-regex -- the control range IS the point
13
13
  const CONTROL_CHARACTER = /[\x00-\x1f\x7f-\x9f]/
14
14
 
15
- export const contentHash = (content: string | Buffer): string =>
15
+ const contentHash = (content: string | Buffer): string =>
16
16
  createHash("sha256").update(content).digest("hex").slice(0, HASH_LENGTH)
17
17
 
18
18
  const realOr = (path: string): string => {
@@ -33,5 +33,5 @@ export {
33
33
  type StateMode,
34
34
  type StateName,
35
35
  } from "./Workflow.js"
36
- export { changedConfigFiles, contentHash, type ConfigFingerprint } from "./fingerprint.js"
37
- export { EngineCore, pin, readPin } from "./pin.js"
36
+ export type { ConfigFingerprint } from "./fingerprint.js"
37
+ export { EngineCore, pin, pinHeader, PIN_FORMAT, readPin } from "./pin.js"
@@ -17,7 +17,7 @@ import { Host, Workspace, type WorkspaceOps } from "../platform/index.js"
17
17
  import { compileConfig, type CompiledConfig } from "./compile.js"
18
18
  import { SETTING_NAME_RULE, isSettingName } from "./vars.js"
19
19
  import { ConfigDiscovery, WORKFLOW_MODULE, type WorkflowModule } from "./discovery.js"
20
- import { configFingerprint, type ConfigFingerprint } from "./fingerprint.js"
20
+ import { changedConfigFiles, configFingerprint, type ConfigFingerprint } from "./fingerprint.js"
21
21
  import { PIN_KEY } from "../install/index.js"
22
22
  import {
23
23
  dedupeDiagnostics,
@@ -50,6 +50,8 @@ export interface ConfigOperations {
50
50
  readonly warnings: readonly Diagnostic[]
51
51
  /** The content hash of every file `gtd.config.ts`'s evaluation touched — itself plus any relative import — by path relative to the root. */
52
52
  readonly fingerprint: ConfigFingerprint
53
+ /** Inside a pin, the files it was bundled from that differ on disk now. */
54
+ readonly drift: readonly string[]
53
55
  }
54
56
 
55
57
  // Everything lives in `message` (not `GtdError.detail`) — `renderFailure`
@@ -204,6 +206,17 @@ export const load: Effect.Effect<
204
206
  fingerprint:
205
207
  pinned?.fingerprint ??
206
208
  configFingerprint(loaded.file.dependencyFiles, host.root, module.read ?? readFromDisk),
209
+ drift:
210
+ pinned === undefined
211
+ ? []
212
+ : changedConfigFiles(
213
+ pinned.fingerprint,
214
+ configFingerprint(
215
+ Object.keys(pinned.fingerprint).map((file) => resolvePath(host.root, file)),
216
+ host.root,
217
+ readFromDisk,
218
+ ),
219
+ ),
207
220
  }
208
221
  })
209
222
 
@@ -7,7 +7,7 @@ import { ownPackage, pinnedWorkflowSource } from "./load.js"
7
7
  import type { WorkflowModule } from "./discovery.js"
8
8
 
9
9
  /** Bumped when a launcher could no longer run an older pin the same way. */
10
- const PIN_FORMAT = 1
10
+ export const PIN_FORMAT = 1
11
11
 
12
12
  /**
13
13
  * The engine a pin carries, as ES module source. Live, the `gtd.core.mjs` the
@@ -57,16 +57,24 @@ export const pin = (
57
57
  ])
58
58
  })
59
59
 
60
+ /** A pin's header line, read without unpacking it; undefined when the bytes are no pin. */
61
+ export const pinHeader = (
62
+ bytes: Uint8Array,
63
+ ): { readonly format: number; readonly version: string } | undefined => {
64
+ const end = bytes.indexOf(0x0a)
65
+ const header =
66
+ end < 0 ? null : /^gtdpin (\d+) gtd (\S+)$/.exec(Buffer.from(bytes.subarray(0, end)).toString())
67
+ return header === null ? undefined : { format: Number(header[1]), version: header[2]! }
68
+ }
69
+
60
70
  /** A pin's header and module source. */
61
71
  export const readPin = (
62
72
  bytes: Uint8Array,
63
73
  ): { readonly format: number; readonly version: string; readonly source: string } => {
64
- const end = bytes.indexOf(0x0a)
65
- const header = /^gtdpin (\d+) gtd (\S+)$/.exec(Buffer.from(bytes.subarray(0, end)).toString())
66
- if (end < 0 || header === null) throw new Error("not a gtd pin")
74
+ const header = pinHeader(bytes)
75
+ if (header === undefined) throw new Error("not a gtd pin")
67
76
  return {
68
- format: Number(header[1]),
69
- version: header[2]!,
70
- source: gunzipSync(bytes.subarray(end + 1)).toString("utf8"),
77
+ ...header,
78
+ source: gunzipSync(bytes.subarray(bytes.indexOf(0x0a) + 1)).toString("utf8"),
71
79
  }
72
80
  }