ostacky 0.5.8 → 0.5.10

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.
@@ -12263,21 +12263,21 @@ var require_errors = /* @__PURE__ */ __commonJSMin((exports) => {
12263
12263
  function extendErrors({ gen, keyword, schemaValue, data, errsCount, it }) {
12264
12264
  if (errsCount === undefined)
12265
12265
  throw new Error("ajv implementation error");
12266
- const err = gen.name("err");
12266
+ const err2 = gen.name("err");
12267
12267
  gen.forRange("i", errsCount, names_1.default.errors, (i) => {
12268
- gen.const(err, (0, codegen_1._)`${names_1.default.vErrors}[${i}]`);
12269
- gen.if((0, codegen_1._)`${err}.instancePath === undefined`, () => gen.assign((0, codegen_1._)`${err}.instancePath`, (0, codegen_1.strConcat)(names_1.default.instancePath, it.errorPath)));
12270
- gen.assign((0, codegen_1._)`${err}.schemaPath`, (0, codegen_1.str)`${it.errSchemaPath}/${keyword}`);
12268
+ gen.const(err2, (0, codegen_1._)`${names_1.default.vErrors}[${i}]`);
12269
+ gen.if((0, codegen_1._)`${err2}.instancePath === undefined`, () => gen.assign((0, codegen_1._)`${err2}.instancePath`, (0, codegen_1.strConcat)(names_1.default.instancePath, it.errorPath)));
12270
+ gen.assign((0, codegen_1._)`${err2}.schemaPath`, (0, codegen_1.str)`${it.errSchemaPath}/${keyword}`);
12271
12271
  if (it.opts.verbose) {
12272
- gen.assign((0, codegen_1._)`${err}.schema`, schemaValue);
12273
- gen.assign((0, codegen_1._)`${err}.data`, data);
12272
+ gen.assign((0, codegen_1._)`${err2}.schema`, schemaValue);
12273
+ gen.assign((0, codegen_1._)`${err2}.data`, data);
12274
12274
  }
12275
12275
  });
12276
12276
  }
12277
12277
  exports.extendErrors = extendErrors;
12278
12278
  function addError(gen, errObj) {
12279
- const err = gen.const("err", errObj);
12280
- gen.if((0, codegen_1._)`${names_1.default.vErrors} === null`, () => gen.assign(names_1.default.vErrors, (0, codegen_1._)`[${err}]`), (0, codegen_1._)`${names_1.default.vErrors}.push(${err})`);
12279
+ const err2 = gen.const("err", errObj);
12280
+ gen.if((0, codegen_1._)`${names_1.default.vErrors} === null`, () => gen.assign(names_1.default.vErrors, (0, codegen_1._)`[${err2}]`), (0, codegen_1._)`${names_1.default.vErrors}.push(${err2})`);
12281
12281
  gen.code((0, codegen_1._)`${names_1.default.errors}++`);
12282
12282
  }
12283
12283
  function returnErrors(it, errs) {
@@ -19496,13 +19496,46 @@ var StdioServerTransport = class {
19496
19496
  };
19497
19497
 
19498
19498
  // assets/mcp/ostacky-controller/index.js
19499
- import { readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs";
19500
- import { dirname } from "node:path";
19499
+ import { readFileSync, writeFileSync, renameSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";
19500
+ import { dirname, basename } from "node:path";
19501
+ var MAX_TASKS = 50;
19502
+ var MAX_SNAPSHOT_JSON_LENGTH = 100 * 1024;
19503
+ var MAX_STATE_FILE_SIZE = 1024 * 1024;
19504
+ function safeJsonStringify(obj, pretty = false) {
19505
+ const seen = new WeakSet;
19506
+ try {
19507
+ return JSON.stringify(obj, (key, value) => {
19508
+ if (typeof value === "object" && value !== null) {
19509
+ if (seen.has(value))
19510
+ return "[Circular]";
19511
+ seen.add(value);
19512
+ }
19513
+ return value;
19514
+ }, pretty ? 2 : undefined);
19515
+ } catch (e) {
19516
+ return `[Unstringifiable: ${e.message}]`;
19517
+ }
19518
+ }
19501
19519
  function log(event, data) {
19502
19520
  const ts = new Date().toISOString();
19503
- const payload = data ? ` ${JSON.stringify(data)}` : "";
19521
+ const payload = data ? ` ${safeJsonStringify(data)}` : "";
19504
19522
  console.error(`[${ts}] ${event}${payload}`);
19505
19523
  }
19524
+ function cleanupTmpFiles(statePath) {
19525
+ if (!statePath)
19526
+ return;
19527
+ const dir = dirname(statePath);
19528
+ const name = basename(statePath);
19529
+ try {
19530
+ for (const entry of readdirSync(dir)) {
19531
+ if (entry.startsWith(name + ".tmp.")) {
19532
+ try {
19533
+ unlinkSync(dir + "/" + entry);
19534
+ } catch {}
19535
+ }
19536
+ }
19537
+ } catch {}
19538
+ }
19506
19539
  var STATES = Object.freeze({
19507
19540
  INTERPRETATION_PENDING: "INTERPRETATION_PENDING",
19508
19541
  CLARIFICATION_PENDING: "CLARIFICATION_PENDING",
@@ -19539,8 +19572,13 @@ class OstackyController {
19539
19572
  #loaded;
19540
19573
  constructor(opts = {}) {
19541
19574
  this.#statePath = opts.statePath;
19542
- this.#state = opts.initialState ? { ...DEFAULT_STATE, ...opts.initialState } : null;
19543
- this.#loaded = false;
19575
+ if (opts.initialState) {
19576
+ this.#state = { ...DEFAULT_STATE, ...opts.initialState };
19577
+ this.#loaded = true;
19578
+ } else {
19579
+ this.#state = null;
19580
+ this.#loaded = false;
19581
+ }
19544
19582
  }
19545
19583
  #load() {
19546
19584
  if (this.#loaded)
@@ -19552,9 +19590,29 @@ class OstackyController {
19552
19590
  }
19553
19591
  try {
19554
19592
  const raw = readFileSync(this.#statePath, "utf8");
19593
+ if (raw.length > MAX_STATE_FILE_SIZE)
19594
+ throw new Error(`State file too large: ${raw.length} bytes`);
19555
19595
  this.#state = { ...DEFAULT_STATE, ...JSON.parse(raw) };
19596
+ this.#loaded = true;
19597
+ return;
19598
+ } catch (err2) {
19599
+ log("warn:load_primary_failed", { error: err2.message });
19600
+ }
19601
+ const backupPath = this.#statePath + ".backup";
19602
+ try {
19603
+ const raw = readFileSync(backupPath, "utf8");
19604
+ if (raw.length > MAX_STATE_FILE_SIZE)
19605
+ throw new Error(`Backup too large: ${raw.length} bytes`);
19606
+ this.#state = { ...DEFAULT_STATE, ...JSON.parse(raw), error: "State restored from backup" };
19607
+ log("warn:state_restored_from_backup");
19608
+ this.#loaded = true;
19609
+ return;
19556
19610
  } catch {
19557
- this.#state = { ...DEFAULT_STATE };
19611
+ this.#state = {
19612
+ ...DEFAULT_STATE,
19613
+ error: `State file corrupt: ${err.message}. No backup available. State reset to default.`
19614
+ };
19615
+ log("warn:state_reset", { error: err.message });
19558
19616
  }
19559
19617
  this.#loaded = true;
19560
19618
  }
@@ -19563,14 +19621,48 @@ class OstackyController {
19563
19621
  return;
19564
19622
  const dir = dirname(this.#statePath);
19565
19623
  mkdirSync(dir, { recursive: true });
19624
+ const serialized = safeJsonStringify(this.#state, true);
19625
+ if (serialized.length > MAX_STATE_FILE_SIZE) {
19626
+ log("warn:state_oversized", { size: serialized.length });
19627
+ this.#state.snapshots = { codegraph: null, execution: null };
19628
+ const trimmed = safeJsonStringify(this.#state, true);
19629
+ if (trimmed.length > MAX_STATE_FILE_SIZE) {
19630
+ log("error:state_too_large_even_after_trim");
19631
+ return;
19632
+ }
19633
+ const tmp2 = this.#statePath + ".tmp." + process.pid;
19634
+ writeFileSync(tmp2, trimmed, "utf8");
19635
+ renameSync(tmp2, this.#statePath);
19636
+ return;
19637
+ }
19566
19638
  const tmp = this.#statePath + ".tmp." + process.pid;
19567
- writeFileSync(tmp, JSON.stringify(this.#state, null, 2), "utf8");
19639
+ writeFileSync(tmp, serialized, "utf8");
19568
19640
  renameSync(tmp, this.#statePath);
19641
+ try {
19642
+ const backupPath = this.#statePath + ".backup";
19643
+ writeFileSync(backupPath, serialized, "utf8");
19644
+ } catch {}
19645
+ }
19646
+ #trimTasks() {
19647
+ if (!this.#state.tasks)
19648
+ return;
19649
+ const entries = Object.entries(this.#state.tasks);
19650
+ if (entries.length <= MAX_TASKS)
19651
+ return;
19652
+ entries.sort((a, b) => {
19653
+ const da = a[1].completedAt || "";
19654
+ const db = b[1].completedAt || "";
19655
+ return db.localeCompare(da);
19656
+ });
19657
+ const trimmed = Object.fromEntries(entries.slice(0, MAX_TASKS));
19658
+ this.#state.tasks = trimmed;
19659
+ log("warn:tasks_trimmed", { before: entries.length, after: MAX_TASKS });
19569
19660
  }
19570
19661
  #transition(to, changes = {}) {
19571
19662
  this.#state.revision++;
19572
19663
  this.#state.state = to;
19573
19664
  Object.assign(this.#state, changes);
19665
+ this.#trimTasks();
19574
19666
  this.#persist();
19575
19667
  }
19576
19668
  #isAllowedTransition(from, via, choiceOrMode) {
@@ -19687,7 +19779,7 @@ class OstackyController {
19687
19779
  return { error: `Cannot record discovery from state ${this.#state.state}` };
19688
19780
  if (!["0", "0+1", "1+"].includes(level))
19689
19781
  return { error: `Invalid level: ${level}` };
19690
- this.#transition("ROUTE_DECISION_PENDING", {
19782
+ this.#transition(to, {
19691
19783
  routeDecisionId: routeDecisionId || "route-" + Date.now(),
19692
19784
  routeChoice: null,
19693
19785
  snapshots: { ...this.#state.snapshots, codegraph: snapshot || this.#state.snapshots.codegraph }
@@ -19872,6 +19964,7 @@ class OstackyController {
19872
19964
  this.#state.fileFingerprints = {};
19873
19965
  this.#state.fileFingerprints[filePath] = fileHash;
19874
19966
  }
19967
+ this.#trimTasks();
19875
19968
  this.#persist();
19876
19969
  return {
19877
19970
  taskId,
@@ -19879,12 +19972,29 @@ class OstackyController {
19879
19972
  totalCompleted: Object.keys(this.#state.tasks).filter((k) => this.#state.tasks[k].status === "COMPLETED").length
19880
19973
  };
19881
19974
  }
19975
+ flush() {
19976
+ this.#persist();
19977
+ }
19882
19978
  }
19883
19979
  var statePath = process.env.OSTACKY_STATE_PATH || ".opencode/ostacky-state.json";
19884
19980
  var controller = new OstackyController({ statePath });
19981
+ function safeHandler(fn) {
19982
+ return async (params) => {
19983
+ try {
19984
+ const result = await fn(params);
19985
+ return { content: [{ type: "text", text: safeJsonStringify(result) }] };
19986
+ } catch (error2) {
19987
+ log("tool:error", { name: fn.name || "anonymous", error: error2.message });
19988
+ return {
19989
+ content: [{ type: "text", text: safeJsonStringify({ error: error2.message }) }],
19990
+ isError: true
19991
+ };
19992
+ }
19993
+ };
19994
+ }
19885
19995
  var server = new McpServer({
19886
19996
  name: "ostacky-controller",
19887
- version: "0.5.8"
19997
+ version: "0.5.10"
19888
19998
  });
19889
19999
  server.registerTool("start_request", {
19890
20000
  description: "Start or resume a new request in the state machine. Call this first.",
@@ -19892,29 +20002,26 @@ server.registerTool("start_request", {
19892
20002
  requestId: string2().optional().describe("Unique request ID"),
19893
20003
  changeId: string2().optional().describe("Optional change ID for OpenSpec tracking")
19894
20004
  })
19895
- }, async ({ requestId, changeId }) => {
20005
+ }, safeHandler(async ({ requestId, changeId }) => {
19896
20006
  log("tool:start_request");
19897
- const result = await controller.startRequest({ requestId, changeId });
19898
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
19899
- });
20007
+ return await controller.startRequest({ requestId, changeId });
20008
+ }));
19900
20009
  server.registerTool("request_clarification", {
19901
20010
  description: "Record that clarification was requested. Transitions to CLARIFICATION_PENDING.",
19902
20011
  inputSchema: object({
19903
20012
  question: string2().optional().describe("The clarification question")
19904
20013
  })
19905
- }, async ({ question }) => {
20014
+ }, safeHandler(async ({ question }) => {
19906
20015
  log("tool:request_clarification");
19907
- const result = await controller.requestClarification({ question });
19908
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
19909
- });
20016
+ return await controller.requestClarification({ question });
20017
+ }));
19910
20018
  server.registerTool("record_clarification", {
19911
20019
  description: "Record that clarification was answered. Transitions to DISCOVERY.",
19912
20020
  inputSchema: object({})
19913
- }, async () => {
20021
+ }, safeHandler(async () => {
19914
20022
  log("tool:record_clarification");
19915
- const result = await controller.recordClarification();
19916
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
19917
- });
20023
+ return await controller.recordClarification();
20024
+ }));
19918
20025
  server.registerTool("record_discovery", {
19919
20026
  description: "Record discovery complete with level classification. Transitions to ROUTE_DECISION_PENDING.",
19920
20027
  inputSchema: object({
@@ -19922,115 +20029,114 @@ server.registerTool("record_discovery", {
19922
20029
  routeDecisionId: string2().optional().describe("Unique route decision ID"),
19923
20030
  snapshot: any().optional().describe("Optional CodeGraph snapshot")
19924
20031
  })
19925
- }, async ({ level, routeDecisionId, snapshot }) => {
20032
+ }, safeHandler(async ({ level, routeDecisionId, snapshot }) => {
19926
20033
  log("tool:record_discovery", { level });
19927
- const result = await controller.recordDiscovery({ level, routeDecisionId, snapshot });
19928
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
19929
- });
20034
+ return await controller.recordDiscovery({ level, routeDecisionId, snapshot });
20035
+ }));
19930
20036
  server.registerTool("consume_route_decision", {
19931
20037
  description: "Consume the route decision (SPEC or DIRECT). Valid only in ROUTE_DECISION_PENDING.",
19932
20038
  inputSchema: object({
19933
20039
  decisionId: string2().describe("Route decision ID from record_discovery"),
19934
20040
  choice: _enum(["SPEC", "DIRECT"]).describe("Route choice")
19935
20041
  })
19936
- }, async ({ decisionId, choice }) => {
20042
+ }, safeHandler(async ({ decisionId, choice }) => {
19937
20043
  log("tool:consume_route_decision", { choice });
19938
- const result = await controller.consumeRouteDecision({ decisionId, choice });
19939
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
19940
- });
20044
+ return await controller.consumeRouteDecision({ decisionId, choice });
20045
+ }));
19941
20046
  server.registerTool("spec_complete", {
19942
20047
  description: "Mark specification phase as complete. Transitions to EXECUTION_ANALYSIS.",
19943
20048
  inputSchema: object({})
19944
- }, async () => {
20049
+ }, safeHandler(async () => {
19945
20050
  log("tool:spec_complete");
19946
- const result = await controller.specComplete();
19947
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
19948
- });
20051
+ return await controller.specComplete();
20052
+ }));
19949
20053
  server.registerTool("record_execution_analysis", {
19950
20054
  description: "Record execution analysis with snapshot. Transitions to EXECUTION_DECISION_PENDING.",
19951
20055
  inputSchema: object({
19952
20056
  executionDecisionId: string2().optional().describe("Unique execution decision ID"),
19953
20057
  snapshot: any().optional().describe("Execution analysis snapshot")
19954
20058
  })
19955
- }, async ({ executionDecisionId, snapshot }) => {
20059
+ }, safeHandler(async ({ executionDecisionId, snapshot }) => {
19956
20060
  log("tool:record_execution_analysis");
19957
- const result = await controller.recordExecutionAnalysis({ executionDecisionId, snapshot });
19958
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
19959
- });
20061
+ return await controller.recordExecutionAnalysis({ executionDecisionId, snapshot });
20062
+ }));
19960
20063
  server.registerTool("consume_execution_decision", {
19961
20064
  description: "Consume the execution mode decision (INLINE or SUBAGENT_DRIVEN).",
19962
20065
  inputSchema: object({
19963
20066
  decisionId: string2().describe("Execution decision ID from record_execution_analysis"),
19964
20067
  mode: _enum(["INLINE", "SUBAGENT_DRIVEN"]).describe("Execution mode")
19965
20068
  })
19966
- }, async ({ decisionId, mode }) => {
20069
+ }, safeHandler(async ({ decisionId, mode }) => {
19967
20070
  log("tool:consume_execution_decision", { mode });
19968
- const result = await controller.consumeExecutionDecision({ decisionId, mode });
19969
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
19970
- });
20071
+ return await controller.consumeExecutionDecision({ decisionId, mode });
20072
+ }));
19971
20073
  server.registerTool("implementation_complete", {
19972
20074
  description: "Mark implementation as complete. Transitions to SYNC.",
19973
20075
  inputSchema: object({})
19974
- }, async () => {
20076
+ }, safeHandler(async () => {
19975
20077
  log("tool:implementation_complete");
19976
- const result = await controller.implementationComplete();
19977
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
19978
- });
20078
+ return await controller.implementationComplete();
20079
+ }));
19979
20080
  server.registerTool("sync_complete", {
19980
20081
  description: "Mark sync as complete. Transitions to DONE.",
19981
20082
  inputSchema: object({})
19982
- }, async () => {
20083
+ }, safeHandler(async () => {
19983
20084
  log("tool:sync_complete");
19984
- const result = await controller.syncComplete();
19985
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
19986
- });
20085
+ return await controller.syncComplete();
20086
+ }));
19987
20087
  server.registerTool("block", {
19988
20088
  description: "Transition to BLOCKED state with an optional reason.",
19989
20089
  inputSchema: object({
19990
20090
  reason: string2().optional().describe("Reason for blocking")
19991
20091
  })
19992
- }, async ({ reason }) => {
20092
+ }, safeHandler(async ({ reason }) => {
19993
20093
  log("tool:block");
19994
- const result = await controller.block({ reason });
19995
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
19996
- });
20094
+ return await controller.block({ reason });
20095
+ }));
19997
20096
  server.registerTool("replan", {
19998
20097
  description: "Replan from BLOCKED state back to INTERPRETATION_PENDING.",
19999
20098
  inputSchema: object({
20000
20099
  reason: string2().optional().describe("Reason for replanning")
20001
20100
  })
20002
- }, async ({ reason }) => {
20101
+ }, safeHandler(async ({ reason }) => {
20003
20102
  log("tool:replan");
20004
- const result = await controller.replan({ reason });
20005
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
20006
- });
20103
+ return await controller.replan({ reason });
20104
+ }));
20007
20105
  server.registerTool("get_state", {
20008
20106
  description: "Get the current controller state (reads persistent store).",
20009
20107
  inputSchema: object({})
20010
- }, async () => {
20011
- const result = await controller.getState();
20012
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
20013
- });
20108
+ }, safeHandler(async () => {
20109
+ return await controller.getState();
20110
+ }));
20014
20111
  server.registerTool("get_tasks", {
20015
20112
  description: "Get current task states.",
20016
20113
  inputSchema: object({})
20017
- }, async () => {
20018
- const result = await controller.getTasks();
20019
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
20020
- });
20114
+ }, safeHandler(async () => {
20115
+ return await controller.getTasks();
20116
+ }));
20021
20117
  server.registerTool("validate_edit", {
20022
- description: "Validate an edit against current file content. Returns EDITABLE, ALREADY_APPLIED, or CONFLICT. " + "Call BEFORE executing an edit tool. Only valid in EXECUTING_INLINE or EXECUTING_SUBAGENTS states.",
20118
+ description: "Validate an edit against current file content. Returns EDITABLE, ALREADY_APPLIED, or CONFLICT. " + "Call BEFORE executing an edit tool. Only valid in EXECUTING_INLINE or EXECUTING_SUBAGENTS states. " + "IMPORTANT: content parameter is REQUIRED. Read the file first, then pass the full content.",
20023
20119
  inputSchema: object({
20024
20120
  oldString: string2().describe("The exact string to find in content (must be unique)."),
20025
20121
  newString: string2().describe("The replacement string."),
20026
- content: string2().describe("The current file content (read fresh with Read tool)."),
20122
+ content: string2().describe("REQUIRED — The current file content. Read the file first with Read tool, then pass the full content here."),
20027
20123
  taskId: string2().optional().describe("Optional task ID for tracking.")
20028
20124
  })
20029
- }, async ({ oldString, newString, content, taskId }) => {
20030
- log("tool:validate_edit", { taskId, oldLen: oldString.length, newLen: newString.length });
20031
- const result = await controller.validateEdit({ oldString, newString, content, taskId });
20032
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
20033
- });
20125
+ }, safeHandler(async ({ oldString, newString, content, taskId }) => {
20126
+ log("tool:validate_edit", {
20127
+ taskId,
20128
+ oldLen: oldString?.length,
20129
+ newLen: newString?.length,
20130
+ hasContent: !!content
20131
+ });
20132
+ if (typeof content !== "string" || typeof oldString !== "string" || typeof newString !== "string") {
20133
+ return {
20134
+ outcome: "CONFLICT",
20135
+ reason: "Missing required fields: content, oldString, and newString are all required. Read the file first, then pass content to validate_edit."
20136
+ };
20137
+ }
20138
+ return await controller.validateEdit({ oldString, newString, content, taskId });
20139
+ }));
20034
20140
  server.registerTool("complete_task", {
20035
20141
  description: "Mark a task as completed and optionally record a file fingerprint. " + "Only valid in EXECUTING_INLINE or EXECUTING_SUBAGENTS states.",
20036
20142
  inputSchema: object({
@@ -20038,19 +20144,44 @@ server.registerTool("complete_task", {
20038
20144
  filePath: string2().optional().describe("Optional file path that was modified."),
20039
20145
  fileHash: string2().optional().describe("Optional SHA-256 hash of the file after modification.")
20040
20146
  })
20041
- }, async ({ taskId, filePath, fileHash }) => {
20147
+ }, safeHandler(async ({ taskId, filePath, fileHash }) => {
20042
20148
  log("tool:complete_task", { taskId, filePath });
20043
- const result = await controller.completeTask({ taskId, filePath, fileHash });
20044
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
20045
- });
20149
+ return await controller.completeTask({ taskId, filePath, fileHash });
20150
+ }));
20151
+ function setupGracefulShutdown(ctrl) {
20152
+ const shutdown = (signal) => {
20153
+ log("shutdown", { signal });
20154
+ try {
20155
+ if (ctrl)
20156
+ ctrl.flush();
20157
+ } catch {}
20158
+ try {
20159
+ cleanupTmpFiles(statePath);
20160
+ } catch {}
20161
+ process.exit(signal === "SIGINT" ? 130 : 0);
20162
+ };
20163
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
20164
+ process.on("SIGINT", () => shutdown("SIGINT"));
20165
+ process.on("unhandledRejection", (reason) => {
20166
+ log("unhandled_rejection", { reason: String(reason) });
20167
+ });
20168
+ }
20046
20169
  async function main() {
20047
20170
  log("Starting ostacky-controller MCP...");
20048
20171
  log("State path:", { path: statePath });
20172
+ cleanupTmpFiles(statePath);
20173
+ setupGracefulShutdown(controller);
20049
20174
  const transport = new StdioServerTransport;
20050
20175
  await server.connect(transport);
20051
20176
  log("ostacky-controller connected and ready");
20052
20177
  }
