@justyork/repo-mind 0.4.0 → 0.7.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 (93) hide show
  1. package/README.md +5 -2
  2. package/dist/ab-demo/compute-pass.d.ts +24 -0
  3. package/dist/ab-demo/compute-pass.js +80 -0
  4. package/dist/ab-demo/live-answer.d.ts +6 -0
  5. package/dist/ab-demo/live-answer.js +89 -0
  6. package/dist/ab-demo/live-eval.d.ts +19 -0
  7. package/dist/ab-demo/live-eval.js +192 -0
  8. package/dist/ab-demo/paths.d.ts +1 -0
  9. package/dist/ab-demo/paths.js +3 -0
  10. package/dist/ab-demo/record-transcript.d.ts +9 -0
  11. package/dist/ab-demo/record-transcript.js +97 -0
  12. package/dist/ab-demo/types.d.ts +57 -0
  13. package/dist/ab-demo/validate-questions.d.ts +5 -0
  14. package/dist/ab-demo/validate-questions.js +32 -0
  15. package/dist/agent-write-gate.d.ts +7 -0
  16. package/dist/agent-write-gate.js +33 -0
  17. package/dist/cli.js +102 -0
  18. package/dist/commands/ab-eval.d.ts +11 -0
  19. package/dist/commands/ab-eval.js +103 -0
  20. package/dist/commands/publish.d.ts +11 -0
  21. package/dist/commands/publish.js +91 -0
  22. package/dist/git/git-exec.d.ts +19 -0
  23. package/dist/git/git-exec.js +89 -0
  24. package/dist/git/publish-pr.d.ts +33 -0
  25. package/dist/git/publish-pr.js +121 -0
  26. package/dist/index/slug.d.ts +2 -0
  27. package/dist/index/slug.js +15 -0
  28. package/dist/mcp/server.js +40 -0
  29. package/dist/publish/batch-publish.d.ts +29 -0
  30. package/dist/publish/batch-publish.js +106 -0
  31. package/dist/tools/create-draft.d.ts +32 -0
  32. package/dist/tools/create-draft.js +163 -0
  33. package/package.json +7 -1
  34. package/ui/dist/assets/{arc-C6B0IXf5.js → arc-DufwQU06.js} +1 -1
  35. package/ui/dist/assets/{architectureDiagram-3BPJPVTR-BwcC0zwn.js → architectureDiagram-3BPJPVTR-CI2fjjSo.js} +1 -1
  36. package/ui/dist/assets/{blockDiagram-GPEHLZMM-DIhdWMA6.js → blockDiagram-GPEHLZMM-DVBIs-Z6.js} +1 -1
  37. package/ui/dist/assets/{c4Diagram-AAUBKEIU-DAe6bsUB.js → c4Diagram-AAUBKEIU-rhAX_HrB.js} +1 -1
  38. package/ui/dist/assets/channel-Q_-BqwUT.js +1 -0
  39. package/ui/dist/assets/{chunk-2J33WTMH-Cy3md3pb.js → chunk-2J33WTMH-CxWSQi5S.js} +1 -1
  40. package/ui/dist/assets/{chunk-4BX2VUAB-CzL8eUDD.js → chunk-4BX2VUAB-BEM-vVbU.js} +1 -1
  41. package/ui/dist/assets/{chunk-55IACEB6-CbthXzDA.js → chunk-55IACEB6-TcrLDHYx.js} +1 -1
  42. package/ui/dist/assets/{chunk-727SXJPM-B7ZW-l9j.js → chunk-727SXJPM-D8g4OcIW.js} +1 -1
  43. package/ui/dist/assets/{chunk-AQP2D5EJ-Vaux-7Ld.js → chunk-AQP2D5EJ-CSvrR8rQ.js} +1 -1
  44. package/ui/dist/assets/{chunk-FMBD7UC4-C6P2YSWU.js → chunk-FMBD7UC4-C14JNc29.js} +1 -1
  45. package/ui/dist/assets/{chunk-ND2GUHAM-gSNqdDAn.js → chunk-ND2GUHAM-Cnb0VFKm.js} +1 -1
  46. package/ui/dist/assets/{chunk-QZHKN3VN-DPPMoZqF.js → chunk-QZHKN3VN-D2pqmAXN.js} +1 -1
  47. package/ui/dist/assets/classDiagram-4FO5ZUOK-CPrZx4R4.js +1 -0
  48. package/ui/dist/assets/classDiagram-v2-Q7XG4LA2-CPrZx4R4.js +1 -0
  49. package/ui/dist/assets/{cose-bilkent-S5V4N54A-Ca4nXCOA.js → cose-bilkent-S5V4N54A-Cli2Sbvt.js} +1 -1
  50. package/ui/dist/assets/{dagre-BM42HDAG-C-4Y4Jyn.js → dagre-BM42HDAG-gUsVuy4s.js} +1 -1
  51. package/ui/dist/assets/{diagram-2AECGRRQ-CNv_WVZk.js → diagram-2AECGRRQ-DTKi_1b6.js} +1 -1
  52. package/ui/dist/assets/{diagram-5GNKFQAL-CNwYGvCw.js → diagram-5GNKFQAL-8Bj0k_-k.js} +1 -1
  53. package/ui/dist/assets/{diagram-KO2AKTUF-CEULTL49.js → diagram-KO2AKTUF-CKOX2fpv.js} +1 -1
  54. package/ui/dist/assets/{diagram-LMA3HP47-sGQpPW5i.js → diagram-LMA3HP47-B12JvpF3.js} +1 -1
  55. package/ui/dist/assets/{diagram-OG6HWLK6-BBEtEBkh.js → diagram-OG6HWLK6-DKeTI9IH.js} +1 -1
  56. package/ui/dist/assets/editor-DZbRkvnr.js +211 -0
  57. package/ui/dist/assets/{erDiagram-TEJ5UH35-B4mEdVPJ.js → erDiagram-TEJ5UH35-oDb31edH.js} +1 -1
  58. package/ui/dist/assets/{flowDiagram-I6XJVG4X-7-OngwLn.js → flowDiagram-I6XJVG4X-SPf3KLS-.js} +1 -1
  59. package/ui/dist/assets/{ganttDiagram-6RSMTGT7-Bq3z_Nvr.js → ganttDiagram-6RSMTGT7-D8oPEcrt.js} +1 -1
  60. package/ui/dist/assets/{gitGraphDiagram-PVQCEYII-BjMdjnGR.js → gitGraphDiagram-PVQCEYII-eI9Yp7d7.js} +1 -1
  61. package/ui/dist/assets/{graph-CwHQTpjf.js → graph-DqCH9VKs.js} +1 -1
  62. package/ui/dist/assets/{infoDiagram-5YYISTIA-ByORmROU.js → infoDiagram-5YYISTIA-CQ6dtyj3.js} +1 -1
  63. package/ui/dist/assets/{ishikawaDiagram-YF4QCWOH-B7yRHlcY.js → ishikawaDiagram-YF4QCWOH-BHnOZ8-9.js} +1 -1
  64. package/ui/dist/assets/{journeyDiagram-JHISSGLW-J5mLr9YH.js → journeyDiagram-JHISSGLW-0t985reH.js} +1 -1
  65. package/ui/dist/assets/{kanban-definition-UN3LZRKU-B3GyGD8X.js → kanban-definition-UN3LZRKU-vPlhgwPS.js} +1 -1
  66. package/ui/dist/assets/main-Io6jccwi.js +232 -0
  67. package/ui/dist/assets/{mermaid.core-C57NVZG0.js → mermaid.core-C2wn2wM5.js} +4 -4
  68. package/ui/dist/assets/{mindmap-definition-RKZ34NQL-CkcXt9tU.js → mindmap-definition-RKZ34NQL-COKv8bQN.js} +1 -1
  69. package/ui/dist/assets/{pieDiagram-4H26LBE5-Y7B3kCyQ.js → pieDiagram-4H26LBE5-CgVeawD6.js} +1 -1
  70. package/ui/dist/assets/{quadrantDiagram-W4KKPZXB-CXKUOw1G.js → quadrantDiagram-W4KKPZXB-Bwpr8PxN.js} +1 -1
  71. package/ui/dist/assets/{requirementDiagram-4Y6WPE33-DBmmmiXs.js → requirementDiagram-4Y6WPE33-Cc8HFBxl.js} +1 -1
  72. package/ui/dist/assets/{sankeyDiagram-5OEKKPKP-BNADiaE4.js → sankeyDiagram-5OEKKPKP-B_1nSAK5.js} +1 -1
  73. package/ui/dist/assets/{sequenceDiagram-3UESZ5HK-CctElOC5.js → sequenceDiagram-3UESZ5HK-hsYrUJza.js} +1 -1
  74. package/ui/dist/assets/{stateDiagram-AJRCARHV-D40_7g7Y.js → stateDiagram-AJRCARHV-BMf4FL5O.js} +1 -1
  75. package/ui/dist/assets/stateDiagram-v2-BHNVJYJU-BpaPdu-O.js +1 -0
  76. package/ui/dist/assets/{theme-DxqwV6dp.js → theme-Dl8H1UuW.js} +1 -1
  77. package/ui/dist/assets/theme-PE9lQ4bz.css +1 -0
  78. package/ui/dist/assets/{timeline-definition-PNZ67QCA-DMVhUcek.js → timeline-definition-PNZ67QCA-Cf3u1h2_.js} +1 -1
  79. package/ui/dist/assets/{vennDiagram-CIIHVFJN-Ci0RqPuV.js → vennDiagram-CIIHVFJN-2uKO3x22.js} +1 -1
  80. package/ui/dist/assets/visual-editor-D1uYYGOW.js +47 -0
  81. package/ui/dist/assets/{wardley-L42UT6IY-Chn0BKir.js → wardley-L42UT6IY-BjLaoVtm.js} +1 -1
  82. package/ui/dist/assets/{wardleyDiagram-YWT4CUSO-BLdXlrC5.js → wardleyDiagram-YWT4CUSO-DlrUKeJk.js} +1 -1
  83. package/ui/dist/assets/{xychartDiagram-2RQKCTM6-C4gOVVe1.js → xychartDiagram-2RQKCTM6-Bzbb3cqD.js} +1 -1
  84. package/ui/dist/graph.html +3 -3
  85. package/ui/dist/index.html +4 -4
  86. package/ui/dist/assets/channel-C8Q7-wTd.js +0 -1
  87. package/ui/dist/assets/classDiagram-4FO5ZUOK-pYCdSDhT.js +0 -1
  88. package/ui/dist/assets/classDiagram-v2-Q7XG4LA2-pYCdSDhT.js +0 -1
  89. package/ui/dist/assets/editor-BcAuZsp8.js +0 -211
  90. package/ui/dist/assets/main-Du7fLv5Y.js +0 -244
  91. package/ui/dist/assets/stateDiagram-v2-BHNVJYJU-DSc6L6rE.js +0 -1
  92. package/ui/dist/assets/theme-BJgORXba.css +0 -1
  93. package/ui/dist/assets/visual-editor-UWcMGp6p.js +0 -39
