halfcycle 0.4.13 → 0.4.14

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 (52) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/bin.bundle.mjs +4105 -369
  3. package/bin/pre-tool-use-filter.sh +93 -1
  4. package/dist/bin.d.ts +1 -0
  5. package/dist/bin.d.ts.map +1 -1
  6. package/dist/bin.js +0 -0
  7. package/dist/build-record/pair-io.d.ts +49 -0
  8. package/dist/build-record/pair-io.d.ts.map +1 -0
  9. package/dist/build-record/render-change.d.ts +25 -0
  10. package/dist/build-record/render-change.d.ts.map +1 -0
  11. package/dist/build-record/render-v2.d.ts +10 -5
  12. package/dist/build-record/render-v2.d.ts.map +1 -1
  13. package/dist/build-record/write-change.d.ts +63 -0
  14. package/dist/build-record/write-change.d.ts.map +1 -0
  15. package/dist/build-record/write.d.ts +5 -15
  16. package/dist/build-record/write.d.ts.map +1 -1
  17. package/dist/change.d.ts +136 -0
  18. package/dist/change.d.ts.map +1 -0
  19. package/dist/check-tasks.d.ts +71 -0
  20. package/dist/check-tasks.d.ts.map +1 -0
  21. package/dist/claude-plan.d.ts +114 -0
  22. package/dist/claude-plan.d.ts.map +1 -0
  23. package/dist/cli-contract.d.ts +12 -1
  24. package/dist/cli-contract.d.ts.map +1 -1
  25. package/dist/cli.js +5192 -2132
  26. package/dist/cli.js.map +3 -3
  27. package/dist/close-phase.d.ts +53 -1
  28. package/dist/close-phase.d.ts.map +1 -1
  29. package/dist/index.js +1529 -911
  30. package/dist/index.js.map +3 -3
  31. package/dist/install-manifest.d.ts +11 -1
  32. package/dist/install-manifest.d.ts.map +1 -1
  33. package/dist/install.d.ts +58 -1
  34. package/dist/install.d.ts.map +1 -1
  35. package/dist/merge-settings.d.ts +21 -2
  36. package/dist/merge-settings.d.ts.map +1 -1
  37. package/dist/open-phase.d.ts +1 -1
  38. package/dist/open-phase.d.ts.map +1 -1
  39. package/dist/pace.d.ts +138 -0
  40. package/dist/pace.d.ts.map +1 -0
  41. package/dist/phase-work.d.ts +37 -0
  42. package/dist/phase-work.d.ts.map +1 -0
  43. package/dist/plan.d.ts +5 -2
  44. package/dist/plan.d.ts.map +1 -1
  45. package/dist/prove.d.ts +77 -0
  46. package/dist/prove.d.ts.map +1 -0
  47. package/dist/review.d.ts +19 -1
  48. package/dist/review.d.ts.map +1 -1
  49. package/dist/system-report.d.ts +1 -1
  50. package/dist/system-report.d.ts.map +1 -1
  51. package/dist/uninstall.d.ts.map +1 -1
  52. package/package.json +11 -11
@@ -7,9 +7,96 @@ halfcycle_pt_skip() {
7
7
  local hc_t=${EPOCHREALTIME:-} hc_status
8
8
  [ "${BASH_VERSINFO[0]:-0}" -ge 5 ] || return 1
9
9
  case $hc_t in [0-9]*[.,][0-9][0-9][0-9]*) ;; *) return 1 ;; esac
10
+ hc_pt_live_text= hc_pt_live_sid= hc_pt_live_agent= hc_pt_live_dir=
10
11
  halfcycle_pt_quiet || return 1
12
+ halfcycle_pt_live || return 1
13
+ halfcycle_pt_own_copy || return 1
11
14
  halfcycle_pt_time_line "$hc_t"
12
15
  }