20053
- main().catch((error2) => {
20054
- console.error("Fatal error:", error2);
20055
- process.exit(1);
20056
- });
20178
+ var isDirectRun = process.argv[1] && (process.argv[1].endsWith("/index.js") || process.argv[1].endsWith("\\index.js"));
20179
+ if (isDirectRun) {
20180
+ main().catch((error2) => {
20181
+ console.error("Fatal error:", error2);
20182
+ process.exit(1);
20183
+ });
20184
+ }
20185
+ export {
20186
+ OstackyController
20187
+ };
package/manifest.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
- "version": "0.5.8",
2
+ "version": "0.5.10",
3
3
  "repo": "JaimeHoracio/Ostacky",
4
- "tag": "v0.5.8",
4
+ "tag": "v0.5.10",
5
5
  "agents": [
6
6
  {
7
7
  "name": "ostacky",
8
8
  "file": "assets/agents/ostacky.md",
9
9
  "description": "Orquestador principal con ruteo por nivel de impacto, máquina de estados persistida (controller MCP), edición segura con 3 outcomes, y delegación en OpenSpec + Superpowers",
10
- "version": "0.5.8",
11
- "sha256": "eb3ab4cfcdedee63dd4aae05d748d34d9cfc3323bdc5f53f06848cfe3efd439f"
10
+ "version": "0.5.10",
11
+ "sha256": "c624b9163a97fe0aa17a4688f59e2bcfa958c64fbde71c05c0c071911f62b7ad"
12
12
  }
13
13
  ],
