@detergent-software/conductor 3.0.0-dev.3 → 3.0.0-dev.30

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 (171) hide show
  1. package/CHANGELOG.md +241 -0
  2. package/README.md +201 -63
  3. package/build/brief-cli.js +1 -1
  4. package/build/brief-cli.js.map +1 -1
  5. package/build/broker-cli.js +24 -25
  6. package/build/broker-cli.js.map +1 -1
  7. package/build/broker.js +66 -92
  8. package/build/broker.js.map +1 -1
  9. package/build/claude.js +50 -7
  10. package/build/claude.js.map +1 -1
  11. package/build/cli.js +143 -63
  12. package/build/cli.js.map +1 -1
  13. package/build/clones.js +92 -0
  14. package/build/clones.js.map +1 -0
  15. package/build/code-host-cli.js +116 -32
  16. package/build/code-host-cli.js.map +1 -1
  17. package/build/code-host-service.js +8 -0
  18. package/build/code-host-service.js.map +1 -1
  19. package/build/code-host.js +22 -11
  20. package/build/code-host.js.map +1 -1
  21. package/build/config.js +58 -14
  22. package/build/config.js.map +1 -1
  23. package/build/console.js +40 -24
  24. package/build/console.js.map +1 -1
  25. package/build/credentials.js +42 -29
  26. package/build/credentials.js.map +1 -1
  27. package/build/daemon.js +38 -22
  28. package/build/daemon.js.map +1 -1
  29. package/build/help.js +179 -42
  30. package/build/help.js.map +1 -1
  31. package/build/history.js +4 -2
  32. package/build/history.js.map +1 -1
  33. package/build/host/credential-manager.js +109 -0
  34. package/build/host/credential-manager.js.map +1 -0
  35. package/build/host/keychain.js +44 -0
  36. package/build/host/keychain.js.map +1 -0
  37. package/build/host/launchd.js +118 -0
  38. package/build/host/launchd.js.map +1 -0
  39. package/build/host/libsecret.js +40 -0
  40. package/build/host/libsecret.js.map +1 -0
  41. package/build/host/macos-terminal.js +63 -0
  42. package/build/host/macos-terminal.js.map +1 -0
  43. package/build/host/platform.js +4 -0
  44. package/build/host/platform.js.map +1 -0
  45. package/build/host/secrets.js +46 -0
  46. package/build/host/secrets.js.map +1 -0
  47. package/build/host/shell.js +26 -0
  48. package/build/host/shell.js.map +1 -0
  49. package/build/host/startup.js +26 -0
  50. package/build/host/startup.js.map +1 -0
  51. package/build/host/systemd.js +91 -0
  52. package/build/host/systemd.js.map +1 -0
  53. package/build/host/task-scheduler.js +78 -0
  54. package/build/host/task-scheduler.js.map +1 -0
  55. package/build/host/terminal.js +46 -0
  56. package/build/host/terminal.js.map +1 -0
  57. package/build/host/tmux.js +39 -0
  58. package/build/host/tmux.js.map +1 -0
  59. package/build/host/windows-terminal.js +46 -0
  60. package/build/host/windows-terminal.js.map +1 -0
  61. package/build/in-process.js +6 -5
  62. package/build/in-process.js.map +1 -1
  63. package/build/inbox.js +63 -17
  64. package/build/inbox.js.map +1 -1
  65. package/build/init.js +508 -0
  66. package/build/init.js.map +1 -0
  67. package/build/landing.js +86 -7
  68. package/build/landing.js.map +1 -1
  69. package/build/lib/providers/ado/code-host.js +5 -1
  70. package/build/lib/providers/ado/code-host.js.map +1 -1
  71. package/build/lib/providers/ado/pipelines.js +130 -22
  72. package/build/lib/providers/ado/pipelines.js.map +1 -1
  73. package/build/lib/providers/ado/trigger.js +5 -0
  74. package/build/lib/providers/ado/trigger.js.map +1 -1
  75. package/build/lib/providers/ado/writes.js +10 -1
  76. package/build/lib/providers/ado/writes.js.map +1 -1
  77. package/build/lib/providers/github/actions.js +90 -3
  78. package/build/lib/providers/github/actions.js.map +1 -1
  79. package/build/lib/providers/github/code-host.js +2 -0
  80. package/build/lib/providers/github/code-host.js.map +1 -1
  81. package/build/lib/providers/github/writes.js +24 -2
  82. package/build/lib/providers/github/writes.js.map +1 -1
  83. package/build/lib/providers/index.js +4 -3
  84. package/build/lib/providers/index.js.map +1 -1
  85. package/build/lib/providers/npm/stage.js +28 -5
  86. package/build/lib/providers/npm/stage.js.map +1 -1
  87. package/build/lib/providers/timing.js +10 -0
  88. package/build/lib/providers/timing.js.map +1 -0
  89. package/build/lib/providers/verdicts/pr.js +47 -6
  90. package/build/lib/providers/verdicts/pr.js.map +1 -1
  91. package/build/lib/providers/verdicts/runs.js +12 -2
  92. package/build/lib/providers/verdicts/runs.js.map +1 -1
  93. package/build/local-files.js +95 -0
  94. package/build/local-files.js.map +1 -0
  95. package/build/manager.js +171 -47
  96. package/build/manager.js.map +1 -1
  97. package/build/note.js +1 -1
  98. package/build/note.js.map +1 -1
  99. package/build/notify.js +101 -39
  100. package/build/notify.js.map +1 -1
  101. package/build/npm-reads.js +323 -0
  102. package/build/npm-reads.js.map +1 -0
  103. package/build/paths.js +44 -1
  104. package/build/paths.js.map +1 -1
  105. package/build/plugin.js +10 -0
  106. package/build/plugin.js.map +1 -1
  107. package/build/profile-fields.js +149 -0
  108. package/build/profile-fields.js.map +1 -0
  109. package/build/profile-layers.js +264 -0
  110. package/build/profile-layers.js.map +1 -0
  111. package/build/profile.js +70 -44
  112. package/build/profile.js.map +1 -1
  113. package/build/project-check-cli.js +18 -10
  114. package/build/project-check-cli.js.map +1 -1
  115. package/build/project-check.js +187 -98
  116. package/build/project-check.js.map +1 -1
  117. package/build/pushover.js +5 -5
  118. package/build/pushover.js.map +1 -1
  119. package/build/registry.js +195 -36
  120. package/build/registry.js.map +1 -1
  121. package/build/release-kinds.js +34 -0
  122. package/build/release-kinds.js.map +1 -0
  123. package/build/rule-actions.js +4 -2
  124. package/build/rule-actions.js.map +1 -1
  125. package/build/rules-service.js +101 -44
  126. package/build/rules-service.js.map +1 -1
  127. package/build/rules.js +15 -15
  128. package/build/rules.js.map +1 -1
  129. package/build/run.js +20 -7
  130. package/build/run.js.map +1 -1
  131. package/build/runs.js +200 -45
  132. package/build/runs.js.map +1 -1
  133. package/build/scheduler.js +11 -1
  134. package/build/scheduler.js.map +1 -1
  135. package/build/server.js +6 -25
  136. package/build/server.js.map +1 -1
  137. package/build/spend.js +4 -0
  138. package/build/spend.js.map +1 -1
  139. package/build/store.js +82 -25
  140. package/build/store.js.map +1 -1
  141. package/build/timeline.js +104 -0
  142. package/build/timeline.js.map +1 -0
  143. package/build/tracker-reads-cli.js +4 -5
  144. package/build/tracker-reads-cli.js.map +1 -1
  145. package/build/train-cli.js +6 -5
  146. package/build/train-cli.js.map +1 -1
  147. package/build/upgrade.js +8 -9
  148. package/build/upgrade.js.map +1 -1
  149. package/build/wait.js +6 -6
  150. package/build/wait.js.map +1 -1
  151. package/build/watchers/ado.js +11 -3
  152. package/build/watchers/ado.js.map +1 -1
  153. package/build/watchers/github.js +15 -4
  154. package/build/watchers/github.js.map +1 -1
  155. package/build/watchers/npm.js +6 -4
  156. package/build/watchers/npm.js.map +1 -1
  157. package/build/webhook.js +80 -0
  158. package/build/webhook.js.map +1 -0
  159. package/build/worktrees.js +199 -92
  160. package/build/worktrees.js.map +1 -1
  161. package/package.json +61 -61
  162. package/plugin/skills/conductor-console/SKILL.md +4 -4
  163. package/plugin/skills/work-item/SKILL.md +13 -11
  164. package/build/checkout-sync.js +0 -249
  165. package/build/checkout-sync.js.map +0 -1
  166. package/build/credential.js +0 -66
  167. package/build/credential.js.map +0 -1
  168. package/build/startup.js +0 -92
  169. package/build/startup.js.map +0 -1
  170. package/build/tab.js +0 -32
  171. package/build/tab.js.map +0 -1
