@plures/praxis 1.2.12 → 1.2.41

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 (85) hide show
  1. package/README.md +63 -0
  2. package/dist/browser/{chunk-VOMLVI6V.js → chunk-BBP2F7TT.js} +70 -1
  3. package/dist/browser/{chunk-K377RW4V.js → chunk-FCEH7WMH.js} +1 -1
  4. package/dist/browser/{engine-YJZV4SLD.js → engine-65QDGCAN.js} +1 -1
  5. package/dist/browser/index.d.ts +104 -2
  6. package/dist/browser/index.js +181 -5
  7. package/dist/browser/integrations/svelte.d.ts +2 -2
  8. package/dist/browser/integrations/svelte.js +2 -2
  9. package/dist/browser/{reactive-engine.svelte-9aS0kTa8.d.ts → reactive-engine.svelte-Cqd8Mod2.d.ts} +56 -1
  10. package/dist/node/{chunk-PRPQO6R5.js → chunk-32YFEEML.js} +1 -1
  11. package/dist/node/{chunk-VOMLVI6V.js → chunk-BBP2F7TT.js} +70 -1
  12. package/dist/node/{chunk-5RH7UAQC.js → chunk-PTH6MD6P.js} +1 -0
  13. package/dist/node/cli/index.cjs +1553 -839
  14. package/dist/node/cli/index.js +39 -2
  15. package/dist/node/cloud/index.d.cts +1 -1
  16. package/dist/node/cloud/index.d.ts +1 -1
  17. package/dist/node/components/index.d.cts +2 -2
  18. package/dist/node/components/index.d.ts +2 -2
  19. package/dist/node/conversations-KQBXTP3N.js +596 -0
  20. package/dist/node/{engine-2DQBKBJC.js → engine-7CXQV6RC.js} +1 -1
  21. package/dist/node/index.cjs +408 -3
  22. package/dist/node/index.d.cts +308 -7
  23. package/dist/node/index.d.ts +308 -7
  24. package/dist/node/index.js +336 -6
  25. package/dist/node/integrations/svelte.cjs +70 -1
  26. package/dist/node/integrations/svelte.d.cts +3 -3
  27. package/dist/node/integrations/svelte.d.ts +3 -3
  28. package/dist/node/integrations/svelte.js +2 -2
  29. package/dist/node/{protocol-Qek7ebBl.d.ts → protocol-BocKczNv.d.cts} +1 -1
  30. package/dist/node/{protocol-Qek7ebBl.d.cts → protocol-BocKczNv.d.ts} +1 -1
  31. package/dist/node/{reactive-engine.svelte-CRNqHlbv.d.ts → reactive-engine.svelte-CGe8SpVE.d.cts} +57 -2
  32. package/dist/node/{reactive-engine.svelte-BFIZfawz.d.cts → reactive-engine.svelte-D-xTDxT5.d.ts} +57 -2
  33. package/dist/node/{terminal-adapter-B-UK_Vdz.d.ts → terminal-adapter-CvIvgTo4.d.ts} +1 -1
  34. package/dist/node/{terminal-adapter-BQSIF5bf.d.cts → terminal-adapter-Db-snPJ3.d.cts} +1 -1
  35. package/dist/node/{validate-CNHUULQE.js → validate-EN3M4FUR.js} +1 -1
  36. package/dist/node/{verify-KLJRXVJS.js → verify-7VZRP2WS.js} +2 -2
  37. package/docs/BOT_UPDATE_POLICY.md +125 -0
  38. package/docs/DOGFOODING_CHECKLIST.md +254 -0
  39. package/docs/DOGFOODING_INDEX.md +169 -0
  40. package/docs/DOGFOODING_QUICK_START.md +140 -0
  41. package/docs/KNO_ENG_EXTRACTION_PLAN.md +577 -0
  42. package/docs/PLURES_TOOLS_INVENTORY.md +170 -0
  43. package/docs/README.md +12 -0
  44. package/docs/TESTING_BOT_WORKFLOWS.md +154 -0
  45. package/docs/conversations/INTEGRATION_POINTS.md +719 -0
  46. package/docs/conversations/README.md +168 -0
  47. package/docs/core/extending-praxis-core.md +604 -0
  48. package/docs/core/praxis-core-api.md +385 -0
  49. package/docs/decision-ledger/contract-index.json +2 -2
  50. package/docs/decision-ledger/decisions/2026-02-01-monorepo-organization.md +130 -0
  51. package/docs/examples/DOGFOODING_WORKFLOW_EXAMPLE.md +295 -0
  52. package/docs/examples/README.md +41 -0
  53. package/docs/workflows/pr-overlap-guard.md +50 -0
  54. package/package.json +7 -2
  55. package/src/__tests__/chronicle.test.ts +512 -0
  56. package/src/__tests__/conversations.test.ts +312 -0
  57. package/src/__tests__/edge-cases.test.ts +1 -1
  58. package/src/__tests__/engine-dx.test.ts +355 -0
  59. package/src/cli/commands/conversations.ts +252 -0
  60. package/src/cli/index.ts +73 -0
  61. package/src/conversations/README.md +230 -0
  62. package/src/conversations/candidate.schema.json +123 -0
  63. package/src/conversations/candidates.ts +114 -0
  64. package/src/conversations/capture.ts +56 -0
  65. package/src/conversations/classify.ts +110 -0
  66. package/src/conversations/conversation.schema.json +106 -0
  67. package/src/conversations/emitters/fs.ts +65 -0
  68. package/src/conversations/emitters/github.ts +115 -0
  69. package/src/conversations/gate.ts +102 -0
  70. package/src/conversations/index.ts +28 -0
  71. package/src/conversations/normalize.ts +51 -0
  72. package/src/conversations/redact.ts +57 -0
  73. package/src/conversations/types.ts +96 -0
  74. package/src/core/chronicle/chronicle.ts +227 -0
  75. package/src/core/chronicle/context.ts +80 -0
  76. package/src/core/chronicle/index.ts +53 -0
  77. package/src/core/chronicle/mcp.ts +135 -0
  78. package/src/core/chronicle/types.ts +61 -0
  79. package/src/core/engine.ts +99 -1
  80. package/src/core/pluresdb/index.ts +22 -0
  81. package/src/core/pluresdb/store.ts +162 -5
  82. package/src/core/rules.ts +12 -0
  83. package/src/dsl/index.ts +6 -0
  84. package/src/index.ts +18 -0
  85. package/src/integrations/pluresdb.ts +22 -0