14
14
  "commands": [
@@ -16,14 +16,14 @@
16
16
  "name": "install-stack",
17
17
  "file": "assets/commands/install-stack.md",
18
18
  "description": "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, Context7, controller MCP)",
19
- "version": "0.5.8",
20
- "sha256": "16538e1ec81887738716dc8f97a3de4f26fc8d218489a6cd20029a8981bdbd95"
19
+ "version": "0.5.10",
20
+ "sha256": "61f18cb616bffe4a982d874ee9426011c203ab9c17efb88d44592e202c699330"
21
21
  },
22
22
  {
23
23
  "name": "opsx-sync",
24
24
  "file": "assets/commands/opsx-sync.md",
25
25
  "description": "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
26
- "version": "0.5.8",
26
+ "version": "0.5.10",
27
27
  "sha256": "ed9948f1910743b672e1dfad496bc96972a224a98b063232be39290d43068c50"
28
28
  }
29
29
  ],
@@ -32,8 +32,8 @@
32
32
  "name": "ostacky-controller",
33
33
  "file": "assets/mcp/ostacky-controller/",
34
34
  "description": "Máquina de estados persistida para Ostacky: 13 tools MCP para ciclo completo de request (start_request → discovery → route → execution → sync → done), edición segura con validación de transiciones y persistencia atómica",