@@ -0,0 +1,149 @@
1
+ import { gt, lt, valid } from 'semver';
2
+ import { z } from 'zod';
3
+ import { parseJsonc } from "./jsonc.js";
4
+ // The first conductor that reads protocol 2: one older refuses the profile on `protocol` first, so
5
+ // a field it already read needs no minimum.
6
+ export const PROTOCOL_2 = '2.0.0';
7
+ // The conductor that first read each profile field as it is written today (ATK-577): a reshaped
8
+ // field takes the version of its new shape. A profile that writes a field newer than PROTOCOL_2
9
+ // needs its `conductor` minimum at or above that version. The type makes a new field a typecheck
10
+ // error until it has a version; the README (On PATH) says which one a new field takes. A key
11
+ // two tracker kinds share takes the newer kind's (Linear's releaseWait is 2.0.0's).
12
+ export const FIELD_SINCE = {
13
+ approvers: PROTOCOL_2,
14
+ areaRelease: '2.2.0-dev.2',
15
+ baseBranch: PROTOCOL_2,
16
+ ci: PROTOCOL_2,
17
+ 'ci.kind': PROTOCOL_2,
18
+ codeHost: PROTOCOL_2,
19
+ 'codeHost.kind': PROTOCOL_2,
20
+ 'codeHost.organization': PROTOCOL_2,
21
+ 'codeHost.owner': PROTOCOL_2,
22
+ 'codeHost.project': PROTOCOL_2,
23
+ 'codeHost.repo': PROTOCOL_2,
24
+ 'codeHost.repository': PROTOCOL_2,
25
+ conductor: '3.0.0-dev.9',
26
+ gates: PROTOCOL_2,
27
+ health: PROTOCOL_2,
28
+ localFilesFrom: '3.0.0-dev.4',
29
+ name: PROTOCOL_2,
30
+ npm: PROTOCOL_2,
31
+ 'npm.package': PROTOCOL_2,
32
+ ports: '3.0.0-dev.4',
33
+ protocol: PROTOCOL_2,
34
+ release: PROTOCOL_2,
35
+ 'release.handoff': PROTOCOL_2,
36
+ releases: PROTOCOL_2,
37
+ 'releases.key': PROTOCOL_2,
38
+ 'releases.kind': PROTOCOL_2,
39
+ 'releases.package': PROTOCOL_2,
40
+ 'releases.pipeline': PROTOCOL_2,
41
+ rules: PROTOCOL_2,
42
+ 'rules.do': PROTOCOL_2,
43
+ 'rules.id': PROTOCOL_2,
44
+ 'rules.note': PROTOCOL_2,
45
+ 'rules.when': PROTOCOL_2,
46
+ setup: '3.0.0-dev.4',
47
+ skills: PROTOCOL_2,
48
+ 'skills.aliases': PROTOCOL_2,
49
+ 'skills.verify': PROTOCOL_2,
50
+ 'skills.work': PROTOCOL_2,
51
+ slots: PROTOCOL_2,
52
+ tracker: PROTOCOL_2,
53
+ 'tracker.areaLabelGroup': PROTOCOL_2,
54
+ 'tracker.areaPrefix': PROTOCOL_2,
55
+ 'tracker.areaRoot': PROTOCOL_2,
56
+ 'tracker.humanLabel': PROTOCOL_2,
57
+ 'tracker.key': PROTOCOL_2,
58
+ 'tracker.kind': PROTOCOL_2,
59
+ 'tracker.organization': PROTOCOL_2,
60
+ 'tracker.owner': PROTOCOL_2,
61
+ 'tracker.priority': PROTOCOL_2,
62
+ 'tracker.project': PROTOCOL_2,
63
+ 'tracker.query': PROTOCOL_2,
64
+ 'tracker.queue': PROTOCOL_2,
65
+ 'tracker.queueLabel': PROTOCOL_2,
66
+ 'tracker.releaseWait': '2.2.0-dev.3',
67
+ 'tracker.releaseWait.label': '2.2.0-dev.3',
68
+ 'tracker.releaseWait.shipped': '2.2.0-dev.3',
69
+ 'tracker.repo': PROTOCOL_2,
70
+ 'tracker.states': PROTOCOL_2,
71
+ 'tracker.team': PROTOCOL_2,
72
+ 'tracker.transitions': PROTOCOL_2,
73
+ 'tracker.transitions.landed': PROTOCOL_2,
74
+ 'tracker.transitions.pr-opened': PROTOCOL_2,
75
+ 'tracker.transitions.started': PROTOCOL_2,
76
+ 'tracker.triageLabel': PROTOCOL_2,
77
+ 'tracker.workItemType': PROTOCOL_2,
78
+ train: PROTOCOL_2,
79
+ 'train.maxHours': PROTOCOL_2,
80
+ 'train.minimum': PROTOCOL_2,
81
+ 'train.repo': PROTOCOL_2,
82
+ weight: PROTOCOL_2,
83
+ work: PROTOCOL_2,
84
+ 'work.hooks': PROTOCOL_2,
85
+ 'work.landing': PROTOCOL_2,
86
+ 'work.landing.review': PROTOCOL_2,
87
+ 'work.landing.reviewTiers': PROTOCOL_2,
88
+ workers: PROTOCOL_2,
89
+ 'workers.tab': PROTOCOL_2,
90
+ };
91
+ // minimumProblems with the version that clears them, for `conduct project check`'s fix (ATK-580).
92
+ export function minimumCheck(text, source, since = FIELD_SINCE) {
93
+ const profile = parseJsonc(text, z.unknown(), source);
94
+ const conductor = typeof profile === 'object' && profile !== null && 'conductor' in profile ? profile.conductor : undefined;
95
+ const minimum = typeof conductor === 'string' ? valid(conductor) : null;
96
+ const theirs = minimum !== null
97
+ ? `the profile's minimum is ${minimum}`
98
+ : conductor === undefined
99
+ ? 'the profile sets no "conductor" minimum'
100
+ : `the profile's minimum ${JSON.stringify(conductor)} is not a version`;
101
+ // `tracker` and `tracker.releaseWait` for `tracker.releaseWait.label`.
102
+ const parents = new Set();
103
+ for (const path of Object.keys(since))
104
+ for (let dot = path.indexOf('.'); dot !== -1; dot = path.indexOf('.', dot + 1))
105
+ parents.add(path.slice(0, dot));
106
+ const problems = [];
107
+ let needs = null;
108
+ for (const path of new Set(writtenFields(profile, parents))) {
109
+ const version = since[path];
110
+ if (version === undefined)
111
+ problems.push(`${path}: no conductor version recorded for it in profile-fields.ts`);
112
+ else if (lt(minimum ?? PROTOCOL_2, version)) {
113
+ problems.push(`${path}: needs conductor ${version} or newer; ${theirs}`);
114
+ if (needs === null || gt(version, needs))
115
+ needs = version;
116
+ }
117
+ }
118
+ // Raising the minimum writes `conductor`, so the version also covers the conductor that reads it.
119
+ const key = since.conductor;
120
+ if (needs !== null && key !== undefined && gt(key, needs))
121
+ needs = key;
122
+ return { needs, problems };
123
+ }
124
+ // Where a profile's `conductor` minimum is below a field it writes, one line each: a field newer
125
+ // than the minimum, or than PROTOCOL_2 when it sets none, and a field with no recorded version. A
126
+ // conductor test holds ATK's profile to it, so a PR that writes a new field raises the minimum too.
127
+ export function minimumProblems(text, source, since = FIELD_SINCE) {
128
+ return minimumCheck(text, source, since).problems;
129
+ }
130
+ // The fields a profile writes, by their paths in FIELD_SINCE, in the order it writes them. Fields
131
+ // a default fills in aren't written, so they need nothing. It looks inside a key only when it is
132
+ // one of `parents`, the paths FIELD_SINCE records fields below, so a record's own names (health's
133
+ // environments) aren't fields.
134
+ function* writtenFields(value, parents, prefix = '') {
135
+ if (Array.isArray(value)) {
136
+ for (const item of value)
137
+ yield* writtenFields(item, parents, prefix);
138
+ return;
139
+ }
140
+ if (value === null || typeof value !== 'object')
141
+ return;
142
+ for (const [key, child] of Object.entries(value)) {
143
+ const path = prefix === '' ? key : `${prefix}.${key}`;
144
+ yield path;
145
+ if (parents.has(path))
146
+ yield* writtenFields(child, parents, path);
147
+ }
148
+ }
149
+ //# sourceMappingURL=profile-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-fields.js","sourceRoot":"","sources":["../src/profile-fields.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAaxC,mGAAmG;AACnG,4CAA4C;AAC5C,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC;AAElC,gGAAgG;AAChG,gGAAgG;AAChG,iGAAiG;AACjG,6FAA6F;AAC7F,oFAAoF;AACpF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,SAAS,EAAE,UAAU;IACrB,WAAW,EAAE,aAAa;IAC1B,UAAU,EAAE,UAAU;IACtB,EAAE,EAAE,UAAU;IACd,SAAS,EAAE,UAAU;IACrB,QAAQ,EAAE,UAAU;IACpB,eAAe,EAAE,UAAU;IAC3B,uBAAuB,EAAE,UAAU;IACnC,gBAAgB,EAAE,UAAU;IAC5B,kBAAkB,EAAE,UAAU;IAC9B,eAAe,EAAE,UAAU;IAC3B,qBAAqB,EAAE,UAAU;IACjC,SAAS,EAAE,aAAa;IACxB,KAAK,EAAE,UAAU;IACjB,MAAM,EAAE,UAAU;IAClB,cAAc,EAAE,aAAa;IAC7B,IAAI,EAAE,UAAU;IAChB,GAAG,EAAE,UAAU;IACf,aAAa,EAAE,UAAU;IACzB,KAAK,EAAE,aAAa;IACpB,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,UAAU;IACnB,iBAAiB,EAAE,UAAU;IAC7B,QAAQ,EAAE,UAAU;IACpB,cAAc,EAAE,UAAU;IAC1B,eAAe,EAAE,UAAU;IAC3B,kBAAkB,EAAE,UAAU;IAC9B,mBAAmB,EAAE,UAAU;IAC/B,KAAK,EAAE,UAAU;IACjB,UAAU,EAAE,UAAU;IACtB,UAAU,EAAE,UAAU;IACtB,YAAY,EAAE,UAAU;IACxB,YAAY,EAAE,UAAU;IACxB,KAAK,EAAE,aAAa;IACpB,MAAM,EAAE,UAAU;IAClB,gBAAgB,EAAE,UAAU;IAC5B,eAAe,EAAE,UAAU;IAC3B,aAAa,EAAE,UAAU;IACzB,KAAK,EAAE,UAAU;IACjB,OAAO,EAAE,UAAU;IACnB,wBAAwB,EAAE,UAAU;IACpC,oBAAoB,EAAE,UAAU;IAChC,kBAAkB,EAAE,UAAU;IAC9B,oBAAoB,EAAE,UAAU;IAChC,aAAa,EAAE,UAAU;IACzB,cAAc,EAAE,UAAU;IAC1B,sBAAsB,EAAE,UAAU;IAClC,eAAe,EAAE,UAAU;IAC3B,kBAAkB,EAAE,UAAU;IAC9B,iBAAiB,EAAE,UAAU;IAC7B,eAAe,EAAE,UAAU;IAC3B,eAAe,EAAE,UAAU;IAC3B,oBAAoB,EAAE,UAAU;IAChC,qBAAqB,EAAE,aAAa;IACpC,2BAA2B,EAAE,aAAa;IAC1C,6BAA6B,EAAE,aAAa;IAC5C,cAAc,EAAE,UAAU;IAC1B,gBAAgB,EAAE,UAAU;IAC5B,cAAc,EAAE,UAAU;IAC1B,qBAAqB,EAAE,UAAU;IACjC,4BAA4B,EAAE,UAAU;IACxC,+BAA+B,EAAE,UAAU;IAC3C,6BAA6B,EAAE,UAAU;IACzC,qBAAqB,EAAE,UAAU;IACjC,sBAAsB,EAAE,UAAU;IAClC,KAAK,EAAE,UAAU;IACjB,gBAAgB,EAAE,UAAU;IAC5B,eAAe,EAAE,UAAU;IAC3B,YAAY,EAAE,UAAU;IACxB,MAAM,EAAE,UAAU;IAClB,IAAI,EAAE,UAAU;IAChB,YAAY,EAAE,UAAU;IACxB,cAAc,EAAE,UAAU;IAC1B,qBAAqB,EAAE,UAAU;IACjC,0BAA0B,EAAE,UAAU;IACtC,OAAO,EAAE,UAAU;IACnB,aAAa,EAAE,UAAU;CACwB,CAAC;AASpD,kGAAkG;AAClG,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,MAAc,EACd,QAA0C,WAAW;IAErD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;IACtD,MAAM,SAAS,GACb,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,WAAW,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5G,MAAM,OAAO,GAAG,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,MAAM,MAAM,GACV,OAAO,KAAK,IAAI;QACd,CAAC,CAAC,4BAA4B,OAAO,EAAE;QACvC,CAAC,CAAC,SAAS,KAAK,SAAS;YACvB,CAAC,CAAC,yCAAyC;YAC3C,CAAC,CAAC,yBAAyB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,mBAAmB,CAAC;IAC9E,uEAAuE;IACvE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;QACnC,KAAK,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAClH,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,KAAK,GAAkB,IAAI,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,IAAI,GAAG,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;QAC5D,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,OAAO,KAAK,SAAS;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,6DAA6D,CAAC,CAAC;aAC1G,IAAI,EAAE,CAAC,OAAO,IAAI,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC;YAC5C,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,qBAAqB,OAAO,cAAc,MAAM,EAAE,CAAC,CAAC;YACzE,IAAI,KAAK,KAAK,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC;gBAAE,KAAK,GAAG,OAAO,CAAC;QAC5D,CAAC;IACH,CAAC;IACD,kGAAkG;IAClG,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC;IAC5B,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,IAAI,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC;QAAE,KAAK,GAAG,GAAG,CAAC;IACvE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC7B,CAAC;AAED,iGAAiG;AACjG,kGAAkG;AAClG,oGAAoG;AACpG,MAAM,UAAU,eAAe,CAC7B,IAAY,EACZ,MAAc,EACd,QAA0C,WAAW;IAErD,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC;AACpD,CAAC;AAED,kGAAkG;AAClG,iGAAiG;AACjG,kGAAkG;AAClG,+BAA+B;AAC/B,QAAQ,CAAC,CAAC,aAAa,CAAC,KAAc,EAAE,OAA4B,EAAE,MAAM,GAAG,EAAE;IAC/E,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,KAAK,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACtE,OAAO;IACT,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO;IACxD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC;QACtD,MAAM,IAAI,CAAC;QACX,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,KAAK,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACpE,CAAC;AACH,CAAC"}
@@ -0,0 +1,264 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { lt, valid } from 'semver';
5
+ import { z } from 'zod';
6
+ import { ConductorError, isMissing } from "./errors.js";
7
+ import { issueLines, parseJsonc } from "./jsonc.js";
8
+ import { packageInfo } from "./package-info.js";
9
+ import { PROJECT_NAME, projectOfPath, projectPaths } from "./paths.js";
10
+ import { PROFILE_FILE, profileSchema, ProfileTooNewError } from "./profile.js";
11
+ import { PROTOCOL } from "./protocol.js";
12
+ // A project's profile in layers (design note v2, section 7): the machine's
13
+ // <home>/projects/<name>.json over the repo's optional .claude/conductor.json. Objects merge key by
14
+ // key with the machine winning, arrays are replaced whole, and `rules` stack: the machine's first,
15
+ // so one of its rules shadows a repo rule with the same id. The merge runs on the files as written,
16
+ // before the schema reads them, so a machine file beside a repo profile can be partial.
17
+ export const LAYERS = ['machine', 'repo'];
18
+ export const profileSourcesSchema = z.object({
19
+ // Each field's source by its dotted path, an array counting as one value; `default` when neither
20
+ // file writes it. `rules` is listed rule by rule instead.
21
+ fields: z.record(z.string(), z.enum(['default', ...LAYERS])),
22
+ // The machine profile, when there is one.
23
+ machine: z.string().nullable(),
24
+ // Where the repo layer was read, when there is one: `.claude/conductor.json at origin/<base>`.
25
+ repo: z.string().nullable(),
26
+ // The layer of each rule of the profile's `rules`, in order.
27
+ rules: z.array(z.enum(LAYERS)),
28
+ });
29
+ export const gitSync = (args, cwd) => {
30
+ const result = spawnSync('git', args, { cwd, encoding: 'utf8', timeout: 30_000, windowsHide: true });
31
+ return {
32
+ code: result.status ?? 1,
33
+ stderr: result.stderr ?? (result.error ? String(result.error) : ''),
34
+ stdout: result.stdout ?? '',
35
+ };
36
+ };
37
+ // The repo layer's path inside a commit: git spells it with forward slashes on every platform.
38
+ const PROFILE_IN_GIT = '.claude/conductor.json';
39
+ const rawSchema = z.record(z.string(), z.unknown());
40
+ // The profile the worker verbs read where they run, without the daemon: the checkout's own
41
+ // .claude/conductor.json (the branch's, as a worker's change to it would be) under the project's
42
+ // machine file. The project is the one whose folder the checkout is in (<home>/projects/<name>/…),
43
+ // else the one its own profile names, so a person's worktree of their own checkout reads too.
44
+ // Null when neither layer exists: no project's checkout.
45
+ export function checkoutProfile(paths, checkout) {
46
+ const repo = readLayer(join(checkout, PROFILE_FILE));
47
+ const name = projectOfPath(paths, checkout) ?? (typeof repo?.raw.name === 'string' ? repo.raw.name : null);
48
+ // A name the schema will refuse names no machine file either.
49
+ const machine = name !== null && PROJECT_NAME.test(name) ? readLayer(projectPaths(paths, name).profile) : null;
50
+ if (name === null || (machine === null && repo === null))
51
+ return null;
52
+ return layerProfile(name, { machine, repo });
53
+ }
54
+ // The layers merged and read: each layer's `conductor` minimum first, then the protocol, then the
55
+ // schema, so a profile written for another version gets one clear message. `name` is the project's
56
+ // registered name, which a machine file may repeat but not change.
57
+ export function layerProfile(name, layers, running) {
58
+ const { machine, repo } = layers;
59
+ const present = [machine, repo].filter((l) => l !== null);
60
+ const source = present.map((l) => l.label).join(' over ');
61
+ if (machine !== null && machine.raw.name !== undefined && machine.raw.name !== name) {
62
+ throw new ConductorError(`${machine.label} renames the project`, [
63
+ `name: ${JSON.stringify(machine.raw.name)} is not "${name}", the name its file and folder give it; drop it, or put "${name}" back`,
64
+ ]);
65
+ }
66
+ for (const layer of present) {
67
+ const minimum = typeof layer.raw.conductor === 'string' ? valid(layer.raw.conductor) : null;
68
+ if (minimum === null)
69
+ continue;
70
+ const version = running ?? packageInfo().version;
71
+ if (lt(version, minimum))
72
+ throw new ProfileTooNewError(layer.label, minimum, version);
73
+ }
74
+ const merged = mergeLayers(machine?.raw ?? {}, repo?.raw ?? {});
75
+ merged.name = name;
76
+ if (merged.protocol !== PROTOCOL) {
77
+ throw new ConductorError(`${source} is invalid`, [
78
+ `protocol: ${JSON.stringify(merged.protocol ?? null)} is not supported; this conductor speaks protocol ${PROTOCOL}`,
79
+ ]);
80
+ }
81
+ const parsed = profileSchema.safeParse(merged);
82
+ if (!parsed.success)
83
+ throw new ConductorError(`${source} is invalid`, issueLines(parsed.error));
84
+ const machineRules = Array.isArray(machine?.raw.rules) ? machine.raw.rules.length : 0;
85
+ return {
86
+ layers,
87
+ profile: parsed.data,
88
+ sources: {
89
+ fields: fieldSources(parsed.data, machine?.raw ?? null, repo?.raw ?? null),
90
+ machine: machine?.label ?? null,
91
+ repo: repo?.label ?? null,
92
+ rules: parsed.data.rules.map((_rule, i) => (i < machineRules ? 'machine' : 'repo')),
93
+ },
94
+ };
95
+ }
96
+ // The repo layer over which the machine layer goes, key by key; `rules` stack, the machine's first.
97
+ export function mergeLayers(machine, repo) {
98
+ const merged = mergeValue(repo, machine);
99
+ if (!isRecord(merged))
100
+ return {};
101
+ const rules = [...listOf(machine.rules), ...listOf(repo.rules)];
102
+ if (machine.rules !== undefined || repo.rules !== undefined)
103
+ merged.rules = rules;
104
+ return merged;
105
+ }
106
+ // The profile of the checkout a verb runs in, as checkoutProfile reads it, under the conductor home
107
+ // `paths` names. Throws a ConductorError when it is no project's checkout or the profile doesn't read.
108
+ export function profileHere(checkout, paths) {
109
+ const layered = checkoutProfile(paths, checkout);
110
+ if (layered === null) {
111
+ throw new ConductorError(`no ${PROFILE_FILE} in ${checkout}`, [
112
+ "run it in a project's worktree or checkout; a project with no committed profile keeps it in ~/.conductor/projects/<name>.json",
113
+ ]);
114
+ }
115
+ return layered.profile;
116
+ }
117
+ // `conduct profile <name>`: the files, each field with its value and source, then the rules in the
118
+ // order they are checked, each with its layer.
119
+ export function profileLines(name, { profile, sources }) {
120
+ const rows = Object.keys(sources.fields)
121
+ .sort()
122
+ .map((path) => [path, show(valueAt(profile, path)), sources.fields[path] ?? 'default']);
123
+ const width = Math.max(0, ...rows.map(([path]) => path.length));
124
+ const lines = [
125
+ `profile ${name}`,
126
+ ` machine: ${sources.machine ?? '(no machine profile)'}`,
127
+ ` repo: ${sources.repo ?? '(no repo profile)'}`,
128
+ '',
129
+ ...rows.map(([path, value, source]) => ` ${path.padEnd(width)} ${source.padEnd(7)} ${value}`),
130
+ ];
131
+ if (profile.rules.length > 0) {
132
+ lines.push('', "rules (the machine's first: a rule shadows a later one with its id):");
133
+ profile.rules.forEach((rule, i) => {
134
+ const does = typeof rule.do === 'string' ? rule.do : rule.do.join('; ');
135
+ const when = rule.when === undefined ? '' : `when ${rule.when} → `;
136
+ lines.push(` ${(sources.rules[i] ?? 'repo').padEnd(7)} ${rule.id ?? `#${i + 1}`}: ${when}${does}`);
137
+ });
138
+ }
139
+ return lines;
140
+ }
141
+ // The profile a daemon-owned clone conducts: the machine file over .claude/conductor.json at
142
+ // origin/<base> in <home>/projects/<name>/repo. The base is the machine's baseBranch, else
143
+ // origin/HEAD's branch; when the repo's file there names another baseBranch, the file is read once
144
+ // more at that one. `running` is this conductor's version, for the layers' `conductor` minimums.
145
+ export function projectProfile(paths, name, git = gitSync, running) {
146
+ const { clone, profile: file } = projectPaths(paths, name);
147
+ if (!existsSync(join(clone, '.git')))
148
+ throw new ConductorError(`${name} has no clone at ${clone}`, ['add it with `conduct project add <url | path>`']);
149
+ const machine = readLayer(file);
150
+ const machineBase = typeof machine?.raw.baseBranch === 'string' ? machine.raw.baseBranch : null;
151
+ let base = machineBase ?? originHead(git, clone);
152
+ let repo = repoLayerAt(git, clone, base);
153
+ const named = typeof repo?.raw.baseBranch === 'string' ? repo.raw.baseBranch : null;
154
+ if (machineBase === null && named !== null && named !== base) {
155
+ base = named;
156
+ repo = repoLayerAt(git, clone, base);
157
+ }
158
+ if (machine === null && repo === null) {
159
+ throw new ConductorError(`${name} has no profile`, [
160
+ `neither ${file} nor ${PROFILE_IN_GIT} at origin/${base} in ${clone}`,
161
+ `write ${file} (a whole profile), or commit ${PROFILE_IN_GIT} to the repo`,
162
+ ]);
163
+ }
164
+ return layerProfile(name, { machine, repo }, running);
165
+ }
166
+ // A profile file's text as a layer. Throws a ConductorError naming the file when it isn't a JSONC
167
+ // object.
168
+ export function rawLayer(text, label) {
169
+ return { label, raw: parseJsonc(text, rawSchema, label) };
170
+ }
171
+ // A profile file as a layer; null when there is no file.
172
+ export function readLayer(file) {
173
+ let text;
174
+ try {
175
+ text = readFileSync(file, 'utf8');
176
+ }
177
+ catch (error) {
178
+ if (isMissing(error))
179
+ return null;
180
+ throw new ConductorError(`could not read ${file}`, [String(error)]);
181
+ }
182
+ return rawLayer(text, file);
183
+ }
184
+ // .claude/conductor.json at origin/<base> in a clone, or null when that commit has none.
185
+ export function repoLayerAt(git, clone, base) {
186
+ const ref = `origin/${base}`;
187
+ if (git(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], clone).code !== 0) {
188
+ throw new ConductorError(`${clone} has no ${ref}`, [
189
+ `check baseBranch ("${base}"); the conductor fetches the clone every few minutes`,
190
+ ]);
191
+ }
192
+ const spec = `${ref}:${PROFILE_IN_GIT}`;
193
+ if (git(['cat-file', '-e', spec], clone).code !== 0)
194
+ return null;
195
+ const shown = git(['show', spec], clone);
196
+ if (shown.code !== 0)
197
+ throw new ConductorError(`git show ${spec} failed in ${clone}`, [shown.stderr.trim()]);
198
+ return rawLayer(shown.stdout, `${PROFILE_IN_GIT} at ${ref} in ${clone}`);
199
+ }
200
+ // Each field's source, by its dotted path in the profile as read: the machine's when its file
201
+ // writes the field, else the repo's, else the default's. Arrays and `rules` count as one value;
202
+ // `name` is the registration's, which only a repo file of the same name can claim.
203
+ function fieldSources(profile, machine, repo) {
204
+ const fields = {};
205
+ const walk = (value, path) => {
206
+ if (isRecord(value) && Object.keys(value).length > 0) {
207
+ for (const [key, child] of Object.entries(value))
208
+ walk(child, [...path, key]);
209
+ return;
210
+ }
211
+ fields[path.join('.')] = has(machine, path) ? 'machine' : has(repo, path) ? 'repo' : 'default';
212
+ };
213
+ for (const [key, value] of Object.entries(profile))
214
+ if (key !== 'rules')
215
+ walk(value, [key]);
216
+ fields.name = repo?.name === profile.name && machine?.name === undefined ? 'repo' : 'machine';
217
+ return fields;
218
+ }
219
+ function has(raw, path) {
220
+ let at = raw;
221
+ for (const key of path) {
222
+ if (!isRecord(at) || !Object.hasOwn(at, key))
223
+ return false;
224
+ at = at[key];
225
+ }
226
+ return at !== undefined;
227
+ }
228
+ function isRecord(value) {
229
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
230
+ }
231
+ function listOf(value) {
232
+ return Array.isArray(value) ? value : [];
233
+ }
234
+ function mergeValue(under, over) {
235
+ if (over === undefined)
236
+ return under;
237
+ if (!isRecord(under) || !isRecord(over))
238
+ return over;
239
+ const merged = { ...under };
240
+ for (const [key, value] of Object.entries(over))
241
+ merged[key] = mergeValue(under[key], value);
242
+ return merged;
243
+ }
244
+ // origin/HEAD's branch in a clone: the remote's default branch, which a clone records.
245
+ function originHead(git, clone) {
246
+ const head = git(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], clone);
247
+ const branch = head.code === 0 ? head.stdout.trim().replace(/^origin\//, '') : '';
248
+ if (branch === '') {
249
+ throw new ConductorError(`${clone} has no origin/HEAD`, [
250
+ `run git -C "${clone}" remote set-head origin --auto, or set baseBranch in the machine profile`,
251
+ ]);
252
+ }
253
+ return branch;
254
+ }
255
+ function show(value) {
256
+ return typeof value === 'string' ? value : (JSON.stringify(value) ?? '(unset)');
257
+ }
258
+ function valueAt(profile, path) {
259
+ let at = profile;
260
+ for (const key of path.split('.'))
261
+ at = isRecord(at) ? at[key] : undefined;
262
+ return at;
263
+ }
264
+ //# sourceMappingURL=profile-layers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-layers.js","sourceRoot":"","sources":["../src/profile-layers.ts"],"names":[],"mappings":"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"}
package/build/profile.js CHANGED
@@ -1,15 +1,18 @@
1
- import { readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
1
+ import { isAbsolute, join } from 'node:path';
2
+ import { lt, valid } from 'semver';
3
3
  import { z } from 'zod';
4
- import { ConductorError, isMissing } from "./errors.js";
4
+ import { ConductorError } from "./errors.js";
5
5
  import { parseJsonc } from "./jsonc.js";
6
+ import { packageInfo } from "./package-info.js";
7
+ import { PROJECT_NAME } from "./paths.js";
6
8
  import { noteName } from "./plugin.js";
7
9
  import { PROTOCOL } from "./protocol.js";
10
+ import { GATES } from "./release-kinds.js";
8
11
  import { ruleInputSchema } from "./rule-input.js";
12
+ import { defaultTag } from "./upgrade.js";
9
13
  export const PROFILE_FILE = join('.claude', 'conductor.json');
10
- const name = z.string().regex(/^[a-z][a-z0-9-]*$/, 'lowercase letters, digits and dashes, starting with a letter');
14
+ const name = z.string().regex(PROJECT_NAME, 'lowercase letters, digits and dashes, starting with a letter');
11
15
  const text = z.string().trim().min(1);
12
- const port = z.int().min(1).max(65535);
13
16
  // The state each phase note moves an item to, over the tracker kind's defaults (tracker-writes.ts);
14
17
  // null leaves it as it is.
15
18
  const transitions = z
@@ -48,33 +51,50 @@ const profileFields = z.strictObject({
48
51
  repo: z.string().regex(/^[\w.-]+$/, 'a GitHub repo name, without the owner'),
49
52
  }),
50
53
  ]),