@@ -689,7 +689,7 @@ cloudCmd.command("usage").description("View cloud usage metrics").action(async (
689
689
  });
690
690
  program.command("verify <type>").description("Verify project implementation (e.g., implementation)").option("-d, --detailed", "Show detailed analysis").action(async (type, options) => {
691
691
  try {
692
- const { verify } = await import("../verify-KLJRXVJS.js");
692
+ const { verify } = await import("../verify-7VZRP2WS.js");
693
693
  await verify(type, options);
694
694
  } catch (error) {
695
695
  console.error("Error verifying:", error);
@@ -698,7 +698,7 @@ program.command("verify <type>").description("Verify project implementation (e.g
698
698
  });
699
699
  program.command("validate").description("Validate contract coverage for rules and constraints").option("--output <format>", "Output format (console, json, sarif)", "console").option("--strict", "Exit with error if contracts are missing", false).option("--registry <path>", "Path to registry module").option("--tests", "Check for tests for each rule/constraint", true).option("--spec", "Check for specs for each rule/constraint", true).option("--emit-facts", "Emit ContractMissing facts JSON payload", false).option("--gap-output <file>", "Write contract-gap payload to file").option("--ledger <dir>", "Write logic ledger snapshots to directory").option("--author <name>", "Author name for ledger entries", "system").action(async (options) => {
700
700
  try {
701
- const { validateCommand } = await import("../validate-CNHUULQE.js");
701
+ const { validateCommand } = await import("../validate-EN3M4FUR.js");
702
702
  await validateCommand(options);
703
703
  } catch (error) {
704
704
  console.error("Error validating contracts:", error);
@@ -714,4 +714,41 @@ program.command("reverse").description("Reverse engineer contracts from existing
714
714
  process.exit(1);
715
715
  }
716
716
  });
717
+ var conversationsCmd = program.command("conversations").description("Conversation ingestion subsystem (capture -> redact -> normalize -> classify -> emit)");
718
+ conversationsCmd.command("capture").description("Capture a conversation from input").option("-i, --input <file>", "Input conversation file").option("-o, --output <file>", "Output file for captured conversation").action(async (options) => {
719
+ try {
720
+ const { captureCommand } = await import("../conversations-KQBXTP3N.js");
721
+ await captureCommand(options);
722
+ } catch (error) {
723
+ console.error("Error capturing conversation:", error);
724
+ process.exit(1);
725
+ }
726
+ });
727
+ conversationsCmd.command("push").description("Process conversation through pipeline (redact -> normalize)").requiredOption("-i, --input <file>", "Input conversation file").option("-o, --output <file>", "Output file for processed conversation").option("--skip-redaction", "Skip PII redaction", false).option("--skip-normalization", "Skip normalization", false).action(async (options) => {
728
+ try {
729
+ const { pushCommand } = await import("../conversations-KQBXTP3N.js");
730
+ await pushCommand(options);
731
+ } catch (error) {
732
+ console.error("Error processing conversation:", error);
733
+ process.exit(1);
734
+ }
735
+ });
736
+ conversationsCmd.command("classify").description("Classify conversation and generate candidate").requiredOption("-i, --input <file>", "Input conversation file").option("-o, --output <file>", "Output file for candidate").action(async (options) => {
737
+ try {
738
+ const { classifyCommand } = await import("../conversations-KQBXTP3N.js");
739
+ await classifyCommand(options);
740
+ } catch (error) {
741
+ console.error("Error classifying conversation:", error);
742
+ process.exit(1);
743
+ }
744
+ });
745
+ conversationsCmd.command("emit").description("Emit candidate to destination (fs or github)").requiredOption("-i, --input <file>", "Input candidate file").requiredOption("-e, --emitter <type>", "Emitter type (fs, github)").option("--output-dir <dir>", "Output directory (for fs emitter)", "./output/candidates").option("--owner <owner>", "GitHub repository owner (for github emitter)").option("--repo <repo>", "GitHub repository name (for github emitter)").option("--token <token>", "GitHub token (for github emitter)").option("--dry-run", "Dry run mode (no actual emission)", false).option("--commit-intent", "REQUIRED: Explicit commit intent for GitHub emission", false).action(async (options) => {
746
+ try {
747
+ const { emitCommand } = await import("../conversations-KQBXTP3N.js");
748
+ await emitCommand(options);
749
+ } catch (error) {
750
+ console.error("Error emitting candidate:", error);
751
+ process.exit(1);
752
+ }
753
+ });
717
754
  program.parse();
@@ -1,4 +1,4 @@
1
- import { b as PraxisFact, a as PraxisEvent } from '../protocol-Qek7ebBl.cjs';
1
+ import { b as PraxisFact, a as PraxisEvent } from '../protocol-BocKczNv.cjs';
2
2
 
3
3
  /**
4
4
  * Cloud Relay Types
@@ -1,4 +1,4 @@
1
- import { b as PraxisFact, a as PraxisEvent } from '../protocol-Qek7ebBl.js';
1
+ import { b as PraxisFact, a as PraxisEvent } from '../protocol-BocKczNv.js';
2
2
 
3
3
  /**
4
4
  * Cloud Relay Types
@@ -1,5 +1,5 @@
1
- import { T as TerminalAdapter } from '../terminal-adapter-BQSIF5bf.cjs';
2
- export { c as createTerminalAdapter } from '../terminal-adapter-BQSIF5bf.cjs';
1
+ import { T as TerminalAdapter } from '../terminal-adapter-Db-snPJ3.cjs';
2
+ export { c as createTerminalAdapter } from '../terminal-adapter-Db-snPJ3.cjs';
3
3
  import '../schema.cjs';
4
4
  import '@plures/pluresdb/local-first';
5
5
 
@@ -1,5 +1,5 @@
1
- import { T as TerminalAdapter } from '../terminal-adapter-B-UK_Vdz.js';
2
- export { c as createTerminalAdapter } from '../terminal-adapter-B-UK_Vdz.js';
1
+ import { T as TerminalAdapter } from '../terminal-adapter-CvIvgTo4.js';
2
+ export { c as createTerminalAdapter } from '../terminal-adapter-CvIvgTo4.js';
3
3
  import '../schema.js';
4
4
  import '@plures/pluresdb/local-first';
5
5
 
@@ -0,0 +1,596 @@
1
+ import "./chunk-QGM4M3NI.js";
2
+
3
+ // src/cli/commands/conversations.ts
4
+ import { promises as fs2 } from "fs";
5
+
6
+ // src/conversations/capture.ts
7
+ import { randomUUID } from "crypto";
8
+ function captureConversation(input) {
9
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10
+ return {
11
+ id: randomUUID(),
12
+ timestamp: now,
13
+ turns: input.turns.map((turn) => ({
14
+ role: turn.role,
15
+ content: turn.content,
16
+ timestamp: turn.timestamp || now,
17
+ metadata: {}
18
+ })),
19
+ metadata: {
20
+ source: input.metadata?.source || "unknown",
21
+ userId: input.metadata?.userId,
22
+ sessionId: input.metadata?.sessionId,
23
+ tags: input.metadata?.tags || []
24
+ },
25
+ redacted: false,
26
+ normalized: false,
27
+ classified: false
28
+ };
29
+ }
30
+ function loadConversation(json) {
31
+ const data = JSON.parse(json);
32
+ if (!data.id || !data.timestamp || !data.turns || !data.metadata) {
33
+ throw new Error("Invalid conversation JSON: missing required fields");
34
+ }
35
+ return data;
36
+ }
37
+
38
+ // src/conversations/redact.ts
39
+ var PII_PATTERNS = [
40
+ // Email addresses
41
+ { pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, replacement: "[EMAIL_REDACTED]" },
42
+ // Phone numbers (simple patterns)
43
+ { pattern: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, replacement: "[PHONE_REDACTED]" },
44
+ // Credit card numbers (basic pattern - intentionally broad for safety)
45
+ { pattern: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, replacement: "[CARD_REDACTED]" },
46
+ // SSN pattern
47
+ { pattern: /\b\d{3}-\d{2}-\d{4}\b/g, replacement: "[SSN_REDACTED]" },
48
+ // IP addresses (basic pattern - may match invalid IPs like 999.999.999.999)
49
+ { pattern: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, replacement: "[IP_REDACTED]" }
50
+ ];
51
+ function redactText(text) {
52
+ let redacted = text;
53
+ for (const { pattern, replacement } of PII_PATTERNS) {
54
+ redacted = redacted.replace(pattern, replacement);
55
+ }
56
+ return redacted;
57
+ }
58
+ function redactConversation(conversation) {
59
+ return {
60
+ ...conversation,
61
+ turns: conversation.turns.map((turn) => ({
62
+ ...turn,
63
+ content: redactText(turn.content)
64
+ })),
65
+ metadata: {
66
+ ...conversation.metadata,
67
+ // Redact userId if it looks like an email
68
+ userId: conversation.metadata.userId ? redactText(conversation.metadata.userId) : void 0
69
+ },
70
+ redacted: true
71
+ };
72
+ }
73
+
74
+ // src/conversations/normalize.ts
75
+ function normalizeWhitespace(text) {
76
+ return text.replace(/\r\n/g, "\n").replace(/\t/g, " ").replace(/\n{3,}/g, "\n\n").trim();
77
+ }
78
+ function normalizeCodeBlocks(text) {
79
+ return text.replace(/```(\w+)?\n/g, (_match, lang) => {
80
+ return lang ? `\`\`\`${lang.toLowerCase()}
81
+ ` : "```\n";
82
+ });
83
+ }
84
+ function normalizeTurn(content) {
85
+ let normalized = content;
86
+ normalized = normalizeWhitespace(normalized);
87
+ normalized = normalizeCodeBlocks(normalized);
88
+ return normalized;
89
+ }
90
+ function normalizeConversation(conversation) {
91
+ return {
92
+ ...conversation,
93
+ turns: conversation.turns.map((turn) => ({
94
+ ...turn,
95
+ content: normalizeTurn(turn.content)
96
+ })),
97
+ normalized: true
98
+ };
99
+ }
100
+
101
+ // src/conversations/classify.ts
102
+ var CLASSIFICATION_RULES = [
103
+ {
104
+ category: "bug-report",
105
+ keywords: ["bug", "error", "crash", "broken", "issue", "problem", "fail", "exception"],
106
+ weight: 1
107
+ },
108
+ {
109
+ category: "feature-request",
110
+ keywords: ["feature", "enhancement", "add", "support", "implement", "would like", "could you"],
111
+ weight: 1
112
+ },
113
+ {
114
+ category: "question",
115
+ keywords: ["how", "what", "why", "when", "where", "?", "help", "question"],
116
+ weight: 0.8
117
+ },
118
+ {
119
+ category: "documentation",
120
+ keywords: ["docs", "documentation", "readme", "guide", "tutorial", "example"],
121
+ weight: 0.9
122
+ },
123
+ {
124
+ category: "performance",
125
+ keywords: ["slow", "performance", "optimize", "speed", "memory", "cpu"],
126
+ weight: 1
127
+ }
128
+ ];
129
+ function extractKeywords(text) {
130
+ return text.toLowerCase().split(/\s+/).map((word) => word.replace(/[^\w]/g, "")).filter((word) => word.length > 2);
131
+ }
132
+ function calculateScores(conversation) {
133
+ const scores = {};
134
+ const allContent = conversation.turns.map((t) => t.content).join(" ");
135
+ const keywords = extractKeywords(allContent);
136
+ const keywordSet = new Set(keywords);
137
+ for (const rule of CLASSIFICATION_RULES) {
138
+ let matchCount = 0;
139
+ for (const keyword of rule.keywords) {
140
+ if (keywordSet.has(keyword.toLowerCase())) {
141
+ matchCount++;
142
+ }
143
+ }
144
+ if (matchCount > 0) {
145
+ scores[rule.category] = matchCount / rule.keywords.length * rule.weight;
146
+ }
147
+ }
148
+ return scores;
149
+ }
150
+ function classifyConversation(conversation) {
151
+ const scores = calculateScores(conversation);
152
+ let bestCategory = "unknown";
153
+ let bestScore = 0;
154
+ for (const [category, score] of Object.entries(scores)) {
155
+ if (score > bestScore) {
156
+ bestScore = score;
157
+ bestCategory = category;
158
+ }
159
+ }
160
+ const classification = {
161
+ category: bestCategory,
162
+ confidence: Math.min(bestScore, 1),
163
+ tags: Object.entries(scores).filter(([_, score]) => score > 0.3).map(([category]) => category)
164
+ };
165
+ return {
166
+ ...conversation,
167
+ classification,
168
+ classified: true
169
+ };
170
+ }
171
+
172
+ // src/conversations/candidates.ts
173
+ import { randomUUID as randomUUID2 } from "crypto";
174
+ function generateCandidate(conversation) {
175
+ if (!conversation.classified || !conversation.classification) {
176
+ throw new Error("Conversation must be classified before generating candidates");
177
+ }
178
+ const { classification } = conversation;
179
+ let type = "documentation";
180
+ if (classification.category === "bug-report") {
181
+ type = "github-issue";
182
+ } else if (classification.category === "feature-request") {
183
+ type = "github-issue";
184
+ } else if (classification.category === "documentation") {
185
+ type = "documentation";
186
+ }
187
+ const firstUserTurn = conversation.turns.find((t) => t.role === "user");
188
+ if (!firstUserTurn) {
189
+ return null;
190
+ }
191
+ const title = generateTitle(firstUserTurn.content, classification.category);
192
+ const body = generateBody(conversation);
193
+ let priority = "medium";
194
+ if (classification.category === "bug-report" && (classification.confidence ?? 0) > 0.7) {
195
+ priority = "high";
196
+ }
197
+ const labels = [...new Set([classification.category, ...classification.tags || []].filter((label) => label !== void 0))];
198
+ return {
199
+ id: randomUUID2(),
200
+ conversationId: conversation.id,
201
+ type,
202
+ title,
203
+ body,
204
+ metadata: {
205
+ priority,
206
+ labels,
207
+ source: {
208
+ conversationId: conversation.id,
209
+ timestamp: conversation.timestamp
210
+ }
211
+ },
212
+ emitted: false
213
+ };
214
+ }
215
+ function generateTitle(content, category) {
216
+ const firstLine = content.split("\n")[0].trim();
217
+ const firstSentence = firstLine.split(/[.!?]/)[0].trim();
218
+ let title = firstSentence.substring(0, 80);
219
+ if (firstSentence.length > 80) {
220
+ title += "...";
221
+ }
222
+ if (category && category !== "unknown") {
223
+ const prefix = category.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
224
+ title = `[${prefix}] ${title}`;
225
+ }
226
+ return title;
227
+ }
228
+ function generateBody(conversation) {
229
+ const parts = [];
230
+ parts.push("## Conversation Summary\n");
231
+ for (const turn of conversation.turns) {
232
+ const roleName = turn.role.charAt(0).toUpperCase() + turn.role.slice(1);
233
+ parts.push(`**${roleName}:**
234
+ ${turn.content}
235
+ `);
236
+ }
237
+ if (conversation.classification) {
238
+ parts.push(`
239
+ ## Classification
240
+ `);
241
+ parts.push(`- Category: ${conversation.classification.category}`);
242
+ parts.push(`- Confidence: ${(conversation.classification.confidence || 0).toFixed(2)}`);
243
+ if (conversation.classification.tags && conversation.classification.tags.length > 0) {
244
+ parts.push(`- Tags: ${conversation.classification.tags.join(", ")}`);
245
+ }
246
+ }
247
+ parts.push(`
248
+ ---
249
+ *Generated from conversation ${conversation.id}*`);
250
+ return parts.join("\n");
251
+ }
252
+
253
+ // src/conversations/gate.ts
254
+ function gateMinimumLength(candidate) {
255
+ const minLength = 50;
256
+ const passed = candidate.body.length >= minLength;
257
+ return {
258
+ name: "minimum-length",
259
+ passed,
260
+ message: passed ? "Content meets minimum length requirement" : `Content too short (${candidate.body.length} < ${minLength} chars)`
261
+ };
262
+ }
263
+ function gateValidTitle(candidate) {
264
+ const passed = candidate.title.length > 10 && candidate.title.length < 200;
265
+ return {
266
+ name: "valid-title",
267
+ passed,
268
+ message: passed ? "Title is valid" : "Title length must be between 10 and 200 characters"
269
+ };
270
+ }
271
+ function gateNotDuplicate(_candidate) {
272
+ return {
273
+ name: "not-duplicate",
274
+ passed: true,
275
+ message: "Duplicate check passed (stub)"
276
+ };
277
+ }
278
+ function gateHasMetadata(candidate) {
279
+ const hasLabels = !!(candidate.metadata.labels && candidate.metadata.labels.length > 0);
280
+ const hasPriority = !!candidate.metadata.priority;
281
+ const passed = hasLabels && hasPriority;
282
+ return {
283
+ name: "has-metadata",
284
+ passed,
285
+ message: passed ? "Candidate has required metadata" : "Missing labels or priority in metadata"
286
+ };
287
+ }
288
+ function applyGates(candidate) {
289
+ const gates = [
290
+ gateMinimumLength(candidate),
291
+ gateValidTitle(candidate),
292
+ gateNotDuplicate(candidate),
293
+ gateHasMetadata(candidate)
294
+ ];
295
+ const allPassed = gates.every((g) => g.passed);
296
+ const failedGates = gates.filter((g) => !g.passed);
297
+ const gateStatus = {
298
+ passed: allPassed,
299
+ reason: allPassed ? "All gates passed" : `Failed gates: ${failedGates.map((g) => g.name).join(", ")}`,
300
+ gates
301
+ };
302
+ return {
303
+ ...candidate,
304
+ gateStatus
305
+ };
306
+ }
307
+ function candidatePassed(candidate) {
308
+ return candidate.gateStatus?.passed || false;
309
+ }
310
+
311
+ // src/conversations/emitters/fs.ts
312
+ import { promises as fs } from "fs";
313
+ import path from "path";
314
+ async function emitToFS(candidate, options) {
315
+ const { outputDir, dryRun = false } = options;
316
+ if (dryRun) {
317
+ console.log(`[DRY RUN] Would emit candidate ${candidate.id} to ${outputDir}`);
318
+ return {
319
+ ...candidate,
320
+ emitted: true,
321
+ emissionResult: {
322
+ success: true,
323
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
324
+ externalId: `fs://${outputDir}/${candidate.id}.json`
325
+ }
326
+ };
327
+ }
328
+ try {
329
+ await fs.mkdir(outputDir, { recursive: true });
330
+ const filename = `${candidate.id}.json`;
331
+ const filepath = path.join(outputDir, filename);
332
+ await fs.writeFile(filepath, JSON.stringify(candidate, null, 2), "utf-8");
333
+ const result = {
334
+ success: true,
335
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
336
+ externalId: `fs://${filepath}`
337
+ };
338
+ return {
339
+ ...candidate,
340
+ emitted: true,
341
+ emissionResult: result
342
+ };
343
+ } catch (error) {
344
+ const result = {
345
+ success: false,
346
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
347
+ error: error instanceof Error ? error.message : "Unknown error"
348
+ };
349
+ return {
350
+ ...candidate,
351
+ emitted: false,
352
+ emissionResult: result
353
+ };
354
+ }
355
+ }
356
+
357
+ // src/conversations/emitters/github.ts
358
+ async function emitToGitHub(candidate, options) {
359
+ const { owner, repo, token, dryRun = false, commitIntent = false } = options;
360
+ if (!commitIntent) {
361
+ const result = {
362
+ success: false,
363
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
364
+ error: "GATE FAILURE: commit_intent must be explicitly set to true for GitHub emission"
365
+ };
366
+ console.error(`[GATE BLOCKED] ${result.error}`);
367
+ return {
368
+ ...candidate,
369
+ emitted: false,
370
+ emissionResult: result
371
+ };
372
+ }
373
+ if (dryRun) {
374
+ console.log(`[DRY RUN] Would create GitHub issue in ${owner}/${repo}`);
375
+ console.log(`Title: ${candidate.title}`);
376
+ console.log(`Labels: ${candidate.metadata.labels?.join(", ") || "none"}`);
377
+ return {
378
+ ...candidate,
379
+ emitted: true,
380
+ emissionResult: {
381
+ success: true,
382
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
383
+ externalId: `github://${owner}/${repo}/issues/DRY_RUN`
384
+ }
385
+ };
386
+ }
387
+ try {
388
+ if (!owner || !repo) {
389
+ throw new Error("GitHub owner and repo are required");
390
+ }
391
+ if (!token) {
392
+ throw new Error("GitHub token is required for emission");
393
+ }
394
+ const issueData = {
395
+ title: candidate.title,
396
+ body: candidate.body,
397
+ labels: candidate.metadata.labels || [],
398
+ assignees: candidate.metadata.assignees || []
399
+ };
400
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues`, {
401
+ method: "POST",
402
+ headers: {
403
+ "Authorization": `token ${token}`,
404
+ "Accept": "application/vnd.github.v3+json",
405
+ "Content-Type": "application/json"
406
+ },
407
+ body: JSON.stringify(issueData)
408
+ });
409
+ if (!response.ok) {
410
+ const errorData = await response.json();
411
+ throw new Error(`GitHub API error: ${errorData.message || response.statusText}`);
412
+ }
413
+ const issue = await response.json();
414
+ const result = {
415
+ success: true,
416
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
417
+ externalId: `github://${owner}/${repo}/issues/${issue.number}`
418
+ };
419
+ console.log(`\u2713 Created GitHub issue #${issue.number}: ${issue.html_url}`);
420
+ return {
421
+ ...candidate,
422
+ emitted: true,
423
+ emissionResult: result
424
+ };
425
+ } catch (error) {
426
+ const result = {
427
+ success: false,
428
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
429
+ error: error instanceof Error ? error.message : "Unknown error"
430
+ };
431
+ console.error(`\u2717 Failed to create GitHub issue: ${result.error}`);
432
+ return {
433
+ ...candidate,
434
+ emitted: false,
435
+ emissionResult: result
436
+ };
437
+ }
438
+ }
439
+
440
+ // src/cli/commands/conversations.ts
441
+ async function captureCommand(options) {
442
+ console.log("\u{1F4DD} Capturing conversation...");
443
+ let conversation;
444
+ if (options.input) {
445
+ const content = await fs2.readFile(options.input, "utf-8");
446
+ conversation = loadConversation(content);
447
+ console.log(`\u2713 Loaded conversation from ${options.input}`);
448
+ } else {
449
+ conversation = captureConversation({
450
+ turns: [
451
+ { role: "user", content: "Hello, I have a question about the feature" },
452
+ { role: "assistant", content: "Sure, I'd be happy to help!" }
453
+ ],
454
+ metadata: {
455
+ source: "cli"
456
+ }
457
+ });
458
+ console.log("\u2713 Created sample conversation");
459
+ }
460
+ if (options.output) {
461
+ await fs2.writeFile(
462
+ options.output,
463
+ JSON.stringify(conversation, null, 2),
464
+ "utf-8"
465
+ );
466
+ console.log(`\u2713 Saved to ${options.output}`);
467
+ } else {
468
+ console.log(JSON.stringify(conversation, null, 2));
469
+ }
470
+ }
471
+ async function pushCommand(options) {
472
+ console.log("\u{1F504} Processing conversation through pipeline...");
473
+ const content = await fs2.readFile(options.input, "utf-8");
474
+ let conversation = loadConversation(content);
475
+ console.log(`\u2713 Loaded conversation ${conversation.id}`);
476
+ if (!options.skipRedaction) {
477
+ conversation = redactConversation(conversation);
478
+ console.log("\u2713 Redacted PII");
479
+ }
480
+ if (!options.skipNormalization) {
481
+ conversation = normalizeConversation(conversation);
482
+ console.log("\u2713 Normalized content");
483
+ }
484
+ if (options.output) {
485
+ await fs2.writeFile(
486
+ options.output,
487
+ JSON.stringify(conversation, null, 2),
488
+ "utf-8"
489
+ );
490
+ console.log(`\u2713 Saved to ${options.output}`);
491
+ } else {
492
+ console.log(JSON.stringify(conversation, null, 2));
493
+ }
494
+ }
495
+ async function classifyCommand(options) {
496
+ console.log("\u{1F3F7}\uFE0F Classifying conversation...");
497
+ const content = await fs2.readFile(options.input, "utf-8");
498
+ let conversation = loadConversation(content);
499
+ console.log(`\u2713 Loaded conversation ${conversation.id}`);
500
+ if (!conversation.redacted) {
501
+ conversation = redactConversation(conversation);
502
+ console.log("\u2713 Applied redaction");
503
+ }
504
+ if (!conversation.normalized) {
505
+ conversation = normalizeConversation(conversation);
506
+ console.log("\u2713 Applied normalization");
507
+ }
508
+ conversation = classifyConversation(conversation);
509
+ console.log(`\u2713 Classified as: ${conversation.classification?.category} (confidence: ${conversation.classification?.confidence?.toFixed(2)})`);
510
+ const candidate = generateCandidate(conversation);
511
+ if (!candidate) {
512
+ console.error("\u2717 Failed to generate candidate");
513
+ process.exit(1);
514
+ }
515
+ console.log(`\u2713 Generated candidate: ${candidate.title}`);
516
+ const gatedCandidate = applyGates(candidate);
517
+ console.log(`
518
+ \u{1F4CB} Gate Results:`);
519
+ for (const gate of gatedCandidate.gateStatus?.gates || []) {
520
+ const icon = gate.passed ? "\u2713" : "\u2717";
521
+ console.log(` ${icon} ${gate.name}: ${gate.message}`);
522
+ }
523
+ const passed = candidatePassed(gatedCandidate);
524
+ console.log(`
525
+ ${passed ? "\u2713 All gates passed" : "\u2717 Some gates failed"}`);
526
+ if (options.output) {
527
+ await fs2.writeFile(
528
+ options.output,
529
+ JSON.stringify(gatedCandidate, null, 2),
530
+ "utf-8"
531
+ );
532
+ console.log(`\u2713 Saved candidate to ${options.output}`);
533
+ } else {
534
+ console.log("\n" + JSON.stringify(gatedCandidate, null, 2));
535
+ }
536
+ }
537
+ async function emitCommand(options) {
538
+ console.log("\u{1F4E4} Emitting candidate...");
539
+ const content = await fs2.readFile(options.input, "utf-8");
540
+ const candidate = JSON.parse(content);
541
+ console.log(`\u2713 Loaded candidate ${candidate.id}`);
542
+ if (!candidatePassed(candidate)) {
543
+ console.error("\u2717 Candidate did not pass gates, refusing to emit");
544
+ console.error(` Reason: ${candidate.gateStatus?.reason}`);
545
+ process.exit(1);
546
+ }
547
+ let result;
548
+ if (options.emitter === "fs") {
549
+ const outputDir = options.outputDir || "./output/candidates";
550
+ result = await emitToFS(candidate, {
551
+ outputDir,
552
+ dryRun: options.dryRun
553
+ });
554
+ if (result.emissionResult?.success) {
555
+ console.log(`\u2713 Emitted to filesystem: ${result.emissionResult.externalId}`);
556
+ } else {
557
+ console.error(`\u2717 Emission failed: ${result.emissionResult?.error}`);
558
+ process.exit(1);
559
+ }
560
+ } else if (options.emitter === "github") {
561
+ if (!options.owner || !options.repo) {
562
+ console.error("\u2717 GitHub emitter requires --owner and --repo");
563
+ process.exit(1);
564
+ }
565
+ if (!options.commitIntent) {
566
+ console.error("");
567
+ console.error("\u26D4 GATE BLOCKED: commit_intent=false");
568
+ console.error("");
569
+ console.error("The GitHub emitter is HARD GATED by the --commit-intent flag.");
570
+ console.error("This prevents accidental issue creation.");
571
+ console.error("");
572
+ console.error("To emit to GitHub, add: --commit-intent");
573
+ console.error("");
574
+ process.exit(1);
575
+ }
576
+ result = await emitToGitHub(candidate, {
577
+ owner: options.owner,
578
+ repo: options.repo,
579
+ token: options.token,
580
+ dryRun: options.dryRun,
581
+ commitIntent: options.commitIntent
582
+ });
583
+ if (result.emissionResult?.success) {
584
+ console.log(`\u2713 Emitted to GitHub: ${result.emissionResult.externalId}`);
585
+ } else {
586
+ console.error(`\u2717 Emission failed: ${result.emissionResult?.error}`);
587
+ process.exit(1);
588
+ }
589
+ }
590
+ }
591
+ export {
592
+ captureCommand,
593
+ classifyCommand,
594
+ emitCommand,
595
+ pushCommand
596
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  LogicEngine,
3
3
  createPraxisEngine
4
- } from "./chunk-VOMLVI6V.js";
4
+ } from "./chunk-BBP2F7TT.js";
5
5
  import "./chunk-QGM4M3NI.js";
6
6
  export {
7
7
  LogicEngine,