@on-par/factory-cli 0.0.0-stage → 0.9.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 (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -2
  3. package/dist/cli/admission.d.ts +17 -0
  4. package/dist/cli/admission.d.ts.map +1 -0
  5. package/dist/cli/admission.js +37 -0
  6. package/dist/cli/admission.js.map +1 -0
  7. package/dist/cli/branch-prefix.d.ts +8 -0
  8. package/dist/cli/branch-prefix.d.ts.map +1 -0
  9. package/dist/cli/branch-prefix.js +16 -0
  10. package/dist/cli/branch-prefix.js.map +1 -0
  11. package/dist/cli/classifier-backtest.d.ts +50 -0
  12. package/dist/cli/classifier-backtest.d.ts.map +1 -0
  13. package/dist/cli/classifier-backtest.js +112 -0
  14. package/dist/cli/classifier-backtest.js.map +1 -0
  15. package/dist/cli/cost-json.d.ts +37 -0
  16. package/dist/cli/cost-json.d.ts.map +1 -0
  17. package/dist/cli/cost-json.js +48 -0
  18. package/dist/cli/cost-json.js.map +1 -0
  19. package/dist/cli/daemon.d.ts +113 -0
  20. package/dist/cli/daemon.d.ts.map +1 -0
  21. package/dist/cli/daemon.js +282 -0
  22. package/dist/cli/daemon.js.map +1 -0
  23. package/dist/cli/doctor.d.ts +127 -0
  24. package/dist/cli/doctor.d.ts.map +1 -0
  25. package/dist/cli/doctor.js +400 -0
  26. package/dist/cli/doctor.js.map +1 -0
  27. package/dist/cli/feedback.d.ts +74 -0
  28. package/dist/cli/feedback.d.ts.map +1 -0
  29. package/dist/cli/feedback.js +180 -0
  30. package/dist/cli/feedback.js.map +1 -0
  31. package/dist/cli/filing.d.ts +22 -0
  32. package/dist/cli/filing.d.ts.map +1 -0
  33. package/dist/cli/filing.js +53 -0
  34. package/dist/cli/filing.js.map +1 -0
  35. package/dist/cli/first-run.d.ts +9 -0
  36. package/dist/cli/first-run.d.ts.map +1 -0
  37. package/dist/cli/first-run.js +34 -0
  38. package/dist/cli/first-run.js.map +1 -0
  39. package/dist/cli/help-groups.d.ts +17 -0
  40. package/dist/cli/help-groups.d.ts.map +1 -0
  41. package/dist/cli/help-groups.js +50 -0
  42. package/dist/cli/help-groups.js.map +1 -0
  43. package/dist/cli/hosted-queue.d.ts +45 -0
  44. package/dist/cli/hosted-queue.d.ts.map +1 -0
  45. package/dist/cli/hosted-queue.js +74 -0
  46. package/dist/cli/hosted-queue.js.map +1 -0
  47. package/dist/cli/hosted-runner.d.ts +44 -0
  48. package/dist/cli/hosted-runner.d.ts.map +1 -0
  49. package/dist/cli/hosted-runner.js +68 -0
  50. package/dist/cli/hosted-runner.js.map +1 -0
  51. package/dist/cli/hosted.d.ts +38 -0
  52. package/dist/cli/hosted.d.ts.map +1 -0
  53. package/dist/cli/hosted.js +82 -0
  54. package/dist/cli/hosted.js.map +1 -0
  55. package/dist/cli/index.d.ts +656 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +4801 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/cli/init-files.d.ts +8 -0
  60. package/dist/cli/init-files.d.ts.map +1 -0
  61. package/dist/cli/init-files.js +45 -0
  62. package/dist/cli/init-files.js.map +1 -0
  63. package/dist/cli/logs.d.ts +27 -0
  64. package/dist/cli/logs.d.ts.map +1 -0
  65. package/dist/cli/logs.js +52 -0
  66. package/dist/cli/logs.js.map +1 -0
  67. package/dist/cli/merge-scope.d.ts +7 -0
  68. package/dist/cli/merge-scope.d.ts.map +1 -0
  69. package/dist/cli/merge-scope.js +14 -0
  70. package/dist/cli/merge-scope.js.map +1 -0
  71. package/dist/cli/octokit.d.ts +43 -0
  72. package/dist/cli/octokit.d.ts.map +1 -0
  73. package/dist/cli/octokit.js +87 -0
  74. package/dist/cli/octokit.js.map +1 -0
  75. package/dist/cli/queue-add-json.d.ts +25 -0
  76. package/dist/cli/queue-add-json.d.ts.map +1 -0
  77. package/dist/cli/queue-add-json.js +24 -0
  78. package/dist/cli/queue-add-json.js.map +1 -0
  79. package/dist/cli/queue-clear.d.ts +15 -0
  80. package/dist/cli/queue-clear.d.ts.map +1 -0
  81. package/dist/cli/queue-clear.js +58 -0
  82. package/dist/cli/queue-clear.js.map +1 -0
  83. package/dist/cli/queue-list.d.ts +24 -0
  84. package/dist/cli/queue-list.d.ts.map +1 -0
  85. package/dist/cli/queue-list.js +25 -0
  86. package/dist/cli/queue-list.js.map +1 -0
  87. package/dist/cli/ready-check.d.ts +98 -0
  88. package/dist/cli/ready-check.d.ts.map +1 -0
  89. package/dist/cli/ready-check.js +169 -0
  90. package/dist/cli/ready-check.js.map +1 -0
  91. package/dist/cli/reset.d.ts +40 -0
  92. package/dist/cli/reset.d.ts.map +1 -0
  93. package/dist/cli/reset.js +215 -0
  94. package/dist/cli/reset.js.map +1 -0
  95. package/dist/cli/run-children.d.ts +24 -0
  96. package/dist/cli/run-children.d.ts.map +1 -0
  97. package/dist/cli/run-children.js +73 -0
  98. package/dist/cli/run-children.js.map +1 -0
  99. package/dist/cli/run-flags.d.ts +14 -0
  100. package/dist/cli/run-flags.d.ts.map +1 -0
  101. package/dist/cli/run-flags.js +43 -0
  102. package/dist/cli/run-flags.js.map +1 -0
  103. package/dist/cli/runs-diffstat.d.ts +43 -0
  104. package/dist/cli/runs-diffstat.d.ts.map +1 -0
  105. package/dist/cli/runs-diffstat.js +91 -0
  106. package/dist/cli/runs-diffstat.js.map +1 -0
  107. package/dist/cli/runs.d.ts +36 -0
  108. package/dist/cli/runs.d.ts.map +1 -0
  109. package/dist/cli/runs.js +102 -0
  110. package/dist/cli/runs.js.map +1 -0
  111. package/dist/cli/staleness.d.ts +30 -0
  112. package/dist/cli/staleness.d.ts.map +1 -0
  113. package/dist/cli/staleness.js +132 -0
  114. package/dist/cli/staleness.js.map +1 -0
  115. package/dist/cli/status-json.d.ts +45 -0
  116. package/dist/cli/status-json.d.ts.map +1 -0
  117. package/dist/cli/status-json.js +22 -0
  118. package/dist/cli/status-json.js.map +1 -0
  119. package/dist/cli/stop-resume-json.d.ts +18 -0
  120. package/dist/cli/stop-resume-json.d.ts.map +1 -0
  121. package/dist/cli/stop-resume-json.js +10 -0
  122. package/dist/cli/stop-resume-json.js.map +1 -0
  123. package/dist/cli/stop-sentinel.d.ts +23 -0
  124. package/dist/cli/stop-sentinel.d.ts.map +1 -0
  125. package/dist/cli/stop-sentinel.js +61 -0
  126. package/dist/cli/stop-sentinel.js.map +1 -0
  127. package/dist/cli/sweep-heartbeat.d.ts +33 -0
  128. package/dist/cli/sweep-heartbeat.d.ts.map +1 -0
  129. package/dist/cli/sweep-heartbeat.js +65 -0
  130. package/dist/cli/sweep-heartbeat.js.map +1 -0
  131. package/dist/cli/unpark.d.ts +28 -0
  132. package/dist/cli/unpark.d.ts.map +1 -0
  133. package/dist/cli/unpark.js +68 -0
  134. package/dist/cli/unpark.js.map +1 -0
  135. package/dist/cli/usage-json.d.ts +20 -0
  136. package/dist/cli/usage-json.d.ts.map +1 -0
  137. package/dist/cli/usage-json.js +36 -0
  138. package/dist/cli/usage-json.js.map +1 -0
  139. package/dist/cli.d.ts +3 -0
  140. package/dist/cli.d.ts.map +1 -0
  141. package/dist/cli.js +6 -0
  142. package/dist/cli.js.map +1 -0
  143. package/dist/entry.d.ts +6 -0
  144. package/dist/entry.d.ts.map +1 -0
  145. package/dist/entry.js +13 -0
  146. package/dist/entry.js.map +1 -0
  147. package/package.json +48 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 On PAR Dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,24 @@
1
- # Temporary Holding Version
1
+ # @on-par/factory-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The `factory` command: ships verified GitHub issues via boss-worker-checker orchestration.
4
+
5
+ Part of [Software Factory](https://github.com/on-par/software-factory), a multi-agent factory that ships verified work autonomously. Most users want the CLI, [`@on-par/factory-cli`](https://www.npmjs.com/package/@on-par/factory-cli); the other packages are its building blocks.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g @on-par/factory-cli
11
+ factory --help
12
+ ```
13
+
14
+ Requires Node.js >= 24, `git`, and an authenticated GitHub CLI (`gh`). See the [Quick Start](https://github.com/on-par/software-factory#quick-start-5-minutes) for model CLIs and first-run setup.
15
+
16
+ ## Documentation
17
+
18
+ - [README and Quick Start](https://github.com/on-par/software-factory#readme)
19
+ - [Architecture and package graph](https://github.com/on-par/software-factory/blob/main/docs/architecture.md)
20
+ - [Changelog](https://github.com/on-par/software-factory/blob/main/CHANGELOG.md)
21
+
22
+ ## License
23
+
24
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,17 @@
1
+ import { type AdmissionStateReader, type QueueCompatibilityVerdict, type QueueSnapshot } from '@on-par/factory-core/internal';
2
+ export interface QueueAdmissionRow {
3
+ lane: string;
4
+ issue: number;
5
+ verdict: QueueCompatibilityVerdict;
6
+ }
7
+ export declare function admissionStateReaderFor(stateDir: string): AdmissionStateReader;
8
+ export declare function formatQueueAdmissionReport(rows: readonly QueueAdmissionRow[]): string;
9
+ export declare function runQueueReconcile(deps: {
10
+ readSnapshot: () => Promise<QueueSnapshot>;
11
+ reader: AdmissionStateReader;
12
+ lane?: string;
13
+ }): Promise<{
14
+ report: string;
15
+ conflicts: number;
16
+ }>;
17
+ //# sourceMappingURL=admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admission.d.ts","sourceRoot":"","sources":["../../src/cli/admission.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,aAAa,EACnB,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,yBAAyB,CAAC;CACpC;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,CAE9E;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,SAAS,iBAAiB,EAAE,GAAG,MAAM,CAgBrF;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,YAAY,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3C,MAAM,EAAE,oBAAoB,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAkBjD"}
@@ -0,0 +1,37 @@
1
+ // packages/cli/src/cli/admission.ts — Factory App admission-state reader wiring + `factory queue reconcile` (#1497).
2
+ import { classifyQueueCompatibility, createFileAdmissionStateReader, formatAdmissionConflict, } from '@on-par/factory-core/internal';
3
+ export function admissionStateReaderFor(stateDir) {
4
+ return createFileAdmissionStateReader({ stateDir });
5
+ }
6
+ export function formatQueueAdmissionReport(rows) {
7
+ const conflicts = rows.filter((row) => row.verdict.kind === 'refuse');
8
+ const lines = [`queue reconcile — ${rows.length} queued issue(s), ${conflicts.length} conflict(s)`];
9
+ if (conflicts.length === 0) {
10
+ lines.push(' no Factory App admission conflicts — the GitHub label queue is authoritative here');
11
+ return lines.join('\n');
12
+ }
13
+ for (const row of conflicts) {
14
+ const verdict = row.verdict;
15
+ const [head, ...rest] = formatAdmissionConflict(row.issue, verdict).split('\n');
16
+ lines.push(` [${row.lane}] ${head}`, ...rest.map((line) => ` ${line}`));
17
+ }
18
+ return lines.join('\n');
19
+ }
20
+ export async function runQueueReconcile(deps) {
21
+ const snapshot = await deps.readSnapshot();
22
+ const entries = deps.lane === undefined ? snapshot.entries : snapshot.entries.filter((entry) => entry.lane === deps.lane);
23
+ const rows = [];
24
+ for (const entry of entries) {
25
+ let lookup;
26
+ try {
27
+ lookup = await deps.reader.read(entry.issue);
28
+ }
29
+ catch (err) {
30
+ lookup = { kind: 'unreadable', detail: err instanceof Error ? err.message : String(err) };
31
+ }
32
+ rows.push({ lane: entry.lane, issue: entry.issue, verdict: classifyQueueCompatibility(entry.issue, lookup) });
33
+ }
34
+ const conflicts = rows.filter((row) => row.verdict.kind === 'refuse').length;
35
+ return { report: formatQueueAdmissionReport(rows), conflicts };
36
+ }
37
+ //# sourceMappingURL=admission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admission.js","sourceRoot":"","sources":["../../src/cli/admission.ts"],"names":[],"mappings":"AAAA,qHAAqH;AAErH,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,EAC9B,uBAAuB,GAIxB,MAAM,+BAA+B,CAAC;AAQvC,MAAM,UAAU,uBAAuB,CAAC,QAAgB;IACtD,OAAO,8BAA8B,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,IAAkC;IAC3E,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IACtE,MAAM,KAAK,GAAG,CAAC,qBAAqB,IAAI,CAAC,MAAM,qBAAqB,SAAS,CAAC,MAAM,cAAc,CAAC,CAAC;IAEpG,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,qFAAqF,CAAC,CAAC;QAClG,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,GAAG,CAAC,OAAiE,CAAC;QACtF,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,uBAAuB,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChF,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAIvC;IACC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;IAC3C,MAAM,OAAO,GACX,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC;IAE5G,MAAM,IAAI,GAAwB,EAAE,CAAC;IACrC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,MAAM,CAAC;QACX,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,EAAE,IAAI,EAAE,YAAqB,EAAE,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACrG,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,0BAA0B,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IAChH,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAC;IAC7E,OAAO,EAAE,MAAM,EAAE,0BAA0B,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;AACjE,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Command } from 'commander';
2
+ export declare const BRANCH_PREFIX_OPTION_DESCRIPTION = "Branch-name prefix for branches this command creates (default: factory)";
3
+ export declare const BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION = "Branch-name prefix of factory branches to act on; legacy ship-it/ branches are always included (default: factory)";
4
+ export declare const INVALID_BRANCH_PREFIX_MESSAGE = "factory: --branch-prefix must contain at least one letter or digit";
5
+ /** undefined -> resolveBranchPrefix(); otherwise the slug-normalized value; null when it has no [a-z0-9]. */
6
+ export declare function resolveBranchPrefixOption(raw: string | undefined): string | null;
7
+ export declare function addBranchPrefixOption(cmd: Command): Command;
8
+ //# sourceMappingURL=branch-prefix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-prefix.d.ts","sourceRoot":"","sources":["../../src/cli/branch-prefix.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,eAAO,MAAM,gCAAgC,4EAC8B,CAAC;AAC5E,eAAO,MAAM,sCAAsC,sHAAsI,CAAC;AAC1L,eAAO,MAAM,6BAA6B,uEAAuE,CAAC;AAElH,6GAA6G;AAC7G,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAIhF;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAE3D"}
@@ -0,0 +1,16 @@
1
+ import { LEGACY_BRANCH_PREFIX, branchPrefixSlug, resolveBranchPrefix, slugify } from '@on-par/factory-core/internal';
2
+ export const BRANCH_PREFIX_OPTION_DESCRIPTION = 'Branch-name prefix for branches this command creates (default: factory)';
3
+ export const BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION = `Branch-name prefix of factory branches to act on; legacy ${LEGACY_BRANCH_PREFIX}/ branches are always included (default: factory)`;
4
+ export const INVALID_BRANCH_PREFIX_MESSAGE = 'factory: --branch-prefix must contain at least one letter or digit';
5
+ /** undefined -> resolveBranchPrefix(); otherwise the slug-normalized value; null when it has no [a-z0-9]. */
6
+ export function resolveBranchPrefixOption(raw) {
7
+ if (raw === undefined)
8
+ return resolveBranchPrefix();
9
+ if (slugify(raw) === '')
10
+ return null;
11
+ return branchPrefixSlug(raw);
12
+ }
13
+ export function addBranchPrefixOption(cmd) {
14
+ return cmd.option('--branch-prefix <prefix>', BRANCH_PREFIX_OPTION_DESCRIPTION);
15
+ }
16
+ //# sourceMappingURL=branch-prefix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-prefix.js","sourceRoot":"","sources":["../../src/cli/branch-prefix.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAErH,MAAM,CAAC,MAAM,gCAAgC,GAC3C,yEAAyE,CAAC;AAC5E,MAAM,CAAC,MAAM,sCAAsC,GAAG,4DAA4D,oBAAoB,mDAAmD,CAAC;AAC1L,MAAM,CAAC,MAAM,6BAA6B,GAAG,oEAAoE,CAAC;AAElH,6GAA6G;AAC7G,MAAM,UAAU,yBAAyB,CAAC,GAAuB;IAC/D,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,mBAAmB,EAAE,CAAC;IACpD,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAY;IAChD,OAAO,GAAG,CAAC,MAAM,CAAC,0BAA0B,EAAE,gCAAgC,CAAC,CAAC;AAClF,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { type DefectSourceClient, type FactoryEvent, type getFactoryPaths, type HumanSourceClient, type ModelRouter } from '@on-par/factory-core';
2
+ import { type ReviewFloorRuleSet } from '@on-par/factory-core/internal';
3
+ export interface ClassifierBacktestOptions {
4
+ since: string;
5
+ limit?: string;
6
+ labels?: string;
7
+ maxCost?: string;
8
+ json?: boolean;
9
+ branchPrefix?: string;
10
+ }
11
+ export interface ClassifierBacktestDeps {
12
+ repoRoot: string;
13
+ paths: ReturnType<typeof getFactoryPaths>;
14
+ owner: string;
15
+ repo: string;
16
+ octokit: HumanSourceClient & DefectSourceClient & {
17
+ rest: {
18
+ issues: {
19
+ get(params: {
20
+ owner: string;
21
+ repo: string;
22
+ issue_number: number;
23
+ }): Promise<{
24
+ data: any;
25
+ }>;
26
+ };
27
+ };
28
+ };
29
+ router: ModelRouter;
30
+ rules: ReviewFloorRuleSet;
31
+ modelPin?: string;
32
+ windowDays: number;
33
+ /** git, run in repoRoot. */
34
+ exec(cmd: string): Promise<{
35
+ stdout: string;
36
+ }>;
37
+ readFile(path: string): string;
38
+ appendFile(path: string, text: string): void;
39
+ ensureDir(path: string): void;
40
+ /** Reads the local events log; [] when it does not exist. */
41
+ readEvents(path: string): FactoryEvent[];
42
+ now(): string;
43
+ log(line: string): void;
44
+ }
45
+ /** Bad command-line input; the caller maps it to exit code 2. */
46
+ export declare class ClassifierBacktestInputError extends Error {
47
+ constructor(message: string);
48
+ }
49
+ export declare function cmdClassifierBacktest(opts: ClassifierBacktestOptions, deps: ClassifierBacktestDeps): Promise<void>;
50
+ //# sourceMappingURL=classifier-backtest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier-backtest.d.ts","sourceRoot":"","sources":["../../src/cli/classifier-backtest.ts"],"names":[],"mappings":"AAIA,OAAO,EAQL,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAOL,KAAK,kBAAkB,EACxB,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,iBAAiB,GACxB,kBAAkB,GAAG;QACnB,IAAI,EAAE;YAAE,MAAM,EAAE;gBAAE,GAAG,CAAC,MAAM,EAAE;oBAAE,KAAK,EAAE,MAAM,CAAC;oBAAC,IAAI,EAAE,MAAM,CAAC;oBAAC,YAAY,EAAE,MAAM,CAAA;iBAAE,GAAG,OAAO,CAAC;oBAAE,IAAI,EAAE,GAAG,CAAA;iBAAE,CAAC,CAAA;aAAE,CAAA;SAAE,CAAC;KAClH,CAAC;IACJ,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,6DAA6D;IAC7D,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CAAC;IACzC,GAAG,IAAI,MAAM,CAAC;IACd,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,iEAAiE;AACjE,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAkCD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,yBAAyB,EAC/B,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,IAAI,CAAC,CA8Ef"}
@@ -0,0 +1,112 @@
1
+ // packages/cli/src/cli/classifier-backtest.ts — `factory classifier backtest`: replay the floor + shadow classifier over merged PRs (#1728)
2
+ import { join } from 'node:path';
3
+ import { styleText } from 'node:util';
4
+ import { detectPostMergeDefects, fetchDefectSources, fetchHumanEventSources, formatClassifierReport, mergedPrRefs, reconstructHumanEvents, summarizeClassifierOutcomes, } from '@on-par/factory-core';
5
+ import { backtestFileName, classifiedRecords, parseBacktestLabels, runClassifierBacktest, selectBacktestPrs, } from '@on-par/factory-core/internal';
6
+ /** Bad command-line input; the caller maps it to exit code 2. */
7
+ export class ClassifierBacktestInputError extends Error {
8
+ constructor(message) {
9
+ super(`factory: ${message}`);
10
+ this.name = 'ClassifierBacktestInputError';
11
+ }
12
+ }
13
+ const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
14
+ function validateOptions(opts) {
15
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(opts.since) || Number.isNaN(Date.parse(opts.since))) {
16
+ throw new ClassifierBacktestInputError(`invalid --since '${opts.since}' — expected YYYY-MM-DD`);
17
+ }
18
+ let limit;
19
+ if (opts.limit !== undefined) {
20
+ limit = Number(opts.limit);
21
+ if (!/^\d+$/.test(opts.limit) || limit < 1) {
22
+ throw new ClassifierBacktestInputError(`invalid --limit '${opts.limit}' — expected a positive integer`);
23
+ }
24
+ }
25
+ let maxCost;
26
+ if (opts.maxCost !== undefined) {
27
+ maxCost = Number(opts.maxCost);
28
+ if (opts.maxCost.trim() === '' || !Number.isFinite(maxCost) || maxCost <= 0) {
29
+ throw new ClassifierBacktestInputError(`invalid --max-cost '${opts.maxCost}' — expected a number of USD > 0`);
30
+ }
31
+ }
32
+ return { limit, maxCost };
33
+ }
34
+ function loadLabels(opts, deps) {
35
+ if (opts.labels === undefined)
36
+ return new Map();
37
+ try {
38
+ return parseBacktestLabels(deps.readFile(opts.labels));
39
+ }
40
+ catch (err) {
41
+ throw new ClassifierBacktestInputError(`--labels ${opts.labels}: ${err?.message ?? err}`);
42
+ }
43
+ }
44
+ export async function cmdClassifierBacktest(opts, deps) {
45
+ const { limit, maxCost } = validateOptions(opts);
46
+ const labels = loadLabels(opts, deps);
47
+ const now = deps.now();
48
+ const { owner, repo, paths } = deps;
49
+ const sources = await fetchHumanEventSources(deps.octokit, owner, repo, null, opts.branchPrefix);
50
+ const selected = selectBacktestPrs(sources, { since: opts.since, limit });
51
+ if (selected.length === 0) {
52
+ deps.log(`No merged factory PRs since ${opts.since}.`);
53
+ return;
54
+ }
55
+ const local = deps.readEvents(paths.events);
56
+ let events = [...local, ...reconstructHumanEvents(selected, local)];
57
+ try {
58
+ const defectSources = await fetchDefectSources(deps.octokit, owner, repo, mergedPrRefs(selected), {
59
+ now,
60
+ windowDays: deps.windowDays,
61
+ });
62
+ events = [...events, ...detectPostMergeDefects(defectSources, events, { now, windowDays: deps.windowDays })];
63
+ }
64
+ catch (err) {
65
+ console.error(styleText('yellow', `factory: post-merge defect signals unavailable (${err?.message ?? err}) — heuristic defect labels omitted (hand labels still apply)`));
66
+ }
67
+ const file = join(paths.state, backtestFileName(now));
68
+ deps.ensureDir(paths.state);
69
+ const checkSha = (sha) => {
70
+ if (!SHA_PATTERN.test(sha))
71
+ throw new Error(`refusing unsafe merge commit sha '${sha}'`);
72
+ return sha;
73
+ };
74
+ const result = await runClassifierBacktest({ sources: selected, events, labels, rules: deps.rules, maxCostUsd: maxCost, now, windowDays: deps.windowDays }, {
75
+ numstat: async (sha) => (await deps.exec(`git diff --numstat --no-renames ${checkSha(sha)}^1 ${sha}`)).stdout,
76
+ diff: async (sha) => (await deps.exec(`git diff --no-renames ${checkSha(sha)}^1 ${sha}`)).stdout,
77
+ getIssue: async (n) => {
78
+ const { data } = await deps.octokit.rest.issues.get({ owner, repo, issue_number: n });
79
+ return { title: data.title ?? '', body: data.body ?? '' };
80
+ },
81
+ specPath: (issue) => join(paths.plans, `issue-${issue}.md`),
82
+ worktree: deps.repoRoot,
83
+ router: deps.router,
84
+ modelPin: deps.modelPin,
85
+ onRecord: (r) => deps.appendFile(file, `${JSON.stringify(r)}\n`),
86
+ });
87
+ const spent = `$${result.spentUsd.toFixed(2)} spent`;
88
+ const covered = result.records.length;
89
+ if (result.budgetReached) {
90
+ deps.log(`budget reached: covered ${covered} of ${result.candidates} PRs (${spent})`);
91
+ }
92
+ else {
93
+ const unpriced = result.unpricedCalls > 0 ? `, ${result.unpricedCalls} unpriced` : '';
94
+ deps.log(`covered ${covered} of ${result.candidates} PRs (${spent}${unpriced})`);
95
+ }
96
+ const report = summarizeClassifierOutcomes(classifiedRecords(result));
97
+ if (opts.json) {
98
+ deps.log(JSON.stringify({
99
+ file,
100
+ covered,
101
+ candidates: result.candidates,
102
+ spentUsd: result.spentUsd,
103
+ budgetReached: result.budgetReached,
104
+ report,
105
+ }));
106
+ return;
107
+ }
108
+ for (const line of formatClassifierReport(report))
109
+ deps.log(line);
110
+ deps.log(`Wrote ${file}`);
111
+ }
112
+ //# sourceMappingURL=classifier-backtest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier-backtest.js","sourceRoot":"","sources":["../../src/cli/classifier-backtest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { type CostEntry } from '@on-par/factory-core';
2
+ /** One cost row in `factory cost --json` (#2269). CostEntry field names. */
3
+ export interface CostRowJson {
4
+ ts: string;
5
+ issue: string;
6
+ task: string;
7
+ model: string;
8
+ inputTokens: number;
9
+ outputTokens: number;
10
+ cacheReadTokens: number | null;
11
+ cacheCreationTokens: number | null;
12
+ /** null when unpriced (ADR-0020); never 0 for an unpriced row. */
13
+ cost: number | null;
14
+ }
15
+ /** Per-model rollup over the (filtered) rows, first-seen model order. */
16
+ export interface CostModelRowJson {
17
+ model: string;
18
+ tasks: number;
19
+ inputTokens: number;
20
+ outputTokens: number;
21
+ /** Sum of priced rows; null when every row for the model is unpriced. */
22
+ cost: number | null;
23
+ unpricedCount: number;
24
+ }
25
+ /** `factory cost --json` payload (#2269). Additive changes only; bump schemaVersion on a breaking change. */
26
+ export interface CostJson {
27
+ schemaVersion: 1;
28
+ rows: CostRowJson[];
29
+ perModel: CostModelRowJson[];
30
+ total: {
31
+ cost: number | null;
32
+ unpricedCount: number;
33
+ };
34
+ }
35
+ /** Build the `factory cost --json` payload; `issue` keeps only that issue's rows. */
36
+ export declare function buildCostJson(entries: CostEntry[], issue?: string): CostJson;
37
+ //# sourceMappingURL=cost-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-json.d.ts","sourceRoot":"","sources":["../../src/cli/cost-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtE,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,kEAAkE;IAClE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,6GAA6G;AAC7G,MAAM,WAAW,QAAQ;IACvB,aAAa,EAAE,CAAC,CAAC;IACjB,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;CACvD;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,CA8C5E"}
@@ -0,0 +1,48 @@
1
+ import { aggregateCosts } from '@on-par/factory-core';
2
+ /** Build the `factory cost --json` payload; `issue` keeps only that issue's rows. */
3
+ export function buildCostJson(entries, issue) {
4
+ const filtered = issue === undefined ? entries : entries.filter((e) => e.issue === String(issue));
5
+ const rows = [];
6
+ const models = new Map();
7
+ for (const e of filtered) {
8
+ const cost = e.unpriced === true || e.cost == null ? null : e.cost;
9
+ const inputTokens = e.inputTokens ?? 0;
10
+ const outputTokens = e.outputTokens ?? 0;
11
+ rows.push({
12
+ ts: e.ts,
13
+ issue: e.issue,
14
+ task: e.task,
15
+ model: e.model,
16
+ inputTokens,
17
+ outputTokens,
18
+ cacheReadTokens: e.cacheReadTokens ?? null,
19
+ cacheCreationTokens: e.cacheCreationTokens ?? null,
20
+ cost,
21
+ });
22
+ let m = models.get(e.model);
23
+ if (m === undefined) {
24
+ m = { tasks: 0, inputTokens: 0, outputTokens: 0, sum: 0, priced: 0, unpriced: 0 };
25
+ models.set(e.model, m);
26
+ }
27
+ m.tasks += 1;
28
+ m.inputTokens += inputTokens;
29
+ m.outputTokens += outputTokens;
30
+ if (cost === null)
31
+ m.unpriced += 1;
32
+ else {
33
+ m.sum += cost;
34
+ m.priced += 1;
35
+ }
36
+ }
37
+ const perModel = [...models].map(([model, m]) => ({
38
+ model,
39
+ tasks: m.tasks,
40
+ inputTokens: m.inputTokens,
41
+ outputTokens: m.outputTokens,
42
+ cost: m.priced === 0 && m.unpriced > 0 ? null : m.sum,
43
+ unpricedCount: m.unpriced,
44
+ }));
45
+ const t = aggregateCosts(filtered).total;
46
+ return { schemaVersion: 1, rows, perModel, total: { cost: t.cost, unpricedCount: t.unpricedCount } };
47
+ }
48
+ //# sourceMappingURL=cost-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-json.js","sourceRoot":"","sources":["../../src/cli/cost-json.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ import { type RepoState } from '@on-par/factory-core/internal';
2
+ export declare const FACTORYD_LABEL = "com.onpar.factoryd";
3
+ /** Expected daemon-control failure. index.ts's commander wrappers map it onto
4
+ * CliExitError (importing CliExitError here would be circular — index.ts
5
+ * imports this module). */
6
+ export declare class DaemonCtlError extends Error {
7
+ readonly code: number;
8
+ constructor(message: string, code: number);
9
+ }
10
+ export type LaunchctlExec = (cmd: string, args: string[]) => Promise<{
11
+ code: number;
12
+ stdout: string;
13
+ stderr: string;
14
+ }>;
15
+ export interface DaemonCtlDeps {
16
+ /** Runs launchctl / ps without a shell. Default: promisified execFile that
17
+ * never rejects on non-zero exit (resolves { code, stdout, stderr }). */
18
+ exec?: LaunchctlExec;
19
+ /** Default os.homedir(). Tests point this at a temp dir. */
20
+ home?: string;
21
+ /** Default process.getuid?.() ?? 0. */
22
+ uid?: number;
23
+ /** Default process.platform. */
24
+ platform?: NodeJS.Platform;
25
+ /** Default process.stdout. */
26
+ out?: {
27
+ write(s: string): unknown;
28
+ };
29
+ /** Default process.execPath. */
30
+ nodePath?: string;
31
+ /** Default realpathSync(process.argv[1]) — unwraps the bin symlink. */
32
+ cliScriptPath?: string;
33
+ /** Default defaultRegistryPath(). */
34
+ registryFile?: string;
35
+ /** Follow-mode poll interval; tests pass ~10. Default 250. */
36
+ pollMs?: number;
37
+ }
38
+ /** Where a given home directory keeps the LaunchAgent plist and the daemon log. */
39
+ export declare function factorydFiles(home: string): {
40
+ launchAgentsDir: string;
41
+ plistPath: string;
42
+ logPath: string;
43
+ };
44
+ /** Resolved options for the foreground `factory daemon run` process. */
45
+ export interface FactorydConfig {
46
+ /** Registry file served (default ~/.factory/registry.json). */
47
+ registryFile: string;
48
+ /** Port to bind on 127.0.0.1 (default DEFAULT_FACTORYD_PORT). */
49
+ port: number;
50
+ /** Opt-in auto-update (#2128). Off unless --auto-update was passed; nothing acts on it yet. */
51
+ autoUpdate: boolean;
52
+ }
53
+ /** Resolves `factory daemon run` options into a FactorydConfig. Throws
54
+ * DaemonCtlError (code 2) on an invalid --port. */
55
+ export declare function resolveFactorydConfig(opts: {
56
+ port?: string;
57
+ registry?: string;
58
+ autoUpdate?: boolean;
59
+ }): FactorydConfig;
60
+ /** Pure renderer for ~/Library/LaunchAgents/com.onpar.factoryd.plist. KeepAlive
61
+ * relaunches factoryd after any exit; RunAtLoad starts it at login/bootstrap;
62
+ * launchd redirects stdout+stderr to the daemon log. ProgramArguments invokes
63
+ * the foreground `factory daemon run` command with its defaults. */
64
+ export declare function renderFactorydPlist(opts: {
65
+ nodePath: string;
66
+ cliScriptPath: string;
67
+ logPath: string;
68
+ }): string;
69
+ /** Extracts the running pid from `launchctl print gui/<uid>/<label>` output. */
70
+ /** `factory daemon status --json` payload (#2267). Additive changes only; bump schemaVersion on a breaking change. */
71
+ export interface DaemonStatusJson {
72
+ schemaVersion: 1;
73
+ running: boolean;
74
+ pid: number | null;
75
+ uptimeSec: number | null;
76
+ plistInstalled: boolean;
77
+ repos: Array<{
78
+ slug: string;
79
+ checkoutPath: string;
80
+ state: RepoState;
81
+ }>;
82
+ }
83
+ /** ps `etime` ([[dd-]hh:]mm:ss) → whole seconds; null when empty or malformed. */
84
+ export declare function parseEtime(etime: string): number | null;
85
+ export declare function parseLaunchctlPid(printOutput: string): number | null;
86
+ /** Last n lines of text, ignoring a trailing newline. */
87
+ export declare function lastLines(text: string, n: number): string[];
88
+ /** The production exec seam: execFile without a shell, resolving (never
89
+ * rejecting) `{ code, stdout, stderr }` — a missing binary or non-numeric
90
+ * error code becomes code 1. Exported for direct coverage in daemon.test.ts. */
91
+ export declare function defaultExec(): LaunchctlExec;
92
+ /** Install (or reinstall) and load the LaunchAgent. Always a full reinstall:
93
+ * regenerate the plist, bootout (ignoring failure — handles both "already
94
+ * loaded" and "not loaded"), then bootstrap into the gui domain so the daemon's
95
+ * Claude workers keep login-keychain access (docs/runbooks/macos-keychain-launchagent.md). */
96
+ export declare function cmdDaemonStart(deps?: DaemonCtlDeps): Promise<void>;
97
+ /** Unload the LaunchAgent. Idempotent: a not-loaded agent is success. The plist
98
+ * stays on disk so `factory daemon start` re-enables it. */
99
+ export declare function cmdDaemonStop(deps?: DaemonCtlDeps): Promise<void>;
100
+ /** Report running/not-loaded, pid + uptime, plist install state, and the
101
+ * attached repos from the registry. Exit code 0 when running, 1 when not.
102
+ * With `opts.json`, print one DaemonStatusJson object instead of text. */
103
+ export declare function cmdDaemonStatus(deps?: DaemonCtlDeps, opts?: {
104
+ json?: boolean;
105
+ }): Promise<void>;
106
+ /** Print the last N lines of ~/.factory/daemon.log; with --follow, keep
107
+ * streaming appended bytes until Ctrl-C (size-offset poll, like logs.ts's
108
+ * followEvents — no `tail -f` child process). Works on any platform. */
109
+ export declare function cmdDaemonLogs(opts: {
110
+ follow?: boolean;
111
+ lines?: string;
112
+ }, deps?: DaemonCtlDeps): Promise<void>;
113
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/cli/daemon.ts"],"names":[],"mappings":"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"}