51
- // The kinds of note that wait on a person in this project.
52
- gates: z.array(z.enum(['merge', 'npm-otp', 'plan'])).min(1),
54
+ // The oldest conductor that reads this profile (ATK-573): the version that first read its newest
55
+ // field (profile-fields.ts records each one's), a -dev.N while that is only on `dev`. parseProfile
56
+ // checks it before the rest, so an older conductor says which version to upgrade to instead of
57
+ // listing fields it doesn't know.
58
+ conductor: z
59
+ .string()
60
+ .refine((v) => valid(v) !== null, 'a version, such as 3.0.0-dev.8')
61
+ .optional(),
62
+ // The kinds of item that wait on a person in this project: plan, merge and each release kind's gate
63
+ // (release-kinds.ts: npm-otp for an npm-stage), which `conduct project check` looks for.
64
+ gates: z.array(z.enum(GATES)).min(1),
53
65
  // Each environment's /health URL, for `conduct wait health <env>`.
54
66
  health: z.record(name, z.url()).optional(),
67
+ // A checkout the operator keeps whose gitignored files matching its .worktreeinclude are copied
68
+ // into each new worktree (design note v2, section 2), for local files `setup` can't build. An
69
+ // absolute path; off when absent.
70
+ localFilesFrom: text.refine((path) => isAbsolute(path), 'an absolute path to a checkout').optional(),
55
71
  name,