16
+ halfcycle_pt_live() {
17
+ local LC_ALL=C hc_val hc_sid hc_agent= hc_cwd hc_dir hc_file hc_text= hc_ng=0 hc_re='^[A-Za-z0-9_-]{1,128}$'
18
+ local -a hc_files
19
+ halfcycle_pt_string session_id
20
+ case $? in 0) hc_sid=$hc_val ;; 1) return 0 ;; *) return 1 ;; esac
21
+ [[ $hc_sid =~ $hc_re ]] || return 0
22
+ halfcycle_pt_string agent_id
23
+ case $? in 0) hc_agent=$hc_val ;; 1) ;; *) return 1 ;; esac
24
+ if [ -n "$hc_agent" ]; then
25
+ [[ $hc_agent =~ $hc_re ]] || return 0
26
+ hc_agent=-$hc_agent
27
+ fi
28
+ halfcycle_pt_string cwd
29
+ case $? in 0) hc_cwd=$hc_val ;; 1) return 0 ;; *) return 1 ;; esac
30
+ [ -n "$hc_cwd" ] || return 0
31
+ case $hc_cwd in *'\'*|*[$'\n\r']*) return 1 ;; esac
32
+ hc_dir=${TMPDIR:-/tmp}
33
+ while [ "${hc_dir%/}" != "$hc_dir" ]; do hc_dir=${hc_dir%/}; done
34
+ shopt -q nullglob && hc_ng=1
35
+ shopt -s nullglob
36
+ hc_files=("$hc_dir"/halfcycle-live-"$hc_sid"/halfcycle-session-[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]-"$hc_sid".live"$hc_agent")
37
+ [ "$hc_ng" = 1 ] || shopt -u nullglob
38
+ [ "${#hc_files[@]}" -eq 1 ] || return 1
39
+ hc_file=$hc_dir/${hc_files[0]##*/}
40
+ IFS= read -r -d '' hc_text < "$hc_file" 2>/dev/null
41
+ [ "${hc_text%%$'\n'*}" = "$hc_cwd" ] || return 1
42
+ { printf '%s' "$hc_text" > "$hc_file"; } 2>/dev/null || return 1
43
+ hc_pt_live_text=$hc_text hc_pt_live_sid=$hc_sid hc_pt_live_agent=${hc_agent#-} hc_pt_live_dir=$hc_dir
44
+ return 0
45
+ }
46
+ halfcycle_pt_own_copy() {
47
+ local LC_ALL=C hc_val hc_top hc_rest hc_d hc_f hc_text= hc_ng=0 hc_others=0
48
+ local -a hc_files
49
+ halfcycle_pt_string tool_name || return 1
50
+ case $hc_val in
51
+ Write|Edit|MultiEdit) ;;
52
+ *) return 0 ;;
53
+ esac
54
+ halfcycle_pt_string file_path || return 1
55
+ case $hc_val in /*) ;; *) return 1 ;; esac
56
+ case $hc_val in *'\'*|*[$'\n\r']*) return 1 ;; esac
57
+ hc_rest=${hc_pt_live_text#*$'\n'}
58
+ hc_top=${hc_rest%%$'\n'*}
59
+ case $hc_top in /?*) ;; *) return 1 ;; esac
60
+ shopt -q nullglob && hc_ng=1
61
+ shopt -s nullglob
62
+ hc_files=("$hc_pt_live_dir"/halfcycle-live-"$hc_pt_live_sid"/halfcycle-session-[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]-"$hc_pt_live_sid".live-*)
63
+ [ "$hc_ng" = 1 ] || shopt -u nullglob
64
+ for hc_f in "${hc_files[@]}"; do
65
+ if [ -n "$hc_pt_live_agent" ]; then
66
+ case $hc_f in *.live-"$hc_pt_live_agent") continue ;; esac
67
+ fi
68
+ hc_others=1
69
+ done
70
+ case $hc_val in
71
+ "$hc_top"/?*) ;;
72
+ *) [ -z "$hc_pt_live_agent" ] && [ "$hc_others" = 0 ] && return 0; return 1 ;;
73
+ esac
74
+ hc_rest=${hc_val#"$hc_top"/}
75
+ case /$hc_rest/ in *//*|*/./*|*/../*) return 1 ;; esac
76
+ hc_d=$hc_top
77
+ while :; do
78
+ case $hc_rest in */*) ;; *) break ;; esac
79
+ hc_d=$hc_d/${hc_rest%%/*}
80
+ hc_rest=${hc_rest#*/}
81
+ [ -h "$hc_d" ] && return 1
82
+ [ -d "$hc_d" ] || break
83
+ { [ -e "$hc_d/.git" ] || [ -h "$hc_d/.git" ]; } && return 1
84
+ done
85
+ [ -z "$hc_pt_live_agent" ] && return 0
86
+ [ "$hc_others" = 0 ] && return 0
87
+ shopt -q nullglob && hc_ng=1
88
+ shopt -s nullglob
89
+ hc_files=("$hc_pt_live_dir"/halfcycle-live-"$hc_pt_live_sid"/halfcycle-session-[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]-"$hc_pt_live_sid".live)
90
+ [ "$hc_ng" = 1 ] || shopt -u nullglob
91
+ [ "${#hc_files[@]}" -eq 1 ] || return 1
92
+ IFS= read -r -d '' hc_text < "$hc_pt_live_dir/${hc_files[0]##*/}" 2>/dev/null
93
+ case $hc_text in *$'\n'*$'\n'*) ;; *) return 1 ;; esac
94
+ hc_text=${hc_text#*$'\n'}
95
+ hc_text=${hc_text%%$'\n'*}
96
+ [ -n "$hc_text" ] || return 1
97
+ [ "$hc_text" = "$hc_top" ] && return 1
98
+ return 0
99
+ }
13
100
  halfcycle_pt_quiet() {
14
101
  local LC_ALL=C hc_status hc_ext=0
15
102
  shopt -q extglob && hc_ext=1
@@ -29,7 +116,6 @@ halfcycle_pt_quiet_input() {
29
116
  case $hc_input in *'"tool_input":{'*) ;; *) return 1 ;; esac
30
117
  halfcycle_pt_once '"tool_input"' || return 1
31
118
  case $hc_tool in
32
- NotebookEdit|Agent) return 0 ;;
33
119
  Bash)
34
120
  halfcycle_pt_string command
35
121
  case $? in 1) return 0 ;; 2) return 1 ;; esac
