@chinhae/atw-cli 0.2.9 → 0.3.0
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 +29 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +243 -123
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +18 -3
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts +0 -3
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +45 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/index.d.ts +8 -0
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +19 -0
- package/dist/configurators/index.js.map +1 -1
- package/dist/migrations/manifests/0.2.10.json +9 -0
- package/dist/migrations/manifests/0.3.0.json +3410 -0
- package/dist/templates/atw/config.yaml +4 -3
- package/dist/templates/atw/index.d.ts +2 -0
- package/dist/templates/atw/index.d.ts.map +1 -1
- package/dist/templates/atw/index.js +4 -0
- package/dist/templates/atw/index.js.map +1 -1
- package/dist/templates/atw/scripts/common/noob_mode.py +102 -0
- package/dist/templates/atw/scripts/common/session_context.py +7 -0
- package/dist/templates/atw/scripts/noob_mode.py +41 -0
- package/dist/templates/atw/scripts/tickets.py +66 -10
- package/dist/templates/atw/workflow.md +81 -17
- package/dist/templates/common/bundled-skills/atw-code-review/SKILL.md +4 -2
- package/dist/templates/common/bundled-skills/atw-codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/dist/templates/common/bundled-skills/atw-diagnosing-bugs/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/atw-domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/dist/templates/common/bundled-skills/atw-domain-modeling/SKILL.md +12 -12
- package/dist/templates/common/bundled-skills/atw-handoff/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-implement/SKILL.md +6 -4
- package/dist/templates/common/bundled-skills/atw-implement-spec/SKILL.md +79 -0
- package/dist/templates/common/bundled-skills/atw-implement-spec/agents/openai.yaml +5 -0
- package/dist/templates/common/bundled-skills/atw-improve-codebase-architecture/SKILL.md +5 -5
- package/dist/templates/common/bundled-skills/atw-init-repo/SKILL.md +6 -4
- package/dist/templates/common/bundled-skills/atw-init-repo/references/domain.md +8 -8
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-atw-local.md +5 -2
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-atw.md +5 -2
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-github.md +5 -4
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-gitlab.md +2 -2
- package/dist/templates/common/bundled-skills/atw-init-repo/references/triage-labels.md +1 -1
- package/dist/templates/common/bundled-skills/atw-interview/SKILL.md +9 -1
- package/dist/templates/common/bundled-skills/atw-map/SKILL.md +5 -5
- package/dist/templates/common/bundled-skills/atw-noob-mode/SKILL.md +101 -0
- package/dist/templates/common/bundled-skills/atw-noob-mode/agents/openai.yaml +3 -0
- package/dist/templates/common/bundled-skills/atw-prototype/scripts/build_explorer.py +15 -9
- package/dist/templates/common/bundled-skills/atw-spec/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-tdd/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/atw-tickets/SKILL.md +4 -4
- package/dist/templates/common/bundled-skills/atw-triage/SKILL.md +2 -2
- package/dist/templates/common/commands/continue.md +1 -1
- package/dist/templates/opencode/plugins/inject-workflow-state.js +29 -1
- package/dist/templates/shared-hooks/inject-subagent-context.py +29 -3
- package/dist/templates/shared-hooks/inject-workflow-state.py +27 -3
- package/dist/templates/shared-hooks/session-start.py +23 -0
- package/dist/templates/snow/SNOW.md +1 -1
- package/dist/utils/file-writer.d.ts.map +1 -1
- package/dist/utils/file-writer.js +29 -16
- package/dist/utils/file-writer.js.map +1 -1
- package/dist/utils/interactive-guard.d.ts +2 -2
- package/dist/utils/interactive-guard.js +2 -2
- package/dist/utils/lang.d.ts +14 -0
- package/dist/utils/lang.d.ts.map +1 -0
- package/dist/utils/lang.js +20 -0
- package/dist/utils/lang.js.map +1 -0
- package/dist/utils/optional-skills.d.ts +90 -0
- package/dist/utils/optional-skills.d.ts.map +1 -0
- package/dist/utils/optional-skills.js +266 -0
- package/dist/utils/optional-skills.js.map +1 -0
- package/dist/utils/template-fetcher.d.ts +6 -0
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +1 -1
- package/dist/utils/template-fetcher.js.map +1 -1
- package/package.json +2 -2
- package/dist/templates/common/bundled-skills/atw-merge/SKILL.md +0 -14
- package/dist/templates/common/bundled-skills/atw-merge/agents/openai.yaml +0 -3
- package/dist/templates/common/bundled-skills/dejargonizer/SKILL.md +0 -133
- package/dist/templates/common/bundled-skills/noob-mode/SKILL.md +0 -175
- package/dist/templates/common/bundled-skills/noob-mode/references/examples.md +0 -418
- package/dist/templates/common/bundled-skills/noob-mode/references/glossary.md +0 -368
- package/dist/templates/common/bundled-skills/oxy-explanation/SKILL.md +0 -80
- package/dist/templates/common/bundled-skills/oxy-learning-hub/SKILL.md +0 -179
- package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/course.css +0 -1044
- package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/course.js +0 -184
- package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/lesson-template.html +0 -125
- package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/summary-template.html +0 -659
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/publishing.md +0 -64
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/summary-mode.md +0 -96
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/GLOSSARY-FORMAT.md +0 -30
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/LEARNING-RECORD-FORMAT.md +0 -42
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/MISSION-FORMAT.md +0 -50
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/RESOURCES-FORMAT.md +0 -40
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching-mode.md +0 -251
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/workspace.md +0 -112
- package/dist/templates/common/bundled-skills/oxy-learning-hub/scripts/validate_html.py +0 -198
- package/dist/templates/common/bundled-skills/show-me/SKILL.md +0 -127
|
@@ -111,9 +111,10 @@ channel:
|
|
|
111
111
|
# Codex (dispatch behavior)
|
|
112
112
|
#-------------------------------------------------------------------------------
|
|
113
113
|
# Codex-only knob; other platforms ignore it. Default ("auto") dispatches
|
|
114
|
-
# atw-review sub-agents for the two review axes.
|
|
115
|
-
# dispatched on any platform: it runs inside
|
|
116
|
-
# session (`atw-implement-agent` ships only as a
|
|
114
|
+
# atw-review sub-agents for the two review axes. On the per-ticket path
|
|
115
|
+
# implementation is never dispatched on any platform: it runs inside
|
|
116
|
+
# `/atw-implement` in the main session (`atw-implement-agent` ships only as a
|
|
117
|
+
# reserved role). Only a `/atw-implement-spec` run dispatches implementers. This does
|
|
117
118
|
# not rely on inherited parent transcripts: `fork_turns` remains
|
|
118
119
|
# caller-controlled, while Codex's native SubagentStart hook injects task
|
|
119
120
|
# context when trusted and child-side loading remains the fallback when it is
|
|
@@ -40,8 +40,10 @@ export declare const commonPackagesContext: string;
|
|
|
40
40
|
export declare const commonWorkflowPhase: string;
|
|
41
41
|
export declare const commonAtwConfig: string;
|
|
42
42
|
export declare const commonSafeCommit: string;
|
|
43
|
+
export declare const commonNoobMode: string;
|
|
43
44
|
export declare const getDeveloperScript: string;
|
|
44
45
|
export declare const initDeveloperScript: string;
|
|
46
|
+
export declare const noobModeScript: string;
|
|
45
47
|
export declare const taskScript: string;
|
|
46
48
|
export declare const getContextScript: string;
|
|
47
49
|
export declare const addSessionScript: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/atw/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAcH,eAAO,MAAM,WAAW,QAAsC,CAAC;AAG/D,eAAO,MAAM,UAAU,QAA6C,CAAC;AACrE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,eAAe,QAA8C,CAAC;AAC3E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,YAAY,QAA2C,CAAC;AACrE,eAAO,MAAM,QAAQ,QAAuC,CAAC;AAC7D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,iBAAiB,QAAiD,CAAC;AAChF,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AACF,eAAO,MAAM,qBAAqB,QAEjC,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AACF,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/atw/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAcH,eAAO,MAAM,WAAW,QAAsC,CAAC;AAG/D,eAAO,MAAM,UAAU,QAA6C,CAAC;AACrE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,eAAe,QAA8C,CAAC;AAC3E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,YAAY,QAA2C,CAAC;AACrE,eAAO,MAAM,QAAQ,QAAuC,CAAC;AAC7D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,iBAAiB,QAAiD,CAAC;AAChF,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AACF,eAAO,MAAM,qBAAqB,QAEjC,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AACF,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,cAAc,QAA8C,CAAC;AAG1E,eAAO,MAAM,kBAAkB,QAA2C,CAAC;AAC3E,eAAO,MAAM,mBAAmB,QAA4C,CAAC;AAC7E,eAAO,MAAM,cAAc,QAAuC,CAAC;AACnE,eAAO,MAAM,UAAU,QAAkC,CAAC;AAC1D,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AACvE,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AACvE,eAAO,MAAM,aAAa,QAAqC,CAAC;AAChE,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AACvE,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAGF,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAC9D,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAC9D,eAAO,MAAM,iBAAiB,QAAgC,CAAC;AAC/D,eAAO,MAAM,qBAAqB,QAAoC,CAAC;AAMvE,eAAO,MAAM,sBAAsB,QAA4C,CAAC;AAChF,eAAO,MAAM,mBAAmB,QAAmC,CAAC;AAEpE;;GAEG;AACH,wBAAgB,aAAa,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAoDnD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAKlD"}
|
|
@@ -50,9 +50,11 @@ export const commonPackagesContext = readTemplate("scripts/common/packages_conte
|
|
|
50
50
|
export const commonWorkflowPhase = readTemplate("scripts/common/workflow_phase.py");
|
|
51
51
|
export const commonAtwConfig = readTemplate("scripts/common/atw_config.py");
|
|
52
52
|
export const commonSafeCommit = readTemplate("scripts/common/safe_commit.py");
|
|
53
|
+
export const commonNoobMode = readTemplate("scripts/common/noob_mode.py");
|
|
53
54
|
// Python scripts - main
|
|
54
55
|
export const getDeveloperScript = readTemplate("scripts/get_developer.py");
|
|
55
56
|
export const initDeveloperScript = readTemplate("scripts/init_developer.py");
|
|
57
|
+
export const noobModeScript = readTemplate("scripts/noob_mode.py");
|
|
56
58
|
export const taskScript = readTemplate("scripts/task.py");
|
|
57
59
|
export const getContextScript = readTemplate("scripts/get_context.py");
|
|
58
60
|
export const addSessionScript = readTemplate("scripts/add_session.py");
|
|
@@ -99,9 +101,11 @@ export function getAllScripts() {
|
|
|
99
101
|
scripts.set("common/workflow_phase.py", commonWorkflowPhase);
|
|
100
102
|
scripts.set("common/atw_config.py", commonAtwConfig);
|
|
101
103
|
scripts.set("common/safe_commit.py", commonSafeCommit);
|
|
104
|
+
scripts.set("common/noob_mode.py", commonNoobMode);
|
|
102
105
|
// Main
|
|
103
106
|
scripts.set("get_developer.py", getDeveloperScript);
|
|
104
107
|
scripts.set("init_developer.py", initDeveloperScript);
|
|
108
|
+
scripts.set("noob_mode.py", noobModeScript);
|
|
105
109
|
scripts.set("task.py", taskScript);
|
|
106
110
|
scripts.set("get_context.py", getContextScript);
|
|
107
111
|
scripts.set("add_session.py", addSessionScript);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/atw/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,SAAS,YAAY,CAAC,YAAoB;IACxC,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AAED,gCAAgC;AAChC,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,qBAAqB,CAAC,CAAC;AAE/D,0BAA0B;AAC1B,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC,4BAA4B,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,6BAA6B,CAAC,CAAC;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,+BAA+B,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,8BAA8B,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,8BAA8B,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,+BAA+B,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,+BAA+B,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,YAAY,GAAG,YAAY,CAAC,0BAA0B,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;AAC7D,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC,uBAAuB,CAAC,CAAC;AAC/D,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC,uBAAuB,CAAC,CAAC;AAC/D,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAC,gCAAgC,CAAC,CAAC;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,8BAA8B,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAC9C,mCAAmC,CACpC,CAAC;AACF,MAAM,CAAC,MAAM,qBAAqB,GAAG,YAAY,CAC/C,oCAAoC,CACrC,CAAC;AACF,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAC7C,kCAAkC,CACnC,CAAC;AACF,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,8BAA8B,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,+BAA+B,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/atw/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Noob mode — a per-developer switch for plain-language replies.
|
|
2
|
+
|
|
3
|
+
A non-technical user turns it on once (the `atw-noob-mode` skill runs
|
|
4
|
+
`noob_mode.py on`). From then on the hooks that already speak to the AI at
|
|
5
|
+
session start and on every turn append a short reminder, so the mode holds
|
|
6
|
+
across long conversations, compaction and new sessions instead of fading the
|
|
7
|
+
way a skill loaded once does.
|
|
8
|
+
|
|
9
|
+
The switch is a `noob_mode=on` line in `.atw/.developer`. That file is
|
|
10
|
+
gitignored and personal by design, so one developer's choice never reaches a
|
|
11
|
+
teammate, and no new ignore rule is needed in existing projects.
|
|
12
|
+
|
|
13
|
+
The reminder text itself lives in workflow.md, in a
|
|
14
|
+
`[workflow-state:noob_mode]` block next to the per-status breadcrumbs — the
|
|
15
|
+
JS/TS hook ports already parse those blocks, so every platform reads one copy.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from .git import main_worktree_root
|
|
24
|
+
from .paths import DIR_WORKFLOW, FILE_DEVELOPER
|
|
25
|
+
|
|
26
|
+
FIELD = "noob_mode"
|
|
27
|
+
BREADCRUMB_TAG = "noob_mode"
|
|
28
|
+
|
|
29
|
+
# Used when workflow.md has no noob_mode block (a custom workflow template, or
|
|
30
|
+
# a workflow.md older than this feature): the mode still has to take effect.
|
|
31
|
+
FALLBACK_REMINDER = (
|
|
32
|
+
"Noob mode is ON — this user is not an engineer. Hold to the "
|
|
33
|
+
"`/atw-noob-mode` rules in every reply: plain words in the user's "
|
|
34
|
+
"language, and every term, command or path explained where it appears."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
_FIELD_RE = re.compile(rf"^{FIELD}=(.*)$", re.MULTILINE)
|
|
38
|
+
_TAG_RE = re.compile(
|
|
39
|
+
rf"\[workflow-state:{BREADCRUMB_TAG}\]\s*\n(.*?)\n\s*\[/workflow-state:{BREADCRUMB_TAG}\]",
|
|
40
|
+
re.DOTALL,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _developer_file(repo_root: Path) -> Path | None:
|
|
45
|
+
"""The identity file this checkout uses, following get_developer():
|
|
46
|
+
its own, else the main checkout's when this is a linked worktree."""
|
|
47
|
+
local = repo_root / DIR_WORKFLOW / FILE_DEVELOPER
|
|
48
|
+
if local.is_file():
|
|
49
|
+
return local
|
|
50
|
+
main_root = main_worktree_root(repo_root)
|
|
51
|
+
if main_root is None:
|
|
52
|
+
return None
|
|
53
|
+
inherited = main_root / DIR_WORKFLOW / FILE_DEVELOPER
|
|
54
|
+
return inherited if inherited.is_file() else None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def is_noob_mode_on(repo_root: Path) -> bool:
|
|
58
|
+
dev_file = _developer_file(repo_root)
|
|
59
|
+
if dev_file is None:
|
|
60
|
+
return False
|
|
61
|
+
try:
|
|
62
|
+
match = _FIELD_RE.search(dev_file.read_text(encoding="utf-8"))
|
|
63
|
+
except OSError:
|
|
64
|
+
return False
|
|
65
|
+
return match is not None and match.group(1).strip() == "on"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def set_noob_mode(repo_root: Path, on: bool) -> bool:
|
|
69
|
+
"""Switch noob mode for this developer.
|
|
70
|
+
|
|
71
|
+
Returns False when there is no identity file to record it in (run
|
|
72
|
+
init_developer.py first); the file's other lines are left as they are.
|
|
73
|
+
"""
|
|
74
|
+
dev_file = _developer_file(repo_root)
|
|
75
|
+
if dev_file is None:
|
|
76
|
+
return False
|
|
77
|
+
lines = [
|
|
78
|
+
line
|
|
79
|
+
for line in dev_file.read_text(encoding="utf-8").splitlines()
|
|
80
|
+
if not line.startswith(f"{FIELD}=")
|
|
81
|
+
]
|
|
82
|
+
if on:
|
|
83
|
+
lines.append(f"{FIELD}=on")
|
|
84
|
+
dev_file.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def noob_mode_reminder(repo_root: Path) -> str:
|
|
89
|
+
"""The `<noob-mode>` block to hand the AI, or "" while the mode is off."""
|
|
90
|
+
if not is_noob_mode_on(repo_root):
|
|
91
|
+
return ""
|
|
92
|
+
body = ""
|
|
93
|
+
try:
|
|
94
|
+
content = (repo_root / DIR_WORKFLOW / "workflow.md").read_text(
|
|
95
|
+
encoding="utf-8"
|
|
96
|
+
)
|
|
97
|
+
match = _TAG_RE.search(content)
|
|
98
|
+
if match:
|
|
99
|
+
body = match.group(1).strip()
|
|
100
|
+
except OSError:
|
|
101
|
+
pass
|
|
102
|
+
return f"<noob-mode>\n{body or FALLBACK_REMINDER}\n</noob-mode>"
|
|
@@ -24,6 +24,7 @@ from pathlib import Path
|
|
|
24
24
|
from .active_task import resolve_context_key
|
|
25
25
|
from .config import get_git_packages
|
|
26
26
|
from .git import run_git
|
|
27
|
+
from .noob_mode import noob_mode_reminder
|
|
27
28
|
from .packages_context import get_packages_section
|
|
28
29
|
from .tasks import iter_active_tasks, load_task, get_all_statuses, children_progress
|
|
29
30
|
from .paths import (
|
|
@@ -606,6 +607,12 @@ def get_context_text(repo_root: Path | None = None) -> str:
|
|
|
606
607
|
lines.append(f"Name: {developer}")
|
|
607
608
|
lines.append("")
|
|
608
609
|
|
|
610
|
+
# Platforms without a hook learn about noob mode from this output alone.
|
|
611
|
+
noob_mode = noob_mode_reminder(repo_root)
|
|
612
|
+
if noob_mode:
|
|
613
|
+
lines.append(noob_mode)
|
|
614
|
+
lines.append("")
|
|
615
|
+
|
|
609
616
|
root_git_info = _collect_root_git_info(repo_root)
|
|
610
617
|
_append_root_git_context(lines, root_git_info)
|
|
611
618
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Switch noob mode (plain-language replies) for the current developer.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python3 noob_mode.py on Turn it on
|
|
7
|
+
python3 noob_mode.py off Turn it off
|
|
8
|
+
python3 noob_mode.py status Show whether it is on
|
|
9
|
+
|
|
10
|
+
The switch is personal — see common/noob_mode.py.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
from common.noob_mode import is_noob_mode_on, set_noob_mode
|
|
18
|
+
from common.paths import DIR_SCRIPTS, DIR_WORKFLOW, FILE_DEVELOPER, get_repo_root
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main() -> None:
|
|
22
|
+
"""CLI entry point."""
|
|
23
|
+
action = sys.argv[1] if len(sys.argv) > 1 else "status"
|
|
24
|
+
if action not in ("on", "off", "status"):
|
|
25
|
+
print(f"Usage: {sys.argv[0]} on|off|status", file=sys.stderr)
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
repo_root = get_repo_root()
|
|
29
|
+
if action != "status" and not set_noob_mode(repo_root, action == "on"):
|
|
30
|
+
print(
|
|
31
|
+
f"Error: no {DIR_WORKFLOW}/{FILE_DEVELOPER} to record noob mode in. "
|
|
32
|
+
f"Run: python3 ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <name>",
|
|
33
|
+
file=sys.stderr,
|
|
34
|
+
)
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
print(f"Noob mode: {'on' if is_noob_mode_on(repo_root) else 'off'}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
main()
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
claim <NN> 校验在 frontier 里 → Impl: doing
|
|
7
7
|
→ 记 implementation_base_sha
|
|
8
8
|
→ 把该票路径写进 <task>/check.jsonl
|
|
9
|
+
claim <NN> --parallel 并行批次认领(/atw-implement-spec 用):允许多张票同时
|
|
10
|
+
doing,不写 check.jsonl 的当前票行,meta.ticket_mode = parallel
|
|
9
11
|
done <NN> 标记 Impl: done,并撤掉 check.jsonl 里的那行
|
|
10
12
|
summary 汇总一行,给 workflow-state 用
|
|
11
13
|
selfcheck 跑内置断言,不依赖仓库状态
|
|
@@ -14,7 +16,7 @@
|
|
|
14
16
|
Blocker 引用不存在 → 失败
|
|
15
17
|
Blocker 形成环 → 失败
|
|
16
18
|
claim 不在 frontier 里的票 → 失败
|
|
17
|
-
已有别的票在 doing →
|
|
19
|
+
已有别的票在 doing → 失败(票默认串行;--parallel 除外)
|
|
18
20
|
|
|
19
21
|
不要把这些函数塞进 task_utils.py / task.py:票是任务目录里的一层,任务目录和
|
|
20
22
|
meta 的读写一律走 task.py,本脚本不直接写 task.json。
|
|
@@ -44,6 +46,12 @@ RE_BLOCKER = re.compile(r"#?(\d+)")
|
|
|
44
46
|
MANAGED_REASON = "ATW current ticket"
|
|
45
47
|
MANIFEST_NAME = "check.jsonl"
|
|
46
48
|
|
|
49
|
+
# 并行批次的标记写在 task.json 的 meta 里 —— 子代理注入 hook 靠它区分
|
|
50
|
+
# 「并行跑着多张票」和「票状态自相矛盾」。
|
|
51
|
+
META_TICKET_MODE = "ticket_mode"
|
|
52
|
+
MODE_PARALLEL = "parallel"
|
|
53
|
+
MODE_SERIAL = "serial"
|
|
54
|
+
|
|
47
55
|
|
|
48
56
|
def field_pattern(name: str) -> re.Pattern[str]:
|
|
49
57
|
"""匹配 `**Impl:** doing` 这类字段行。"""
|
|
@@ -199,6 +207,17 @@ def task_dir(root: Path, override: str | None) -> Path:
|
|
|
199
207
|
return (root / rel).resolve()
|
|
200
208
|
|
|
201
209
|
|
|
210
|
+
def ticket_mode(task: Path) -> str:
|
|
211
|
+
"""只读 meta.ticket_mode;写一律走 task.py set-meta。"""
|
|
212
|
+
try:
|
|
213
|
+
data = json.loads((task / "task.json").read_text(encoding="utf-8"))
|
|
214
|
+
except (OSError, json.JSONDecodeError):
|
|
215
|
+
return MODE_SERIAL
|
|
216
|
+
meta = data.get("meta") if isinstance(data, dict) else None
|
|
217
|
+
mode = meta.get(META_TICKET_MODE) if isinstance(meta, dict) else None
|
|
218
|
+
return MODE_PARALLEL if mode == MODE_PARALLEL else MODE_SERIAL
|
|
219
|
+
|
|
220
|
+
|
|
202
221
|
def set_meta(root: Path, task: Path, key: str, value: str) -> None:
|
|
203
222
|
run(
|
|
204
223
|
[
|
|
@@ -289,14 +308,10 @@ def cmd_frontier(tickets: list[Ticket], **_) -> int:
|
|
|
289
308
|
return 0
|
|
290
309
|
|
|
291
310
|
|
|
292
|
-
def
|
|
293
|
-
|
|
294
|
-
if target.impl == "doing":
|
|
295
|
-
print(f"{target.label} 已经在 doing,无需重复 claim")
|
|
296
|
-
return 0
|
|
297
|
-
# 票默认串行:同一任务一次只推进一张
|
|
311
|
+
def ensure_claimable(tickets: list[Ticket], target: Ticket, parallel: bool) -> None:
|
|
312
|
+
# 票默认串行:同一任务一次只推进一张。并行批次是唯一的例外
|
|
298
313
|
busy = [t for t in tickets if t.impl == "doing"]
|
|
299
|
-
if busy:
|
|
314
|
+
if busy and not parallel:
|
|
300
315
|
raise TicketError(
|
|
301
316
|
f"{busy[0].label} 还在 doing。票默认串行,先 done 它再 claim 下一张"
|
|
302
317
|
)
|
|
@@ -307,9 +322,32 @@ def cmd_claim(tickets: list[Ticket], root: Path, task: Path, num: str, **_) -> i
|
|
|
307
322
|
else f"Impl 是 {target.impl},不是 ready"
|
|
308
323
|
raise TicketError(f"{target.label} 不在 frontier 里({reason})")
|
|
309
324
|
|
|
325
|
+
|
|
326
|
+
def cmd_claim(tickets: list[Ticket], root: Path, task: Path, num: str,
|
|
327
|
+
parallel: bool = False, **_) -> int:
|
|
328
|
+
target = pick(tickets, num)
|
|
329
|
+
if target.impl == "doing":
|
|
330
|
+
print(f"{target.label} 已经在 doing,无需重复 claim")
|
|
331
|
+
return 0
|
|
332
|
+
first_of_batch = not any(t.impl == "doing" for t in tickets)
|
|
333
|
+
ensure_claimable(tickets, target, parallel)
|
|
334
|
+
|
|
310
335
|
set_impl(target, "doing")
|
|
336
|
+
if parallel:
|
|
337
|
+
# 基线只在批次的第一张票上记:之后 HEAD 随合并前移,再记就把已合并的
|
|
338
|
+
# 票排除在整批审查的 diff 之外了
|
|
339
|
+
if first_of_batch:
|
|
340
|
+
record_base_sha(root, task)
|
|
341
|
+
# 并行批次没有「唯一的当前票」,check.jsonl 里不留当前票行
|
|
342
|
+
rewrite_manifest(root, task, None)
|
|
343
|
+
if ticket_mode(task) != MODE_PARALLEL:
|
|
344
|
+
set_meta(root, task, META_TICKET_MODE, MODE_PARALLEL)
|
|
345
|
+
print(f"claimed {target.label} → Impl: doing(并行批次)")
|
|
346
|
+
return 0
|
|
311
347
|
record_base_sha(root, task)
|
|
312
348
|
rewrite_manifest(root, task, target)
|
|
349
|
+
if ticket_mode(task) == MODE_PARALLEL:
|
|
350
|
+
set_meta(root, task, META_TICKET_MODE, MODE_SERIAL)
|
|
313
351
|
print(f"claimed {target.label} → Impl: doing")
|
|
314
352
|
return 0
|
|
315
353
|
|
|
@@ -347,7 +385,7 @@ def cmd_summary(tickets: list[Ticket], **_) -> int:
|
|
|
347
385
|
doing = [t for t in tickets if t.impl == "doing"]
|
|
348
386
|
done = [t for t in tickets if t.impl == "done"]
|
|
349
387
|
parts = [f"票 {len(tickets)} 张", f"done {len(done)}"]
|
|
350
|
-
parts.append(
|
|
388
|
+
parts.append("doing " + ("、".join(t.label for t in doing) or "-"))
|
|
351
389
|
parts.append("frontier " + ("、".join(f"{t.num:02d}" for t in frontier(tickets)) or "-"))
|
|
352
390
|
print(" | ".join(parts))
|
|
353
391
|
return 0
|
|
@@ -414,6 +452,21 @@ def cmd_selfcheck(**_) -> int:
|
|
|
414
452
|
# 重复 done 幂等,不报错
|
|
415
453
|
assert cmd_done(load_tickets(tmp), tmp, tmp, "02") == 0
|
|
416
454
|
|
|
455
|
+
with tempfile.TemporaryDirectory() as d:
|
|
456
|
+
tmp = Path(d)
|
|
457
|
+
ts = build(tmp, [(1, "doing", "none"), (2, "ready", "none"), (3, "ready", "02")])
|
|
458
|
+
fails(lambda: ensure_claimable(ts, ts[1], parallel=False), "票默认串行")
|
|
459
|
+
ensure_claimable(ts, ts[1], parallel=True) # 并行批次放行第二张 doing
|
|
460
|
+
# 并行不绕过阻塞:Blocker 没 done 的票照样认领不了
|
|
461
|
+
fails(lambda: ensure_claimable(ts, ts[2], parallel=True), "不在 frontier 里")
|
|
462
|
+
|
|
463
|
+
with tempfile.TemporaryDirectory() as d:
|
|
464
|
+
tmp = Path(d)
|
|
465
|
+
assert ticket_mode(tmp) == MODE_SERIAL # 没有 task.json 按串行算
|
|
466
|
+
(tmp / "task.json").write_text(
|
|
467
|
+
json.dumps({"meta": {META_TICKET_MODE: MODE_PARALLEL}}), encoding="utf-8")
|
|
468
|
+
assert ticket_mode(tmp) == MODE_PARALLEL
|
|
469
|
+
|
|
417
470
|
with tempfile.TemporaryDirectory() as d:
|
|
418
471
|
tmp = Path(d)
|
|
419
472
|
fails(lambda: build(tmp, [(1, "ready", "09")]), "不存在的票")
|
|
@@ -473,6 +526,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
473
526
|
p.add_argument("command", choices=sorted(COMMANDS))
|
|
474
527
|
p.add_argument("num", nargs="?", help="票号,claim / done 用")
|
|
475
528
|
p.add_argument("--task-dir", help="仓库相对的任务目录,缺省问 task.py current")
|
|
529
|
+
p.add_argument("--parallel", action="store_true",
|
|
530
|
+
help="claim 用:并行批次认领,允许多张票同时 doing")
|
|
476
531
|
args = p.parse_args(argv)
|
|
477
532
|
|
|
478
533
|
try:
|
|
@@ -484,7 +539,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
484
539
|
task = task_dir(root, args.task_dir)
|
|
485
540
|
tickets = load_tickets(task / "issues")
|
|
486
541
|
return COMMANDS[args.command](
|
|
487
|
-
tickets=tickets, root=root, task=task, num=args.num
|
|
542
|
+
tickets=tickets, root=root, task=task, num=args.num,
|
|
543
|
+
parallel=args.parallel,
|
|
488
544
|
)
|
|
489
545
|
except TicketError as e:
|
|
490
546
|
print(f"错误:{e}", file=sys.stderr)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
1. **Plan before code** — figure out what to do before you start
|
|
8
8
|
2. **Specs injected, not remembered** — guidelines are injected via hook/skill, not recalled from memory
|
|
9
9
|
3. **Persist everything** — research, decisions, and lessons all go to files; conversations get compacted, files don't
|
|
10
|
-
4. **One ticket at a time** — serial
|
|
10
|
+
4. **One ticket at a time, unless the user asks for the whole spec at once** — serial by default: claim, finish, close, then take the next. `/atw-implement-spec` is the one parallel path, and only the user starts it
|
|
11
11
|
5. **Capture learnings** — after each ticket, review and write new knowledge back to spec
|
|
12
12
|
|
|
13
13
|
---
|
|
@@ -24,6 +24,16 @@ python3 ./.atw/scripts/init_developer.py <your-name>
|
|
|
24
24
|
|
|
25
25
|
Creates `.atw/.developer` (gitignored) + `.atw/workspace/<your-name>/`.
|
|
26
26
|
|
|
27
|
+
### Noob Mode
|
|
28
|
+
|
|
29
|
+
For a user who is not an engineer. While it is on, every reply in every stage is in plain language: terms explained where they appear, each action explained before it happens (what, why, how risky, what approving or declining leads to), errors and results retold with what they affect and what comes next. The full rules are `/atw-noob-mode`.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python3 ./.atw/scripts/noob_mode.py on|off|status
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The user switches it by saying "开启小白模式" / "关闭小白模式" or running `/atw-noob-mode`; the skill runs the script. The switch is one line in that user's own `.atw/.developer`, so it is personal and never committed. It changes how the AI talks to the user — not the stages, the stops, or what goes into task files.
|
|
36
|
+
|
|
27
37
|
### Spec System
|
|
28
38
|
|
|
29
39
|
`.atw/spec/` holds coding guidelines organized by package and layer.
|
|
@@ -109,6 +119,7 @@ Implementation tickets live in `{TASK_DIR}/issues/NN-slug.md` and carry two fiel
|
|
|
109
119
|
python3 ./.atw/scripts/tickets.py list [--task-dir <dir>] # every ticket + state
|
|
110
120
|
python3 ./.atw/scripts/tickets.py frontier [--task-dir <dir>] # ready tickets with dependencies cleared
|
|
111
121
|
python3 ./.atw/scripts/tickets.py claim NN [--task-dir <dir>] # ready → doing
|
|
122
|
+
python3 ./.atw/scripts/tickets.py claim NN --parallel # ready → doing alongside others (`/atw-implement-spec` only)
|
|
112
123
|
python3 ./.atw/scripts/tickets.py done NN [--task-dir <dir>] # doing → done
|
|
113
124
|
python3 ./.atw/scripts/tickets.py summary [--task-dir <dir>]
|
|
114
125
|
python3 ./.atw/scripts/tickets.py selfcheck
|
|
@@ -116,6 +127,8 @@ python3 ./.atw/scripts/tickets.py selfcheck
|
|
|
116
127
|
|
|
117
128
|
**The current ticket is the single `issues/` ticket marked `**Impl:** doing`.** The per-turn hook reads that directly from the ticket files. Two tickets marked `doing` at once is a broken state, not a choice — clear it before continuing.
|
|
118
129
|
|
|
130
|
+
The one exception is a `/atw-implement-spec` run. Its orchestrator claims with `--parallel`, which records `meta.ticket_mode = parallel` in `task.json`; with that set, the hook reads several `doing` tickets as the run in progress rather than a contradiction, and no single ticket is the current one. The next plain `claim` puts the task back in serial mode.
|
|
131
|
+
|
|
119
132
|
Decision tickets from the map live in `{TASK_DIR}/map-issues/` and use a different vocabulary (`Type` / `Status` / `Blocked by`). **Never mix the two directories**: `tickets.py` only reads `issues/`, and a decision ticket filed there corrupts the frontier in both directions.
|
|
120
133
|
|
|
121
134
|
### Workspace System
|
|
@@ -192,7 +205,7 @@ python3 ./.atw/scripts/get_context.py --mode phase --step <X.Y> # detailed guid
|
|
|
192
205
|
|
|
193
206
|
```
|
|
194
207
|
Phase 1: Plan → discover, specify, and (when needed) slice into tickets
|
|
195
|
-
Phase 2: Execute → one ticket at a time
|
|
208
|
+
Phase 2: Execute → one ticket at a time through the full implement chain, or the whole spec in one parallel run
|
|
196
209
|
Phase 3: Finish → read-only acceptance check, then archive
|
|
197
210
|
```
|
|
198
211
|
|
|
@@ -230,7 +243,7 @@ Outside these four, the AI advances stages on its own. It does not stop to ask "
|
|
|
230
243
|
|---|---|---|
|
|
231
244
|
| ① | Before creating a task | Does this work deserve a record — the user's |
|
|
232
245
|
| ② | After the spec is written | Is the spec right — the user's |
|
|
233
|
-
| ③ | Before
|
|
246
|
+
| ③ | Before implementation starts | The user runs `/atw-implement` per ticket, or `/atw-implement-spec` once for the whole spec; the AI cannot invoke either |
|
|
234
247
|
| ④ | Before archiving | "This is done" — the user's acceptance |
|
|
235
248
|
|
|
236
249
|
### Planning Artifacts
|
|
@@ -266,6 +279,17 @@ Inspect the task directory named above and repair its task.json. It must be a va
|
|
|
266
279
|
Preserve existing task fields and artifacts. If the correct status cannot be determined safely, ask the user before reconstructing the record.
|
|
267
280
|
[/workflow-state:task_error]
|
|
268
281
|
|
|
282
|
+
<!-- Appended to every per-turn breadcrumb, whatever the status, while this developer has noob mode on. Not a task status. -->
|
|
283
|
+
|
|
284
|
+
[workflow-state:noob_mode]
|
|
285
|
+
Noob mode is ON — this user is not an engineer. Hold to these rules in every reply, in every stage (the full set is `/atw-noob-mode`):
|
|
286
|
+
- Plain words in the user's language. Explain any unavoidable term, command, path or flag in the same sentence it appears in.
|
|
287
|
+
- Before anything they must approve or that is hard to undo: what will happen, why, how risky it is, and what approving or declining leads to.
|
|
288
|
+
- Say who did what, what you actually ran versus what you only expect, and every decision you made for them.
|
|
289
|
+
- Retell sub-agent reports, command output and errors the same way: what happened, what it affects, what comes next.
|
|
290
|
+
Only the user turns it off ("关闭小白模式" / "turn off noob mode"): run `python3 ./.atw/scripts/noob_mode.py off`.
|
|
291
|
+
[/workflow-state:noob_mode]
|
|
292
|
+
|
|
269
293
|
### Phase 1: Plan
|
|
270
294
|
|
|
271
295
|
- 1.0 Create task `[required · once]` (only after stop ①)
|
|
@@ -309,18 +333,20 @@ When the tickets are written, run `task.py start <task>` (step 1.5) to enter imp
|
|
|
309
333
|
- 2.3 Relay the review reports `[required · repeatable]`
|
|
310
334
|
- 2.4 Close the ticket `[required · repeatable]`
|
|
311
335
|
- 2.5 Roll back `[on demand]`
|
|
336
|
+
- 2.7 Whole-spec run `[alternative to 2.1–2.4 · once]` (stop ③; the user picks it)
|
|
312
337
|
|
|
313
338
|
<!-- Per-turn breadcrumb: stage 'implement' — set by task.py start, held for all of Phase 2 -->
|
|
314
339
|
|
|
315
340
|
[workflow-state:implement]
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
341
|
+
Stop ③: implementation is the user's to start. You may not invoke either skill below yourself, and you may not do the implementation inline instead. When the task has tickets, name both ways and let the user pick; a task with no tickets has only the first.
|
|
342
|
+
Per-ticket path (the default): one ticket at a time. Run `tickets.py frontier`, pick one, `tickets.py claim NN` — never hold two tickets at `doing` — then tell the user to run `/atw-implement` for the claimed ticket. One user invocation per ticket.
|
|
343
|
+
Whole-spec path: the user runs `/atw-implement-spec` once. It owns the run from the first claim to the closing commit: it claims with `tickets.py claim NN --parallel`, dispatches implementer sub-agents across the frontier, each in its own worktree, lands everything on one integration branch, marks each ticket `done` as its work merges, reviews that branch once at the end, and writes spec updates back before its closing commit. Do not claim, review or commit around it. It needs a platform that dispatches sub-agents.
|
|
344
|
+
On the per-ticket path, that one `/atw-implement` run is a closed chain and must not be broken up from the outside: implement (test-first where it fits) → run tests, full suite on the last pass → review → handle findings → write spec updates back → commit. Never instruct it to skip the review or the commit.
|
|
319
345
|
Findings triage: spec-axis findings and anything the standards axis calls a hard violation get fixed first, then a full re-review, then the commit. Judgement calls ship and get reported.
|
|
320
346
|
Relay every review report to the user verbatim — Standards and Spec, plus Visual when the ticket changed UI — even when the verdict is "nothing found". No report reaching you is not a passing review.
|
|
321
347
|
Any sub-agent dispatched from here starts its prompt with `Active task: <task path from task.py current>` — the role files read that line to find the task, and it is the context hooks' fallback when session resolution misses.
|
|
322
|
-
Main-session default: run the process; the implementation itself is written inside `/atw-implement`, never by a dispatched `atw-implement-agent` — that role file is reserved and is not a step in this flow. The only sub-agent
|
|
323
|
-
|
|
348
|
+
Main-session default: run the process; on the per-ticket path the implementation itself is written inside `/atw-implement`, never by a dispatched `atw-implement-agent` — that role file is reserved and is not a step in this flow. The only sub-agent that path raises is `atw-review` (twice, from inside `/atw-implement` — three times when the ticket changes UI and a Visual review runs). Implementer sub-agents exist only inside a `/atw-implement-spec` run, dispatched by that skill and by nothing else. Sub-agent self-exemption: this breadcrumb reaches sub-agent turns on some hosts. If you are already running as `atw-review`, do NOT spawn another `atw-review` — do the review you were dispatched for. If you were dispatched by `/atw-implement-spec` as an implementer or merger, stop ③ is already satisfied — do the work you were dispatched for and dispatch nothing. Dispatch is main session only.
|
|
349
|
+
Per-ticket path: then `tickets.py done NN` and back to the frontier. Either path: when the frontier is empty and every ticket is `done`, run `task.py set-status <task> accept`.
|
|
324
350
|
[/workflow-state:implement]
|
|
325
351
|
|
|
326
352
|
### Phase 3: Finish
|
|
@@ -363,12 +389,15 @@ When a user request matches one of these intents inside an active task, route fi
|
|
|
363
389
|
- Writing or revising the spec → `/atw-spec`
|
|
364
390
|
- Slicing a confirmed spec into tickets → `/atw-tickets`
|
|
365
391
|
- Starting a claimed ticket → `/atw-implement` (the user runs it; stop ③)
|
|
392
|
+
- Implementing every ticket of a sliced spec in one parallel run → `/atw-implement-spec` (the user runs it; stop ③)
|
|
366
393
|
- Building or polishing a screen for a claimed ticket → `/atw-ui`, used from inside `/atw-implement`
|
|
367
394
|
- Same bug fixed more than once → `/atw-diagnosing-bugs`
|
|
368
395
|
- Knowledge worth keeping outside a ticket → `/atw-update-spec`
|
|
396
|
+
- The user cannot follow the terms, an approval prompt or an error, or says "开启小白模式" / "关闭小白模式" → `/atw-noob-mode` (in any stage, with or without a task)
|
|
369
397
|
|
|
370
|
-
|
|
371
|
-
`/atw-askme-with-docs`, `/atw-map`, `/atw-spec`, `/atw-tickets`, `/atw-implement
|
|
398
|
+
Seven of these are user-triggered and the AI cannot invoke them: `/atw-askme`,
|
|
399
|
+
`/atw-askme-with-docs`, `/atw-map`, `/atw-spec`, `/atw-tickets`, `/atw-implement`,
|
|
400
|
+
`/atw-implement-spec`.
|
|
372
401
|
Name the one that fits and let the user run it — calling the Skill tool on one
|
|
373
402
|
returns an error and nothing else.
|
|
374
403
|
|
|
@@ -377,7 +406,7 @@ returns an error and nothing else.
|
|
|
377
406
|
- Consent to create a task is not consent to implement. Implementation starts at `task.py start`, after the user confirms `prd.md`.
|
|
378
407
|
- Stops ①–④ are the user's calls. Never advance through one by inferring the answer.
|
|
379
408
|
- Planning must be persisted to task artifacts; the review chain must run before reporting a ticket complete.
|
|
380
|
-
- Never mark two tickets `**Impl:** doing` at the same time.
|
|
409
|
+
- Never mark two tickets `**Impl:** doing` at the same time. The only thing that may is a `/atw-implement-spec` run, through `tickets.py claim NN --parallel`.
|
|
381
410
|
|
|
382
411
|
### Loading Step Detail
|
|
383
412
|
|
|
@@ -558,9 +587,18 @@ Ready to leave Phase 1:
|
|
|
558
587
|
|
|
559
588
|
## Phase 2: Execute
|
|
560
589
|
|
|
561
|
-
Goal: turn the confirmed spec into committed
|
|
590
|
+
Goal: turn the confirmed spec into committed, reviewed code.
|
|
591
|
+
|
|
592
|
+
There are two ways through this phase, and the user picks at stop ③:
|
|
593
|
+
|
|
594
|
+
| Path | What the user runs | How the tickets move |
|
|
595
|
+
|---|---|---|
|
|
596
|
+
| Per ticket (the default) | `/atw-implement`, once per ticket | Steps 2.1–2.4: one ticket at a time, each reviewed before it is closed |
|
|
597
|
+
| Whole spec | `/atw-implement-spec`, once | Step 2.7: implementer sub-agents work the frontier in parallel on one integration branch, reviewed once at the end |
|
|
598
|
+
|
|
599
|
+
A task with no tickets has only the per-ticket path. The whole-spec path also needs a platform that dispatches sub-agents; on one that does not, name only the per-ticket path.
|
|
562
600
|
|
|
563
|
-
The main session runs the process.
|
|
601
|
+
The main session runs the process. On the per-ticket path it does not write the implementation itself and it does not dispatch an implementation sub-agent — the work happens inside `/atw-implement`, which the user starts. On the whole-spec path the main session is the orchestrator `/atw-implement-spec` describes, and the implementers it dispatches are that skill's, not a step of this file.
|
|
564
602
|
|
|
565
603
|
**Sub-agent dispatch protocol** — applies to every platform and every sub-agent
|
|
566
604
|
dispatched anywhere in this workflow (the review sub-agents raised inside the
|
|
@@ -582,9 +620,12 @@ not spawn another `atw-review`.
|
|
|
582
620
|
Without this the guidance a sub-agent inherits reads as an instruction to
|
|
583
621
|
dispatch, and it dispatches itself.
|
|
584
622
|
|
|
585
|
-
Implementation is never dispatched. `atw-implement-agent`
|
|
586
|
-
role file so the injection path stays wired (D23), but no
|
|
587
|
-
workflow spawns it — the code is written inside `/atw-implement`.
|
|
623
|
+
Implementation is never dispatched on the per-ticket path. `atw-implement-agent`
|
|
624
|
+
ships as a reserved role file so the injection path stays wired (D23), but no
|
|
625
|
+
step in this workflow spawns it — the code is written inside `/atw-implement`.
|
|
626
|
+
A `/atw-implement-spec` run does dispatch implementers, as general-purpose
|
|
627
|
+
sub-agents carrying that skill's own brief — not `atw-implement-agent`, which
|
|
628
|
+
may not commit.
|
|
588
629
|
|
|
589
630
|
On Grok Build, use `spawn_subagent` with `subagent_type` set to the ATW agent
|
|
590
631
|
name (e.g. `atw-review`). On Kimi Code, dispatch the built-in `coder` /
|
|
@@ -666,6 +707,26 @@ python3 ./.atw/scripts/task.py set-status <task-dir> accept
|
|
|
666
707
|
|
|
667
708
|
If the same issue has now been fixed more than once, stop patching and tell the user to run `/atw-diagnosing-bugs` — it classifies the root cause, explains why the earlier fixes did not hold, and proposes prevention worth writing into `.atw/spec/`.
|
|
668
709
|
|
|
710
|
+
#### 2.7 Whole-spec run `[alternative to 2.1–2.4 · once]`
|
|
711
|
+
|
|
712
|
+
**Stop ③**, the other way through it: the user runs `/atw-implement-spec` instead of `/atw-implement`. You cannot invoke it, and you do not start it because it looks faster — the user is trading a per-ticket look at the work for one review at the end, and that trade is theirs.
|
|
713
|
+
|
|
714
|
+
It replaces steps 2.1–2.4 for the whole task. The skill carries the procedure; what this file needs you to know is what changes around it:
|
|
715
|
+
|
|
716
|
+
- **Tickets run in parallel.** The orchestrator claims with `tickets.py claim NN --parallel`, so several tickets sit at `**Impl:** doing` at once and none of them is "the current ticket". Do not claim by hand during the run.
|
|
717
|
+
- **Work lands on an integration branch**, not the branch the task started on. Each implementer sub-agent works in its own worktree and its own branch; a merger sub-agent lands each one. `task.json.branch` records the integration branch.
|
|
718
|
+
- **One review, at the end.** The review compares the code against the whole spec, so it only makes sense once every ticket has landed. Its findings are fixed once and re-checked once; whatever is still open after that comes back to the user rather than starting another round.
|
|
719
|
+
- **Spec updates still land before the closing commit**, once for the run instead of once per ticket.
|
|
720
|
+
- **Review reports are relayed as written** (step 2.3 applies unchanged).
|
|
721
|
+
|
|
722
|
+
The run stops and reports when a ticket cannot be finished as written, a merge cannot be resolved, or the tests cannot be made to pass. Everything already merged stays. Roll back as in 2.5; a ticket left at `doing` by a stopped run is finished with `/atw-implement` or by running `/atw-implement-spec` again.
|
|
723
|
+
|
|
724
|
+
When every ticket is `done`:
|
|
725
|
+
|
|
726
|
+
```bash
|
|
727
|
+
python3 ./.atw/scripts/task.py set-status <task-dir> accept
|
|
728
|
+
```
|
|
729
|
+
|
|
669
730
|
---
|
|
670
731
|
|
|
671
732
|
## Phase 3: Finish
|
|
@@ -683,6 +744,8 @@ python3 ./.atw/scripts/tickets.py summary # every ticket done
|
|
|
683
744
|
git status --porcelain # clean tree; the chain commits as it goes
|
|
684
745
|
```
|
|
685
746
|
|
|
747
|
+
After a whole-spec run the work sits on the integration branch named in `task.json.branch`. Say so in the report: merging it is the user's call, made after acceptance.
|
|
748
|
+
|
|
686
749
|
**Stop ④**: present that and wait. Whether the work is done is the user's judgement, not yours — you can report that every criterion is met, but you cannot accept on their behalf.
|
|
687
750
|
|
|
688
751
|
#### 3.2 Archive `[required · once]`
|
|
@@ -709,7 +772,7 @@ Edit the corresponding step's walkthrough body in the Phase 1 / 2 / 3 sections a
|
|
|
709
772
|
|
|
710
773
|
- No active task must triage first and ask for task-creation consent before creating an ATW task.
|
|
711
774
|
- Each of the four stops must stay reachable and must stay the user's call.
|
|
712
|
-
- Every execution path must keep the
|
|
775
|
+
- Every execution path must keep review and spec update inside the run, never after it: before each ticket's commit on the per-ticket path, before the closing commit on the integration branch on the whole-spec path.
|
|
713
776
|
|
|
714
777
|
Structure is guarded by `packages/cli/test/scripts/workflow-structure.test.ts`, which runs with `pnpm test` — there is no separate step to remember.
|
|
715
778
|
|
|
@@ -727,6 +790,7 @@ All tag blocks live in the `## Phase Index` section above, immediately after eac
|
|
|
727
790
|
| All of Phase 2 | `[workflow-state:implement]` |
|
|
728
791
|
| All of Phase 3 up to archive | `[workflow-state:accept]` |
|
|
729
792
|
| Archive interrupted; task still under `.atw/tasks/` | `[workflow-state:completed]` |
|
|
793
|
+
| Any of the above, while the developer has noob mode on (appended, not a status) | `[workflow-state:noob_mode]` |
|
|
730
794
|
|
|
731
795
|
Directly edit the body of the corresponding `[workflow-state:STATUS]` block. After editing, run `atw update` (if you're a template maintainer) or restart your AI session (if you're customizing your own project) — no script changes required.
|
|
732
796
|
|