@hackerrank/astra-cli 0.1.23 → 0.1.24

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/README.md CHANGED
@@ -80,7 +80,7 @@ astra -m claude-sonnet-5 # start chatting
80
80
  astra -m claude-sonnet-5 -y # auto-run commands (no prompts)
81
81
  ```
82
82
 
83
- In-REPL commands: `/help /exit /clear /history /tokens /yolo`.
83
+ In-REPL commands: `/help /plan <task> /exit /clear /history /tokens /yolo`.
84
84
 
85
85
  ### Autonomous task run
86
86
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hackerrank/astra-cli",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "Minimal zero-dependency AI coding agent for the HackerRank AI Gateway.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/prompts.js CHANGED
@@ -102,3 +102,27 @@ export function render(template, vars) {
102
102
  key in vars && vars[key] != null ? String(vars[key]) : ""
103
103
  );
104
104
  }
105
+
106
+ export const PLAN_PROMPT_TEMPLATE = `Please explore the workspace and create a clear, actionable plan for this task:
107
+
108
+ Task:
109
+ {{task}}
110
+
111
+ Instructions for planning:
112
+ 1. Run read-only commands (e.g. ls, find, grep, cat) to explore and understand the relevant files.
113
+ 2. Present a structured plan with:
114
+ - **Objective & Scope**: Summary of what needs to be done.
115
+ - **Files to Modify / Create**: List of target files.
116
+ - **Self-Testing & Verification Strategy**: Exact test commands or verification scripts to write/run.
117
+ - **Self-Review Checklist**: Code quality, edge cases, and cleanliness checks.
118
+ 3. Finish with a chat response presenting the complete plan so the user can review it before execution.`;
119
+
120
+ export const AUTONOMOUS_EXECUTION_PROMPT = `The plan has been approved. Execute the plan end-to-end now in autonomous mode without asking further questions.
121
+
122
+ Workflow requirements:
123
+ 1. Implement the changes with clean, focused edits.
124
+ 2. Self-verify by writing or running test scripts to prove the solution works.
125
+ 3. Review your changes with \`git diff\` (or inspect modified files) to ensure no regressions, unintended files, or debug prints remain.
126
+ 4. If any tests or checks fail, diagnose and fix the errors.
127
+ 5. When completely finished, verified, and reviewed, run:
128
+ \`echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT\``;
package/src/repl.js CHANGED
@@ -17,6 +17,7 @@ import readline from "node:readline";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import { ask, saveAgentPrefs } from "./config.js";
19
19
  import { AVAILABLE_MODELS, REASONING_LEVELS } from "./models.js";
20
+ import { PLAN_PROMPT_TEMPLATE, AUTONOMOUS_EXECUTION_PROMPT, render } from "./prompts.js";
20
21
 
