@chinhae/atw-cli 0.2.6 → 0.2.8

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 (29) hide show
  1. package/dist/migrations/manifests/0.2.7.json +9 -0
  2. package/dist/migrations/manifests/0.2.8.json +9 -0
  3. package/dist/templates/atw/scripts/common/active_task.py +5 -7
  4. package/dist/templates/common/bundled-skills/atw-askme/agents/openai.yaml +1 -1
  5. package/dist/templates/common/bundled-skills/atw-askme-with-docs/agents/openai.yaml +1 -1
  6. package/dist/templates/common/bundled-skills/atw-code-review/agents/openai.yaml +1 -1
  7. package/dist/templates/common/bundled-skills/atw-codebase-design/agents/openai.yaml +1 -1
  8. package/dist/templates/common/bundled-skills/atw-diagnosing-bugs/SKILL.md +1 -1
  9. package/dist/templates/common/bundled-skills/atw-diagnosing-bugs/agents/openai.yaml +1 -1
  10. package/dist/templates/common/bundled-skills/atw-domain-modeling/SKILL.md +1 -1
  11. package/dist/templates/common/bundled-skills/atw-domain-modeling/agents/openai.yaml +1 -1
  12. package/dist/templates/common/bundled-skills/atw-handoff/agents/openai.yaml +1 -1
  13. package/dist/templates/common/bundled-skills/atw-implement/agents/openai.yaml +1 -1
  14. package/dist/templates/common/bundled-skills/atw-improve-codebase-architecture/SKILL.md +1 -1
  15. package/dist/templates/common/bundled-skills/atw-improve-codebase-architecture/agents/openai.yaml +1 -1
  16. package/dist/templates/common/bundled-skills/atw-interview/agents/openai.yaml +1 -1
  17. package/dist/templates/common/bundled-skills/atw-map/agents/openai.yaml +1 -1
  18. package/dist/templates/common/bundled-skills/atw-merge/agents/openai.yaml +1 -1
  19. package/dist/templates/common/bundled-skills/atw-prototype/agents/openai.yaml +1 -1
  20. package/dist/templates/common/bundled-skills/atw-research/agents/openai.yaml +1 -1
  21. package/dist/templates/common/bundled-skills/atw-spec/agents/openai.yaml +1 -1
  22. package/dist/templates/common/bundled-skills/atw-tdd/agents/openai.yaml +1 -1
  23. package/dist/templates/common/bundled-skills/atw-tickets/SKILL.md +1 -1
  24. package/dist/templates/common/bundled-skills/atw-tickets/agents/openai.yaml +1 -1
  25. package/dist/templates/common/bundled-skills/atw-triage/SKILL.md +1 -1
  26. package/dist/templates/common/bundled-skills/atw-triage/agents/openai.yaml +1 -1
  27. package/dist/templates/pi/extensions/atw/index.ts.txt +75 -8
  28. package/dist/templates/shared-hooks/inject-subagent-context.py +5 -1
  29. package/package.json +2 -2
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": "0.2.7",
3
+ "description": "Active-task pointers are no longer borrowed by count (#608 upstream); the Pi extension inherits a pointer only through the window's own session lineage and tells a fresh window to bind the sole in-progress task instead of asking.",
4
+ "breaking": false,
5
+ "recommendMigrate": false,
6
+ "changelog": "resolve_active_task() no longer treats a sole runtime session file as proof of ownership: allow_single_session_fallback defaults to False and only inject-subagent-context.py opts in, because copilot/gemini/qoder child agents cannot inherit a parent session id (mirrors upstream Trellis #608). Main sessions on every platform therefore see no active task until task.py start binds the window. The Pi extension now moves the previous session's pointer to the new session when session_start reports reason=new or fork with a previousSessionFile — the lineage Pi itself declares for /clear and fork — so a /clear in the same window keeps its task without counting pointer files or trusting key prefixes (#512 holds). Its startup <ready> block also tells a fresh window with exactly one implement/accept-stage task assigned to the developer to run task.py start for it rather than asking whether to create a task, and to ask when several qualify or the only candidate is still in planning.",
7
+ "migrations": [],
8
+ "notes": "No migration required. Changed files are all tracked by atw update: .atw/scripts/common/active_task.py, the shared inject-subagent-context.py hook under each platform directory, and .pi/extensions/atw/index.ts. Expect one explicit task.py start per new window on every platform from now on; a Pi /clear keeps the binding automatically."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": "0.2.8",
3
+ "description": "Bundled skills now present themselves as ATW instead of Oxyteam in Codex and every other skill picker.",
4
+ "breaking": false,
5
+ "recommendMigrate": false,
6
+ "changelog": "The display_name in every bundled skill's agents/openai.yaml still read \"Oxyteam …\", so Codex listed the skills under a name that no longer exists; all 18 now read \"ATW …\" (ATW Implement, ATW Spec, ATW Triage, …), matching the ATW Meta / ATW Session Insight convention already used elsewhere. Five SKILL.md headings (diagnosing-bugs, domain-modeling, improve-codebase-architecture, tickets, triage) carried the same stale title and were renamed alongside. No behaviour change.",
7
+ "migrations": [],
8
+ "notes": "No migration required. The 23 touched files live under the bundled skills that atw update tracks in every platform directory (.agents, .claude, .cursor, .omp, .opencode, .codex where present), so a plain atw update picks them up as Template updated."
9
+ }
@@ -648,17 +648,15 @@ def resolve_active_task(
648
648
  platform_input: dict[str, Any] | None = None,
649
649
  platform: str | None = None,
650
650
  *,
651
- allow_single_session_fallback: bool = True,
651
+ allow_single_session_fallback: bool = False,
652
652
  allow_environment_context: bool = True,
653
653
  ) -> ActiveTask:
654
654
  """Resolve the active task from session runtime state only.
655
655
 
656
- A stale session task is returned as stale. Missing context identity or a
657
- missing/empty session context falls back to single-session inference: if
658
- exactly one session file exists in the runtime, return its task with
659
- source_type="session-fallback" — covers pull-based platform sub-agents
660
- (copilot, gemini, qoder) that don't inherit the parent's session id. ≥2
661
- files or 0 files yield ActiveTask(None) — refuses to guess across windows.
656
+ A stale session task is returned as stale.
657
+ Missing or unmatched session identity does not infer ownership from the
658
+ number of session files. Pull-based child-agent callers that cannot inherit
659
+ a parent identity must opt into the compatibility fallback explicitly.
662
660
  """
663
661
  context_key = resolve_context_key(
664
662
  platform_input,
@@ -1,5 +1,5 @@
1
1
  interface:
2
- display_name: "Oxyteam Ask Me"
2
+ display_name: "ATW Ask Me"
3
3
  short_description: "通过访谈完善计划"
4
4
  policy:
5
5
  allow_implicit_invocation: false
@@ -1,5 +1,5 @@
1
1
  interface:
2
- display_name: "Oxyteam Ask Me with Docs"
2
+ display_name: "ATW Ask Me with Docs"
3
3
  short_description: "设计访谈计划并编写其文档"
4
4
  policy:
5
5
  allow_implicit_invocation: false
@@ -1,3 +1,3 @@
1
1
  interface:
2
- display_name: "Oxyteam Code Review"
2
+ display_name: "ATW Code Review"
3
3
  short_description: "审查标准和规范的差异"
@@ -1,3 +1,3 @@
1
1
  interface:
2
- display_name: "Oxyteam Codebase Design"
2
+ display_name: "ATW Codebase Design"
3
3
  short_description: "深度模块设计术语"
@@ -3,7 +3,7 @@ name: atw-diagnosing-bugs
3
3
  description: "用于排查顽固错误和性能退化的诊断循环。当用户说“诊断”/“调试这个”,或报告某功能失效、抛出异常、失败或运行缓慢时,请使用此循环。"
4
4
  ---
5
5
 
6
- # Oxyteam Diagnosing Bugs
6
+ # ATW Diagnosing Bugs
7
7
 
8
8
  A discipline for hard bugs. Skip phases only when explicitly justified.
9
9
 
@@ -1,3 +1,3 @@
1
1
  interface:
2
- display_name: "Oxyteam Diagnosing Bugs"
2
+ display_name: "ATW Diagnosing Bugs"
3
3
  short_description: "诊断棘手的问题和回归问题"
@@ -3,7 +3,7 @@ name: atw-domain-modeling
3
3
  description: "构建并完善项目的领域模型。适用于讨论代码库术语、编写或编辑 CONTEXT.md 文件,以及记录或编辑 ADR 时。"
4
4
  ---
5
5
 
6
- # Oxyteam Domain Modeling
6
+ # ATW Domain Modeling
7
7
 
8
8
  Actively build and sharpen the project's domain model as you design. This is the *active* discipline — challenging terms, inventing edge-case scenarios, and writing the glossary and decisions down the moment they crystallise. (Merely *reading* `CONTEXT.md` for vocabulary is not this skill — that's a one-line habit any skill can do. This skill is for when you're changing the model, not just consuming it.)
9
9
 
@@ -1,3 +1,3 @@
1
1
  interface:
2
- display_name: "Oxyteam Domain Modeling"
2
+ display_name: "ATW Domain Modeling"
3
3
  short_description: "构建并优化领域模型"
@@ -1,5 +1,5 @@
1
1
  interface:
2
- display_name: "Oxyteam Handoff"
2
+ display_name: "ATW Handoff"
3
3
  short_description: "将一次对话浓缩为一次交接"
4
4
  policy:
5
5
  allow_implicit_invocation: false
@@ -1,5 +1,5 @@
1
1
  interface:
2
- display_name: "Oxyteam Implement"
2
+ display_name: "ATW Implement"
3
3
  short_description: "根据规格说明或工单进行开发"
4
4
  policy:
5
5
  allow_implicit_invocation: false
@@ -4,7 +4,7 @@ description: "扫描代码库以发现可深入挖掘的机会,将其以可视
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
- # Oxyteam Improve Codebase Architecture
7
+ # ATW Improve Codebase Architecture
8
8
 
9
9
  Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability.
10
10
 
@@ -1,5 +1,5 @@
1
1
  interface:
2
- display_name: "Oxyteam Improve Codebase Architecture"
2
+ display_name: "ATW Improve Codebase Architecture"
3
3
  short_description: "发现并深入访谈架构改进机会"
4
4
  policy:
5
5
  allow_implicit_invocation: false
@@ -1,3 +1,3 @@
1
1
  interface:
2
- display_name: "Oxyteam Interview"
2
+ display_name: "ATW Interview"
3
3
  short_description: "通过决策树逐轮澄清计划、方案和关键决策"
@@ -1,5 +1,5 @@
1
1
  interface:
2
- display_name: "Oxyteam Map"
2
+ display_name: "ATW Map"
3
3
  short_description: "将大型工作任务映射为决策工单"
4
4
  policy:
5
5
  allow_implicit_invocation: false
@@ -1,3 +1,3 @@
1
1
  interface:
2
- display_name: "Oxyteam Merge"
2
+ display_name: "ATW Merge"
3
3
  short_description: "解决合并与变基冲突"
@@ -1,3 +1,3 @@
1
1
  interface:
2
- display_name: "Oxyteam Prototype"
2
+ display_name: "ATW Prototype"
3
3
  short_description: "用于解答设计问题的原型"
@@ -1,3 +1,3 @@
1
1
  interface:
2
- display_name: "Oxyteam Research"
2
+ display_name: "ATW Research"
3
3
  short_description: "来自高可信度来源的研究"
@@ -1,5 +1,5 @@
1
1
  interface:
2
- display_name: "Oxyteam Spec"
2
+ display_name: "ATW Spec"
3
3
  short_description: "将对话转化为技术规格书"
4
4
  policy:
5
5
  allow_implicit_invocation: false
@@ -1,3 +1,3 @@
1
1
  interface:
2
- display_name: "Oxyteam TDD"
2
+ display_name: "ATW TDD"
3
3
  short_description: "测试驱动的红-绿-重构"
@@ -4,7 +4,7 @@ description: "将计划、规格说明或当前对话拆分为一组“追踪子
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
- # Oxyteam Tickets
7
+ # ATW Tickets
8
8
 
9
9
  Break a plan, spec, or conversation into a set of **tickets** — tracer-bullet vertical slices, each declaring the tickets that **block** it.
10
10
 
@@ -1,5 +1,5 @@
1
1
  interface:
2
- display_name: "Oxyteam Tickets"
2
+ display_name: "ATW Tickets"
3
3
  short_description: "将计划拆分为“追踪子弹”工单"
4
4
  policy:
5
5
  allow_implicit_invocation: false
@@ -4,7 +4,7 @@ description: "通过由分流角色组成的状态机处理问题和外部PR—
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
- # Oxyteam Triage
7
+ # ATW Triage
8
8
 
9
9
  Move issues on the project issue tracker through a small state machine of triage roles.
10
10
 
@@ -1,5 +1,5 @@
1
1
  interface:
2
- display_name: "Oxyteam Triage"
2
+ display_name: "ATW Triage"
3
3
  short_description: "通过分诊角色处理问题"
4
4
  policy:
5
5
  allow_implicit_invocation: false
@@ -1,4 +1,13 @@
1
- import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
1
+ import {
2
+ closeSync,
3
+ existsSync,
4
+ openSync,
5
+ readFileSync,
6
+ readSync,
7
+ realpathSync,
8
+ renameSync,
9
+ statSync,
10
+ } from "node:fs";
2
11
  import { createHash, randomBytes } from "node:crypto";
3
12
  import {
4
13
  delimiter,
@@ -1055,17 +1064,19 @@ function parseAgentFM(c: string): AgentConfig {
1055
1064
  return cfg;
1056
1065
  }
1057
1066
 
1067
+ function keyForSessionId(sessionId: string): string {
1068
+ const normalized = sessionId.replace(/[^A-Za-z0-9._-]+/g, "_");
1069
+ if (!normalized) return `pi_${hash(sessionId)}`;
1070
+ return `pi_${normalized}${normalized === sessionId ? "" : `_${hash(sessionId)}`}`;
1071
+ }
1072
+
1058
1073
  function contextKey(input?: unknown, ctx?: PiExtensionContext): string | null {
1059
1074
  const sessionId =
1060
1075
  callStr(ctx?.sessionManager?.getSessionId, ctx?.sessionManager) ??
1061
1076
  str(process.env.PI_SESSION_ID) ??
1062
1077
  str(process.env.PI_SESSIONID) ??
1063
1078
  lookupStr(input, ["session_id", "sessionId", "sessionID"]);
1064
- if (sessionId) {
1065
- const normalized = sessionId.replace(/[^A-Za-z0-9._-]+/g, "_");
1066
- if (!normalized) return `pi_${hash(sessionId)}`;
1067
- return `pi_${normalized}${normalized === sessionId ? "" : `_${hash(sessionId)}`}`;
1068
- }
1079
+ if (sessionId) return keyForSessionId(sessionId);
1069
1080
  const transcriptPath =
1070
1081
  callStr(ctx?.sessionManager?.getSessionFile, ctx?.sessionManager) ??
1071
1082
  lookupStr(input, ["transcript_path", "transcriptPath", "transcript"]);
@@ -1117,6 +1128,55 @@ function readTaskDir(root: string, key: string | null): string | null {
1117
1128
  }
1118
1129
  }
1119
1130
 
1131
+ // ── Session lineage ───────────────────────────────────────────────────
1132
+ // 只读 pi 会话文件的首行头部拿会话 id;会话文件可能很大,不整读。
1133
+ function readSessionHeaderId(sessionFile: string): string | null {
1134
+ let fd: number | null = null;
1135
+ try {
1136
+ fd = openSync(sessionFile, "r");
1137
+ const buf = Buffer.alloc(65536);
1138
+ const n = readSync(fd, buf, 0, buf.length, 0);
1139
+ const line = buf.toString("utf-8", 0, n).split("\n")[0] ?? "";
1140
+ const header = JSON.parse(line) as JsonObject;
1141
+ return header.type === "session" ? str(header.id) : null;
1142
+ } catch {
1143
+ return null;
1144
+ } finally {
1145
+ if (fd !== null) closeSync(fd);
1146
+ }
1147
+ }
1148
+
1149
+ // 同一窗口用 /clear(reason=new)或 fork 换出新会话时,把前一个会话的活动任务指针
1150
+ // 搬到新会话名下。只认 pi 在 session_start 事件里给出的 previousSessionFile 这条
1151
+ // 明确血缘,不看运行时目录里有几个指针、也不看前缀(#512)。新开的窗口
1152
+ // (reason=startup)没有前身,什么都不继承。搬移而非复制,避免 /clear 链条堆积指针。
1153
+ function inheritSessionPointer(
1154
+ root: string,
1155
+ key: string | null,
1156
+ event: unknown,
1157
+ ): boolean {
1158
+ if (!key || !isObj(event)) return false;
1159
+ const reason = str(event.reason);
1160
+ const previous = str(event.previousSessionFile);
1161
+ if ((reason !== "new" && reason !== "fork") || !previous) return false;
1162
+ const parentId = readSessionHeaderId(previous);
1163
+ if (!parentId) return false;
1164
+ const parentKey = keyForSessionId(parentId);
1165
+ if (parentKey === key) return false;
1166
+ const dir = join(root, ".atw", ".runtime", "sessions");
1167
+ const ownPath = join(dir, `${key}.json`);
1168
+ const parentPath = join(dir, `${parentKey}.json`);
1169
+ try {
1170
+ if (existsSync(ownPath) || !existsSync(parentPath)) return false;
1171
+ const ctx = JSON.parse(readText(parentPath)) as JsonObject;
1172
+ if (!str(ctx.current_task)) return false;
1173
+ renameSync(parentPath, ownPath);
1174
+ return true;
1175
+ } catch {
1176
+ return false;
1177
+ }
1178
+ }
1179
+
1120
1180
  // ── Workflow State Breadcrumb ─────────────────────────────────────────
1121
1181
  const WF_RE =
1122
1182
  /\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n([\s\S]*?)\n\s*\[\/workflow-state:\1\]/g;
@@ -1194,7 +1254,13 @@ function buildStartupContext(
1194
1254
  FIRST_REPLY_NOTICE,
1195
1255
  overview,
1196
1256
  workflow,
1197
- "<ready>\nUse the current workflow state to decide whether to create, continue, or skip an ATW task.\n</ready>",
1257
+ [
1258
+ "<ready>",
1259
+ "Use the current workflow state to decide whether to create, continue, or skip an ATW task.",
1260
+ "When CURRENT TASK is (none) but ACTIVE TASKS lists exactly one task assigned to you that is already at the implement or accept stage, this window is merely not bound to it yet: run `python3 ./.atw/scripts/task.py start <task-dir>` for that task, then continue from its stage. Do not ask whether to create a new task in that case.",
1261
+ "If several tasks qualify, or the only candidate is still in a planning stage (discover / specify / slice), ask which task to bind instead of running start: start moves a planning-stage task straight into implement.",
1262
+ "</ready>",
1263
+ ].join("\n"),
1198
1264
  ]
1199
1265
  .filter(Boolean)
1200
1266
  .join("\n\n");
@@ -1916,7 +1982,8 @@ export default function atwExtension(pi: {
1916
1982
 
1917
1983
  // Events
1918
1984
  pi.on?.("session_start", (event, ctx) => {
1919
- getKey(event, ctx);
1985
+ const k = getKey(event, ctx);
1986
+ inheritSessionPointer(resolveRoot(ctx), k, event);
1920
1987
  ctx?.ui?.notify?.(
1921
1988
  "ATW project context is available. Use /atw-start to bootstrap or /atw-continue to resume.",
1922
1989
  "info",
@@ -1093,7 +1093,11 @@ def main():
1093
1093
 
1094
1094
  # Get current task directory. May be None here — the AGENTS_REQUIRE_TASK
1095
1095
  # check below is what refuses to continue without one.
1096
- task_dir = get_current_task(repo_root, input_data)
1096
+ task_dir = get_current_task(
1097
+ repo_root,
1098
+ input_data,
1099
+ allow_single_session_fallback=True,
1100
+ )
1097
1101
 
1098
1102
  # implement/check need task directory
1099
1103
  if subagent_type in AGENTS_REQUIRE_TASK:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chinhae/atw-cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "A Team Workflow — an AI coding workflow framework that encodes your team's conventions into the process",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "inquirer": "^9.3.7",
34
34
  "undici": "^6.21.0",
35
35
  "zod": "^4.4.2",
36
- "@chinhae/atw-core": "0.2.6"
36
+ "@chinhae/atw-core": "0.2.8"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@eslint/js": "^9.18.0",