35
- "version": "0.5.8",
36
- "sha256": "d78ceb0d761c40212a0fe39db8872f58c78de235e1b0833be4accb8adcc45462"
35
+ "version": "0.5.10",
36
+ "sha256": "4469b9e26910a5ba016a0fe7a2d77507d40715d95ce4cbb31c498bf7001b4d0d"
37
37
  }
38
38
  ],
39
39
  "skills": [
@@ -41,98 +41,98 @@
41
41
  "name": "thinking",
42
42
  "file": "assets/skills/thinking/SKILL.md",
43
43
  "description": "Skill unificado de pensamiento con dos modos: creative-design (producción de diseño → transición a writing-plans o openspec-propose) y open-exploration (exploración libre)",
44
- "version": "0.5.8",
44
+ "version": "0.5.10",
45
45
  "sha256": "6a1fdac86357f89e132013e56cb84c1920880c1c035a8dcf53f69e01824724bb"
46
46
  },
47
47
  {
48
48
  "name": "execution-mode-evaluation",
49
49
  "file": "assets/skills/execution-mode-evaluation/SKILL.md",
50
50
  "description": "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
51
- "version": "0.5.8",
51
+ "version": "0.5.10",
52
52
  "sha256": "048925aec576dff014eed0b44e2a553c635ae405f5a245773f480be9b84d18a7"
53
53
  },