21
22
  const C = {
22
23
  dim: (s) => `\x1b[2m${s}\x1b[0m`,
@@ -517,6 +518,7 @@ Interactive commands:
517
518
  /help show this help
518
519
  /exit, /quit leave (session is saved)
519
520
  /clear start a fresh conversation (new context)
521
+ /plan <task> plan a task, review it, then execute autonomously with self-review
520
522
  /history print the message history
521
523
  /tokens show token usage so far
522
524
  /yolo toggle auto-run of commands (no confirmation)
@@ -643,6 +645,25 @@ async function runStickyRepl(agent, model, yolo, intro = [], { runBench = null }
643
645
  if (cmd === "history") { printHistory(agent, log); continue; }
644
646
  if (cmd === "tokens") { log(tokensLine(model)); continue; }
645
647
  if (cmd === "yolo") { yolo = !yolo; log(C.dim(`[astra] auto-run ${yolo ? "ON" : "OFF"}.`)); refresh(); continue; }
648
+ if (cmd === "plan") {
649
+ const taskText = line.slice(line.indexOf("plan") + 4).trim();
650
+ const prevYolo = yolo;
651
+ await handlePlanWorkflow({
652
+ taskText,
653
+ agent,
654
+ model,
655
+ log,
656
+ readInput: (promptText) => screen.readLine(promptText, { guardEnter: true, echo: true }),
657
+ startBusy: (label) => screen.startBusy(label),
658
+ stopBusy: () => screen.stopBusy(),
659
+ interrupt,
660
+ setYolo: (v) => { yolo = v; },
661
+ onProgress: () => refresh(),
662
+ });
663
+ yolo = prevYolo;
664
+ refresh();
665
+ continue;
666
+ }
646
667
  log(C.dim(`[astra] unknown command: /${cmd} (try /help)`));
647
668
  continue;
648
669
  }
@@ -742,6 +763,25 @@ async function runPlainRepl(agent, model, yolo) {
742
763
  if (cmd === "history") { printHistory(agent, log); continue; }
743
764
  if (cmd === "tokens") { console.error(tokensLine(model)); continue; }
744
765
  if (cmd === "yolo") { yolo = !yolo; console.error(C.dim(`[astra] auto-run ${yolo ? "ON" : "OFF"}.`)); continue; }
766
+ if (cmd === "plan") {
767
+ const taskText = line.slice(line.indexOf("plan") + 4).trim();
768
+ const prevYolo = yolo;
769
+ await handlePlanWorkflow({
770
+ taskText,
771
+ agent,
772
+ model,
773
+ log: (t) => console.error(t),
774
+ readInput: (promptText) => new Promise((res) => rl.question(promptText, res)),
775
+ startBusy: () => {},
776
+ stopBusy: () => {},
777
+ interrupt: { aborted: false, quit: false },
778
+ setYolo: (v) => { yolo = v; },
779
+ onProgress: () => printFooterInline(agent, model, yolo),
780
+ });
781
+ yolo = prevYolo;
782
+ printFooterInline(agent, model, yolo);
783
+ continue;
784
+ }
745
785
  console.error(C.dim(`[astra] unknown command: /${cmd} (try /help)`));
746
786
  continue;
747
787
  }
@@ -859,3 +899,161 @@ function tokensLine(model) {
859
899
  const dollars = usd < 0.01 ? "$" + usd.toFixed(5) : "$" + usd.toFixed(4);
860
900
  return C.dim(`[astra] tokens: prompt=${pt} completion=${ct} total=${pt + ct} · cost=${dollars} (${src})`);
861
901
  }
902
+
903
+
904
+ /**
905
+ * Run the plan-and-execute workflow:
906
+ * 1. Prompt model to explore workspace and draft a structured plan.
907
+ * 2. Pause and prompt user for approval / feedback.
908
+ * 3. On approval, execute autonomously to completion with auto-review and auto-fix.
909
+ */
910
+ export async function handlePlanWorkflow({
911
+ taskText,
912
+ agent,
913
+ model,
914
+ log,
915
+ readInput,
916
+ startBusy,
917
+ stopBusy,
918
+ interrupt,
919
+ setYolo = () => {},
920
+ onProgress = () => {},
921
+ }) {
922
+ let task = taskText;
923
+ if (!task) {
924
+ task = (await readInput(C.yellow("Enter task to plan: "))).trim();
925
+ }
926
+ if (!task) {
927
+ log(C.dim("[astra] plan cancelled (empty task)."));
928
+ return;
929
+ }
930
+
931
+ log(C.cyan(`[astra] exploring workspace and drafting plan for: "${task}"...`));
932
+ agent.addUserMessage(render(PLAN_PROMPT_TEMPLATE, { task }));
933
+
934
+ model._abort = new AbortController();
935
+ model.signal = model._abort.signal;
936
+ startBusy("planning");
937
+ try {
938
+ await driveUntilChat(agent, log, interrupt);
939
+ } finally {
940
+ stopBusy();
941
+ model.signal = null;
942
+ model._abort = null;
943
+ }
944
+ if (interrupt?.quit || interrupt?.aborted) return;
945
+
946
+ // Review gate loop
947
+ let approved = false;
948
+ while (!approved) {
949
+ const ans = (await readInput(
950
+ C.yellow("\nApprove plan and begin autonomous execution? [Y/n/feedback] ")
951
+ )).trim();
952
+ const lower = ans.toLowerCase();
953
+
954
+ if (lower === "n" || lower === "no" || lower === "cancel" || lower === "abort") {
955
+ log(C.dim("[astra] plan cancelled. Returning to interactive mode."));
956
+ return;
957
+ }
958
+ if (ans === "" || lower === "y" || lower === "yes") {
959
+ approved = true;
960
+ break;
961
+ }
962
+
963
+ // Feedback provided: refine plan
964
+ log(C.cyan(`[astra] updating plan with feedback: "${ans}"...`));
965
+ agent.addUserMessage(
966
+ `Here is feedback on the plan: "${ans}". Please refine the plan accordingly and present the updated plan.`
967
+ );
968
+ model._abort = new AbortController();
969
+ model.signal = model._abort.signal;
970
+ startBusy("planning");
971
+ try {
972
+ await driveUntilChat(agent, log, interrupt);
973
+ } finally {
974
+ stopBusy();
975
+ model.signal = null;
976
+ model._abort = null;
977
+ }
978
+ if (interrupt?.quit || interrupt?.aborted) return;
979
+ }
980
+
981
+ // Execute autonomously
982
+ log(C.green("\n[astra] plan approved! Starting unattended autonomous execution with auto-review & auto-fix..."));
983
+ const prevMode = agent.mode;
984
+ setYolo(true);
985
+ agent.mode = "autonomous";
986
+ agent.addUserMessage(AUTONOMOUS_EXECUTION_PROMPT);
987
+
988
+ model._abort = new AbortController();
989
+ model.signal = model._abort.signal;
990
+ startBusy("executing plan");
991
+ let status = "Completed";
992
+ try {
993
+ status = await driveAutonomous(agent, log, interrupt);
994
+ } catch (err) {
995
+ log(C.red(`[astra] error during autonomous execution: ${err?.message || err}`));
996
+ status = "Error";
997
+ } finally {
998
+ stopBusy();
999
+ model.signal = null;
1000
+ model._abort = null;
1001
+ agent.mode = prevMode;
1002
+ agent.exitStatus = null;
1003
+ onProgress();
1004
+ }
1005
+
1006
+ log(C.green(`\n[astra] plan execution finished (${status}). You are now back in interactive mode.`));
1007
+ }
1008
+
1009
+ /**
1010
+ * Drive turns autonomously until the model submits with the completion sentinel.
1011
+ */
1012
+ export async function driveAutonomous(agent, log, interrupt = null) {
1013
+ while (true) {
1014
+ if (interrupt && interrupt.aborted) {
1015
+ log(C.yellow("[astra] autonomous execution interrupted — returning to prompt."));
1016
+ return "Interrupted";
1017
+ }
1018
+ let turn;
1019
+ try {
1020
+ turn = await agent.runTurn();
1021
+ } catch (err) {
1022
+ if (err && (err.name === "GatewayError" || err.name === "ContextWindowError")) {
1023
+ log(C.red(`[astra] ${err.message}`));
1024
+ return err.name;
1025
+ }
1026
+ throw err;
1027
+ }
1028
+
1029
+ if (interrupt && interrupt.aborted) {
1030
+ log(C.yellow("[astra] autonomous execution interrupted — returning to prompt."));
1031
+ return "Interrupted";
1032
+ }
1033
+ if (turn.kind === "exit") {
1034
+ log(C.dim(`[astra] autonomous run ended: ${turn.exit_status}`));
1035
+ return turn.exit_status;
1036
+ }
1037
+ if (turn.kind === "command") {
1038
+ const rc = turn.returncode;
1039
+ const tag = rc === 0 ? C.dim(`[rc ${rc}]`) : C.red(`[rc ${rc}]`);
1040
+ log(tag + "\n" + indent(turn.output));
1041
+ continue;
1042
+ }
1043
+ if (turn.kind === "declined") {
1044
+ log(C.dim("[astra] command skipped."));
1045
+ continue;
1046
+ }
1047
+ if (turn.kind === "format_error") {
1048
+ log(C.dim(`[astra] (reprompting: ${turn.error})`));
1049
+ continue;
1050
+ }
1051
+ if (turn.kind === "chat") {
1052
+ log("\n" + C.cyan("astra › ") + turn.content.trim());
1053
+ agent.addUserMessage(
1054
+ "Continue executing the approved plan in autonomous mode. When all changes are implemented, tested, and self-reviewed, run:\necho COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT"
1055
+ );
1056
+ continue;
1057
+ }
1058
+ }
1059
+ }