ahead-pi 0.8.2 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ahead-pi",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "description": "AHEAD workflow enforcement and context for Pi",
5
5
  "keywords": [
6
6
  "ahead",
package/src/guidance.ts CHANGED
@@ -424,7 +424,9 @@ export function nextAction(state: RunState, workflow: WorkflowDefinition): Guide
424
424
  ? "Ask AI to challenge the human option and expand alternatives"
425
425
  : state.phase.id === "plan"
426
426
  ? "Ask AI to challenge the human first-pass plan"
427
- : `Ask AI to contribute ${artifact.title}`;
427
+ : artifact.title.startsWith("AI ")
428
+ ? `Ask AI for ${lowercaseFirst(artifact.title.slice(3))}`
429
+ : `Ask AI to contribute ${artifact.title}`;
428
430
  return artifact.required
429
431
  ? { actor, label, artifactKind: artifact.kind }
430
432
  : { actor, label, artifactKind: artifact.kind, optional: true };
@@ -533,8 +535,10 @@ export function buildArtifactTemplate(
533
535
  }
534
536
 
535
537
  /**
536
- * Insert inspiration-only example lines into each field as HTML comments.
537
- * Validation strips comments, so an untouched field still counts as empty.
538
+ * Insert inspiration-only example lines into each field as plain text with
539
+ * an "ex. - " prefix. Validation strips these lines for the emptiness
540
+ * check and rejects any saved form that still contains them, so no example
541
+ * line can ever persist in a recorded artifact.
538
542
  */
539
543
  export function insertFieldExamples(template: string, perField: string[][]): string {
540
544
  let updated = template;
@@ -548,16 +552,27 @@ export function insertFieldExamples(template: string, perField: string[][]): str
548
552
  }
549
553
  updated = updated.replace(
550
554
  marker,
551
- [
552
- marker,
553
- "<!-- Examples — inspiration only, not requirements. Delete them and write your own answer. -->",
554
- ...examples.map((example) => `<!-- ~ ${example} ~ -->`),
555
- ].join("\n"),
555
+ [marker, ...examples.map((example) => `ex. - ${example}`)].join("\n"),
556
556
  );
557
557
  }
558
558
  return updated;
559
559
  }
560
560
 
561
+ const EXAMPLE_LINE_PATTERN = /^\s*ex\.\s*-\s?/;
562
+
563
+ function stripExampleLines(text: string): { cleaned: string; hadExamples: boolean } {
564
+ const kept: string[] = [];
565
+ let hadExamples = false;
566
+ for (const line of text.split("\n")) {
567
+ if (EXAMPLE_LINE_PATTERN.test(line)) {
568
+ hadExamples = true;
569
+ continue;
570
+ }
571
+ kept.push(line);
572
+ }
573
+ return { cleaned: kept.join("\n").trim(), hadExamples };
574
+ }
575
+
561
576
  export function validateArtifactForm(content: string, prompts: string[]): string[] {
562
577
  const errors: string[] = [];
563
578
  for (const [index, prompt] of prompts.entries()) {
@@ -575,17 +590,28 @@ export function validateArtifactForm(content: string, prompts: string[]): string
575
590
  .slice(beginIndex + begin.length, endIndex)
576
591
  .replace(/<!--[\s\S]*?-->/g, "")
577
592
  .trim();
578
- if (!response) {
579
- errors.push(prompt);
593
+ const { cleaned, hadExamples } = stripExampleLines(response);
594
+ if (!cleaned) {
595
+ errors.push(
596
+ hadExamples ? `${prompt} (replace the “ex. -” example lines with your own answer)` : prompt,
597
+ );
598
+ continue;
599
+ }
600
+ if (hadExamples) {
601
+ errors.push(`${prompt} (remove the leftover “ex. -” example lines)`);
580
602
  continue;
581
603
  }
582
- if (/^(?:n\/?a|not applicable)\s*[.!]?$/i.test(response)) {
604
+ if (/^(?:n\/?a|not applicable)\s*[.!]?$/i.test(cleaned)) {
583
605
  errors.push(`${prompt} (explain why it is not applicable)`);
584
606
  }
585
607
  }
586
608
  return errors;
587
609
  }
588
610
 
611
+ function lowercaseFirst(value: string): string {
612
+ return value.charAt(0).toLowerCase() + value.slice(1);
613
+ }
614
+
589
615
  function formatArtifactStatus(artifact: ArtifactState): string {
590
616
  const owner = artifact.actor === "ai" ? "AI" : artifact.actor === "human" ? "you" : "you/AI";
591
617
  return `${artifact.present ? "✓" : "○"} ${artifact.title} [${owner}]`;
package/src/index.ts CHANGED
@@ -702,7 +702,7 @@ async function openAheadMode(
702
702
  });
703
703
  } else if (missingRequired.length === 0) {
704
704
  actions.push({
705
- label: `Continue without optional AI contribution · Accept ${state.gate.title}`,
705
+ label: `Continue without optional AI contribution · ${state.gate.title}`,
706
706
  run: async () => acceptAndContinue(ctx),
707
707
  });
708
708
  }
@@ -752,8 +752,12 @@ async function openAheadMode(
752
752
  });
753
753
  }
754
754
 
755
+ // The generic challenger appears only when the next move is the human's.
756
+ // When the primary action is already an AI contribution, the formal AI
757
+ // artifact owns that job and a second "challenge me" entry is noise.
755
758
  if (
756
759
  state.allowed_ai_capabilities.length > 0 &&
760
+ action.actor !== "ai" &&
757
761
  state.artifacts.some(
758
762
  (artifact) => artifact.present && artifact.recorded_by?.kind === "human" && artifact.path,
759
763
  )
@@ -1617,15 +1621,27 @@ async function recordHumanArtifact(
1617
1621
  const prompts = promptsForArtifact(state.workflow_id, state.phase.id, artifact.kind);
1618
1622
  let template = await humanArtifactTemplate(store, state, run, artifact.kind, artifact.title);
1619
1623
  if (ctx.hasUI && artifact.kind !== "review-disposition" && prompts.length > 0) {
1620
- if (ctx.model) {
1621
- ctx.ui.notify("Drafting two example prompts per field (inspiration only)…", "info");
1624
+ // Visible progress while drafting: the loader row and footer status show
1625
+ // until the call resolves, so the wait never looks like a hang.
1626
+ const showProgress = ctx.model !== undefined;
1627
+ if (showProgress) {
1628
+ ctx.ui.setWorkingMessage("Drafting example prompts (inspiration only)…");
1629
+ ctx.ui.setStatus("ahead-examples", "Drafting example prompts…");
1630
+ }
1631
+ let draft;
1632
+ try {
1633
+ draft = await draftFieldExamples(ctx, {
1634
+ workflowTitle: engine.getWorkflow(state.workflow_id).title,
1635
+ phaseTitle: state.phase.title,
1636
+ runTitle: run.title,
1637
+ fields: prompts,
1638
+ });
1639
+ } finally {
1640
+ if (showProgress) {
1641
+ ctx.ui.setWorkingMessage();
1642
+ ctx.ui.setStatus("ahead-examples", undefined);
1643
+ }
1622
1644
  }
1623
- const draft = await draftFieldExamples(ctx, {
1624
- workflowTitle: engine.getWorkflow(state.workflow_id).title,
1625
- phaseTitle: state.phase.title,
1626
- runTitle: run.title,
1627
- fields: prompts,
1628
- });
1629
1645
  if (draft.examples) {
1630
1646
  template = insertFieldExamples(template, draft.examples);
1631
1647
  } else if (draft.skipped === "no-auth") {