54
54
  {
55
55
  "name": "writing-plans",
56
56
  "file": "assets/skills/writing-plans/SKILL.md",
57
57
  "description": "Skill de planificación de implementación (Superpowers) — execution handoff removido, Ostacky decide modo de ejecución",
58
- "version": "0.5.8",
58
+ "version": "0.5.10",
59
59
  "sha256": "13f6e43522567505da90793934d414d290d56f4ea587ac696f66416af9e7633f"
60
60
  },
61
61
  {
62
62
  "name": "tdd",
63
63
  "file": "assets/skills/tdd/SKILL.md",
64
64
  "description": "Skill de test-driven development (Superpowers)",
65
- "version": "0.5.8",
65
+ "version": "0.5.10",
66
66
  "sha256": "7eacd8ee81dc5c0b85065c0392e37ee10314661f7edb59dd8e70a9e0dae8f371"
67
67
  },
68
68
  {
69
69
  "name": "subagent-driven-development",
70
70
  "file": "assets/skills/subagent-driven-development/SKILL.md",
71
71
  "description": "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
72
- "version": "0.5.8",
72
+ "version": "0.5.10",
73
73
  "sha256": "cdacae2e5b86f6a642ad0316172cee7dc8d19e909ef4f9ab7fcf7e84a248dda3"
74
74
  },