56
72
  // The package `conduct wait npm <version>` means.
57
73
  npm: z.strictObject({ package: text }).optional(),
58
- // The dev servers' base ports by name, such as { "web": 3000, "api": 7071 }: the main checkout
59
- // serves on them, and the worktree the port broker gives offset N on each base + N, up to maxSlot.
74
+ // The ports a worktree's dev servers need, by name, such as ["web", "api"]. The port broker gives
75
+ // each worktree its own from the machine's pool, as CONDUCTOR_PORT_<NAME> to `setup` and the worker.
60
76
  ports: z
61
- .object({ maxSlot: z.int().min(0).max(1000).default(50) })
62
- .catchall(port)
63
- .refine((ports) => Object.keys(ports).some((key) => key !== 'maxSlot'), 'declare at least one base port, such as "web": 3000')
64
- // A misspelt maxSlot would otherwise read as a port named "maxslot".
65
- .refine((ports) => Object.keys(ports).every((key) => key === 'maxSlot' || !key.toLowerCase().replace(/[-_]/g, '').startsWith('maxslot')), 'maxSlot is spelt "maxSlot"'),
77
+ .array(z.string().regex(/^[a-z][a-z0-9_]*$/, 'lowercase letters, digits and _, starting with a letter'))
78
+ .refine((names) => new Set(names).size === names.length, 'each port name once')
79
+ .default([]),
66
80
  // The worker protocol the project's work skill speaks (protocol.ts); only this conductor's.
