@aipper/aiws-spec 0.0.28 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/docs/cli-interface.md +10 -12
  2. package/docs/opencode-autonomous-swarm.md +178 -0
  3. package/docs/opencode-omo-adapter.md +123 -4
  4. package/docs/opencode-omo-validation-checklist.md +47 -0
  5. package/docs/opencode-subagent-first.md +187 -0
  6. package/docs/workflow-delegation-context-injection.md +217 -0
  7. package/docs/workflow-delegation-contracts.json +108 -2
  8. package/docs/workflow-delegation-contracts.md +13 -0
  9. package/docs/workflow-delegation-contracts.schema.json +152 -0
  10. package/docs/workflow-governance-rules.json +47 -6
  11. package/docs/workflow-governance-rules.md +8 -7
  12. package/docs/workflow-governance-rules.schema.json +39 -1
  13. package/docs/workflow-review-gates.json +6 -0
  14. package/docs/workflow-review-gates.md +14 -5
  15. package/docs/workflow-review-gates.schema.json +4 -0
  16. package/docs/workflow-router-rules.json +72 -8
  17. package/docs/workflow-router-rules.md +21 -14
  18. package/docs/workflow-router-rules.schema.json +4 -0
  19. package/docs/workflow-stage-contracts.json +24 -5
  20. package/docs/workflow-stage-contracts.md +16 -15
  21. package/docs/workflow-stage-contracts.schema.json +12 -1
  22. package/package.json +1 -1
  23. package/templates/workspace/.agents/skills/using-aiws/SKILL.md +16 -10
  24. package/templates/workspace/.agents/skills/ws-commit/SKILL.md +6 -118
  25. package/templates/workspace/.agents/skills/ws-deliver/SKILL.md +6 -218
  26. package/templates/workspace/.agents/skills/ws-dev/SKILL.md +82 -141
  27. package/templates/workspace/.agents/skills/ws-finish/SKILL.md +9 -204
  28. package/templates/workspace/.agents/skills/ws-handoff/SKILL.md +10 -44
  29. package/templates/workspace/.agents/skills/ws-migrate/SKILL.md +6 -42
  30. package/templates/workspace/.agents/skills/ws-plan-verify/SKILL.md +6 -58
  31. package/templates/workspace/.agents/skills/ws-pull/SKILL.md +8 -109
  32. package/templates/workspace/.agents/skills/ws-push/SKILL.md +8 -100
  33. package/templates/workspace/.agents/skills/ws-review/SKILL.md +6 -1
  34. package/templates/workspace/.agents/skills/ws-submodule-setup/SKILL.md +10 -57
  35. package/templates/workspace/.agents/skills/ws-verify-before-complete/SKILL.md +12 -53
  36. package/templates/workspace/{changes → .aiws/changes}/README.md +20 -23
  37. package/templates/workspace/{changes → .aiws/changes}/templates/proposal.md +12 -12
  38. package/templates/workspace/{changes → .aiws/changes}/templates/tasks.md +8 -8
  39. package/templates/workspace/{issues → .aiws/issues}/problem-issues.csv +1 -1
  40. package/templates/workspace/.aiws/journal/.gitkeep +1 -0
  41. package/templates/workspace/{requirements → .aiws/requirements}/requirements-issues.csv +1 -1
  42. package/templates/workspace/.aiws/tmp/.gitkeep +0 -0
  43. package/templates/workspace/{tools → .aiws/tools}/requirements_contract.py +2 -2
  44. package/templates/workspace/{tools → .aiws/tools}/requirements_contract_sync.py +2 -2
  45. package/templates/workspace/{tools → .aiws/tools}/requirements_flow_gen.py +1 -1
  46. package/templates/workspace/{tools → .aiws/tools}/server_test_runner.py +10 -10
  47. package/templates/workspace/{tools → .aiws/tools}/ws_change_check.py +23 -23
  48. package/templates/workspace/{tools → .aiws/tools}/ws_resolve_sub_target.sh +2 -2
  49. package/templates/workspace/{tools → .aiws/tools}/ws_tasks_plan.py +3 -3
  50. package/templates/workspace/.claude/commands/ws-review.md +5 -1
  51. package/templates/workspace/.claude/settings.json.example +26 -0
  52. package/templates/workspace/.claude/skills/ws-commit/SKILL.md +6 -118
  53. package/templates/workspace/.claude/skills/ws-deliver/SKILL.md +6 -218
  54. package/templates/workspace/.claude/skills/ws-dev/SKILL.md +52 -141
  55. package/templates/workspace/.claude/skills/ws-finish/SKILL.md +9 -204
  56. package/templates/workspace/.claude/skills/ws-handoff/SKILL.md +10 -44
  57. package/templates/workspace/.claude/skills/ws-migrate/SKILL.md +6 -42
  58. package/templates/workspace/.claude/skills/ws-plan-verify/SKILL.md +6 -49
  59. package/templates/workspace/.claude/skills/ws-pull/SKILL.md +8 -109
  60. package/templates/workspace/.claude/skills/ws-push/SKILL.md +8 -100
  61. package/templates/workspace/.claude/skills/ws-review/SKILL.md +6 -1
  62. package/templates/workspace/.claude/skills/ws-submodule-setup/SKILL.md +10 -57
  63. package/templates/workspace/.claude/skills/ws-verify-before-complete/SKILL.md +12 -53
  64. package/templates/workspace/.githooks/pre-commit +13 -0
  65. package/templates/workspace/.githooks/pre-push +7 -0
  66. package/templates/workspace/.opencode/command/ws-auto.md +33 -0
  67. package/templates/workspace/.opencode/command/ws-autonomy.md +25 -0
  68. package/templates/workspace/.opencode/command/ws-review.md +5 -1
  69. package/templates/workspace/.opencode/commands/ws-auto.md +33 -0
  70. package/templates/workspace/.opencode/commands/ws-autonomy.md +25 -0
  71. package/templates/workspace/.opencode/commands/ws-commit.md +4 -56
  72. package/templates/workspace/.opencode/commands/ws-deliver.md +10 -50
  73. package/templates/workspace/.opencode/commands/ws-finish.md +11 -3
  74. package/templates/workspace/.opencode/commands/ws-handoff.md +9 -17
  75. package/templates/workspace/.opencode/commands/ws-migrate.md +10 -17
  76. package/templates/workspace/.opencode/commands/ws-plan-verify.md +5 -15
  77. package/templates/workspace/.opencode/commands/ws-pull.md +6 -75
  78. package/templates/workspace/.opencode/commands/ws-push.md +7 -82
  79. package/templates/workspace/.opencode/commands/ws-review.md +5 -1
  80. package/templates/workspace/.opencode/commands/ws-submodule-setup.md +8 -47
  81. package/templates/workspace/.opencode/commands/ws-verify-before-complete.md +10 -19
  82. package/templates/workspace/.opencode/helpers/approval-whitelist-check.sh +148 -0
  83. package/templates/workspace/.opencode/helpers/approval-whitelist-run.sh +82 -0
  84. package/templates/workspace/.opencode/helpers/approval-whitelist-watchdog.sh +144 -0
  85. package/templates/workspace/.opencode/helpers/tmux-swarm-rescue.sh +56 -0
  86. package/templates/workspace/.opencode/helpers/tmux-swarm-scan.sh +46 -0
  87. package/templates/workspace/.opencode/lib/aiws-context.js +953 -0
  88. package/templates/workspace/.opencode/oh-my-opencode.json.example +64 -4
  89. package/templates/workspace/.opencode/plugins/aiws-inject-context.js +201 -0
  90. package/templates/workspace/.opencode/plugins/aiws-session-start.js +86 -0
  91. package/templates/workspace/.opencode/plugins/aiws-workflow-state.js +116 -0
  92. package/templates/workspace/.opencode/scripts/ws-plan-gen-submodule-targets.sh +27 -0
  93. package/templates/workspace/.opencode/scripts/ws-plan-setup-worktree.sh +31 -0
  94. package/templates/workspace/.opencode/skills/using-aiws/SKILL.md +130 -77
  95. package/templates/workspace/.opencode/skills/ws-analyze/SKILL.md +1 -1
  96. package/templates/workspace/.opencode/skills/ws-auto/SKILL.md +46 -0
  97. package/templates/workspace/.opencode/skills/ws-autonomy/SKILL.md +62 -0
  98. package/templates/workspace/.opencode/skills/ws-bugfix/SKILL.md +1 -1
  99. package/templates/workspace/.opencode/skills/ws-commit/SKILL.md +6 -118
  100. package/templates/workspace/.opencode/skills/ws-delegate/SKILL.md +115 -40
  101. package/templates/workspace/.opencode/skills/ws-deliver/SKILL.md +6 -218
  102. package/templates/workspace/.opencode/skills/ws-dev/SKILL.md +105 -146
  103. package/templates/workspace/.opencode/skills/ws-dev-lite/SKILL.md +23 -7
  104. package/templates/workspace/.opencode/skills/ws-finish/SKILL.md +9 -204
  105. package/templates/workspace/.opencode/skills/ws-frontend-design/SKILL.md +1 -1
  106. package/templates/workspace/.opencode/skills/ws-handoff/SKILL.md +10 -44
  107. package/templates/workspace/.opencode/skills/ws-intake/SKILL.md +43 -4
  108. package/templates/workspace/.opencode/skills/ws-migrate/SKILL.md +6 -42
  109. package/templates/workspace/.opencode/skills/ws-plan/SKILL.md +15 -55
  110. package/templates/workspace/.opencode/skills/ws-plan-verify/SKILL.md +6 -49
  111. package/templates/workspace/.opencode/skills/ws-preflight/SKILL.md +1 -1
  112. package/templates/workspace/.opencode/skills/ws-pull/SKILL.md +8 -109
  113. package/templates/workspace/.opencode/skills/ws-push/SKILL.md +8 -100
  114. package/templates/workspace/.opencode/skills/ws-quality-review/SKILL.md +7 -2
  115. package/templates/workspace/.opencode/skills/ws-req-change/SKILL.md +1 -1
  116. package/templates/workspace/.opencode/skills/ws-req-contract-sync/SKILL.md +1 -1
  117. package/templates/workspace/.opencode/skills/ws-req-contract-validate/SKILL.md +1 -1
  118. package/templates/workspace/.opencode/skills/ws-req-flow-sync/SKILL.md +1 -1
  119. package/templates/workspace/.opencode/skills/ws-req-review/SKILL.md +1 -1
  120. package/templates/workspace/.opencode/skills/ws-review/SKILL.md +35 -4
  121. package/templates/workspace/.opencode/skills/ws-rule/SKILL.md +1 -1
  122. package/templates/workspace/.opencode/skills/ws-spec-review/SKILL.md +1 -1
  123. package/templates/workspace/.opencode/skills/ws-submodule-setup/SKILL.md +10 -57
  124. package/templates/workspace/.opencode/skills/ws-verify-before-complete/SKILL.md +45 -55
  125. package/templates/workspace/AGENTS.md +32 -84
  126. package/templates/workspace/AI_PROJECT.md +5 -5
  127. package/templates/workspace/AI_WORKSPACE.md +1 -1
  128. package/templates/workspace/REQUIREMENTS.md +1 -1
  129. package/templates/workspace/gitignore +1 -1
  130. package/templates/workspace/manifest.json +215 -395
  131. /package/templates/workspace/{changes → .aiws/changes}/templates/design.md +0 -0
  132. /package/templates/workspace/{memory-bank → .aiws/memory-bank}/README.md +0 -0
  133. /package/templates/workspace/{memory-bank → .aiws/memory-bank}/architecture.md +0 -0
  134. /package/templates/workspace/{memory-bank → .aiws/memory-bank}/implementation-plan.md +0 -0
  135. /package/templates/workspace/{memory-bank → .aiws/memory-bank}/progress.md +0 -0
  136. /package/templates/workspace/{memory-bank → .aiws/memory-bank}/tech-stack.md +0 -0
  137. /package/templates/workspace/{requirements → .aiws/requirements}/CHANGELOG.md +0 -0
  138. /package/templates/workspace/{secrets → .aiws/secrets}/test-accounts.example.json +0 -0