75
75
  {
76
76
  "name": "dispatching-parallel-agents",
77
77
  "file": "assets/skills/dispatching-parallel-agents/SKILL.md",
78
78
  "description": "Skill de dispatch paralelo de agentes (Superpowers)",
79
- "version": "0.5.8",
79
+ "version": "0.5.10",
80
80
  "sha256": "281edf0c38f358497c7e2066fa8217a2ba3e2a39b4205c4af0c41d328fc035a1"
81
81
  },
82
82
  {
83
83
  "name": "review",
84
84
  "file": "assets/skills/review/SKILL.md",
85
85
  "description": "Skill de revisión de código (Superpowers)",
86
- "version": "0.5.8",
86
+ "version": "0.5.10",
87
87
  "sha256": "14831d9ef3746ef6e2e6047c868fe7aa296d01f0644227ce776fe99436357b1a"
88
88
  },
89
89
  {
90
90
  "name": "receiving-code-review",
91
91
  "file": "assets/skills/receiving-code-review/SKILL.md",
92
92
  "description": "Skill de recibir y procesar feedback de code review",
93
- "version": "0.5.8",
93
+ "version": "0.5.10",
94
94
  "sha256": "0a5780e8a41539d15428114b7c46f36670acc16aba0db3348d36be1175433872"
95
95
  },