@@ -225,6 +311,7 @@ halfcycle_pt_records() {
225
311
  halfcycle_pt_holds 'features' $'f?(\x01)e?(\x01)a?(\x01)t?(\x01)u?(\x01)r?(\x01)e?(\x01)s' && return 1
226
312
  halfcycle_pt_holds 'decisions' $'d?(\x01)e?(\x01)c?(\x01)i?(\x01)s?(\x01)i?(\x01)o?(\x01)n?(\x01)s' && return 1
227
313
  halfcycle_pt_holds 'build-tasks.json' $'b?(\x01)u?(\x01)i?(\x01)l?(\x01)d?(\x01)-?(\x01)t?(\x01)a?(\x01)s?(\x01)k?(\x01)s?(\x01).?(\x01)j?(\x01)s?(\x01)o?(\x01)n' && return 1
314
+ halfcycle_pt_holds 'task-plan.json' $'t?(\x01)a?(\x01)s?(\x01)k?(\x01)-?(\x01)p?(\x01)l?(\x01)a?(\x01)n?(\x01).?(\x01)j?(\x01)s?(\x01)o?(\x01)n' && return 1
228
315
  halfcycle_pt_holds 'build-records' $'b?(\x01)u?(\x01)i?(\x01)l?(\x01)d?(\x01)-?(\x01)r?(\x01)e?(\x01)c?(\x01)o?(\x01)r?(\x01)d?(\x01)s' && return 1
229
316
  halfcycle_pt_holds '.claude' $'.?(\x01)c?(\x01)l?(\x01)a?(\x01)u?(\x01)d?(\x01)e' && return 1
230
317
  return 0
@@ -286,6 +373,11 @@ halfcycle_pt_branch() {
286
373
  local hc_dir hc_dotgit hc_gitdir hc_text hc_line hc_ptr hc_path
287
374
  hc_branch=null
288
375
  case $1 in /*) ;; *) return 0 ;; esac
376
+ hc_dir=$1
377
+ while [ -n "$hc_dir" ] && [ "$hc_dir" != / ]; do
378
+ [ -L "$hc_dir" ] && return 1
379
+ hc_dir=${hc_dir%/*}
380
+ done
289
381
  halfcycle_pt_resolve "$1"
290
382
  hc_dir=$hc_path
291
383
  [ -d "$hc_dir" ] || return 0
package/dist/bin.d.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  * halfcycle draw architecture|phase <phase> — draw the architecture or a phase's pictures into docs/diagrams (`draw/`)
16
16
  * halfcycle plan <phase> [--show] — check a phase's plan and report it; list it (`plan.ts`)
17
17
  * halfcycle review <phase> --show | --answer <part> "…" — list a phase's close review; answer a question (`review.ts`)
18
+ * halfcycle prove <phase> <task> --label "…" (--before|--after) [-- <cmd>] — run a check once on this machine and record what it saw (`prove.ts`)
18
19
  * halfcycle uninstall [--remove-credential] — take back what the installer wrote here
19
20
  *
20
21
  * NO `ci` VERB (T-25, operator decision 2026-09-22(c)) — `ci bind`/`ci unbind`
package/dist/bin.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG"}
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG"}
package/dist/bin.js CHANGED
File without changes
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The file discipline every record-pair writer shares: exclusive create, the
3
+ * write-then-rename replace, and the no-half-pair create of a `.json` beside its `.md`.
4
+ *
5
+ * ONE COPY FOR TWO WRITERS. A phase's Build Record (`write.ts`, `writeRecordPair`) and
6
+ * a small change's record (`write-change.ts`, `writeChangeRecordPair`) write their pairs
7
+ * through these helpers, and `draw`'s three-file writer goes through the same seam, so
8
+ * there is one exclusive-create on disk, not one per writer. What each writer decides
9
+ * for itself is only what to do when a file is already in the way: the phase writer may
10
+ * replace its own earlier record, the small-change writer, whose record is write-once,
11
+ * never replaces.
12
+ *
13
+ * Nothing here builds a path. Every caller composes and checks its own paths before it
14
+ * hands them over (INV-023).
15
+ */
16
+ /**
17
+ * The file operations a pair writer makes, behind one seam so a test can make
18
+ * the SECOND write fail and watch the first come back out. `createExclusive` opens
19
+ * with `wx` (never truncates an existing file) and removes what it created if the
20
+ * write itself fails; `rename` replaces in one step on the same filesystem.
21
+ *
22
+ * ONE SEAM FOR TEXT AND BYTES. `draw`'s three-file writer (`../draw/write.ts`) goes
23
+ * through this same seam with a PNG's bytes, so a string is written as UTF-8 and a
24
+ * byte array as itself.
25
+ */
26
+ export interface RecordPairIo {
27
+ createExclusive(path: string, data: string | Uint8Array): void;
28
+ rename(from: string, to: string): void;
29
+ }
30
+ export declare const NODE_RECORD_PAIR_IO: RecordPairIo;
31
+ /** Serialise a served record for the `.json`: 2-space indent, trailing newline. */
32
+ export declare function serialiseServedRecord(rawRecord: unknown): string;
33
+ /** Is `err` the file system saying the file is already there? */
34
+ export declare function isEexist(err: unknown): boolean;
35
+ /** The text at `path`, or `null` when it cannot be read as a file. */
36
+ export declare function readIfPresent(path: string): string | null;
37
+ /**
38
+ * Create both files of a pair exclusively, `.json` first. `created`: both are down.
39
+ * `exists`: one of the two was already there, and nothing this call created is left
40
+ * behind (a `.json` it created is removed again), so the caller's own replace rule
41
+ * decides from a clean state. Any other failure is thrown, with nothing left behind.
42
+ */
43
+ export declare function createPairExclusive(io: RecordPairIo, jsonPath: string, jsonText: string, mdPath: string, mdText: string): 'created' | 'exists';
44
+ /**
45
+ * Write `text` to `tmp` and rename it over `finalPath`. A `.tmp` left by an earlier
46
+ * crash is removed first and never read. On failure the temporary file is removed.
47
+ */
48
+ export declare function replaceOne(io: RecordPairIo, tmp: string, finalPath: string, text: string): void;
49
+ //# sourceMappingURL=pair-io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pair-io.d.ts","sourceRoot":"","sources":["../../src/build-record/pair-io.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IAC/D,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AA2BD,eAAO,MAAM,mBAAmB,EAAE,YAGjC,CAAC;AAEF,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAEhE;AAED,iEAAiE;AACjE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAE9C;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMzD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,YAAY,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,SAAS,GAAG,QAAQ,CAYtB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAS/F"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The Markdown half of a small change's record pair (feature `three-paths`, *The
3
+ * writer*): `renderChangeRecordMarkdown` turns a frozen `smallChangeRecordSchema` value
4
+ * into the `.md` written beside its `.json` under `docs/build-records/changes/`.
5
+ *
6
+ * A SMALL CHANGE IS NOT A PHASE, and its record never reads as one: no line here says
7
+ * approved, unapproved, or asks for an approval (three-paths AC6, INV-024). A small
8
+ * change carries no approval to give or to lack, so the words never appear.
9
+ *
10
+ * WHOSE WORDS ARE WHOSE (INV-025). What Halfcycle counted is under "What Halfcycle
11
+ * saw"; the agent's one line, a reviewer's finding, a diagnosis and a call are each
12
+ * marked as the agent's own. A change that ended without a count (stopped, or moved up
13
+ * by the person) says Halfcycle did not count it, never that nothing changed. A window
14
+ * with no recorded check run reads as not observed, never as nothing found (INV-021).
15
+ *
16
+ * Every string from the record is printed through the Build Record renderer's own
17
+ * escapers (`printRecordText` for a slot with a line of its own, `printStrictText` for
18
+ * a slot sharing a line), so record text cannot become markup. One home for that rule.
19
+ *
20
+ * SHIPS TO CLIENT DISK inside the CLI: no comment sits beside an object member.
21
+ */
22
+ import type { SmallChangeRecord } from '@halfcycle/events';
23
+ /** The `.md` of a small change's record. Pure: the same record renders the same text. */
24
+ export declare function renderChangeRecordMarkdown(record: SmallChangeRecord): string;
25
+ //# sourceMappingURL=render-change.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-change.d.ts","sourceRoot":"","sources":["../../src/build-record/render-change.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAsH3D,yFAAyF;AACzF,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CA4C5E"}
@@ -16,10 +16,12 @@
16
16
  * bytes, whatever key order a database hands back.
