@chinhae/atw-cli 0.5.0 → 0.7.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 +128 -451
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +24 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/channel/adapters/claude.d.ts +44 -2
- package/dist/commands/channel/adapters/claude.d.ts.map +1 -1
- package/dist/commands/channel/adapters/claude.js +124 -23
- package/dist/commands/channel/adapters/claude.js.map +1 -1
- package/dist/commands/channel/adapters/codex.d.ts +36 -6
- package/dist/commands/channel/adapters/codex.d.ts.map +1 -1
- package/dist/commands/channel/adapters/codex.js +150 -10
- package/dist/commands/channel/adapters/codex.js.map +1 -1
- package/dist/commands/channel/adapters/index.d.ts +33 -4
- package/dist/commands/channel/adapters/index.d.ts.map +1 -1
- package/dist/commands/channel/adapters/index.js +159 -6
- package/dist/commands/channel/adapters/index.js.map +1 -1
- package/dist/commands/channel/adapters/omp.d.ts +106 -0
- package/dist/commands/channel/adapters/omp.d.ts.map +1 -0
- package/dist/commands/channel/adapters/omp.js +405 -0
- package/dist/commands/channel/adapters/omp.js.map +1 -0
- package/dist/commands/channel/adapters/pi.d.ts +123 -0
- package/dist/commands/channel/adapters/pi.d.ts.map +1 -0
- package/dist/commands/channel/adapters/pi.js +387 -0
- package/dist/commands/channel/adapters/pi.js.map +1 -0
- package/dist/commands/channel/adapters/types.d.ts +16 -0
- package/dist/commands/channel/adapters/types.d.ts.map +1 -1
- package/dist/commands/channel/agent-loader.d.ts +1 -0
- package/dist/commands/channel/agent-loader.d.ts.map +1 -1
- package/dist/commands/channel/agent-loader.js +45 -5
- package/dist/commands/channel/agent-loader.js.map +1 -1
- package/dist/commands/channel/dev-parse-trace.d.ts +1 -1
- package/dist/commands/channel/dev-parse-trace.d.ts.map +1 -1
- package/dist/commands/channel/dev-parse-trace.js +25 -2
- package/dist/commands/channel/dev-parse-trace.js.map +1 -1
- package/dist/commands/channel/index.d.ts.map +1 -1
- package/dist/commands/channel/index.js +44 -12
- package/dist/commands/channel/index.js.map +1 -1
- package/dist/commands/channel/run.d.ts +39 -2
- package/dist/commands/channel/run.d.ts.map +1 -1
- package/dist/commands/channel/run.js +227 -68
- package/dist/commands/channel/run.js.map +1 -1
- package/dist/commands/channel/spawn.d.ts +7 -2
- package/dist/commands/channel/spawn.d.ts.map +1 -1
- package/dist/commands/channel/spawn.js +35 -1
- package/dist/commands/channel/spawn.js.map +1 -1
- package/dist/commands/channel/store/events.d.ts +2 -2
- package/dist/commands/channel/store/events.d.ts.map +1 -1
- package/dist/commands/channel/store/events.js +1 -1
- package/dist/commands/channel/store/events.js.map +1 -1
- package/dist/commands/channel/supervisor/shutdown.d.ts +2 -0
- package/dist/commands/channel/supervisor/shutdown.d.ts.map +1 -1
- package/dist/commands/channel/supervisor/shutdown.js +6 -1
- package/dist/commands/channel/supervisor/shutdown.js.map +1 -1
- package/dist/commands/channel/supervisor.d.ts +2 -2
- package/dist/commands/channel/supervisor.d.ts.map +1 -1
- package/dist/commands/channel/supervisor.js +26 -3
- package/dist/commands/channel/supervisor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +6 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mem.d.ts +2 -1
- package/dist/commands/mem.d.ts.map +1 -1
- package/dist/commands/mem.js +162 -13
- package/dist/commands/mem.js.map +1 -1
- package/dist/commands/mode.d.ts.map +1 -1
- package/dist/commands/mode.js +5 -3
- package/dist/commands/mode.js.map +1 -1
- package/dist/commands/uninstall.d.ts +5 -3
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +40 -16
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts +34 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +209 -11
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/verify.d.ts +18 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +75 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +3 -2
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/omp.d.ts +22 -0
- package/dist/configurators/omp.d.ts.map +1 -1
- package/dist/configurators/omp.js +101 -2
- package/dist/configurators/omp.js.map +1 -1
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +39 -7
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +5 -1
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/constants/paths.d.ts +4 -0
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +4 -0
- package/dist/constants/paths.js.map +1 -1
- package/dist/migrations/manifests/0.6.0.json +442 -0
- package/dist/migrations/manifests/0.7.0.json +1378 -0
- package/dist/templates/atw/definition-of-done.md +65 -0
- package/dist/templates/atw/gitignore.txt +7 -0
- package/dist/templates/atw/index.d.ts +11 -0
- package/dist/templates/atw/index.d.ts.map +1 -1
- package/dist/templates/atw/index.js +22 -0
- package/dist/templates/atw/index.js.map +1 -1
- package/dist/templates/atw/scripts/common/active_task.py +2 -2
- package/dist/templates/atw/scripts/common/workflow_phase.py +33 -0
- package/dist/templates/atw/scripts/cross_model_review.py +3312 -0
- package/dist/templates/atw/scripts/decision_log.py +2002 -0
- package/dist/templates/atw/scripts/floor_guard.py +642 -0
- package/dist/templates/atw/scripts/task.py +25 -6
- package/dist/templates/atw/scripts/tickets.py +480 -10
- package/dist/templates/atw/workflow-general.md +6 -1
- package/dist/templates/atw/workflow.md +346 -26
- package/dist/templates/claude/settings.json +12 -0
- package/dist/templates/codex/hooks/session-start.py +2 -2
- package/dist/templates/common/bundled-skills/atw-askme/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-askme-with-docs/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-channel/SKILL.md +5 -2
- package/dist/templates/common/bundled-skills/atw-channel/references/command-reference.md +44 -5
- package/dist/templates/common/bundled-skills/atw-channel/references/progress-debugging.md +9 -0
- package/dist/templates/common/bundled-skills/atw-channel/references/workers.md +48 -4
- package/dist/templates/common/bundled-skills/atw-code-review/SKILL.md +78 -3
- package/dist/templates/common/bundled-skills/atw-codebase-design/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/atw-codebase-design/{DESIGN-IT-TWICE.md → design-it-twice.md} +3 -3
- package/dist/templates/common/bundled-skills/atw-cross-model-review/SKILL.md +361 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/agents/openai.yaml +5 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/references/four-tiers.md +42 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/references/log-review-questions.md +38 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/references/questions.md +54 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/references/recheck-questions.md +42 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/references/report-format.md +22 -0
- package/dist/templates/common/bundled-skills/atw-diagnosing-bugs/SKILL.md +13 -2
- package/dist/templates/common/bundled-skills/atw-domain-modeling/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/atw-handoff/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-implement/SKILL.md +37 -1
- package/dist/templates/common/bundled-skills/atw-implement-all/SKILL.md +219 -24
- package/dist/templates/common/bundled-skills/atw-implement-spec/SKILL.md +237 -18
- package/dist/templates/common/bundled-skills/atw-improve-codebase-architecture/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/atw-init-repo/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-atw-local.md +7 -1
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-atw.md +7 -1
- package/dist/templates/common/bundled-skills/atw-map/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-meta/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-meta/references/customize-local/add-project-local-conventions.md +4 -3
- package/dist/templates/common/bundled-skills/atw-meta/references/customize-local/change-skills-or-commands.md +21 -16
- package/dist/templates/common/bundled-skills/atw-meta/references/customize-local/change-task-lifecycle.md +1 -1
- package/dist/templates/common/bundled-skills/atw-meta/references/customize-local/overview.md +1 -0
- package/dist/templates/common/bundled-skills/atw-meta/references/local-architecture/task-system.md +2 -1
- package/dist/templates/common/bundled-skills/atw-meta/references/platform-files/hooks-and-settings.md +1 -0
- package/dist/templates/common/bundled-skills/atw-noob-mode/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-performance/SKILL.md +134 -0
- package/dist/templates/common/bundled-skills/atw-performance/agents/openai.yaml +3 -0
- package/dist/templates/common/bundled-skills/atw-performance/references/optimization-patterns.md +250 -0
- package/dist/templates/common/bundled-skills/atw-performance/references/performance-checklist.md +238 -0
- package/dist/templates/common/bundled-skills/atw-performance/references/performance-optimization.md +264 -0
- package/dist/templates/common/bundled-skills/atw-prototype/SKILL.md +3 -3
- package/dist/templates/common/bundled-skills/atw-prototype/{LOGIC.md → logic.md} +1 -1
- package/dist/templates/common/bundled-skills/atw-prototype/references/design-direction.md +91 -58
- package/dist/templates/common/bundled-skills/atw-prototype/references/direction-persuasion.md +25 -0
- package/dist/templates/common/bundled-skills/atw-prototype/references/style-explorer.md +1 -1
- package/dist/templates/common/bundled-skills/atw-prototype/ui.md +194 -0
- package/dist/templates/common/bundled-skills/atw-reflect/SKILL.md +77 -0
- package/dist/templates/common/bundled-skills/atw-reflect/agents/openai.yaml +5 -0
- package/dist/templates/common/bundled-skills/atw-reflect/references/divergent-reviewer.md +38 -0
- package/dist/templates/common/bundled-skills/atw-reflect/references/judgment-reviewer.md +37 -0
- package/dist/templates/common/bundled-skills/atw-reflect/references/synthesizer.md +63 -0
- package/dist/templates/common/bundled-skills/atw-reflect/references/tooling-reviewer.md +48 -0
- package/dist/templates/common/bundled-skills/atw-security/SKILL.md +248 -0
- package/dist/templates/common/bundled-skills/atw-security/agents/openai.yaml +3 -0
- package/dist/templates/common/bundled-skills/atw-security/references/hardening-patterns.md +324 -0
- package/dist/templates/common/bundled-skills/atw-security/references/security-checklist.md +248 -0
- package/dist/templates/common/bundled-skills/atw-session-insight/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-session-insight/references/cli-quick-reference.md +11 -2
- package/dist/templates/common/bundled-skills/atw-spec/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-tdd/SKILL.md +25 -0
- package/dist/templates/common/bundled-skills/atw-tickets/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-triage/SKILL.md +5 -5
- package/dist/templates/common/bundled-skills/atw-ui/SKILL.md +99 -31
- package/dist/templates/common/bundled-skills/atw-ui/references/accessibility-checklist.md +160 -0
- package/dist/templates/common/bundled-skills/atw-ui/references/layout-and-viewport.md +7 -5
- package/dist/templates/common/bundled-skills/atw-ui/references/media.md +117 -20
- package/dist/templates/common/bundled-skills/atw-ui/references/motion.md +47 -23
- package/dist/templates/common/bundled-skills/atw-ui/references/play.md +18 -0
- package/dist/templates/common/bundled-skills/atw-ui/references/reviewer-manual.md +73 -0
- package/dist/templates/common/bundled-skills/atw-ui/references/scroll-narrative.md +98 -0
- package/dist/templates/common/bundled-skills/atw-ui/references/tools.md +46 -0
- package/dist/templates/common/bundled-skills/atw-ui/references/visual-language.md +40 -24
- package/dist/templates/common/bundled-skills/atw-ui/references/visual-review.md +26 -82
- package/dist/templates/common/bundled-skills/atw-ui/scripts/shoot.mjs +1721 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/SKILL.md +58 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/agents/openai.yaml +5 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/feature-map-example/README.md +47 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/feature-map-example/create-note.md +39 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/feature-map-example/search.md +45 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/generate.md +107 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/health-check.md +100 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/verify-skill-template.md +99 -0
- package/dist/templates/common/bundled-skills/atw-what-happened/SKILL.md +49 -0
- package/dist/templates/common/bundled-skills/atw-what-happened/agents/openai.yaml +5 -0
- package/dist/templates/common/commands/finish-work.md +10 -1
- package/dist/templates/common/commands/start.md +3 -1
- package/dist/templates/common/skills/update-spec.md +40 -4
- package/dist/templates/copilot/hooks/session-start.py +2 -2
- package/dist/templates/dsh/DSH.md +2 -2
- package/dist/templates/markdown/spec/backend/quality-guidelines.md.txt +10 -0
- package/dist/templates/markdown/spec/frontend/quality-guidelines.md.txt +10 -0
- package/dist/templates/omp/config.yml +15 -0
- package/dist/templates/omp/extensions/atw/index.ts.txt +135 -18
- package/dist/templates/omp/index.d.ts +1 -0
- package/dist/templates/omp/index.d.ts.map +1 -1
- package/dist/templates/omp/index.js +3 -0
- package/dist/templates/omp/index.js.map +1 -1
- package/dist/templates/opencode/lib/session-utils.js +4 -2
- package/dist/templates/opencode/plugins/inject-workflow-state.js +5 -1
- package/dist/templates/pi/extensions/atw/index.ts.txt +80 -7
- package/dist/templates/shared-hooks/index.d.ts +8 -1
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +8 -0
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-workflow-state.py +131 -14
- package/dist/templates/shared-hooks/session-start.py +4 -2
- package/dist/templates/shared-hooks/show-notice.py +135 -0
- package/dist/templates/snow/SNOW.md +1 -1
- package/dist/utils/initialized.d.ts +9 -0
- package/dist/utils/initialized.d.ts.map +1 -0
- package/dist/utils/initialized.js +24 -0
- package/dist/utils/initialized.js.map +1 -0
- package/dist/utils/managed-removal.d.ts +2 -0
- package/dist/utils/managed-removal.d.ts.map +1 -1
- package/dist/utils/managed-removal.js +23 -3
- package/dist/utils/managed-removal.js.map +1 -1
- package/dist/utils/optional-skills.d.ts +2 -0
- package/dist/utils/optional-skills.d.ts.map +1 -1
- package/dist/utils/optional-skills.js +1 -1
- package/dist/utils/optional-skills.js.map +1 -1
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +3 -0
- package/dist/utils/template-hash.js.map +1 -1
- package/dist/utils/uninstall-scrubbers.d.ts +10 -0
- package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
- package/dist/utils/uninstall-scrubbers.js +50 -0
- package/dist/utils/uninstall-scrubbers.js.map +1 -1
- package/dist/utils/verify-entries.d.ts +51 -0
- package/dist/utils/verify-entries.d.ts.map +1 -0
- package/dist/utils/verify-entries.js +399 -0
- package/dist/utils/verify-entries.js.map +1 -0
- package/package.json +2 -2
- package/dist/templates/common/bundled-skills/atw-prototype/UI.md +0 -174
- package/dist/templates/common/bundled-skills/atw-prototype/references/visual-language.md +0 -88
- package/dist/templates/common/bundled-skills/atw-prototype/references/visual-review.md +0 -63
- package/dist/templates/common/bundled-skills/atw-ui/references/imagery.md +0 -69
- /package/dist/templates/common/bundled-skills/atw-codebase-design/{DEEPENING.md → deepening.md} +0 -0
- /package/dist/templates/common/bundled-skills/atw-domain-modeling/{ADR-FORMAT.md → adr-format.md} +0 -0
- /package/dist/templates/common/bundled-skills/atw-domain-modeling/{GLOSSARY-FORMAT.md → glossary-format.md} +0 -0
- /package/dist/templates/common/bundled-skills/atw-improve-codebase-architecture/{HTML-REPORT.md → html-report.md} +0 -0
- /package/dist/templates/common/bundled-skills/atw-triage/{AGENT-BRIEF.md → agent-brief.md} +0 -0
- /package/dist/templates/common/bundled-skills/atw-triage/{OUT-OF-SCOPE.md → out-of-scope.md} +0 -0
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
openSync,
|
|
5
5
|
readFileSync,
|
|
6
6
|
readSync,
|
|
7
|
+
readdirSync,
|
|
7
8
|
realpathSync,
|
|
8
9
|
renameSync,
|
|
9
10
|
statSync,
|
|
@@ -1180,6 +1181,55 @@ function inheritSessionPointer(
|
|
|
1180
1181
|
// ── Workflow State Breadcrumb ─────────────────────────────────────────
|
|
1181
1182
|
const WF_RE =
|
|
1182
1183
|
/\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n([\s\S]*?)\n\s*\[\/workflow-state:\1\]/g;
|
|
1184
|
+
// Mirrors select_platform_blocks() in .atw/scripts/common/workflow_phase.py:
|
|
1185
|
+
// keep only the `[Platform, ...]` blocks naming this platform, or every block
|
|
1186
|
+
// when none does; marker lines are always dropped.
|
|
1187
|
+
const PLATFORM_MARKER_RE = /^\[(\/?)([A-Za-z][^[\]]*)\]\s*$/;
|
|
1188
|
+
function selectPlatformBlocks(body: string, platform: string): string {
|
|
1189
|
+
const norm = (s: string) => s.toLowerCase().replace(/[-_ ]/g, "");
|
|
1190
|
+
const names = (m: RegExpMatchArray) => (m[2] ?? "").split(",").map((n) => norm(n.trim()));
|
|
1191
|
+
const lines = body.split("\n");
|
|
1192
|
+
const anyMatch = lines.some((l) => {
|
|
1193
|
+
const m = l.match(PLATFORM_MARKER_RE);
|
|
1194
|
+
return !!m && m[1] !== "/" && names(m).includes(norm(platform));
|
|
1195
|
+
});
|
|
1196
|
+
const out: string[] = [];
|
|
1197
|
+
let keep = true;
|
|
1198
|
+
for (const line of lines) {
|
|
1199
|
+
const m = line.match(PLATFORM_MARKER_RE);
|
|
1200
|
+
if (m) {
|
|
1201
|
+
keep = m[1] === "/" || !anyMatch || names(m).includes(norm(platform));
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
if (keep) out.push(line);
|
|
1205
|
+
}
|
|
1206
|
+
return out.join("\n");
|
|
1207
|
+
}
|
|
1208
|
+
// Mirrors DECISION_LOG_REMINDER / has_decision_log() in the shared per-turn
|
|
1209
|
+
// Python hook: same sentence, implement or accept only.
|
|
1210
|
+
const DECISION_LOG_REMINDER =
|
|
1211
|
+
"This task has a decision log: keep recording it at key points with `python3 ./.atw/scripts/decision_log.py`; in this conversation, the start row (`decision_log.py start`) goes in just before the first row you log — `add` and the other writing commands write it first when it is missing, so a conversation with nothing to log writes none; read the last rows they print, and when another conversation wrote in between they write only the start row and stop: read it, then run the same command again.";
|
|
1212
|
+
// Mirrors DECISION_LOG_ARCHIVE_REMINDER in the shared per-turn Python hook:
|
|
1213
|
+
// same sentence, accept only (with a log), placed before DECISION_LOG_REMINDER.
|
|
1214
|
+
const DECISION_LOG_ARCHIVE_REMINDER =
|
|
1215
|
+
"This task has a decision log: once accepted, after the acceptance row and before `task.py archive`, log one row for the archive (`decision_log.py add`); it goes into the archive commit with the task.";
|
|
1216
|
+
// Mirrors DECISION_LOG_REPORT_REMINDER in the shared per-turn Python hook:
|
|
1217
|
+
// same sentence, implement or accept (with a log), just before DECISION_LOG_REMINDER.
|
|
1218
|
+
const DECISION_LOG_REPORT_REMINDER =
|
|
1219
|
+
"This task has a decision log: when the user asks how things stand or for a report (汇报一下, 现在什么情况, what's the status, report), rebuild it from the task directory in four blocks — `python3 ./.atw/scripts/decision_log.py pending --report` prints the draft; post it in plain words, keep the plain-words lines it carries with noob mode on, and fill in what it marks. This is the task's report, not `atw-what-happened`: that skill only the user starts, and it tells what changed in this chat since their last message.";
|
|
1220
|
+
// Mirrors DECISION_LOG_NO_TASK_REPORT_REMINDER / any_task_has_decision_log() in
|
|
1221
|
+
// the shared per-turn Python hook: same sentence, no current task only.
|
|
1222
|
+
const DECISION_LOG_NO_TASK_REPORT_REMINDER =
|
|
1223
|
+
"No task is current, but some task under `.atw/tasks/` has a decision log: when the user asks how things stand or for a report (汇报一下, 现在什么情况, what's the status, report), rebuild it from that task's directory in four blocks — list the active tasks with `python3 ./.atw/scripts/task.py list` and see which of their directories hold a `decision-log.md` (if the user named the task or said enough to tell which one, take that one; if not, only one, take it; several, ask the user which), then `python3 ./.atw/scripts/decision_log.py pending --report --task-dir <dir>` prints the draft; post it in plain words, keep the plain-words lines it carries with noob mode on, and fill in what it marks. This is the task's report, not `atw-what-happened`: that skill only the user starts, and it tells what changed in this chat since their last message.";
|
|
1224
|
+
// 只看 .atw/tasks/ 下一层,归档里的不算
|
|
1225
|
+
function anyTaskHasDecisionLog(root: string): boolean {
|
|
1226
|
+
const tasks = join(root, ".atw", "tasks");
|
|
1227
|
+
try {
|
|
1228
|
+
return readdirSync(tasks).some((name) => exists(join(tasks, name, "decision-log.md")));
|
|
1229
|
+
} catch {
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1183
1233
|
function workflowBreadcrumb(root: string, key: string | null): string {
|
|
1184
1234
|
const wf = readText(join(root, ".atw", "workflow.md"));
|
|
1185
1235
|
if (!wf) return "";
|
|
@@ -1191,7 +1241,9 @@ function workflowBreadcrumb(root: string, key: string | null): string {
|
|
|
1191
1241
|
}
|
|
1192
1242
|
const dir = readTaskDir(root, key);
|
|
1193
1243
|
let header = "Status: no_task",
|
|
1194
|
-
lookup = "no_task"
|
|
1244
|
+
lookup = "no_task",
|
|
1245
|
+
decisionLog = false,
|
|
1246
|
+
accept = false;
|
|
1195
1247
|
if (dir) {
|
|
1196
1248
|
try {
|
|
1197
1249
|
const d = JSON.parse(readText(join(dir, "task.json"))) as JsonObject;
|
|
@@ -1200,11 +1252,23 @@ function workflowBreadcrumb(root: string, key: string | null): string {
|
|
|
1200
1252
|
if (status) {
|
|
1201
1253
|
header = `Task: ${id} (${status})`;
|
|
1202
1254
|
lookup = status;
|
|
1255
|
+
decisionLog =
|
|
1256
|
+
(status === "implement" || status === "accept") &&
|
|
1257
|
+
exists(join(dir, "decision-log.md"));
|
|
1258
|
+
accept = status === "accept";
|
|
1203
1259
|
}
|
|
1204
1260
|
} catch {}
|
|
1205
1261
|
}
|
|
1206
|
-
const body =
|
|
1207
|
-
|
|
1262
|
+
const body = selectPlatformBlocks(
|
|
1263
|
+
templates[lookup] ?? "Refer to workflow.md for current step.",
|
|
1264
|
+
"Pi",
|
|
1265
|
+
);
|
|
1266
|
+
const tail = decisionLog
|
|
1267
|
+
? `${accept ? `\n${DECISION_LOG_ARCHIVE_REMINDER}` : ""}\n${DECISION_LOG_REPORT_REMINDER}\n${DECISION_LOG_REMINDER}`
|
|
1268
|
+
: lookup === "no_task" && anyTaskHasDecisionLog(root)
|
|
1269
|
+
? `\n${DECISION_LOG_NO_TASK_REPORT_REMINDER}`
|
|
1270
|
+
: "";
|
|
1271
|
+
return `<workflow-state>\n${header}\n${body}${tail}\n</workflow-state>`;
|
|
1208
1272
|
}
|
|
1209
1273
|
|
|
1210
1274
|
// ── Session Overview ───────────────────────────────────────────────────
|
|
@@ -1250,16 +1314,16 @@ function buildStartupContext(
|
|
|
1250
1314
|
): string {
|
|
1251
1315
|
const workflow = workflowOverview(root, key);
|
|
1252
1316
|
// get_context.py puts a <conventions> block in the overview only for a
|
|
1253
|
-
// `general` project
|
|
1254
|
-
//
|
|
1317
|
+
// `general` project: there <ready> defers to <atw-workflow>, which has its
|
|
1318
|
+
// own way to pick up unfinished work. In dev mode <ready> points to the
|
|
1319
|
+
// per-turn no-task breadcrumb for binding an already-existing task.
|
|
1255
1320
|
const ready = overview.includes("\n<conventions>\n")
|
|
1256
1321
|
? [
|
|
1257
1322
|
"Do what <atw-workflow> above says for the current task's status. With no current task, it also says when to create one and how to pick up unfinished work.",
|
|
1258
1323
|
]
|
|
1259
1324
|
: [
|
|
1260
1325
|
"Use the current workflow state to decide whether to create, continue, or skip an ATW task.",
|
|
1261
|
-
"
|
|
1262
|
-
"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.",
|
|
1326
|
+
"With no current task, if the request is about a task that already exists — the user names it, or asks to continue or accept and only one open task fits — make it this session's task first, as the no-task breadcrumb says; do not ask whether to create a new one.",
|
|
1263
1327
|
];
|
|
1264
1328
|
return [
|
|
1265
1329
|
"<session-context>\nATW compact SessionStart context. Use it to orient the session; load details on demand.\n</session-context>",
|
|
@@ -2032,6 +2096,10 @@ export default function atwExtension(pi: {
|
|
|
2032
2096
|
return undefined;
|
|
2033
2097
|
});
|
|
2034
2098
|
pi.on?.("before_agent_start", (event, ctx) => {
|
|
2099
|
+
// Same switch as the other platforms' hooks: channel sets ATW_HOOKS=0 on
|
|
2100
|
+
// its helpers, which must not receive prompts written for the main AI.
|
|
2101
|
+
if (process.env.ATW_HOOKS === "0" || process.env.ATW_DISABLE_HOOKS === "1")
|
|
2102
|
+
return undefined;
|
|
2035
2103
|
const k = getKey(event, ctx);
|
|
2036
2104
|
const key = cacheKey(k, ctx);
|
|
2037
2105
|
const cur = (event as { systemPrompt?: string }).systemPrompt ?? "";
|
|
@@ -2089,4 +2157,9 @@ export default function atwExtension(pi: {
|
|
|
2089
2157
|
pi.on?.("context", (event, ctx) => {
|
|
2090
2158
|
getKey(event, ctx);
|
|
2091
2159
|
});
|
|
2160
|
+
// Compaction can fold the persisted runtime-context message into its
|
|
2161
|
+
// summary. Forget what was sent so the next turn re-sends it unchanged.
|
|
2162
|
+
pi.on?.("session_compact", () => {
|
|
2163
|
+
lastSentRuntimeCtx.clear();
|
|
2164
|
+
});
|
|
2092
2165
|
}
|
|
@@ -11,7 +11,7 @@ export interface HookScript {
|
|
|
11
11
|
/** Script content — no placeholders, ready to write directly */
|
|
12
12
|
content: string;
|
|
13
13
|
}
|
|
14
|
-
export type SharedHookName = "session-start.py" | "inject-shell-session-context.py" | "inject-workflow-state.py" | "inject-subagent-context.py";
|
|
14
|
+
export type SharedHookName = "session-start.py" | "inject-shell-session-context.py" | "inject-workflow-state.py" | "inject-subagent-context.py" | "show-notice.py";
|
|
15
15
|
export type SharedHookPlatform = "claude" | "cursor" | "codex" | "gemini" | "qoder" | "copilot" | "codebuddy" | "droid" | "kiro" | "trae" | "zcode";
|
|
16
16
|
/**
|
|
17
17
|
* Which shared hooks each platform actually invokes. Single source of truth
|
|
@@ -51,6 +51,13 @@ export type SharedHookPlatform = "claude" | "cursor" | "codex" | "gemini" | "qod
|
|
|
51
51
|
* (per-turn breadcrumb), and `inject-subagent-context.py` (sub-agent
|
|
52
52
|
* spawn). The scripts emit a plain-text Kiro branch — Kiro adds a hook's
|
|
53
53
|
* stdout directly to the conversation context (no JSON envelope).
|
|
54
|
+
* - `show-notice.py` — Claude Code only (PostToolUse, matcher `Bash`): after
|
|
55
|
+
* `cross_model_review.py afk-notice|prepare|prepare-log` it hands the
|
|
56
|
+
* `notice` the script wrote to `.atw/.runtime/hook-notice.json` to Claude
|
|
57
|
+
* Code as `systemMessage`, which Claude Code shows the user without the
|
|
58
|
+
* model in between (task decision-log-afk ticket 26: Claude Code 2.1.302
|
|
59
|
+
* rewrote the paragraph instead of posting it). Other platforms keep the
|
|
60
|
+
* skills' "post it word for word".
|
|
54
61
|
* - Claude Code `statusLine` is intentionally not installed by default.
|
|
55
62
|
* Users can add their own statusLine command in `.claude/settings.json`,
|
|
56
63
|
* or opt in to the ATW one via `atw init --with-statusline`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,iCAAiC,GACjC,0BAA0B,GAC1B,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,iCAAiC,GACjC,0BAA0B,GAC1B,4BAA4B,GAC5B,gBAAgB,CAAC;AAErB,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,OAAO,GACP,SAAS,GACT,WAAW,GACX,OAAO,GACP,MAAM,GACN,MAAM,GACN,OAAO,CAAC;AAEZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAC3C,kBAAkB,EAClB,SAAS,cAAc,EAAE,CAqD1B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,EAAE,CAWnD;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,kBAAkB,GAC3B,UAAU,EAAE,CAGd"}
|
|
@@ -51,6 +51,13 @@ function readTemplate(relativePath) {
|
|
|
51
51
|
* (per-turn breadcrumb), and `inject-subagent-context.py` (sub-agent
|
|
52
52
|
* spawn). The scripts emit a plain-text Kiro branch — Kiro adds a hook's
|
|
53
53
|
* stdout directly to the conversation context (no JSON envelope).
|
|
54
|
+
* - `show-notice.py` — Claude Code only (PostToolUse, matcher `Bash`): after
|
|
55
|
+
* `cross_model_review.py afk-notice|prepare|prepare-log` it hands the
|
|
56
|
+
* `notice` the script wrote to `.atw/.runtime/hook-notice.json` to Claude
|
|
57
|
+
* Code as `systemMessage`, which Claude Code shows the user without the
|
|
58
|
+
* model in between (task decision-log-afk ticket 26: Claude Code 2.1.302
|
|
59
|
+
* rewrote the paragraph instead of posting it). Other platforms keep the
|
|
60
|
+
* skills' "post it word for word".
|
|
54
61
|
* - Claude Code `statusLine` is intentionally not installed by default.
|
|
55
62
|
* Users can add their own statusLine command in `.claude/settings.json`,
|
|
56
63
|
* or opt in to the ATW one via `atw init --with-statusline`
|
|
@@ -67,6 +74,7 @@ export const SHARED_HOOKS_BY_PLATFORM = {
|
|
|
67
74
|
"session-start.py",
|
|
68
75
|
"inject-workflow-state.py",
|
|
69
76
|
"inject-subagent-context.py",
|
|
77
|
+
"show-notice.py",
|
|
70
78
|
],
|
|
71
79
|
cursor: [
|
|
72
80
|
"session-start.py",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,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;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,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;AA6BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAGjC;IACF,MAAM,EAAE;QACN,kBAAkB;QAClB,0BAA0B;QAC1B,4BAA4B;QAC5B,gBAAgB;KACjB;IACD,MAAM,EAAE;QACN,kBAAkB;QAClB,iCAAiC;QACjC,4BAA4B;KAC7B;IACD,KAAK,EAAE,CAAC,0BAA0B,EAAE,4BAA4B,CAAC;IACjE,MAAM,EAAE;QACN,kBAAkB;QAClB,iCAAiC;QACjC,0BAA0B;KAC3B;IACD,KAAK,EAAE;QACL,kBAAkB;QAClB,iCAAiC;QACjC,0BAA0B;KAC3B;IACD,OAAO,EAAE,CAAC,0BAA0B,CAAC;IACrC,SAAS,EAAE;QACT,kBAAkB;QAClB,iCAAiC;QACjC,0BAA0B;QAC1B,4BAA4B;KAC7B;IACD,KAAK,EAAE;QACL,kBAAkB;QAClB,iCAAiC;QACjC,0BAA0B;QAC1B,4BAA4B;KAC7B;IACD,IAAI,EAAE;QACJ,kBAAkB;QAClB,0BAA0B;QAC1B,4BAA4B;KAC7B;IACD,IAAI,EAAE;QACJ,kBAAkB;QAClB,iCAAiC;QACjC,0BAA0B;KAC3B;IACD,KAAK,EAAE;QACL,kBAAkB;QAClB,iCAAiC;QACjC,0BAA0B;QAC1B,4BAA4B;KAC7B;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC;SACjC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;SAChC,IAAI,EAAE,CAAC;IAEV,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,+BAA+B,CAC7C,QAA4B;IAE5B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,wBAAwB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpE,OAAO,oBAAoB,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -160,13 +160,13 @@ def _resolve_active_task(root: Path, input_data: dict):
|
|
|
160
160
|
|
|
161
161
|
def get_active_task(
|
|
162
162
|
root: Path, input_data: dict
|
|
163
|
-
) -> tuple[str, str, str] | None:
|
|
163
|
+
) -> tuple[str, str, str, Path] | None:
|
|
164
164
|
"""Return active task data, a task-record error, or no task pointer.
|
|
165
165
|
|
|
166
|
-
``(task_id, "task_error", source)`` is distinct from ``None``: a
|
|
167
|
-
pointer can exist even when its task record is missing or
|
|
168
|
-
that state needs a diagnostic breadcrumb rather than the
|
|
169
|
-
prompt.
|
|
166
|
+
``(task_id, "task_error", source, task_dir)`` is distinct from ``None``: a
|
|
167
|
+
session pointer can exist even when its task record is missing or
|
|
168
|
+
unreadable, and that state needs a diagnostic breadcrumb rather than the
|
|
169
|
+
normal ``no_task`` prompt.
|
|
170
170
|
"""
|
|
171
171
|
active = _resolve_active_task(root, input_data)
|
|
172
172
|
if not active.task_path:
|
|
@@ -176,23 +176,23 @@ def get_active_task(
|
|
|
176
176
|
if not task_dir.is_absolute():
|
|
177
177
|
task_dir = root / task_dir
|
|
178
178
|
if active.stale:
|
|
179
|
-
return task_dir.name, f"stale_{active.source_type}", active.source
|
|
179
|
+
return task_dir.name, f"stale_{active.source_type}", active.source, task_dir
|
|
180
180
|
|
|
181
181
|
task_json = task_dir / "task.json"
|
|
182
182
|
if not task_json.is_file():
|
|
183
|
-
return task_dir.name, "task_error", active.source
|
|
183
|
+
return task_dir.name, "task_error", active.source, task_dir
|
|
184
184
|
try:
|
|
185
185
|
data = json.loads(task_json.read_text(encoding="utf-8"))
|
|
186
186
|
except (json.JSONDecodeError, OSError):
|
|
187
|
-
return task_dir.name, "task_error", active.source
|
|
187
|
+
return task_dir.name, "task_error", active.source, task_dir
|
|
188
188
|
if not isinstance(data, dict):
|
|
189
|
-
return task_dir.name, "task_error", active.source
|
|
189
|
+
return task_dir.name, "task_error", active.source, task_dir
|
|
190
190
|
|
|
191
191
|
task_id = data.get("id") or task_dir.name
|
|
192
192
|
status = data.get("status", "")
|
|
193
193
|
if not isinstance(status, str) or not status:
|
|
194
|
-
return task_dir.name, "task_error", active.source
|
|
195
|
-
return task_id, status, active.source
|
|
194
|
+
return task_dir.name, "task_error", active.source, task_dir
|
|
195
|
+
return task_id, status, active.source, task_dir
|
|
196
196
|
|
|
197
197
|
|
|
198
198
|
# ---------------------------------------------------------------------------
|
|
@@ -359,12 +359,80 @@ def resolve_breadcrumb_key(
|
|
|
359
359
|
return status
|
|
360
360
|
|
|
361
361
|
|
|
362
|
+
# 决定日志提醒:pi、omp 扩展里的 DECISION_LOG_REMINDER 与这里逐字相同。
|
|
363
|
+
DECISION_LOG_REMINDER = (
|
|
364
|
+
"This task has a decision log: keep recording it at key points with "
|
|
365
|
+
"`python3 ./.atw/scripts/decision_log.py`; in this conversation, the "
|
|
366
|
+
"start row (`decision_log.py start`) goes in just before the first row you "
|
|
367
|
+
"log — `add` and the other writing commands write it first when it is "
|
|
368
|
+
"missing, so a conversation with nothing to log writes none; read the last "
|
|
369
|
+
"rows they print, and when another conversation wrote in between they "
|
|
370
|
+
"write only the start row and stop: read it, then run the same command "
|
|
371
|
+
"again."
|
|
372
|
+
)
|
|
373
|
+
# accept 阶段另加归档那一行的提醒,接在「Once accepted, run task.py archive」之后;pi、omp 扩展同名常量逐字相同
|
|
374
|
+
DECISION_LOG_ARCHIVE_REMINDER = (
|
|
375
|
+
"This task has a decision log: once accepted, after the acceptance row "
|
|
376
|
+
"and before `task.py archive`, log one row for the archive "
|
|
377
|
+
"(`decision_log.py add`); it goes into the archive commit with the task."
|
|
378
|
+
)
|
|
379
|
+
# 用户问进度、要汇报时怎么拼(prd L 节):implement、accept 有日志时都出,紧挨在 DECISION_LOG_REMINDER 之前;pi、omp 扩展同名常量逐字相同
|
|
380
|
+
DECISION_LOG_REPORT_REMINDER = (
|
|
381
|
+
"This task has a decision log: when the user asks how things stand or for "
|
|
382
|
+
"a report (汇报一下, 现在什么情况, what's the status, report), rebuild it "
|
|
383
|
+
"from the task directory in four blocks — "
|
|
384
|
+
"`python3 ./.atw/scripts/decision_log.py pending --report` prints the "
|
|
385
|
+
"draft; post it in plain words, keep the plain-words lines it carries "
|
|
386
|
+
"with noob mode on, and fill in what it marks. This is the task's report, "
|
|
387
|
+
"not `atw-what-happened`: that skill only the user starts, and it tells "
|
|
388
|
+
"what changed in this chat since their last message."
|
|
389
|
+
)
|
|
390
|
+
# 没绑任务时问进度、要汇报(工单 27):.atw/tasks/ 下有任务带日志时出;pi、omp 扩展同名常量逐字相同
|
|
391
|
+
DECISION_LOG_NO_TASK_REPORT_REMINDER = (
|
|
392
|
+
"No task is current, but some task under `.atw/tasks/` has a decision "
|
|
393
|
+
"log: when the user asks how things stand or for a report (汇报一下, "
|
|
394
|
+
"现在什么情况, what's the status, report), rebuild it from that task's "
|
|
395
|
+
"directory in four blocks — list the active tasks with "
|
|
396
|
+
"`python3 ./.atw/scripts/task.py list` and see which of their directories "
|
|
397
|
+
"hold a `decision-log.md` (if the user named the task or said enough to "
|
|
398
|
+
"tell which one, take that one; if not, only one, take it; several, ask "
|
|
399
|
+
"the user which), then `python3 ./.atw/scripts/decision_log.py pending "
|
|
400
|
+
"--report --task-dir <dir>` prints the draft; post it in plain words, "
|
|
401
|
+
"keep the plain-words lines it carries with noob mode on, and fill in what "
|
|
402
|
+
"it marks. This is the task's report, not `atw-what-happened`: that skill "
|
|
403
|
+
"only the user starts, and it tells what changed in this chat since their "
|
|
404
|
+
"last message."
|
|
405
|
+
)
|
|
406
|
+
DECISION_LOG_STATUSES = ("implement", "accept")
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def has_decision_log(status: str, task_dir: Path) -> bool:
|
|
410
|
+
"""Whether the breadcrumb carries DECISION_LOG_REMINDER: an implement or
|
|
411
|
+
accept task whose directory holds ``decision-log.md``. Planning stages
|
|
412
|
+
never do — their decisions are the user's and live in prd.md and tickets.
|
|
413
|
+
"""
|
|
414
|
+
return status in DECISION_LOG_STATUSES and (task_dir / "decision-log.md").is_file()
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def any_task_has_decision_log(root: Path) -> bool:
|
|
418
|
+
"""Whether a task directly under ``.atw/tasks/`` holds a ``decision-log.md``
|
|
419
|
+
file. One level only, so archived tasks never count; cheap enough to run
|
|
420
|
+
every turn.
|
|
421
|
+
"""
|
|
422
|
+
try:
|
|
423
|
+
return any((d / "decision-log.md").is_file() for d in (root / ".atw" / "tasks").iterdir())
|
|
424
|
+
except OSError:
|
|
425
|
+
return False
|
|
426
|
+
|
|
427
|
+
|
|
362
428
|
def build_breadcrumb(
|
|
363
429
|
task_id: Optional[str],
|
|
364
430
|
status: str,
|
|
365
431
|
templates: dict[str, str],
|
|
366
432
|
source: str | None = None,
|
|
367
433
|
breadcrumb_key: str | None = None,
|
|
434
|
+
task_has_log: bool = False,
|
|
435
|
+
repo_has_task_log: bool = False,
|
|
368
436
|
) -> str:
|
|
369
437
|
"""Build the <workflow-state>...</workflow-state> block.
|
|
370
438
|
|
|
@@ -372,6 +440,14 @@ def build_breadcrumb(
|
|
|
372
440
|
- Unknown status (no tag, or workflow.md missing) → generic
|
|
373
441
|
"Refer to workflow.md for current step." line
|
|
374
442
|
- `no_task` pseudo-status (task_id is None) → header omits task info
|
|
443
|
+
- ``task_has_log`` (the current task carries its decision log, see
|
|
444
|
+
``has_decision_log``) →
|
|
445
|
+
DECISION_LOG_REMINDER as the block's last line,
|
|
446
|
+
DECISION_LOG_REPORT_REMINDER just before it, and in accept
|
|
447
|
+
DECISION_LOG_ARCHIVE_REMINDER before that
|
|
448
|
+
- ``repo_has_task_log`` (no current task, but some task under
|
|
449
|
+
``.atw/tasks/`` has a decision log) →
|
|
450
|
+
DECISION_LOG_NO_TASK_REPORT_REMINDER as the block's last line
|
|
375
451
|
"""
|
|
376
452
|
lookup_key = breadcrumb_key or status
|
|
377
453
|
body = templates.get(lookup_key)
|
|
@@ -379,10 +455,38 @@ def build_breadcrumb(
|
|
|
379
455
|
body = templates.get(status)
|
|
380
456
|
if body is None:
|
|
381
457
|
body = "Refer to workflow.md for current step."
|
|
458
|
+
if repo_has_task_log:
|
|
459
|
+
body = f"{body}\n{DECISION_LOG_NO_TASK_REPORT_REMINDER}"
|
|
460
|
+
if task_has_log:
|
|
461
|
+
if status == "accept":
|
|
462
|
+
body = f"{body}\n{DECISION_LOG_ARCHIVE_REMINDER}"
|
|
463
|
+
body = f"{body}\n{DECISION_LOG_REPORT_REMINDER}\n{DECISION_LOG_REMINDER}"
|
|
382
464
|
header = f"Status: {status}" if task_id is None else f"Task: {task_id} ({status})"
|
|
383
465
|
return f"<workflow-state>\n{header}\n{body}\n</workflow-state>"
|
|
384
466
|
|
|
385
467
|
|
|
468
|
+
def _select_platform_blocks(root: Path, body: str, platform: str | None, config: dict) -> str:
|
|
469
|
+
"""Keep only this platform's `[Platform]` block in a breadcrumb body.
|
|
470
|
+
|
|
471
|
+
The accept breadcrumb gives the cross-model review input once per tool;
|
|
472
|
+
a host handed all of them has picked another tool's form. A platform
|
|
473
|
+
with no block of its own gets every line (each names its tool). An
|
|
474
|
+
install whose scripts predate the helper gets the body unchanged.
|
|
475
|
+
"""
|
|
476
|
+
scripts_dir = root / ".atw" / "scripts"
|
|
477
|
+
if str(scripts_dir) not in sys.path:
|
|
478
|
+
sys.path.insert(0, str(scripts_dir))
|
|
479
|
+
try:
|
|
480
|
+
from common.workflow_phase import ( # type: ignore[import-not-found]
|
|
481
|
+
resolve_effective_platform,
|
|
482
|
+
select_platform_blocks,
|
|
483
|
+
)
|
|
484
|
+
except Exception:
|
|
485
|
+
return body
|
|
486
|
+
effective = resolve_effective_platform(platform, config) if platform else None
|
|
487
|
+
return select_platform_blocks(body, effective)
|
|
488
|
+
|
|
489
|
+
|
|
386
490
|
def _is_general_project(root: Path) -> bool:
|
|
387
491
|
"""Whether this is a `general` project, as `common.config` reads the mode.
|
|
388
492
|
|
|
@@ -473,20 +577,33 @@ def main() -> int:
|
|
|
473
577
|
|
|
474
578
|
templates = load_breadcrumbs(root)
|
|
475
579
|
platform = _detect_platform(data)
|
|
580
|
+
templates = {
|
|
581
|
+
key: _select_platform_blocks(root, body, platform, config)
|
|
582
|
+
for key, body in templates.items()
|
|
583
|
+
}
|
|
476
584
|
task = get_active_task(root, data)
|
|
477
585
|
if task is None:
|
|
478
586
|
# No active task — still emit a breadcrumb nudging AI toward
|
|
479
587
|
# atw-brainstorm + task.py create when user describes real work.
|
|
480
588
|
no_task_key = resolve_breadcrumb_key("no_task", platform, config)
|
|
481
589
|
breadcrumb = build_breadcrumb(
|
|
482
|
-
None,
|
|
590
|
+
None,
|
|
591
|
+
"no_task",
|
|
592
|
+
templates,
|
|
593
|
+
breadcrumb_key=no_task_key,
|
|
594
|
+
repo_has_task_log=any_task_has_decision_log(root),
|
|
483
595
|
)
|
|
484
596
|
else:
|
|
485
|
-
task_id, status, source = task
|
|
597
|
+
task_id, status, source, task_dir = task
|
|
486
598
|
status_key = resolve_breadcrumb_key(status, platform, config)
|
|
487
599
|
source_for_breadcrumb = None if platform == "codex" else source
|
|
488
600
|
breadcrumb = build_breadcrumb(
|
|
489
|
-
task_id,
|
|
601
|
+
task_id,
|
|
602
|
+
status,
|
|
603
|
+
templates,
|
|
604
|
+
source_for_breadcrumb,
|
|
605
|
+
breadcrumb_key=status_key,
|
|
606
|
+
task_has_log=has_decision_log(status, task_dir),
|
|
490
607
|
)
|
|
491
608
|
if platform == "codex":
|
|
492
609
|
parts: list[str] = []
|
|
@@ -454,8 +454,10 @@ def _get_task_status(atw_dir: Path, input_data: dict, general: bool = False) ->
|
|
|
454
454
|
)
|
|
455
455
|
return (
|
|
456
456
|
"Status: NO ACTIVE TASK\n"
|
|
457
|
-
"Next-Action:
|
|
458
|
-
"
|
|
457
|
+
"Next-Action: If the request is about a task that already exists — the user names it, or asks to continue or accept and only one open task fits — make it this session's task first, as the no-task breadcrumb says; do not ask whether to create a new one. "
|
|
458
|
+
"Otherwise classify the current turn before creating any ATW task. "
|
|
459
|
+
"Simple conversation / small task asks whether this turn should create an ATW task. "
|
|
460
|
+
"In that ask, say what each answer means: without a task there is no requirements document (`prd.md`) and no formal acceptance (each requirement checked with evidence, the user signs off); a task adds writing the requirements, the user confirming the spec, implementing against it, and that acceptance before archiving. "
|
|
459
461
|
"Complex task asks whether task creation and planning are allowed."
|
|
460
462
|
)
|
|
461
463
|
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""ATW start-notice hook (Claude Code PostToolUse, matcher ``Bash``).
|
|
3
|
+
|
|
4
|
+
Claude Code 2.1.302 as the host rewrote the paragraph that
|
|
5
|
+
``cross_model_review.py afk-notice`` / ``prepare`` / ``prepare-log`` print
|
|
6
|
+
as ``notice`` instead of posting it word for word, however the skills put it
|
|
7
|
+
(task decision-log-afk tickets 20, 23, 28, 26). So the script also writes the
|
|
8
|
+
paragraph to ``.atw/.runtime/hook-notice.json``, and this hook, right after
|
|
9
|
+
one of those commands ends, hands it to Claude Code as ``systemMessage``:
|
|
10
|
+
Claude Code shows that to the user itself, the model never sees it. An
|
|
11
|
+
``additionalContext`` line tells the model the paragraph has been shown, so it
|
|
12
|
+
does not post it again.
|
|
13
|
+
|
|
14
|
+
Verified on Claude Code 2.1.302 (``claude -p --output-format stream-json
|
|
15
|
+
--verbose``): the message arrives as ``{"type":"system","subtype":
|
|
16
|
+
"informational","content":"PostToolUse:Bash says: <text>"}`` (every line of
|
|
17
|
+
the text gets that prefix), the session record keeps it as an
|
|
18
|
+
``attachment`` of type ``hook_system_message`` with the text unchanged, and the
|
|
19
|
+
model, asked to quote any hook message it saw, saw none.
|
|
20
|
+
|
|
21
|
+
The file is shown once and then removed. Nothing is printed when the command
|
|
22
|
+
is not one of the three, when the file was written by another of them, when
|
|
23
|
+
it was written in another conversation (its ``session`` is the ATW context key
|
|
24
|
+
``resolve_context_key`` gave the script; this hook computes the same key from
|
|
25
|
+
its own input — when either side has none, it is shown as before), when there
|
|
26
|
+
is no file, outside an ATW project, or with ATW hooks turned off.
|
|
27
|
+
|
|
28
|
+
Claude Code caps ``systemMessage`` at 10,000 characters and replaces a longer
|
|
29
|
+
one with a file path and a 2,000-character preview. A paragraph over
|
|
30
|
+
``SHOW_LIMIT`` is therefore not shown at all: ``additionalContext`` tells the
|
|
31
|
+
model to post it itself, word for word.
|
|
32
|
+
|
|
33
|
+
The command names in ``COMMAND_RE`` are the script's ``NOTICE_COMMANDS``:
|
|
34
|
+
change one, change the other.
|
|
35
|
+
"""
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import json
|
|
39
|
+
import os
|
|
40
|
+
import re
|
|
41
|
+
import sys
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
from typing import Optional
|
|
44
|
+
|
|
45
|
+
NOTICE_FILE = Path(".atw") / ".runtime" / "hook-notice.json"
|
|
46
|
+
# Claude Code 的上限是 10,000 字,留出余量
|
|
47
|
+
SHOW_LIMIT = 8000
|
|
48
|
+
COMMAND_RE = re.compile(r"cross_model_review\.py['\"]?\s+(afk-notice|prepare-log|prepare)(?![\w-])")
|
|
49
|
+
SHOWN = (
|
|
50
|
+
"ATW: Claude Code has just shown the user the `notice` that `cross_model_review.py {command}` printed "
|
|
51
|
+
"({which}), word for word, as a message of its own. Do not post it again and do not reword it; "
|
|
52
|
+
"go on with the next step."
|
|
53
|
+
)
|
|
54
|
+
TOO_LONG = (
|
|
55
|
+
"ATW: the `notice` that `cross_model_review.py {command}` printed ({which}) is too long for Claude Code "
|
|
56
|
+
"to show whole ({length} characters, over {limit}), so it was not shown. Post it yourself, word for word, "
|
|
57
|
+
"as a message of its own, as the skill says."
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def find_atw_root(start: Path) -> Optional[Path]:
|
|
62
|
+
cur = start.resolve()
|
|
63
|
+
for d in (cur, *cur.parents):
|
|
64
|
+
if (d / ".atw").is_dir():
|
|
65
|
+
return d
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _context_key(root: Path, data: dict) -> Optional[str]:
|
|
70
|
+
"""ATW's own key for this conversation, the same way the script got it."""
|
|
71
|
+
scripts_dir = root / ".atw" / "scripts"
|
|
72
|
+
if str(scripts_dir) not in sys.path:
|
|
73
|
+
sys.path.insert(0, str(scripts_dir))
|
|
74
|
+
try:
|
|
75
|
+
from common.active_task import resolve_context_key # type: ignore[import-not-found]
|
|
76
|
+
except ImportError:
|
|
77
|
+
return None
|
|
78
|
+
return resolve_context_key(data, platform="claude")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def main() -> int:
|
|
82
|
+
if os.environ.get("ATW_HOOKS") == "0" or os.environ.get("ATW_DISABLE_HOOKS") == "1":
|
|
83
|
+
return 0
|
|
84
|
+
try:
|
|
85
|
+
# 按字节读、输出全用 ASCII 转义:Windows 上的默认编码碰不到中文
|
|
86
|
+
data = json.loads(sys.stdin.buffer.read().decode("utf-8", errors="replace"))
|
|
87
|
+
except (ValueError, OSError):
|
|
88
|
+
return 0
|
|
89
|
+
if not isinstance(data, dict) or data.get("tool_name") != "Bash":
|
|
90
|
+
return 0
|
|
91
|
+
tool_input = data.get("tool_input")
|
|
92
|
+
command = tool_input.get("command") if isinstance(tool_input, dict) else None
|
|
93
|
+
match = COMMAND_RE.search(command) if isinstance(command, str) else None
|
|
94
|
+
if match is None:
|
|
95
|
+
return 0
|
|
96
|
+
cwd = data.get("cwd")
|
|
97
|
+
root = find_atw_root(Path(cwd) if isinstance(cwd, str) and cwd else Path.cwd())
|
|
98
|
+
if root is None:
|
|
99
|
+
return 0
|
|
100
|
+
path = root / NOTICE_FILE
|
|
101
|
+
try:
|
|
102
|
+
saved = json.loads(path.read_text(encoding="utf-8"))
|
|
103
|
+
except (OSError, ValueError):
|
|
104
|
+
return 0
|
|
105
|
+
if not isinstance(saved, dict) or saved.get("command") != match.group(1):
|
|
106
|
+
return 0
|
|
107
|
+
text = saved.get("text")
|
|
108
|
+
if not isinstance(text, str) or not text:
|
|
109
|
+
return 0
|
|
110
|
+
written_in = saved.get("session")
|
|
111
|
+
here = _context_key(root, data) if written_in else None
|
|
112
|
+
if written_in and here and written_in != here:
|
|
113
|
+
# 别的对话写的:留给它,不在这里显示
|
|
114
|
+
return 0
|
|
115
|
+
path.unlink(missing_ok=True)
|
|
116
|
+
command_name = match.group(1)
|
|
117
|
+
which = {"zh": "the Chinese paragraph", "en": "the English paragraph"}.get(
|
|
118
|
+
saved.get("lang"), "both the Chinese and the English paragraph"
|
|
119
|
+
)
|
|
120
|
+
if len(text) > SHOW_LIMIT:
|
|
121
|
+
out: dict = {"hookSpecificOutput": {
|
|
122
|
+
"hookEventName": "PostToolUse",
|
|
123
|
+
"additionalContext": TOO_LONG.format(command=command_name, which=which, length=len(text), limit=SHOW_LIMIT),
|
|
124
|
+
}}
|
|
125
|
+
else:
|
|
126
|
+
out = {"systemMessage": text, "hookSpecificOutput": {
|
|
127
|
+
"hookEventName": "PostToolUse",
|
|
128
|
+
"additionalContext": SHOWN.format(command=command_name, which=which),
|
|
129
|
+
}}
|
|
130
|
+
print(json.dumps(out))
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if __name__ == "__main__":
|
|
135
|
+
sys.exit(main())
|
|
@@ -50,7 +50,7 @@ the AI do the rest. There are no prompt commands to run and no project agents.
|
|
|
50
50
|
|
|
51
51
|
| Capability | Status |
|
|
52
52
|
| ------------------------------------------ | ---------------------------------------------------------------------------- |
|
|
53
|
-
| Skills (`.snow/skills/atw-*/SKILL.md`) | Works — the
|
|
53
|
+
| Skills (`.snow/skills/atw-*/SKILL.md`) | Works — the seven skills of `general` mode |
|
|
54
54
|
| Context hooks (`.snow/hooks/`) | Inject model context via stdout JSON + write `.snow/log/atw-context.txt` |
|
|
55
55
|
| Prompt commands, project agents | None in `general` mode |
|
|
56
56
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** `.atw/` 里归用户、卸载后仍留着的条目。只剩它们时项目不算装了 ATW。 */
|
|
2
|
+
export declare const ATW_ENTRIES_KEPT_BY_UNINSTALL: readonly string[];
|
|
3
|
+
/**
|
|
4
|
+
* 项目里装没装 ATW:`.atw/` 存在,且不是只剩卸载后留下的用户条目。
|
|
5
|
+
* 不看 `.atw/.version`——早期版本装的项目可能没有它;空的 `.atw/` 照旧算装过
|
|
6
|
+
* (由各命令自己报清单缺失)。
|
|
7
|
+
*/
|
|
8
|
+
export declare function isAtwInitialized(cwd: string): boolean;
|
|
9
|
+
//# sourceMappingURL=initialized.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initialized.d.ts","sourceRoot":"","sources":["../../src/utils/initialized.ts"],"names":[],"mappings":"AAKA,8CAA8C;AAC9C,eAAO,MAAM,6BAA6B,EAAE,SAAS,MAAM,EAE1D,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAWrD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { DIR_NAMES } from "../constants/paths.js";
|
|
4
|
+
/** `.atw/` 里归用户、卸载后仍留着的条目。只剩它们时项目不算装了 ATW。 */
|
|
5
|
+
export const ATW_ENTRIES_KEPT_BY_UNINSTALL = [
|
|
6
|
+
DIR_NAMES.SKILL_NOTES,
|
|
7
|
+
];
|
|
8
|
+
/**
|
|
9
|
+
* 项目里装没装 ATW:`.atw/` 存在,且不是只剩卸载后留下的用户条目。
|
|
10
|
+
* 不看 `.atw/.version`——早期版本装的项目可能没有它;空的 `.atw/` 照旧算装过
|
|
11
|
+
* (由各命令自己报清单缺失)。
|
|
12
|
+
*/
|
|
13
|
+
export function isAtwInitialized(cwd) {
|
|
14
|
+
let entries;
|
|
15
|
+
try {
|
|
16
|
+
entries = fs.readdirSync(path.join(cwd, DIR_NAMES.WORKFLOW));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return (entries.length === 0 ||
|
|
22
|
+
entries.some((entry) => !ATW_ENTRIES_KEPT_BY_UNINSTALL.includes(entry)));
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=initialized.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initialized.js","sourceRoot":"","sources":["../../src/utils/initialized.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD,8CAA8C;AAC9C,MAAM,CAAC,MAAM,6BAA6B,GAAsB;IAC9D,SAAS,CAAC,WAAW;CACtB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,CACL,OAAO,CAAC,MAAM,KAAK,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,6BAA6B,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CACxE,CAAC;AACJ,CAAC"}
|
|
@@ -30,6 +30,8 @@ export interface ManagedRemovalPlan {
|
|
|
30
30
|
deletions: PlannedDeletion[];
|
|
31
31
|
modifications: PlannedModification[];
|
|
32
32
|
removeAtwDir: boolean;
|
|
33
|
+
/** `.atw/` 里归用户、卸载留着的条目(磁盘上有的);非空时 `.atw/` 只删其余条目。 */
|
|
34
|
+
keptAtwEntries: string[];
|
|
33
35
|
allPosixPaths: string[];
|
|
34
36
|
}
|
|
35
37
|
export interface BuildManagedRemovalPlanOptions {
|