@@ -0,0 +1,7 @@
1
+ export interface AgentWriteGate {
2
+ enabled: boolean;
3
+ reason: string;
4
+ }
5
+ /** Agent write tools are enabled when the kill-switch passes or REPOMIND_AGENT_WRITE=1. */
6
+ export declare function getAgentWriteGate(cwd?: string): AgentWriteGate;
7
+ export declare function isAgentWriteEnabled(cwd?: string): boolean;
@@ -0,0 +1,33 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { resolveAbDemoRoot } from './ab-demo/paths.js';
4
+ function readLatestAbResults(cwd) {
5
+ const latestPath = path.join(resolveAbDemoRoot(cwd), 'results', 'latest.json');
6
+ if (!fs.existsSync(latestPath)) {
7
+ return null;
8
+ }
9
+ try {
10
+ return JSON.parse(fs.readFileSync(latestPath, 'utf8'));
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ /** Agent write tools are enabled when the kill-switch passes or REPOMIND_AGENT_WRITE=1. */
17
+ export function getAgentWriteGate(cwd = process.cwd()) {
18
+ if (process.env.REPOMIND_AGENT_WRITE === '1') {
19
+ return { enabled: true, reason: 'REPOMIND_AGENT_WRITE=1' };
20
+ }
21
+ const latest = readLatestAbResults(cwd);
22
+ const pass = latest?.pass === true || latest?.arms?.pass === true;
23
+ if (pass) {
24
+ return { enabled: true, reason: 'ab-demo kill-switch pass' };
25
+ }
26
+ return {
27
+ enabled: false,
28
+ reason: 'Agent write is disabled until the ab-demo kill-switch passes. Set REPOMIND_AGENT_WRITE=1 to override locally.',
29
+ };
30
+ }
31
+ export function isAgentWriteEnabled(cwd = process.cwd()) {
32
+ return getAgentWriteGate(cwd).enabled;
33
+ }
package/dist/cli.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import { runAbEval } from './commands/ab-eval.js';
2
3
  import { runCheck } from './commands/check.js';
3
4
  import { runExport } from './commands/export.js';
4
5
  import { runInit } from './commands/init.js';
5
6
  import { runPrepare } from './commands/prepare.js';
7
+ import { runPublish } from './commands/publish.js';
6
8
  import { runSetup } from './commands/setup.js';
7
9
  import { runSyncLinks } from './commands/sync-links.js';
8
10
  import { runUi } from './commands/ui.js';
@@ -17,6 +19,8 @@ Usage:
17
19
  repo-mind export [--force] [--cwd <dir>]
18
20
  repo-mind prepare [--all] [--dry-run] [--cwd <dir>] [relative-path]
19
21
  repo-mind sync-links [--dry-run] [--no-convert-body] [--no-sync-related] [--cwd <dir>]
22
+ repo-mind publish [--pr] [--dry-run] [--draft <id>] [--message <text>] [--title <text>] [--body <text>] [--cwd <dir>]
23
+ repo-mind ab-eval --cwd <dir> [--questions <path>] [--output <path>] [--dry-run]
20
24
  repo-mind mcp
21
25
  repo-mind ui [--port <n>] [--cwd <dir>]
22
26
 
@@ -27,6 +31,8 @@ Commands:
27
31
  export Write agents.md export to repo root
28
32
  prepare Add RepoMind frontmatter to markdown files (--all for batch)
29
33
  sync-links Convert markdown links to wikilinks and sync related frontmatter
34
+ publish Publish active drafts to docs/; --pr opens a GitHub pull request
35
+ ab-eval Live A/B eval on a project docs/ corpus (skyforge dogfood gate)
30
36
  mcp Start the MCP stdio server
31
37
  ui Confluence-style workspace over docs/ (127.0.0.1)
32
38
  `);
@@ -40,6 +46,10 @@ function parseArgs(argv) {
40
46
  flags.all = true;
41
47
  continue;
42
48
  }
49
+ if (arg === '--pr') {
50
+ flags.pr = true;
51
+ continue;
52
+ }
43
53
  if (arg === '--dry-run') {
44
54
  flags.dryRun = true;
45
55
  continue;
@@ -74,6 +84,58 @@ function parseArgs(argv) {
74
84
  i += 1;
75
85
  continue;
76
86
  }
87
+ if (arg === '--message' && rest[i + 1]) {
88
+ flags.message = rest[i + 1];
89
+ i += 1;
90
+ continue;
91
+ }
92
+ if (arg === '--title' && rest[i + 1]) {
93
+ flags.title = rest[i + 1];
94
+ i += 1;
95
+ continue;
96
+ }
97
+ if (arg === '--body' && rest[i + 1]) {
98
+ flags.body = rest[i + 1];
99
+ i += 1;
100
+ continue;
101
+ }
102
+ if (arg === '--draft' && rest[i + 1]) {
103
+ const existing = flags.draft;
104
+ const next = rest[i + 1];
105
+ flags.draft = existing ? `${existing},${next}` : next;
106
+ i += 1;
107
+ continue;
108
+ }
109
+ if (arg === '--questions' && rest[i + 1]) {
110
+ flags.questions = rest[i + 1];
111
+ i += 1;
112
+ continue;
113
+ }
114
+ if (arg === '--output' && rest[i + 1]) {
115
+ flags.output = rest[i + 1];
116
+ i += 1;
117
+ continue;
118
+ }
119
+ if (arg === '--record-scores' && rest[i + 1]) {
120
+ flags.recordScores = rest[i + 1];
121
+ i += 1;
122
+ continue;
123
+ }
124
+ if (arg === '--scores' && rest[i + 1]) {
125
+ flags.scores = rest[i + 1];
126
+ i += 1;
127
+ continue;
128
+ }
129
+ if (arg === '--baseline-transcript' && rest[i + 1]) {
130
+ flags.baselineTranscript = rest[i + 1];
131
+ i += 1;
132
+ continue;
133
+ }
134
+ if (arg === '--repomind-transcript' && rest[i + 1]) {
135
+ flags.repomindTranscript = rest[i + 1];
136
+ i += 1;
137
+ continue;
138
+ }
77
139
  if (arg === '--help' || arg === '-h') {
78
140
  flags.help = true;
79
141
  }
@@ -91,6 +153,16 @@ async function main() {
91
153
  !arg.startsWith('-') &&
92
154
  argv[index - 1] !== '--cwd' &&
93
155
  argv[index - 1] !== '--port' &&
156
+ argv[index - 1] !== '--message' &&
157
+ argv[index - 1] !== '--title' &&
158
+ argv[index - 1] !== '--body' &&
159
+ argv[index - 1] !== '--draft' &&
160
+ argv[index - 1] !== '--questions' &&
161
+ argv[index - 1] !== '--output' &&
162
+ argv[index - 1] !== '--record-scores' &&
163
+ argv[index - 1] !== '--scores' &&
164
+ argv[index - 1] !== '--baseline-transcript' &&
165
+ argv[index - 1] !== '--repomind-transcript' &&
94
166
  arg !== argv[1]);
95
167
  switch (command) {
96
168
  case 'init':
@@ -126,6 +198,36 @@ async function main() {
126
198
  syncRelated: flags.noSyncRelated !== true,
127
199
  }));
128
200
  break;
201
+ case 'ab-eval':
202
+ process.exit(runAbEval({
203
+ cwd,
204
+ questions: typeof flags.questions === 'string' ? flags.questions : undefined,
205
+ output: typeof flags.output === 'string' ? flags.output : undefined,
206
+ dryRun: flags.dryRun === true,
207
+ recordScores: typeof flags.recordScores === 'string' ? flags.recordScores : undefined,
208
+ scoresFile: typeof flags.scores === 'string' ? flags.scores : undefined,
209
+ baselineTranscript: typeof flags.baselineTranscript === 'string'
210
+ ? flags.baselineTranscript
211
+ : undefined,
212
+ repomindTranscript: typeof flags.repomindTranscript === 'string'
213
+ ? flags.repomindTranscript
214
+ : undefined,
215
+ }));
216
+ break;
217
+ case 'publish':
218
+ process.exit(runPublish({
219
+ cwd,
220
+ pr: flags.pr === true,
221
+ dryRun: flags.dryRun === true,
222
+ message: typeof flags.message === 'string' ? flags.message : undefined,
223
+ prTitle: typeof flags.title === 'string' ? flags.title : undefined,
224
+ prBody: typeof flags.body === 'string' ? flags.body : undefined,
225
+ draftIds: typeof flags.draft === 'string'
226
+ ? flags.draft.split(',').map((id) => id.trim()).filter(Boolean)
227
+ : undefined,
228
+ skipPush: process.env.REPOMIND_PUBLISH_SKIP_REMOTE === '1',
229
+ }));
230
+ break;
129
231
  case 'mcp':
130
232
  await startMcpServer();
131
233
  break;
@@ -0,0 +1,11 @@
1
+ export interface AbEvalOptions {
2
+ cwd?: string;
3
+ questions?: string;
4
+ output?: string;
5
+ dryRun?: boolean;
6
+ recordScores?: string;
7
+ scoresFile?: string;
8
+ baselineTranscript?: string;
9
+ repomindTranscript?: string;
10
+ }
11
+ export declare function runAbEval(options?: AbEvalOptions): number;
@@ -0,0 +1,103 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { mergeHumanScores, runLiveEval } from '../ab-demo/live-eval.js';
4
+ import { resolveAbDemoRoot, resultsDir } from '../ab-demo/paths.js';
5
+ import { defaultSkyforgeQuestionsPath } from '../ab-demo/validate-questions.js';
6
+ function printUsage() {
7
+ console.log(`repo-mind ab-eval — live A/B eval on a project docs/ corpus
8
+
9
+ Usage:
10
+ repo-mind ab-eval --cwd <project> [--questions <path>] [--output <path>] [--dry-run]
11
+ repo-mind ab-eval --record-scores <results.json> [--scores <scores.json>] [--output <path>]
12
+
13
+ Options:
14
+ --cwd <dir> Project root with docs/ (required for eval run)
15
+ --questions <path> Questions JSON (default: ab-demo/skyforge-questions.json)
16
+ --output <path> Results JSON (default: ab-demo/results/skyforge-<date>.json)
17
+ --dry-run Validate questions + anchors only
18
+ --record-scores <file> Merge humanScores into results and compute pass
19
+ --scores <file> JSON array of { questionId, baseline, repomind } scores
20
+ --baseline-transcript Optional transcript JSONL for baseline token override
21
+ --repomind-transcript Optional transcript JSONL for repomind token override
22
+ `);
23
+ }
24
+ function parseScoresFile(filePath) {
25
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
26
+ if (!Array.isArray(parsed)) {
27
+ throw new Error('--scores file must be a JSON array');
28
+ }
29
+ return parsed.map((raw, index) => {
30
+ if (!raw || typeof raw !== 'object') {
31
+ throw new Error(`scores[${index}] must be an object`);
32
+ }
33
+ const record = raw;
34
+ if (typeof record.questionId !== 'string' || typeof record.baseline !== 'number') {
35
+ throw new Error(`scores[${index}] requires questionId and baseline`);
36
+ }
37
+ if (typeof record.repomind !== 'number') {
38
+ throw new Error(`scores[${index}] requires repomind`);
39
+ }
40
+ return {
41
+ questionId: record.questionId,
42
+ baseline: record.baseline,
43
+ repomind: record.repomind,
44
+ reviewer: typeof record.reviewer === 'string' ? record.reviewer : undefined,
45
+ notes: typeof record.notes === 'string' ? record.notes : undefined,
46
+ };
47
+ });
48
+ }
49
+ function defaultOutputPath(abRoot) {
50
+ const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
51
+ return path.join(resultsDir(abRoot), `skyforge-${stamp}.json`);
52
+ }
53
+ export function runAbEval(options = {}) {
54
+ if (options.recordScores) {
55
+ const scoresPath = options.scoresFile;
56
+ if (!scoresPath) {
57
+ console.error('--record-scores requires --scores <file>');
58
+ return 1;
59
+ }
60
+ try {
61
+ const scores = parseScoresFile(scoresPath);
62
+ const merged = mergeHumanScores(options.recordScores, scores, options.output);
63
+ console.log(`pass: ${merged.pass}`);
64
+ console.log(`tokenPass: ${merged.tokenPass}, hallucinationPass: ${merged.hallucinationPass}`);
65
+ if (merged.categoryWins) {
66
+ for (const row of merged.categoryWins) {
67
+ console.log(` ${row.category}: repomindWins=${row.repomindWins} (hallucination ${row.repomindHallucinationTotal} vs ${row.baselineHallucinationTotal})`);
68
+ }
69
+ }
70
+ console.log(`wrote ${options.output ?? options.recordScores}`);
71
+ return 0;
72
+ }
73
+ catch (error) {
74
+ const message = error instanceof Error ? error.message : String(error);
75
+ console.error(message);
76
+ return 1;
77
+ }
78
+ }
79
+ const cwd = options.cwd;
80
+ if (!cwd) {
81
+ console.error('--cwd is required for live eval');
82
+ printUsage();
83
+ return 1;
84
+ }
85
+ const abRoot = resolveAbDemoRoot(cwd);
86
+ const questionsFile = options.questions ?? defaultSkyforgeQuestionsPath(abRoot);
87
+ const outputPath = options.output ?? defaultOutputPath(abRoot);
88
+ try {
89
+ return runLiveEval({
90
+ cwd,
91
+ questionsFile,
92
+ outputPath,
93
+ dryRun: options.dryRun,
94
+ baselineTranscript: options.baselineTranscript,
95
+ repomindTranscript: options.repomindTranscript,
96
+ });
97
+ }
98
+ catch (error) {
99
+ const message = error instanceof Error ? error.message : String(error);
100
+ console.error(message);
101
+ return 1;
102
+ }
103
+ }
@@ -0,0 +1,11 @@
1
+ export interface PublishOptions {
2
+ cwd?: string;
3
+ pr?: boolean;
4
+ dryRun?: boolean;
5
+ message?: string;
6
+ prTitle?: string;
7
+ prBody?: string;
8
+ draftIds?: string[];
9
+ skipPush?: boolean;
10
+ }
11
+ export declare function runPublish(options?: PublishOptions): number;
@@ -0,0 +1,91 @@
1
+ import path from 'node:path';
2
+ import { runCheck } from './check.js';
3
+ import { DocIndex } from '../index/doc-index.js';
4
+ import { formatDryRunPublishPr, runPublishPr } from '../git/publish-pr.js';
5
+ import { batchPublishDrafts, defaultCommitMessage, defaultPrBody, defaultPrTitle, } from '../publish/batch-publish.js';
6
+ import { openDraftsDb } from '../ui/db/drafts-db.js';
7
+ export function runPublish(options = {}) {
8
+ const cwd = path.resolve(options.cwd ?? process.cwd());
9
+ const index = new DocIndex(cwd);
10
+ const knowledgeRoot = index.getKnowledgeRoot();
11
+ if (!knowledgeRoot) {
12
+ console.error('no docs/ found — run `repo-mind init` or create a docs/ directory');
13
+ return 1;
14
+ }
15
+ const db = openDraftsDb(knowledgeRoot);
16
+ try {
17
+ const active = db.listActive();
18
+ if (active.length === 0) {
19
+ console.error('no active drafts to publish');
20
+ return 1;
21
+ }
22
+ const batch = batchPublishDrafts(index, db, {
23
+ draftIds: options.draftIds,
24
+ dryRun: options.dryRun,
25
+ });
26
+ if (batch.failures.length > 0) {
27
+ for (const failure of batch.failures) {
28
+ console.error(`${failure.slug}: ${failure.message}`);
29
+ }
30
+ return 1;
31
+ }
32
+ if (batch.published.length === 0) {
33
+ console.error('no drafts matched the publish request');
34
+ return 1;
35
+ }
36
+ if (!options.dryRun) {
37
+ const checkCode = runCheck({ cwd });
38
+ if (checkCode !== 0) {
39
+ console.error('publish aborted — fix check violations before committing');
40
+ return checkCode;
41
+ }
42
+ }
43
+ for (const item of batch.published) {
44
+ if (options.dryRun) {
45
+ console.log(`dry-run: would publish ${item.slug} → ${item.path}`);
46
+ }
47
+ else {
48
+ const relativePath = path.relative(path.dirname(knowledgeRoot), item.path);
49
+ console.log(`published ${item.slug} → ${relativePath}`);
50
+ }
51
+ }
52
+ if (!options.pr) {
53
+ return 0;
54
+ }
55
+ const repoRoot = path.dirname(knowledgeRoot);
56
+ const message = options.message ?? defaultCommitMessage(batch.published);
57
+ const prTitle = options.prTitle ?? defaultPrTitle(batch.published);
58
+ const prBody = options.prBody ?? defaultPrBody(batch.published);
59
+ const prResult = runPublishPr({
60
+ repoRoot,
61
+ published: batch.published,
62
+ message,
63
+ prTitle,
64
+ prBody,
65
+ dryRun: options.dryRun,
66
+ skipPush: options.skipPush,
67
+ });
68
+ if (!prResult.ok) {
69
+ console.error(`${prResult.error.code}: ${prResult.error.message}`);
70
+ if (prResult.error.hint) {
71
+ console.error(prResult.error.hint);
72
+ }
73
+ return 1;
74
+ }
75
+ if (options.dryRun) {
76
+ console.log(formatDryRunPublishPr(batch.published, prResult.data));
77
+ return 0;
78
+ }
79
+ console.log(`branch: ${prResult.data.branch}`);
80
+ if (prResult.data.pushed) {
81
+ console.log(`pushed: origin/${prResult.data.branch}`);
82
+ }
83
+ if (prResult.data.prUrl) {
84
+ console.log(`pr: ${prResult.data.prUrl}`);
85
+ }
86
+ return 0;
87
+ }
88
+ finally {
89
+ db.close();
90
+ }
91
+ }
@@ -0,0 +1,19 @@
1
+ export declare class GitCommandError extends Error {
2
+ readonly command: string;
3
+ constructor(command: string, message: string);
4
+ }
5
+ export declare function commandExists(command: string): boolean;
6
+ export declare function findGitRoot(startDir: string): string | null;
7
+ export declare function gitExec(repoRoot: string, args: string[]): string;
8
+ export declare function toRepoRelativePath(repoRoot: string, absolutePath: string): string;
9
+ export declare function currentBranch(repoRoot: string): string;
10
+ export declare function branchExists(repoRoot: string, branchName: string): boolean;
11
+ export declare function createBranch(repoRoot: string, branchName: string): void;
12
+ export declare function stagePaths(repoRoot: string, relativePaths: string[]): void;
13
+ export declare function commitStaged(repoRoot: string, message: string): string;
14
+ export declare function pushBranch(repoRoot: string, branchName: string): void;
15
+ export declare function createPullRequest(repoRoot: string, options: {
16
+ title: string;
17
+ body: string;
18
+ base?: string;
19
+ }): string;
@@ -0,0 +1,89 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ export class GitCommandError extends Error {
5
+ command;
6
+ constructor(command, message) {
7
+ super(message);
8
+ this.name = 'GitCommandError';
9
+ this.command = command;
10
+ }
11
+ }
12
+ export function commandExists(command) {
13
+ try {
14
+ execFileSync(command, ['--version'], { stdio: 'ignore' });
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ export function findGitRoot(startDir) {
22
+ let current = path.resolve(startDir);
23
+ while (true) {
24
+ if (fs.existsSync(path.join(current, '.git'))) {
25
+ return current;
26
+ }
27
+ const parent = path.dirname(current);
28
+ if (parent === current) {
29
+ return null;
30
+ }
31
+ current = parent;
32
+ }
33
+ }
34
+ export function gitExec(repoRoot, args) {
35
+ try {
36
+ return execFileSync('git', args, {
37
+ cwd: repoRoot,
38
+ encoding: 'utf8',
39
+ stdio: ['ignore', 'pipe', 'pipe'],
40
+ }).trim();
41
+ }
42
+ catch (error) {
43
+ const stderr = error && typeof error === 'object' && 'stderr' in error
44
+ ? String(error.stderr)
45
+ : String(error);
46
+ throw new GitCommandError(`git ${args.join(' ')}`, stderr.trim() || 'git command failed');
47
+ }
48
+ }
49
+ export function toRepoRelativePath(repoRoot, absolutePath) {
50
+ return path.relative(repoRoot, absolutePath).replace(/\\/g, '/');
51
+ }
52
+ export function currentBranch(repoRoot) {
53
+ return gitExec(repoRoot, ['rev-parse', '--abbrev-ref', 'HEAD']);
54
+ }
55
+ export function branchExists(repoRoot, branchName) {
56
+ try {
57
+ gitExec(repoRoot, ['show-ref', '--verify', '--quiet', `refs/heads/${branchName}`]);
58
+ return true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ export function createBranch(repoRoot, branchName) {
65
+ gitExec(repoRoot, ['checkout', '-b', branchName]);
66
+ }
67
+ export function stagePaths(repoRoot, relativePaths) {
68
+ if (relativePaths.length === 0) {
69
+ return;
70
+ }
71
+ gitExec(repoRoot, ['add', '--', ...relativePaths]);
72
+ }
73
+ export function commitStaged(repoRoot, message) {
74
+ return gitExec(repoRoot, ['commit', '-m', message]);
75
+ }
76
+ export function pushBranch(repoRoot, branchName) {
77
+ gitExec(repoRoot, ['push', '-u', 'origin', branchName]);
78
+ }
79
+ export function createPullRequest(repoRoot, options) {
80
+ const args = ['pr', 'create', '--title', options.title, '--body', options.body];
81
+ if (options.base) {
82
+ args.push('--base', options.base);
83
+ }
84
+ return execFileSync('gh', args, {
85
+ cwd: repoRoot,
86
+ encoding: 'utf8',
87
+ stdio: ['ignore', 'pipe', 'pipe'],
88
+ }).trim();
89
+ }
@@ -0,0 +1,33 @@
1
+ import { type PublishedDraft } from '../publish/batch-publish.js';
2
+ export type PublishPrErrorCode = 'ENOENT_REPO' | 'NO_CHANGES' | 'GH_MISSING' | 'GIT_ERROR' | 'PUSH_FAILED';
3
+ export interface PublishPrSuccess {
4
+ branch: string;
5
+ commitMessage: string;
6
+ stagedPaths: string[];
7
+ prUrl?: string;
8
+ pushed: boolean;
9
+ }
10
+ export interface PublishPrError {
11
+ code: PublishPrErrorCode;
12
+ message: string;
13
+ hint?: string;
14
+ }
15
+ export type PublishPrResult = {
16
+ ok: true;
17
+ data: PublishPrSuccess;
18
+ } | {
19
+ ok: false;
20
+ error: PublishPrError;
21
+ };
22
+ export interface PublishPrOptions {
23
+ repoRoot: string;
24
+ published: PublishedDraft[];
25
+ message: string;
26
+ prTitle: string;
27
+ prBody: string;
28
+ dryRun?: boolean;
29
+ skipPush?: boolean;
30
+ baseBranch?: string;
31
+ }
32
+ export declare function runPublishPr(options: PublishPrOptions): PublishPrResult;
33
+ export declare function formatDryRunPublishPr(published: PublishedDraft[], result: PublishPrSuccess): string;
@@ -0,0 +1,121 @@
1
+ import { commandExists, createBranch, createPullRequest, findGitRoot, GitCommandError, pushBranch, stagePaths, commitStaged, } from '../git/git-exec.js';
2
+ import { buildPublishBranchName, relativeDocsPaths, resolveUniqueBranchName, } from '../publish/batch-publish.js';
3
+ export function runPublishPr(options) {
4
+ const gitRoot = findGitRoot(options.repoRoot);
5
+ if (!gitRoot) {
6
+ return {
7
+ ok: false,
8
+ error: {
9
+ code: 'ENOENT_REPO',
10
+ message: 'no git repository found — initialize git first',
11
+ },
12
+ };
13
+ }
14
+ const stagedPaths = relativeDocsPaths(gitRoot, options.published
15
+ .map((item) => item.path)
16
+ .filter((item) => item !== '(dry-run)' && item !== '(unresolved)'));
17
+ if (stagedPaths.length === 0) {
18
+ return {
19
+ ok: false,
20
+ error: { code: 'NO_CHANGES', message: 'no published files to commit' },
21
+ };
22
+ }
23
+ const branch = resolveUniqueBranchName(gitRoot, buildPublishBranchName(options.published));
24
+ if (options.dryRun) {
25
+ return {
26
+ ok: true,
27
+ data: {
28
+ branch,
29
+ commitMessage: options.message,
30
+ stagedPaths,
31
+ pushed: false,
32
+ },
33
+ };
34
+ }
35
+ try {
36
+ createBranch(gitRoot, branch);
37
+ stagePaths(gitRoot, stagedPaths);
38
+ commitStaged(gitRoot, options.message);
39
+ }
40
+ catch (error) {
41
+ const message = error instanceof GitCommandError ? error.message : String(error);
42
+ return {
43
+ ok: false,
44
+ error: { code: 'GIT_ERROR', message, hint: 'Resolve git state and retry publish --pr.' },
45
+ };
46
+ }
47
+ if (options.skipPush) {
48
+ return {
49
+ ok: true,
50
+ data: {
51
+ branch,
52
+ commitMessage: options.message,
53
+ stagedPaths,
54
+ pushed: false,
55
+ },
56
+ };
57
+ }
58
+ if (!commandExists('gh')) {
59
+ return {
60
+ ok: false,
61
+ error: {
62
+ code: 'GH_MISSING',
63
+ message: 'GitHub CLI (gh) is not installed',
64
+ hint: `Branch ${branch} was created locally with commit staged paths. Install gh and run: gh pr create`,
65
+ },
66
+ };
67
+ }
68
+ try {
69
+ pushBranch(gitRoot, branch);
70
+ }
71
+ catch (error) {
72
+ const message = error instanceof GitCommandError ? error.message : String(error);
73
+ return {
74
+ ok: false,
75
+ error: {
76
+ code: 'PUSH_FAILED',
77
+ message,
78
+ hint: `Branch ${branch} exists locally. Configure a remote and push manually.`,
79
+ },
80
+ };
81
+ }
82
+ try {
83
+ const prUrl = createPullRequest(gitRoot, {
84
+ title: options.prTitle,
85
+ body: options.prBody,
86
+ base: options.baseBranch,
87
+ });
88
+ return {
89
+ ok: true,
90
+ data: {
91
+ branch,
92
+ commitMessage: options.message,
93
+ stagedPaths,
94
+ prUrl,
95
+ pushed: true,
96
+ },
97
+ };
98
+ }
99
+ catch (error) {
100
+ const message = error instanceof Error ? error.message : String(error);
101
+ return {
102
+ ok: false,
103
+ error: {
104
+ code: 'GIT_ERROR',
105
+ message,
106
+ hint: `Branch ${branch} was pushed. Create the PR manually with gh pr create.`,
107
+ },
108
+ };
109
+ }
110
+ }
111
+ export function formatDryRunPublishPr(published, result) {
112
+ const lines = [
113
+ 'dry-run: would publish drafts:',
114
+ ...published.map((item) => ` - ${item.slug} (${item.title})`),
115
+ `dry-run: would create branch ${result.branch}`,
116
+ `dry-run: would commit ${result.stagedPaths.length} file(s):`,
117
+ ...result.stagedPaths.map((item) => ` - ${item}`),
118
+ `dry-run: would push and run gh pr create`,
119
+ ];
120
+ return lines.join('\n');
121
+ }
@@ -1,5 +1,7 @@
1
1
  export declare const SLUG_PATTERN: RegExp;
2
2
  export declare function isValidSlug(slug: string): boolean;
3
+ /** Derives a kebab-case slug from a human title. */
4
+ export declare function slugFromTitle(title: string): string;
3
5
  /** Builds a stable slug from a path relative to the docs root. */
4
6
  export declare function slugFromRelativePath(relativePath: string): string;
5
7
  export declare function resolveDocPath(knowledgeRoot: string, typeDir: string, slug: string): string | null;