17
17
  *
18
18
  * IT DOES NOT PARSE. The caller parses the record first; the type here is the
19
- * contract's own inferred type. Two of the contract's functions are imported at
19
+ * contract's own inferred type. Four of the contract's functions are imported at
20
20
  * runtime, because each is the one home of what it composes: `codeLinkLine`, the
21
- * code line every reader prints for a part, and `removalsOf`, the parts a closed
22
- * review leaves to take out of the system description. Neither is restated here.
21
+ * code line every reader prints for a part; `removalsOf`, the parts a closed
22
+ * review leaves to take out of the system description; `hasOwnerReview`, whether a
23
+ * record kept the owner's review (v3 and v4 did, v2 did not); and `keptPathOf`, the
24
+ * phase's path a v4 record kept. None is restated here, and no format is compared.
23
25
  * The one cross-half rule it relies
24
26
  * on — both halves describe the same closes, paired by `sequence` — it checks
25
27
  * itself and throws on, because printing a close without its other half would
@@ -77,8 +79,11 @@ export declare function latestCloseHeadline(record: BuildRecord): string;
77
79
  * learned an earlier round said something else).
78
80
  *
79
81
  * Render a parsed Build Record as markdown: v2's observed and attested halves, which
80
- * a v3 record carries unchanged, and for a v3 record the owner's review after the
81
- * latest close (RM1–RM3, feature `close-review-and-carry-drop`).
82
+ * a v3 or v4 record carries unchanged; for a v4 record its path; for a v3 or v4
83
+ * record the owner's review after the latest close (RM1–RM3, feature
84
+ * `close-review-and-carry-drop`); and for a v4 record alone the calls made without asking
85
+ * (C1–C5) and the owner's answers to checks Halfcycle never ran, after the review. An
86
+ * older record prints neither section, so its Markdown is unchanged.
82
87
  *
83
88
  * Throws, in client words, if the two halves describe different closes — which a
84
89
  * parsed record cannot do. Output uses `\n` only and ends in exactly one `\n`.