67
81
  protocol: z.literal(PROTOCOL),
68
82
  release: z.strictObject({ handoff: text }),
69
- // The pipelines that stage a release for a merge (`conduct runs`, `wait merge`): `npm-stage` stages
70
- // `package` on npm for a person's `npm stage approve`. `pipeline` names it as --for does: its YAML
71
- // file name (release-cli), path or own name.
83
+ // The pipelines that stage a release for a merge (`conduct runs`, `wait merge`), each of a kind
84
+ // release-kinds.ts reads: `npm-stage` stages `package` on npm for a person's `npm stage approve`.
85
+ // `pipeline` names it as --for does: its YAML file name (release-cli), path or own name.
72
86
  releases: z
73
- .array(z.strictObject({ key: name, kind: z.literal('npm-stage'), package: text, pipeline: text }))
87
+ .array(z.discriminatedUnion('kind', [
88
+ z.strictObject({ key: name, kind: z.literal('npm-stage'), package: text, pipeline: text }),
89
+ ]))
74
90
  .default([]),
75
91
  // Project rules (design note, section 9): checked before the machine's and the defaults, for
76
92
  // this project's items only. They change by PR, and the daemon reloads them on save.
77
93
  rules: z.array(ruleInputSchema).default([]),
94
+ // Shell commands run in order in each new worktree, after `localFilesFrom`'s copy (design note v2,
95
+ // section 2): they build its gitignored files (installs, local settings) from what the repo commits.
96
+ // The first that fails fails the launch.
97
+ setup: z.array(text).default([]),
78
98
  // `aliases`: older names of the work skill. Notes headed with one still count, so an item in