@@ -2,16 +2,76 @@
2
2
  "agents": {
3
3
  "planner-sisyphus": {
4
4
  "enabled": true,
5
- "replace_plan": false
5
+ "replace_plan": false,
6
+ "categories": [
7
+ "planning",
8
+ "autonomous"
9
+ ],
10
+ "prompt_append": "You are running inside an AIWS workspace. Before coding, read AI_PROJECT.md, REQUIREMENTS.md, and AI_WORKSPACE.md. Stay autonomous until the task is complete. Completion means: truth files read, task/change bound, required verify commands run, review/evidence written, and aiws validate . or change validate completed when applicable. Stop early only for a real blocker: missing user decision, missing credential, external dependency failure, or sandbox/permission failure."
6
11
  },
7
12
  "librarian": {
8
- "enabled": true
13
+ "enabled": true,
14
+ "categories": [
15
+ "docs",
16
+ "research"
17
+ ]
9
18
  },
10
19
  "explore": {
11
- "enabled": true
20
+ "enabled": true,
21
+ "categories": [
22
+ "code",
23
+ "analysis"
24
+ ]
12
25
  },
13
26
  "oracle": {
14
- "enabled": true
27
+ "enabled": true,
28
+ "categories": [
29
+ "review",
30
+ "qa"
31
+ ]
32
+ }
33
+ },
34
+ "backgroundTasks": {
35
+ "enabled": true,
36
+ "maxJobs": 3,
37
+ "defaultTimeoutMs": 1200000
38
+ },
39
+ "experimental": {
40
+ "auto_resume": true
41
+ },
42
+ "aiws": {
43
+ "autonomy": {
44
+ "approval_whitelist": {
45
+ "enabled": true,
46
+ "mode": "assist-only",
47
+ "host_permission_mode": "manual-only",
48
+ "read_only_commands": [
49
+ "rg",
50
+ "cat",
51
+ "sed",
52
+ "ls",
53
+ "find",
54
+ "git status",
55
+ "git diff"
56
+ ],
57
+ "write_allow_paths": [
58
+ ".agentdocs/tmp/",
59
+ "changes/*/analysis/",
60
+ "changes/*/review/",
61
+ "changes/*/evidence/"
62
+ ],
63
+ "deny_paths": [
64
+ "secrets/",
65
+ ".env*"
66
+ ],
67
+ "deny_commands": [
68
+ "rm",
69
+ "mv",
70
+ "git commit",
71
+ "git push",
72
+ "npm publish"
73
+ ]
74
+ }
15
75
  }
16
76
  }
