prd-to-skill 1.0.2 → 1.0.3

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 (3) hide show
  1. package/README.md +5 -5
  2. package/dist/index.js +109 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -39,12 +39,12 @@ Output directories are created automatically if they don't exist.
39
39
 
40
40
  The tool auto-detects your provider from environment variables (checked in this order):
41
41
 
42
- | Provider | Env Var | Default Model |
43
- | --------- | ------------------- | -------------------------- |
42
+ | Provider | Env Var | Default Model |
43
+ | --------- | ------------------- | ---------------------------- |
44
44
  | Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-6-20250217` |
45
- | OpenAI | `OPENAI_API_KEY` | `gpt-5.4` |
46
- | Google | `GOOGLE_API_KEY` | `gemini-2.5-flash` |
47
- | Mistral | `MISTRAL_API_KEY` | `mistral-large-latest` |
45
+ | OpenAI | `OPENAI_API_KEY` | `gpt-5.4` |
46
+ | Google | `GOOGLE_API_KEY` | `gemini-2.5-flash` |
47
+ | Mistral | `MISTRAL_API_KEY` | `mistral-large-latest` |
48
48
 
49
49
  ## Usage
50
50
 
package/dist/index.js CHANGED
@@ -35,6 +35,22 @@ var extractText = async (filePath) => {
35
35
  }
36
36
  return text;
37
37
  };
38
+ var TRUNCATION_PATTERNS = [
39
+ /[a-z,]\s*$/,
40
+ // ends mid-word or after a comma
41
+ /\(\s*e\.g\.\s*,?\s*$/,
42
+ // ends mid-parenthetical like "(e.g.,"
43
+ /:\s*$/,
44
+ // ends with a colon expecting more
45
+ /\b(and|or|the|a|an|to|for|in|of|with|by|from|as|is|are|was|were|that|this|which|when|where|how|but|if|not|on|at|into|about|than|then|such|each|per|via)\s*$/i
46
+ ];
47
+ var detectTruncation = (text) => {
48
+ const lastLine = text.split("\n").filter(Boolean).pop() ?? "";
49
+ const trimmed = lastLine.trim();
50
+ if (!trimmed) return false;
51
+ if (!/[.!?:)\]"']$/.test(trimmed)) return true;
52
+ return TRUNCATION_PATTERNS.some((p) => p.test(trimmed));
53
+ };
38
54
 
39
55
  // src/prompt.ts
40
56
  var BASE_SYSTEM_PROMPT = `You are a technical writer specializing in AI coding assistant instruction files. You will receive the text content of a Product Requirements Document (PRD). Your job is to convert it into an instruction file for an AI coding tool.
@@ -88,14 +104,27 @@ Convert the PRD requirements into ACTIONABLE INSTRUCTIONS for an AI agent. Do NO
88
104
  - Remove filler, marketing language, and non-technical content
89
105
 