79
99
  // flight when the work skill is renamed keeps its phases, approvals and review threads.
80
100
  skills: z.strictObject({ aliases: z.array(text).default([]), verify: text, work: text }),
@@ -176,8 +196,8 @@ const profileFields = z.strictObject({
176
196
  .prefault({}),
177
197
  })
178
198
  .prefault({}),
179
- // How the daemon starts this project's workers. `tab` opens each in a Windows Terminal tab
180
- // attached to its background session.
199
+ // Moved to the machine's config.json (workers.tab, ATK-529), and ignored here. Still read, so a
200
+ // profile written for an older conductor isn't refused; `conduct project check` warns about it.
181
201
  workers: z.strictObject({ tab: z.boolean().default(false) }).prefault({}),
182
202
  });
183
203
  // ADO Boards takes its organization and project from an Azure DevOps code host, and GitHub Issues
@@ -215,11 +235,6 @@ export function adoOrganization(profile) {
215
235
  return profile.codeHost.organization;
216
236
  return profile.tracker.kind === 'ado-boards' ? (profile.tracker.organization ?? null) : null;
217
237
  }
218
- // The profile's base ports by name, without maxSlot.
219
- export function basePorts(profile) {
220
- const { maxSlot: _maxSlot, ...ports } = profile.ports;
221
- return ports;
222
- }
223
238
  // The `<skill> · ` headings that mark a project's notes: its work skill's, then each alias's.
