tor-to-proposal 2.1.1 → 2.1.4

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.
package/bin/bid-pack.mjs CHANGED
@@ -139,6 +139,29 @@ function buildQuestions() {
139
139
  return { generatedAt: new Date().toISOString(), dir: dir, openCount: items.length, items };
140
140
  }
141
141
 
142
+ // Per-question "how to answer" hint, shown in the wizard prompt and the
143
+ // questions table so users know the accepted format before replying.
144
+ function answerFormat(item) {
145
+ switch (item.id) {
146
+ case 'Q-PRICING-CONFIRM': return 'type yes to use the saved rates — or new numbers like: base 420 loading 0.25 contingency 0.1';
147
+ case 'Q-PRICING-BASE': return 'a number, e.g. 400';
148
+ case 'Q-PRICING-LOADING': return 'a decimal, e.g. 0.25 (= 25%)';
149
+ case 'Q-PRICING-CONTINGENCY': return 'a decimal, e.g. 0.10 (= 10%)';
150
+ case 'Q-PRICING-CURRENCY': return 'a currency code, e.g. USD';
151
+ case 'Q-EFFORT-DAYS': return 'a number of person-days, e.g. 25';
152
+ case 'Q-AVAILABILITY': return 'a date, e.g. 1 November 2026';
153
+ case 'Q-VALIDITY': return 'a phrase, e.g. 90 days from submission';
154
+ case 'Q-FILL-CONSULTANT': return 'the consultant name as it should appear in the document';
155
+ case 'Q-FILL-REIMBURSABLES': return 'a sentence about which costs are reimbursable';
156
+ case 'Q-FILL-TAXES': return 'a sentence about your tax status';
157
+ }
158
+ if (item.id.startsWith('Q-CVE-')) return 'the exact true wording that supports the line — or DELETE to remove the line';
159
+ if (item.type === 'number') return `a number${item.unit ? ` in ${item.unit}` : ''}`;
160
+ if (item.type === 'date') return 'a date, e.g. 1 November 2026';
161
+ if (item.type === 'confirm') return 'yes or no';
162
+ return 'a short sentence';
163
+ }
164
+
142
165
  function writeQuestions(q) {
143
166
  fs.writeFileSync(path.join(outDir, 'questions.json'), JSON.stringify(q, null, 2));
144
167
  const yours = q.items.filter((i) => i.type !== 'client');
@@ -147,7 +170,7 @@ function writeQuestions(q) {
147
170
  const intro = `Reply with the numbered answers; your assistant writes them to answers.json and runs:\n\n tor-to-proposal bid-pack apply --answers answers.json --dir ${dir}\n`;
148
171
  const md =
149
172
  `# Bid questions — answer these in one go\n\n` +
150
- (yours.length ? intro + '\n' + mdTable(['#', 'Question', 'Why it is asked'], yours.map((i) => [++n, `**${i.id}** — ${i.question}${i.default !== undefined && i.default !== null ? ` (default: ${i.default})` : ''}`, i.why || ''])) + '\n' : 'None — everything mechanical is resolved.\n') +
173
+ (yours.length ? intro + '\n' + mdTable(['#', 'Question', 'How to answer', 'Why it is asked'], yours.map((i) => [++n, `**${i.id}** — ${i.question}${i.default !== undefined && i.default !== null ? ` (default: ${i.default})` : ''}`, answerFormat(i), i.why || ''])) + '\n' : 'None — everything mechanical is resolved.\n') +
151
174
  (clients.length ? `\n## To send to the CLIENT (clarifications, before the cutoff)\n${clients.map((i) => `- ${i.question}`).join('\n')}\n` : '');
152
175
  writeOut(path.join(outDir, 'questions.md'), md);
153
176
  }
@@ -336,6 +359,8 @@ function apply() {
336
359
  const r = run('pricing-model.mjs', argv, { label: 'pricing-model' });
337
360
  pricingRan = r !== null;
338
361
  process.stderr.write(r === null ? 'pricing NOT written — check the values; the pricing question stays open.\n' : `pricing written from user-confirmed numbers -> ${outDir}/pricing.json\n`);
362
+ } else if (answers['Q-PRICING-CONFIRM'] !== undefined) {
363
+ process.stderr.write('Q-PRICING-CONFIRM: no usable pricing in that answer — reply "yes" to use your saved rates, or new numbers like "base 420 loading 0.25". The pricing question stays open.\n');
339
364
  }
340
365
 
341
366
  // side effect: (re)render the financial proposal when its inputs are ready
@@ -355,6 +380,8 @@ function apply() {
355
380
  fresh.items = fresh.items.filter((i) => i.answer === null);
356
381
  fresh.openCount = fresh.items.length;
357
382
  writeQuestions(fresh);
383
+ const stillYours = fresh.items.filter((i) => i.type !== 'client');
384
+ const stillClient = fresh.items.filter((i) => i.type === 'client');
358
385
  const state = readState();
359
386
  state.steps.questions = { status: 'done', open: fresh.openCount };
360
387
  state.steps.pricing = { status: inDir('pricing.json') ? 'done' : 'awaiting-user-numbers' };
@@ -363,7 +390,13 @@ function apply() {
363
390
 
364
391
  process.stdout.write(
365
392
  `# Apply: ${applied} answer(s) recorded, ${fresh.openCount} still open\n\n` +
366
- (fresh.openCount ? mdTable(['#', 'Still open'], fresh.items.map((i, ix) => [ix + 1, `**${i.id}** — ${i.question}`])) + '\n' : 'All questions resolved. Draft/generate, then: bid-pack pack --dir ' + dir + '\n') +
393
+ (stillYours.length
394
+ ? `## Still open — answer these (bid-pack ask)\n${mdTable(['#', 'Question'], stillYours.map((i, ix) => [ix + 1, `**${i.id}** — ${i.question}`]))}\n`
395
+ : '') +
396
+ (stillClient.length
397
+ ? `## Reminders — ACTIONS for you, not answers\n(These describe things to DO outside the tool: contact the client, verify a fact. They never close by typing an answer here.)\n${mdTable(['#', 'Action'], stillClient.map((i, ix) => [ix + 1, `**${i.id}** — ${i.question}`]))}\n`
398
+ : '') +
399
+ (!fresh.openCount ? `All questions resolved. Draft/generate, then: bid-pack pack --dir ${dir}\n` : '') +
367
400
  (fills.length ? `\nFill answers recorded in out/fill-answers.json — apply them to the drafts, re-run the lints, and the questions disappear once the [FILL]s are gone.\n` : '') +
368
401
  (evidence.length ? `\nCV evidence entries: ${evidence.length} (out/cv-evidence.json) — cv-tailor lint accepts these as user-input anchors.\n` : '')
369
402
  );
@@ -414,12 +447,16 @@ async function askWizard() {
414
447
  );
415
448
 
416
449
  const answers = {};
450
+ // a pricing answer is only usable if it is "yes" or contains a base number —
451
+ // anything else would silently skip pricing and re-open the question later,
452
+ // so the wizard rejects it on the spot and re-asks
453
+ const pricingAnswerUsable = (a) => /^yes$/i.test(a) || /\bbase\b\s*[\d]/i.test(a);
417
454
  let i = 0;
418
455
  while (i < yours.length) {
419
456
  const item = yours[i];
420
457
  const cur = item.default !== undefined && item.default !== null ? String(item.default) : '';
421
458
  const tail = item.unit ? ` (${item.unit})` : '';
422
- const a = await askLine(`(${i + 1}/${yours.length}) ${item.question}${tail}${cur ? ` [${cur}]` : ''}\n> `);
459
+ const a = await askLine(`(${i + 1}/${yours.length}) ${item.question}${tail}${cur ? ` [${cur}]` : ''}\n How to answer: ${answerFormat(item)}\n> `);
423
460
  if (a === 'EOF' || a.toLowerCase() === 'quit') {
424
461
  rl.close();
425
462
  process.stdout.write('\nCancelled — nothing was saved. (Your documents are untouched; run bid-pack ask again anytime.)\n');
@@ -436,6 +473,10 @@ async function askWizard() {
436
473
  process.stdout.write(' This one needs an answer from you (no suggested default). Type it, or "quit" to stop.\n');
437
474
  continue;
438
475
  }
476
+ if (item.id === 'Q-PRICING-CONFIRM' && a !== '' && !pricingAnswerUsable(a)) {
477
+ process.stdout.write(' I can\'t price from that alone. Either type "yes" to use the saved rates shown above, or give new numbers like: base 420 loading 0.25 contingency 0.1\n');
478
+ continue;
479
+ }
439
480
  answers[item.id] = a === '' ? item.default : a;
440
481
  i += 1;
441
482
  }
package/mcp/server.mjs CHANGED
@@ -322,7 +322,7 @@ function handle(msg) {
322
322
  return {
323
323
  protocolVersion: typeof params?.protocolVersion === 'string' ? params.protocolVersion : '2024-11-05',
324
324
  capabilities: { tools: {} },
325
- serverInfo: { name: 'tor-to-proposal', version: '2.1.1' },
325
+ serverInfo: { name: 'tor-to-proposal', version: '2.1.4' },
326
326
  };
327
327
  }
328
328
  if (typeof method === 'string' && method.startsWith('notifications/')) return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tor-to-proposal",
3
- "version": "2.1.1",
3
+ "version": "2.1.4",
4
4
  "description": "The bid tool that refuses to lie. ToR + CV in -> verified bid pack out: cover letter, tailored CV, technical response, financial proposal, submission checklist. Deterministic CLI + agent skill for UN / NGO / government consultants.",
5
5
  "type": "module",
6
6
  "bin": {