96
96
  {
97
97
  "name": "openspec-propose",
98
98
  "file": "assets/skills/openspec-propose/SKILL.md",
99
99
  "description": "Skill de generación de proposal (OpenSpec)",
100
- "version": "0.5.8",
100
+ "version": "0.5.10",
101
101
  "sha256": "3306b21ba9cb8c1611e8ba335126982f3767f7d317b5ed506c57a14c5f88b263"
102
102
  },
103
103
  {
104
104
  "name": "openspec-apply-change",
105
105
  "file": "assets/skills/openspec-apply-change/SKILL.md",
106
106
  "description": "Skill de aplicación de change (OpenSpec)",
107
- "version": "0.5.8",
107
+ "version": "0.5.10",
108
108
  "sha256": "31c1313c3de4616a07efd89306c4c058175d018f42d4381cc7da7030fd03ba30"
109
109
  },
110
110
  {
111
111
  "name": "openspec-archive-change",
112
112
  "file": "assets/skills/openspec-archive-change/SKILL.md",
113
113
  "description": "Skill de archivo de change (OpenSpec)",
114
- "version": "0.5.8",
114
+ "version": "0.5.10",
115
115
  "sha256": "5bf65d1848457bd57be1d21707e55fe6746cdee33bce2f32f12d87661b6aa3a4"
116
116
  },