224
239
  // Without a profile, work-issue's, which every project used before profiles named their own. The
225
240
  // conductor plugin's skills head their notes with the bare name: `work-item · `, not
@@ -233,11 +248,30 @@ export function notePrefixes(profile) {
233
248
  export function trackerKey(profile) {
234
249
  return profile.tracker.kind === 'linear' ? profile.tracker.team : profile.tracker.key;
235
250
  }
236
- const protocolOnly = z.looseObject({ protocol: z.unknown().optional() });
237
- // The protocol is checked first, so a profile written for another version gets one clear message
238
- // instead of a list of schema differences.
239
- export function parseProfile(text, source) {
240
- const { protocol } = parseJsonc(text, protocolOnly, source);
251
+ const versionsOnly = z.looseObject({ conductor: z.unknown().optional(), protocol: z.unknown().optional() });
252
+ // A profile whose `conductor` minimum is newer than this conductor. `upgrade` brings the machine up
253
+ // to it: from `dev` for a -dev.N minimum, else from `latest`.
254
+ export class ProfileTooNewError extends ConductorError {
255
+ upgrade;
256
+ constructor(source, minimum, running) {
257
+ const upgrade = `conduct upgrade --tag ${defaultTag(minimum)}`;
258
+ super(`${source} is invalid`, [`conductor: needs ${minimum} or newer; this is ${running}: run \`${upgrade}\``]);
259
+ this.name = 'ProfileTooNewError';
260
+ this.upgrade = upgrade;
261
+ }
262
+ }
263
+ // The conductor minimum, then the protocol, are checked first, so a profile written for another
264
+ // version gets one clear message instead of a list of schema differences. `running` is this
265
+ // conductor's version, read only for a profile with a minimum.
266
+ export function parseProfile(text, source, running) {
267
+ const { conductor, protocol } = parseJsonc(text, versionsOnly, source);
268
+ // As semver reads it: `v3.0.0-dev.9` and `3.0.0-dev.9+abc` both mean 3.0.0-dev.9.
269
+ const minimum = typeof conductor === 'string' ? valid(conductor) : null;
270
+ if (minimum !== null) {
271
+ const version = running ?? packageInfo().version;
272
+ if (lt(version, minimum))
273
+ throw new ProfileTooNewError(source, minimum, version);
274
+ }
241
275
  if (protocol !== PROTOCOL) {
242
276
  throw new ConductorError(`${source} is invalid`, [
243
277
  `protocol: ${JSON.stringify(protocol ?? null)} is not supported; this conductor speaks protocol ${PROTOCOL}`,
@@ -245,19 +279,11 @@ export function parseProfile(text, source) {
245
279
  }
246
280
  return parseJsonc(text, profileSchema, source);
247
281
  }
248
- export function readProfile(root) {
249
- const path = join(root, PROFILE_FILE);
250
- let source;
251
- try {
252
- source = readFileSync(path, 'utf8');
253
- }
254
- catch (error) {
255
- if (!isMissing(error))
256
- throw error;
257
- throw new ConductorError(`no ${PROFILE_FILE} in ${root}`, [
258
- 'a conducted project commits its profile at .claude/conductor.json',
259
- ]);
260
- }
261
- return { path, profile: parseProfile(source, path) };
282
+ // Why a profile didn't read, in one line: a ConductorError's problems after its file line (the
283
+ // version it needs, the fields), so a verb that turns it into its own refusal keeps them.
284
+ export function profileProblem(error) {
285
+ if (!(error instanceof ConductorError))
286
+ return error instanceof Error ? error.message : String(error);
287
+ return error.details.length > 0 ? error.details.join('; ') : error.message;
262
288
  }
263
289
  //# sourceMappingURL=profile.js.map