17
77
  }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * aiws Context Injection Plugin
3
+ *
4
+ * Injects aiws context JSONL when task() is called with sub-agent types.
5
+ * Hook: tool.execute.before — intercepts task() calls, reads <role>-context.jsonl
6
+ * from changes/<id>/analysis/, and injects into the agent prompt.
7
+ *
8
+ * Reference: trellis/packages/cli/src/templates/opencode/plugins/inject-subagent-context.js
9
+ */
10
+
11
+ import { existsSync } from "fs"
12
+ import { join } from "path"
13
+ import { AiwsContext, AiwsSessionJournal, debugLog } from "../lib/aiws-context.js"
14
+
15
+ // Role detection — check args.subagent_type first, then fall back to prompt marker
16
+ // Prompt should include: "role: <role>" as a line in the prompt
17
+ const ROLE_MARKER_RE = /^\s*role\s*[:=]\s*(\w+)\s*$/im
18
+
19
+ function detectRole(args, originalPrompt) {
20
+ // Priority 1: args.subagent_type (set by task() caller)
21
+ if (args?.subagent_type) {
22
+ const t = String(args.subagent_type).toLowerCase().replace(/^(?:trellis|aiws)-/, "")
23
+ if (["worker", "explorer", "reviewer", "planner"].includes(t)) return t
24
+ }
25
+ // Priority 2: "role:" marker in prompt body
26
+ const roleMatch = originalPrompt?.match(ROLE_MARKER_RE)
27
+ if (roleMatch) return roleMatch[1].toLowerCase()
28
+ // Priority 3: "subagent_type" or "agent_type" in prompt body
29
+ const typeMatch = originalPrompt?.match(/\b(?:subagent_type|agent_type)\s*[:=]\s*(\w+)\s*/im)
30
+ if (typeMatch) return typeMatch[1].toLowerCase().replace(/^trellis-/, "")
31
+ return null
32
+ }
33
+
34
+ const FIRST_REPLY_INSTRUCTION = `<aiws-context-instruction>
35
+ After reading the JSONL context files, confirm by listing the files you read and their key points (≤3 per file).
36
+
37
+ **Context file path:** changes/<change-id>/analysis/<role>-context.jsonl
38
+
39
+ **Execution order:**
40
+ 1. Read the JSONL file with the Read tool
41
+ 2. Follow priority order: high → medium → low
42
+ 3. If sections defined, only read those line ranges for files > 200 lines
43
+ 4. If any file is inaccessible, flag it but continue with what's available
44
+
45
+ **Your first response** must begin with:
46
+ \`\`\`
47
+ Read context files: [list of files read]
48
+ Key points: [brief summary per file]
49
+ Status: READY | NEEDS_CONTEXT
50
+ \`\`\`
51
+ </aiws-context-instruction>`
52
+
53
+ /**
54
+ * Resolve the active change ID by scanning changes/ directory.
55
+ */
56
+ function resolveActiveChangeId(ctx) {
57
+ const active = ctx.getActiveChange()
58
+ return active ? active.id : null
59
+ }
60
+
61
+ /**
62
+ * Find JSONL context file for a given role in the active change.
63
+ */
64
+ function resolveRoleJsonl(ctx, changeId, role) {
65
+ if (!changeId || !role) return null
66
+ const jsonlPath = join(ctx.directory, "changes", changeId, "analysis", `${role}-context.jsonl`)
67
+ return existsSync(jsonlPath) ? jsonlPath : null
68
+ }
69
+
70
+ /**
71
+ * Build fallback context when no curated JSONL exists (G7).
72
+ * Injects at minimum: change ID, phase, spec index.
73
+ */
74
+ function buildFallbackContext(ctx, changeId, role) {
75
+ const parts = []
76
+ parts.push(`<aiws-context-fallback>
77
+ No curated context JSONL for role "${role}" in change "${changeId}".
78
+ Using project context as minimal scaffold.
79
+ </aiws-context-fallback>`)
80
+
81
+ const changeDetail = ctx.getChangeDetail(changeId)
82
+ if (changeDetail) {
83
+ const lines = [
84
+ `Change: ${changeDetail.id}`,
85
+ `Phase: ${changeDetail.phase}`,
86
+ ]
87
+ if (changeDetail.planFiles.length > 0) {
88
+ lines.push(`Plans: ${changeDetail.planFiles.join(", ")}`)
89
+ }
90
+ parts.push(`<change-context>\n${lines.join("\n")}\n</change-context>`)
91
+ }
92
+
93
+ const specIndex = ctx.getSpecIndex()
94
+ if (specIndex) {
95
+ parts.push(`<spec-index>\n${specIndex.trimEnd()}\n</spec-index>`)
96
+ }
97
+
98
+ parts.push(`<aiws-context-instruction>
99
+ No JSONL context file was found for your role.
100
+ Review the change context and spec index above for relevant constraints.
101
+ Focus on the spec documents that apply to your task.
102
+ If you need more context, use the Glob and Grep tools to discover the codebase.
103
+ </aiws-context-instruction>`)
104
+
105
+ return parts.join("\n\n")
106
+ }
107
+
108
+ /**
109
+ * Write journal entry for a fallback context injection.
110
+ * Extracted to avoid repeating the sessionKey/journal creation in both fallback paths.
111
+ */
112
+ function _writeFallbackJournal(ctx, input, role, changeId, reason) {
113
+ const sessionKey = ctx.getSessionKey({ sessionID: input?.session_id || "" })
114
+ if (sessionKey) {
115
+ try {
116
+ const journal = new AiwsSessionJournal(ctx.directory)
117
+ journal.writeDelegation(sessionKey, role, changeId, {
118
+ contextFiles: 0,
119
+ hasFallback: true,
120
+ reason,
121
+ })
122
+ } catch {
123
+ // journal write failure should never break plugin flow
124
+ }
125
+ }
126
+ }
127
+
128
+ export default async ({ directory }) => {
129
+ const ctx = new AiwsContext(directory)
130
+ debugLog("inject", "Plugin loaded, directory:", directory)
131
+
132
+ return {
133
+ "tool.execute.before": async (input, output) => {
134
+ try {
135
+ if (process.env.AIWS_HOOKS === "0" || process.env.AIWS_DISABLE_HOOKS === "1") {
136
+ return
137
+ }
138
+
139
+ const toolName = input?.tool?.toLowerCase()
140
+ if (toolName !== "task") return
141
+
142
+ const args = output?.args
143
+ if (!args) return
144
+
145
+ const originalPrompt = args.prompt || ""
146
+ if (!originalPrompt) return
147
+
148
+ // Extract role using detectRole (checks subagent_type first, then prompt markers)
149
+ const role = detectRole(args, originalPrompt) || "worker"
150
+ debugLog("inject", "Task tool called, role:", role)
151
+
152
+ // Resolve active change
153
+ const changeId = resolveActiveChangeId(ctx)
154
+ if (!changeId) {
155
+ debugLog("inject", "No active change found, skipping")
156
+ return
157
+ }
158
+
159
+ // Find context JSONL for this role
160
+ const jsonlPath = resolveRoleJsonl(ctx, changeId, role)
161
+ if (!jsonlPath) {
162
+ debugLog("inject", `No context JSONL for role "${role}" in change "${changeId}", injecting fallback`)
163
+ _writeFallbackJournal(ctx, input, role, changeId, "no_jsonl_file")
164
+ const fallbackCtx = buildFallbackContext(ctx, changeId, role)
165
+ args.prompt = `${fallbackCtx}\n\n---\n\n${originalPrompt}`
166
+ return
167
+ }
168
+
169
+ // Read JSONL and build context
170
+ const entries = ctx.readJsonlWithFiles(jsonlPath)
171
+ if (entries.length === 0) {
172
+ debugLog("inject", `Empty context JSONL for role "${role}" in change "${changeId}", injecting fallback`)
173
+ _writeFallbackJournal(ctx, input, role, changeId, "empty_jsonl")
174
+ const fallbackCtx = buildFallbackContext(ctx, changeId, role)
175
+ args.prompt = `${fallbackCtx}\n\n---\n\n${originalPrompt}`
176
+ return
177
+ }
178
+
179
+ const contextBlock = ctx.buildContextBlock(entries)
180
+ debugLog("inject", `Injected context for role "${role}", entries: ${entries.length}`)
181
+
182
+ // Record delegation event to journal
183
+ const sessionKey = ctx.getSessionKey({ sessionID: input?.session_id || "" })
184
+ if (sessionKey) {
185
+ const journal = new AiwsSessionJournal(ctx.directory)
186
+ journal.writeDelegation(sessionKey, role, changeId, {
187
+ contextFiles: entries.length,
188
+ originalPromptSnippet: originalPrompt.slice(0, 120),
189
+ hasFallback: false,
190
+ })
191
+ }
192
+
193
+ // Prepend context + instruction to the prompt
194
+ args.prompt = `${contextBlock}\n\n${FIRST_REPLY_INSTRUCTION}\n\n---\n\n${originalPrompt}`
195
+
196
+ } catch (error) {
197
+ debugLog("inject", "Error in tool.execute.before:", error.message, error.stack)
198
+ }
199
+ },
200
+ }
201
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * aiws Session Start Plugin
3
+ *
4
+ * Injects aiws context (spec index, change status, handoff) when user
5
+ * sends the first message in a new session.
6
+ *
7
+ * Hook: chat.message — modifies the output to prepend context.
8
+ *
9
+ * Reference: trellis/packages/cli/src/templates/opencode/plugins/session-start.js
10
+ */
11
+
12
+ import { AiwsContext, buildSessionContext, contextCollector, debugLog } from "../lib/aiws-context.js"
13
+
14
+ export default async ({ directory, client }) => {
15
+ const ctx = new AiwsContext(directory)
16
+ debugLog("session", "Plugin loaded, directory:", directory)
17
+
18
+ return {
19
+ event: ({ event }) => {
20
+ try {
21
+ if (event?.type === "session.compacted" && event?.properties?.sessionID) {
22
+ contextCollector.clear(event.properties.sessionID)
23
+ debugLog("session", "Cleared processed flag after compaction for session:", event.properties.sessionID)
24
+ }
25
+ } catch (error) {
26
+ debugLog("session", "Error in event hook:", error instanceof Error ? error.message : String(error))
27
+ }
28
+ },
29
+
30
+ "chat.message": async (input, output) => {
31
+ try {
32
+ const sessionKey = ctx.getSessionKey(input)
33
+ if (!sessionKey) {
34
+ debugLog("session", "No session key available, skipping")
35
+ return
36
+ }
37
+
38
+ const agent = input.agent || ""
39
+ if (agent && (agent.startsWith("trellis-") || agent.startsWith("aiws-"))) {
40
+ debugLog("session", "Skipping sub-agent turn:", agent)
41
+ return
42
+ }
43
+
44
+ if (process.env.AIWS_HOOKS === "0" || process.env.AIWS_DISABLE_HOOKS === "1") {
45
+ debugLog("session", "Skipping — AIWS_HOOKS disabled")
46
+ return
47
+ }
48
+
49
+ if (process.env.OPENCODE_NON_INTERACTIVE === "1") {
50
+ debugLog("session", "Skipping — non-interactive mode")
51
+ return
52
+ }
53
+
54
+ if (contextCollector.isProcessed(sessionKey)) {
55
+ debugLog("session", "Skipping — session already processed:", sessionKey)
56
+ return
57
+ }
58
+
59
+ const context = buildSessionContext(ctx, input)
60
+ debugLog("session", "Built context, length:", context.length)
61
+
62
+ if (!context) {
63
+ debugLog("session", "Empty context, nothing to inject")
64
+ return
65
+ }
66
+
67
+ const parts = output?.parts || []
68
+ const textPartIndex = parts.findIndex(p => p.type === "text" && p.text !== undefined)
69
+
70
+ if (textPartIndex !== -1) {
71
+ const originalText = parts[textPartIndex].text || ""
72
+ parts[textPartIndex].text = `${context}\n\n---\n\n${originalText}`
73
+ debugLog("session", "Injected context into chat.message text part, length:", context.length)
74
+ } else {
75
+ const injectedPart = { type: "text", text: context }
76
+ parts.unshift(injectedPart)
77
+ debugLog("session", "Prepended new text part with context, length:", context.length)
78
+ }
79
+
80
+ contextCollector.markProcessed(sessionKey)
81
+ } catch (error) {
82
+ debugLog("session", "Error in chat.message:", error.message, error.stack)
83
+ }
84
+ },
85
+ }
86
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * aiws Workflow State Injection Plugin
3
+ *
4
+ * Per-turn chat.message breadcrumb showing active change + current phase.
5
+ * Unlike session-start, this fires on EVERY user message so long
6
+ * conversations don't lose context of what's being worked on.
7
+ *
8
+ * Reference: trellis/packages/cli/src/templates/opencode/plugins/inject-workflow-state.js
9
+ */
10
+
11
+ import { AiwsContext, AiwsSessionJournal, debugLog } from "../lib/aiws-context.js"
12
+
13
+ /**
14
+ * Build the workflow-state breadcrumb block.
15
+ */
16
+ function buildBreadcrumb(change, phase) {
17
+ const parts = []
18
+ parts.push("<workflow-state>")
19
+
20
+ if (change) {
21
+ parts.push(`Active Change: ${change.id}`)
22
+ parts.push(`Change Dir: changes/${change.id}/`)
23
+
24
+ if (change.planFiles.length > 0) {
25
+ parts.push(`Plans: ${change.planFiles.slice(0, 3).join(", ")}`)
26
+ }
27
+ } else {
28
+ parts.push("Active Change: none")
29
+ }
30
+
31
+ if (phase) {
32
+ parts.push(`Phase: ${phase} (${AiwsContext.PHASES.indexOf(phase) + 1}/${AiwsContext.PHASES.length})`)
33
+ }
34
+
35
+ // Phase-specific next actions — aligned with AiwsContext.PHASES state machine
36
+ // Subagent-first policy: ready-for-dev defaults to aiws-worker; in-progress defaults to aiws-reviewer
37
+ const nextActions = {
38
+ intake: "Next: ws-plan to create a change and execution plan",
39
+ planning: "Next: ws-plan-verify to review the plan, or ws-dev to begin implementation",
40
+ "ready-for-dev": "Next: dispatch aiws-worker (subagent-first); main session coordinates, does not write code directly",
41
+ "in-progress": "Next: dispatch aiws-reviewer + ws-review to audit changes",
42
+ review: "Next: ws-commit to validate and commit",
43
+ finished: "Next: ws-finish to finalize and archive the change",
44
+ }
45
+ if (phase && nextActions[phase]) {
46
+ parts.push(nextActions[phase])
47
+ }
48
+
49
+ if (!change && !phase) {
50
+ parts.push("No active work detected. Start with ws-preflight or ws-plan.")
51
+ }
52
+
53
+ // Available phases reference — aligned with AiwsContext.PHASES
54
+ parts.push(`Phases: ${AiwsContext.PHASES.join(" → ")}`)
55
+
56
+ parts.push("</workflow-state>")
57
+ return parts.join("\n")
58
+ }
59
+
60
+ export default async ({ directory }) => {
61
+ const ctx = new AiwsContext(directory)
62
+ debugLog("workflow-state", "Plugin loaded, directory:", directory)
63
+
64
+ return {
65
+ "chat.message": async (input, output) => {
66
+ try {
67
+ // Skip sub-agent turns
68
+ const agent = input?.agent || ""
69
+ if (agent.startsWith("trellis-") || agent.startsWith("aiws-")) {
70
+ debugLog("workflow-state", "Skipping subagent turn:", agent)
71
+ return
72
+ }
73
+
74
+ if (process.env.AIWS_HOOKS === "0" || process.env.AIWS_DISABLE_HOOKS === "1") {
75
+ return
76
+ }
77
+
78
+ if (process.env.OPENCODE_NON_INTERACTIVE === "1") {
79
+ return
80
+ }
81
+
82
+ if (!ctx.isAiwsProject()) {
83
+ return
84
+ }
85
+
86
+ const change = ctx.getActiveChange()
87
+ const phase = change ? change.phase : null
88
+ const breadcrumb = buildBreadcrumb(change, phase)
89
+
90
+ // Inject into the output text part (no dedup — show every turn)
91
+ const parts = output?.parts || []
92
+ const textIdx = parts.findIndex(p => p.type === "text" && p.text !== undefined)
93
+ if (textIdx !== -1) {
94
+ parts[textIdx].text = `${breadcrumb}\n\n${parts[textIdx].text}`
95
+ } else {
96
+ parts.unshift({ type: "text", text: breadcrumb })
97
+ }
98
+
99
+ debugLog("workflow-state", "Injected breadcrumb:", change?.id || "none", "phase:", phase || "none")
100
+
101
+ // Record breadcrumb event to journal (non-blocking)
102
+ try {
103
+ const sessionKey = ctx.getSessionKey({ sessionID: input?.session_id || "" })
104
+ if (sessionKey) {
105
+ const journal = new AiwsSessionJournal(ctx.directory)
106
+ journal.writeBreadcrumb(sessionKey, phase || "unknown", change?.id || null)
107
+ }
108
+ } catch {
109
+ // Journal write failure should never break the plugin
110
+ }
111
+ } catch (error) {
112
+ debugLog("workflow-state", "Error in chat.message:", error instanceof Error ? error.message : String(error))
113
+ }
114
+ },
115
+ }
116
+ }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ # ws-plan: generate submodules.targets for the current change
3
+ # Usage: bash ws-plan-gen-submodule-targets.sh <change-id>
4
+ set -euo pipefail
5
+
6
+ change_id="${1:?missing change_id}"
7
+ targets=".aiws/changes/${change_id}/submodules.targets"
8
+
9
+ mkdir -p ".aiws/changes/${change_id}"
10
+
11
+ if [[ -f "${targets}" ]]; then
12
+ bak="${targets}.bak.$(date -u +%Y%m%d-%H%M%SZ)"
13
+ cp "${targets}" "${bak}"
14
+ echo "info: backup: ${bak}"
15
+ fi
16
+
17
+ : > "${targets}"
18
+ echo "# path<TAB>target_branch<TAB>remote(optional, default=origin)" >> "${targets}"
19
+
20
+ while read -r key sub_path; do
21
+ name="${key#submodule.}"; name="${name%.path}"
22
+ b="$(git config --file .gitmodules --get "submodule.${name}.branch" 2>/dev/null || true)"
23
+ [[ "${b:-}" == "." ]] && b="$(git branch --show-current)" # '.' means "follow superproject branch"
24
+ printf "%s\t%s\t%s\n" "${sub_path}" "${b:-<fill-me>}" "origin" >> "${targets}"
25
+ done < <(git config --file .gitmodules --get-regexp '^submodule\..*\.path$' 2>/dev/null || true)
26
+
27
+ echo "ok: wrote ${targets}"
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ # ws-plan: create change context with worktree/submodule support
3
+ # Usage: bash ws-plan-setup-worktree.sh <change-id>
4
+ set -euo pipefail
5
+
6
+ change_id="${1:?missing change_id}"
7
+
8
+ # Guard: refuse to create context on dirty working tree
9
+ if [[ -n "$(git status --porcelain)" ]]; then
10
+ echo "error: working tree dirty before ws-plan creates change context"
11
+ echo "hint: commit/stash first, or continue inside an existing change/<change-id> context"
12
+ exit 2
13
+ fi
14
+
15
+ has_commits=0
16
+ git rev-parse --verify HEAD >/dev/null 2>&1 && has_commits=1
17
+
18
+ has_submodules=0
19
+ if [[ -f .gitmodules ]] && git config --file .gitmodules --get-regexp '^submodule\..*\.path$' >/dev/null 2>&1; then
20
+ has_submodules=1
21
+ fi
22
+
23
+ if [[ "${has_commits}" -eq 1 ]]; then
24
+ if [[ "${has_submodules}" -eq 1 ]]; then
25
+ aiws change start "${change_id}" --hooks --worktree --submodules
26
+ else
27
+ aiws change start "${change_id}" --hooks --worktree
28
+ fi
29
+ else
30
+ aiws change start "${change_id}" --hooks --no-switch
31
+ fi