90
106
  Output ONLY the complete file. No preamble, no explanation, no code fences wrapping the entire output.`;
91
- var buildMessages = (prdText, name, target, description) => {
92
- const systemPrompt = `${BASE_SYSTEM_PROMPT}
107
+ var buildMessages = (prdText, name, target, description, truncated = false) => {
108
+ let systemPrompt = `${BASE_SYSTEM_PROMPT}
93
109
 
94
110
  ## Output Format (${target.name})
95
111
 
96
112
  You are generating a file for: ${target.description}
97
113
 
98
114
  ${target.formatInstructions}`;
115
+ if (truncated) {
116
+ systemPrompt += `
117
+
118
+ ## Important: Incomplete Document
119
+
120
+ The provided PRD appears to be truncated \u2014 it ends abruptly mid-sentence or mid-section. Handle this as follows:
121
+ - Generate the instruction file based ONLY on the content that is present. Do NOT invent or guess missing requirements.
122
+ - At the very end of the output, add a section:
123
+
124
+ ## Incomplete Sections
125
+
126
+ Note which sections or topics appear to be cut off or missing based on context clues (e.g., a numbered list that stops, a heading with no content, a sentence that ends mid-thought). List them as bullet points so the user knows what to add.`;
127
+ }
99
128
  let userContent = `Here is the PRD content to convert into an AI coding instruction file:
100
129
 
101
130
  <prd>
@@ -383,6 +412,32 @@ var writeSkillFile = async (content, outputPath) => {
383
412
  await writeFile(outputPath, normalized, "utf-8");
384
413
  };
385
414
 
415
+ // src/spinner.ts
416
+ var frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
417
+ var createSpinner = (message) => {
418
+ let i = 0;
419
+ const stream = process.stderr;
420
+ const write = (text) => {
421
+ stream.write(`\r\x1B[K${text}`);
422
+ };
423
+ write(`${frames[0]} ${message}`);
424
+ const timer = setInterval(() => {
425
+ i = (i + 1) % frames.length;
426
+ write(`${frames[i]} ${message}`);
427
+ }, 80);
428
+ return {
429
+ update: (msg) => {
430
+ message = msg;
431
+ write(`${frames[i]} ${message}`);
432
+ },
433
+ stop: (msg) => {
434
+ clearInterval(timer);
435
+ write(`${msg}
436
+ `);
437
+ }
438
+ };
439
+ };
440
+
386
441
  // src/generate.ts
387
442
  var generate = async (options) => {
388
443
  const provider = detectProvider(options.provider);
@@ -395,22 +450,68 @@ var generate = async (options) => {
395
450
  console.error(`Provider: ${provider.name}`);
396
451
  console.error(`Model: ${model}`);
397
452
  console.error(`Target: ${target.name} (${target.description})`);
398
- console.error(`Extracting text from: ${options.filePath}`);
399
453
  }
454
+ const spinner = createSpinner("Extracting text from document...");
400
455
  const prdText = await extractText(options.filePath);
401
- if (options.verbose) {
402
- console.error(`Extracted ${prdText.length} characters`);
456
+ const isTruncated = detectTruncation(prdText);
457
+ if (isTruncated) {
458
+ spinner.stop(
459
+ `\u26A0 Extracted ${prdText.length.toLocaleString()} characters \u2014 document appears to be incomplete or cut off`
460
+ );
461
+ console.error(
462
+ " The document ends abruptly. The generated output will cover only what was provided."
463
+ );
464
+ } else {
465
+ spinner.stop(`\u2713 Extracted ${prdText.length.toLocaleString()} characters`);
403
466
  }
404
- const messages = buildMessages(prdText, name, target, options.description);
405
467
  if (options.verbose) {
406
- console.error("Sending to LLM...");
468
+ console.error(` Source: ${options.filePath}`);
407
469
  }
470
+ const messages = buildMessages(prdText, name, target, options.description, isTruncated);
471
+ const llmSpinner = createSpinner(
472
+ `Generating ${target.name} file via ${provider.name} (${model})...`
473
+ );
474
+ const startTime = Date.now();
475
+ const elapsed = setInterval(() => {
476
+ const secs = Math.floor((Date.now() - startTime) / 1e3);
477
+ llmSpinner.update(
478
+ `Generating ${target.name} file via ${provider.name} (${model})... ${secs}s`
479
+ );
480
+ }, 1e3);
408
481
  const result = await provider.complete(
409
482
  { apiKey, model, maxTokens: options.maxTokens },
410
483
  messages
411
484
  );
485
+ clearInterval(elapsed);
486
+ const totalSecs = ((Date.now() - startTime) / 1e3).toFixed(1);
487
+ const outputTruncated = detectTruncation(result);
488
+ if (outputTruncated) {
489
+ llmSpinner.stop(`\u26A0 Generated in ${totalSecs}s \u2014 output appears cut off`);
490
+ } else {
491
+ llmSpinner.stop(`\u2713 Generated in ${totalSecs}s`);
492
+ }
493
+ const writeSpinner = createSpinner("Writing file...");
412
494
  await writeSkillFile(result, outputPath);
413
- console.log(`${target.name} file written to: ${outputPath}`);
495
+ writeSpinner.stop(`\u2713 Saved to ${outputPath}`);
496
+ if (outputTruncated) {
497
+ const nextMaxTokens = options.maxTokens * 2;
498
+ console.error("");
499
+ console.error(
500
+ "\u26A0 The generated output appears incomplete \u2014 the LLM likely hit the token limit."
501
+ );
502
+ console.error("");
503
+ console.error(" To fix this, try one of the following:");
504
+ console.error("");
505
+ console.error(
506
+ ` 1. Increase max tokens: prd-to-skill ${options.filePath} --max-tokens ${nextMaxTokens}`
507
+ );
508
+ console.error(` 2. Use a higher-capacity model with a larger output window`);
509
+ console.error(
510
+ ` 3. Split your PRD into smaller focused documents and generate a skill for each`
511
+ );
512
+ console.error("");
513
+ console.error(` Current --max-tokens: ${options.maxTokens}`);
514
+ }
414
515
  };
415
516
 
416
517
  // src/help.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prd-to-skill",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Convert PRD documents (PDF/DOCX) into AI coding assistant instruction files",
5
5
  "type": "module",
6
6
  "bin": {