117
117
  {
118
118
  "name": "using-git-worktrees",
119
119
  "file": "assets/skills/using-git-worktrees/SKILL.md",
120
120
  "description": "Skill de uso de git worktrees para aislamiento de trabajo",
121
- "version": "0.5.8",
121
+ "version": "0.5.10",
122
122
  "sha256": "33433c24f753d566cde16c0931f70d746b6b0cb4d1f3daaa619e928b009e4fc9"
123
123
  },
124
124
  {
125
125
  "name": "using-superpowers",
126
126
  "file": "assets/skills/using-superpowers/SKILL.md",
127
127
  "description": "Skill de orquestación de Superpowers skills",
128
- "version": "0.5.8",
128
+ "version": "0.5.10",
129
129
  "sha256": "fba542003aa788b5edb4ae2d1135773a1dde8111d1dd60378e2f61b6695d46fc"
130
130
  },
131
131
  {
132
132
  "name": "writing-skills",
133
133
  "file": "assets/skills/writing-skills/SKILL.md",
134
134
  "description": "Skill de creación y edición de skills",
135
- "version": "0.5.8",
135
+ "version": "0.5.10",
136
136
  "sha256": "7f74ffe049283803640d78f8c9ac1ea5f5e626dedb885ecff29a87c5b5d41598"
137
137
  }
138
138
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ostacky",
3
- "version": "0.5.8",
3
+ "version": "0.5.10",
4
4
  "description": "Instalador interactivo de agentes y comandos para OpenCode",
5
5
  "type": "module",
6
6
  "bin": {