@@ -1 +1 @@
1
- {"version":3,"file":"render-v2.d.ts","sourceRoot":"","sources":["../../src/build-record/render-v2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAGL,KAAK,WAAW,EAKjB,MAAM,mBAAmB,CAAC;AA8J3B;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAepD;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASpD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOvD;AAgMD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAO/D;AAmRD;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAyEvE"}
1
+ {"version":3,"file":"render-v2.d.ts","sourceRoot":"","sources":["../../src/build-record/render-v2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAgBL,KAAK,WAAW,EAQjB,MAAM,mBAAmB,CAAC;AA8J3B;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAepD;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASpD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOvD;AA2MD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAO/D;AAyTD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAgFvE"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The small-change record pair writer (feature `three-paths`, *The writer*).
3
+ *
4
+ * A small change's record is not a phase's Build Record, so it never goes through
5
+ * `writeRecordPair`: it has its own folder, `docs/build-records/changes/`, its own
6
+ * renderer (`render-change.ts`) and a narrower replace rule.
7
+ *
8
+ * THE FILENAME COMES FROM THE CALLER'S `sortingId` ARGUMENT, never from the record
9
+ * (INV-023). The argument is checked as a UUID (`sortingIdSchema`, the contracts' one
10
+ * home of a change id) here, where every caller converges, before any path is made,
11
+ * so the CLI's own check is not the only one. `record.sortingId` is never read for a
12
+ * path: a record naming another change, or `../x`, still lands at the argument's name.
13
+ * After the join, every path, temporary ones included, is checked to be a plain file
14
+ * directly inside the folder, comparing two operands that move independently: the
15
+ * folder (from `repoRoot` and a constant) and the candidate (from the argument).
16
+ *
17
+ * WRITE-ONCE, SO NEVER REPLACED. The service freezes a small change's record once, so
18
+ * the same `.json` bytes already there are up to date (the `.md` is rendered again,
19
+ * so a renderer change never refuses the CLI's own pair), and anything else in the way
20
+ * is refused, naming the file. The exclusive create and the no-half-pair rule are the
21
+ * shared ones in `pair-io.ts`.
22
+ *
23
+ * All-or-nothing: the id is checked, every path asserted and both texts rendered
24
+ * before the folder is made or the first file created.
25
+ */
26
+ import { type SmallChangeRecord } from '@halfcycle/events';
27
+ import { type RecordPairIo } from './pair-io.js';
28
+ /** The one output folder for small-change records, relative to the project root. */
29
+ export declare const CHANGE_RECORD_DIR: string;
30
+ /** A change id that cannot name a record file: refused before any path is made. */
31
+ export declare class InvalidChangeIdError extends Error {
32
+ readonly id: unknown;
33
+ constructor(id: unknown);
34
+ }
35
+ /**
36
+ * Assert that a joined path is still a plain file directly inside `outDir`. Exported
37
+ * so a test can hand it a candidate outside the folder: no id that passes the UUID
38
+ * check can produce one, so only a direct call can tell this guard from a deleted one.
39
+ */
40
+ export declare function assertInsideChangeDir(outDir: string, candidate: string, id: unknown): void;
41
+ /**
42
+ * What `writeChangeRecordPair` did. `created`: neither file existed. `up-to-date`: the
43
+ * `.json` already held these exact bytes, and the `.md` was rendered again.
44
+ * `in-the-way`: something else is at one of the two paths; nothing was written and
45
+ * `path` names it.
46
+ */
47
+ export type ChangeRecordPairResult = {
48
+ kind: 'created' | 'up-to-date';
49
+ jsonPath: string;
50
+ mdPath: string;
51
+ } | {
52
+ kind: 'in-the-way';
53
+ path: string;
54
+ };
55
+ /**
56
+ * Write a small change's record pair into `docs/build-records/changes/`: the `.json`
57
+ * as the service sent it, the `.md` rendered from the parsed record.
58
+ *
59
+ * @throws InvalidChangeIdError when `sortingId` is not a change id.
60
+ * @throws the file system's own error when a write fails; nothing is left behind.
61
+ */
62
+ export declare function writeChangeRecordPair(repoRoot: string, sortingId: string, rawRecord: unknown, record: SmallChangeRecord, io?: RecordPairIo): ChangeRecordPairResult;
63
+ //# sourceMappingURL=write-change.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-change.d.ts","sourceRoot":"","sources":["../../src/build-record/write-change.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE5E,OAAO,EAML,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAEtB,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,QAA2C,CAAC;AAE1E,mFAAmF;AACnF,qBAAa,oBAAqB,SAAQ,KAAK;IACjC,QAAQ,CAAC,EAAE,EAAE,OAAO;gBAAX,EAAE,EAAE,OAAO;CAIjC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAI1F;AAED;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAC9B;IAAE,IAAI,EAAE,SAAS,GAAG,YAAY,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,iBAAiB,EACzB,EAAE,GAAE,YAAkC,GACrC,sBAAsB,CA0BxB"}
@@ -25,6 +25,7 @@
25
25
  * before the `mkdirSync` and the first create, so a refused record leaves no file
26
26
  * and no empty directory behind.
27
27
  */
28
+ import { NODE_RECORD_PAIR_IO, serialiseServedRecord, type RecordPairIo } from './pair-io.js';
28
29
  import { type BuildRecord } from '@halfcycle/events';
29
30
  /** The one and only output directory (relative to the target repo root). */
30
31
  export declare const BUILD_RECORD_DIR: string;
@@ -43,20 +44,11 @@ export declare const BUILD_RECORD_DIR: string;
43
44
  */
44
45
  export declare function assertInsideOutDir(outDir: string, candidate: string, identity: unknown): void;
45
46
  /**
46
- * The file operations `writeRecordPair` makes, behind one seam so a test can make
47
- * the SECOND write fail and watch the first come back out. `createExclusive` opens
48
- * with `wx` (never truncates an existing file) and removes what it created if the
49
- * write itself fails; `rename` replaces in one step on the same filesystem.
50
- *
51
- * ONE SEAM FOR TEXT AND BYTES. `draw`'s three-file writer (`../draw/write.ts`) goes
52
- * through this same seam with a PNG's bytes, so a string is written as UTF-8 and a
53
- * byte array as itself, and there is one exclusive-create on disk, not two.
47
+ * The shared file discipline (`pair-io.ts`): one exclusive-create, one replace and one
48
+ * no-half-pair create for this writer and the small-change writer. Re-exported so the
49
+ * callers that have always imported them from here keep doing so.
54
50
  */
55
- export interface RecordPairIo {
56
- createExclusive(path: string, data: string | Uint8Array): void;
57
- rename(from: string, to: string): void;
58
- }
59
- export declare const NODE_RECORD_PAIR_IO: RecordPairIo;
51
+ export { NODE_RECORD_PAIR_IO, serialiseServedRecord, type RecordPairIo };
60
52
  /**
61
53
  * What `writeRecordPair` did. `created`: neither file existed. `up-to-date`: the
62
54
  * `.json` already held these exact bytes, and the `.md` was rendered again.
@@ -72,8 +64,6 @@ export type RecordPairResult = {
72
64
  kind: 'in-the-way';
73
65
  path: string;
74
66
  };
75
- /** Serialise a served record for the `.json`: 2-space indent, trailing newline. */
76
- export declare function serialiseServedRecord(rawRecord: unknown): string;
77
67
  /**
78
68
  * Write a phase's Build Record pair into `docs/build-records/`: the `.json` as the
79
69
  * service sent it, and the `.md` rendered from the parsed record.
@@ -1 +1 @@
1
- {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/build-record/write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAcH,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAE3B,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,QAAgC,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAM7F;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IAC/D,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AA2BD,eAAO,MAAM,mBAAmB,EAAE,YAGjC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,SAAS,GAAG,YAAY,GAAG,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjF;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAEhE;AA0CD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,WAAW,EACnB,EAAE,GAAE,YAAkC,GACrC,gBAAgB,CAyDlB"}
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/build-record/write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,EACL,mBAAmB,EAInB,qBAAqB,EACrB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAE3B,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,QAAgC,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAM7F;AAED;;;;GAIG;AACH,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,KAAK,YAAY,EAAE,CAAC;AAEzE;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,SAAS,GAAG,YAAY,GAAG,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjF;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AA8BzC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,WAAW,EACnB,EAAE,GAAE,YAAkC,GACrC,gBAAgB,CAkDlB"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * `npx halfcycle@latest change` — sorting a change, moving it up, and ending a small
3
+ * change (Safe, lean, fast · E3, feature `three-paths` AC2–AC4, AC6's writer, AC8's
4
+ * missing route; `mini-phase` *The CLI*).
5
+ *
6
+ * THREE FORMS, required flags declared once in `cli-contract.ts` (`missingRequirements`
7
+ * enforces them, and the remedy-string seam test reads the same table):
8
+ * - `change sort --request "<line>" --touches <path>… [--contract <from>:<to>]…
9
+ * [--stored-data] [--standing-rule] [--new-part]` checks `docs/system.json` exactly as
10
+ * `system` does (`checkAndReportDescription`, the one path), reads HEAD as the start
11
+ * commit (a project with no commit cannot sort), sends the description, then the
12
+ * sort, and prints control's answer: its sentence, the sorting's id, what to say next.
13
+ * The path is never worked out here: control re-runs the one rule (`sortChange`) and
14
+ * records its own answer. The request is checked locally with the contracts' own
15
+ * `checkChangeSortRequest` first, so a request control would refuse costs no request.
16
+ * - `change up <mini|full> --change <id>` moves an unfinished small change up and, the
17
+ * change now being finished, writes its record pair; `--phase <phase>` moves a mini
18
+ * phase up instead. A move down is refused here with the contracts' own sentence,
19
+ * before anything is sent; control refuses it again, as it refuses every move down.
20
+ * - `change done <id>` reads the record FIRST. A record found means the change has
21
+ * ended: the pair is written, the outcome printed, and nothing else happens — no
22
+ * runner is started, no report is sent. Only when the service says the change has
23
+ * not ended does it start the runner's `observe --change <id>`, which reads the
24
+ * basis, counts or finishes by the work standing, and prints one line this module
25
+ * parses with `observeChangeRunResultSchema` (one home, shared with the runner).
26
+ *
27
+ * THE RECORD'S FILENAME COMES FROM THE `<id>` ARGUMENT, checked as a change id here and
28
+ * again by the writer, never from a field of the record (INV-023).
29
+ *
30
+ * A SMALL CHANGE IS NOT A PHASE (INV-024): no line here asks for, mentions or marks an
31
+ * approval. Every sentence about a path is the contracts' `CHANGE_PATH_WORDS` or
32
+ * control's own answer; none is restated here (INV-003). Nothing a step id; the next
33
+ * thing to say is the phrase control's answer carries (INV-016).
34
+ *
35
+ * TOPOLOGY. Every route here takes the project credential alone: no GitHub
36
+ * installation, no open phase, no portal session (INV-022). A service without a route
37
+ * is told apart by `isMissingRoute`, and the CLI says so and exits 1.
38
+ *
39
+ * INV-001: Node builtins, the global `fetch`, `@halfcycle/events` and `@halfcycle/core`
40
+ * only; the runner is spawned, never imported. SHIPS TO CLIENT DISK inside the CLI: no
41
+ * comment sits beside an object member, and every string is a sentence a person reads.
42
+ */
43
+ import { type ChangeContract, type ObserveChangeRunResult, type SmallChangeRecord } from '@halfcycle/events';
44
+ import { type HeadCommitOutcome } from './close-phase.js';
45
+ import type { RepoCredential } from './open-phase.js';
46
+ import { type SystemDeps, type SystemRun } from './system-report.js';
47
+ import { type ChangeRecordPairResult } from './build-record/write-change.js';
48
+ /** The verb's usage, one home: printed by `--help` and by a usage error. */
49
+ export declare const CHANGE_USAGE: string;
50
+ /** What one invocation asks for, once its words have been read. */
51
+ export type ChangeArgs = {
52
+ readonly form: 'sort';
53
+ readonly request: string;
54
+ readonly touches: readonly string[];
55
+ readonly contracts: readonly ChangeContract[];
56
+ readonly storedData: boolean;
57
+ readonly standingRule: boolean;
58
+ readonly newPart: boolean;
59
+ readonly repoRoot?: string;
60
+ } | {
61
+ readonly form: 'up';
62
+ readonly to: 'mini' | 'full';
63
+ readonly target: {
64
+ readonly kind: 'change';
65
+ readonly sortingId: string;
66
+ } | {
67
+ readonly kind: 'phase';
68
+ readonly phase: string;
69
+ };
70
+ readonly repoRoot?: string;
71
+ } | {
72
+ readonly form: 'done';
73
+ readonly sortingId: string;
74
+ readonly repoRoot?: string;
75
+ };
76
+ /** The words read, or the line to print and the code to exit with. Nothing was sent. */
77
+ export type ParsedChange = {
78
+ ok: true;
79
+ args: ChangeArgs;
80
+ } | {
81
+ ok: false;
82
+ line: string;
83
+ exitCode: 1 | 2;
84
+ };
85
+ /**
86
+ * Read `rest` (the words after `change`). The required flags are the contract's
87
+ * (`missingRequirements`), refused first with the contract's own sentence; then each
88
+ * form's own words are read, and anything else is a usage error. Nothing is read from
89
+ * disk and nothing is sent.
90
+ */
91
+ export declare function parseChangeArgs(rest: readonly string[]): ParsedChange;
92
+ /** The pieces the verb reaches outside itself through; replaced in tests. */
93
+ export interface ChangeDeps extends SystemDeps {
94
+ readonly readHead: (repoRoot: string) => HeadCommitOutcome;
95
+ readonly fetch: typeof fetch;
96
+ readonly observeChange: (credential: RepoCredential, repoRoot: string, sortingId: string) => Promise<ObserveChangeRunResult>;
97
+ readonly writePair: (repoRoot: string, sortingId: string, rawRecord: unknown, record: SmallChangeRecord) => ChangeRecordPairResult;
98
+ }
99
+ /**
100
+ * The one line the `observe --change` child printed, read through the shared
101
+ * `observeChangeRunResultSchema`, or `unreachable` when stdout holds no such line.
102
+ */
103
+ export declare function readObserveChangeLine(stdout: string): ObserveChangeRunResult;
104
+ /**
105
+ * End a small change through the runner's `observe --change <id>`, spawned the way the
106
+ * close step spawns the runner's verbs (`runRunnerChild`), with the same credential.
107
+ * The id is the checked argument. Never throws.
108
+ */
109
+ export declare function runObserveChangeChild(binary: string | Error, credential: RepoCredential, repoRoot: string, sortingId: string): Promise<ObserveChangeRunResult>;
110
+ /** The line naming what to say next: the phrase the service's answer carries. */
111
+ export declare function nextSayLine(say: string): string;
112
+ /** The command that ends a small change, or writes its record again: the one remedy. */
113
+ export declare function changeDoneCommand(sortingId: string): string;
114
+ /** What a finished change's record says about how it ended, in the contracts' words. */
115
+ export declare function recordOutcomeLines(record: SmallChangeRecord): string;
116
+ /**
117
+ * `change sort`. The local check runs with the description that just passed and the
118
+ * HEAD just read; the description version is not known until the service answers, so
119
+ * the local check is given the lowest version there is, which no rule reads beyond its
120
+ * shape. The request that leaves carries the version the service gave.
121
+ */
122
+ export declare function runChangeSort(repoRoot: string, args: Extract<ChangeArgs, {
123
+ form: 'sort';
124
+ }>, deps: ChangeDeps): Promise<SystemRun>;
125
+ /** `change up`: a small change by its id, or a mini phase by its name. */
126
+ export declare function runChangeUp(repoRoot: string, args: Extract<ChangeArgs, {
127
+ form: 'up';
128
+ }>, deps: ChangeDeps): Promise<SystemRun>;
129
+ /**
130
+ * `change done <id>`. The record is read first; the runner is started only when the
131
+ * service says the change has not ended (three-paths AC4).
132
+ */
133
+ export declare function runChangeDone(repoRoot: string, sortingId: string, deps: ChangeDeps): Promise<SystemRun>;
134
+ /** The whole verb, once its words are read: the form's run. */
135
+ export declare function runChange(repoRoot: string, args: ChangeArgs, deps: ChangeDeps): Promise<SystemRun>;
136
+ //# sourceMappingURL=change.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change.d.ts","sourceRoot":"","sources":["../src/change.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * `npx halfcycle@latest check-tasks <phase> [--with-task-list] [--repo <root>]`
3
+ * (`collision-free-parallel-work` AC3; `checks-as-work-lands` AC3, AC6).
4
+ *
5
+ * OFFLINE, ALWAYS. This verb reads at most two files on the person's own machine —
6
+ * `docs/build-records/task-plan.json` and, with `--with-task-list`,
7
+ * `docs/build-records/build-tasks.json` — and nothing else. It opens no credential
8
+ * file, makes no network call, and needs no sign-in. Everything it prints comes from
9
+ * `checkTaskPlan` (`@halfcycle/core`), which is pure: this module's whole job is
10
+ * reading the two files, handing them to that function, and printing what it
11
+ * returns.
12
+ *
13
+ * ONE HOME FOR THE PLAN'S SHAPE AND ITS RULES (INV-003): `taskPlanSchema`,
14
+ * `taskListFileSchema`, `checkTaskPlan` and every line it prints live in
15
+ * `@halfcycle/core`'s `task-plan.ts`. This file restates none of them — it only
16
+ * turns a missing or unparsable file into the one kind of answer this verb gives for
17
+ * that (exit 2, naming the file), and turns the function's lines into the exit code
18
+ * the spec pins (0 clean, 1 any refusal).
19
+ *
20
+ * THE TASK LIST IS READ ONLY WHEN ASKED. Without `--with-task-list` this verb never
21
+ * opens `build-tasks.json`, so a stale or disagreeing list on disk from an earlier
22
+ * phase changes nothing about a planning run — the one case
23
+ * `collision-free-parallel-work`'s *Decisions taken* exists to prevent.
24
+ *
25
+ * A MISSING TASK LIST IS NOT AN ERROR. Only the plan file is required input; a task
26
+ * list that is missing, names another phase, or carries no phase at all (the shape
27
+ * written before this release) reads as `checkTaskPlan`'s note N1 and exits 0 on an
28
+ * otherwise clean plan. A plan file in the same state is always a hard failure (exit
29
+ * 2), because the plan is this verb's one required input.
30
+ *
31
+ * SHIPS TO CLIENT DISK inside the CLI (INV-001: Node builtins, `@halfcycle/core`
32
+ * only — no network, so no `@halfcycle/events` and no `fetch`). Every string here is
33
+ * a sentence a person reads in their own terminal, so none names an internal
34
+ * register, a route, or this project's own tooling; no comment sits beside an
35
+ * object member.
36
+ */
37
+ /** The usage line, printed for no phase, an extra argument or an unknown flag. */
38
+ export declare const CHECK_TASKS_USAGE = "npx halfcycle@latest check-tasks <phase> [--with-task-list] [--repo <root>]";
39
+ /**
40
+ * What the arguments after `check-tasks` came to. Exactly one positional (the
41
+ * phase), at most one `--with-task-list` and at most one `--repo <root>`; anything
42
+ * else, no phase included, is usage.
43
+ */
44
+ export type CheckTasksArgs = {
45
+ ok: true;
46
+ phase: string;
47
+ withTaskList: boolean;
48
+ repoRoot: string | undefined;
49
+ } | {
50
+ ok: false;
51
+ };
52
+ export declare function parseCheckTasksArgs(argv: readonly string[]): CheckTasksArgs;
53
+ /** What a run of the verb came to. */
54
+ export interface CheckTasksRun {
55
+ readonly stdout: string;
56
+ readonly stderr: string;
57
+ readonly exitCode: number;
58
+ }
59
+ /**
60
+ * The whole verb. Reads `docs/build-records/task-plan.json` under `repoRoot`, and,
61
+ * with `withTaskList`, `docs/build-records/build-tasks.json`; hands both to
62
+ * `checkTaskPlan` and prints its lines, one per line, in the order it returns them,
63
+ * or `checkTaskPlan`'s own clear line when there is nothing to report. Never throws.
64
+ *
65
+ * Exit 0 when nothing refuses, 1 when anything does, 2 when the plan file is
66
+ * missing or either file cannot be read or parsed as JSON or does not match the
67
+ * shape this tool reads (naming which file, and where). A missing task list is not
68
+ * an error (see the module header) and never changes the exit code on its own.
69
+ */
70
+ export declare function runCheckTasks(repoRoot: string, phase: string, withTaskList: boolean): CheckTasksRun;
71
+ //# sourceMappingURL=check-tasks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-tasks.d.ts","sourceRoot":"","sources":["../src/check-tasks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAgBH,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,gFAAgF,CAAC;AAE/G;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAChF;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC;AAElB,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAuB3E;AA2CD,sCAAsC;AACtC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,aAAa,CA4BnG"}