ecoportal-api-v2 3.3.1 → 3.3.3
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.
- checksums.yaml +4 -4
- data/.ai-assistance/code/double_model_patterns.md +133 -0
- data/.ai-assistance/conventions/code-working-tree-protocol.md +176 -0
- data/.ai-assistance/scripts/token-logger.js +220 -0
- data/.ai-assistance/scripts/token-report.ts +158 -0
- data/.ai-assistance/scripts/token-session-start.js +66 -0
- data/.ai-assistance/skills/eP_AI_Manager/SKILL.md +262 -0
- data/.ai-assistance/token-budget.json +44 -0
- data/.claude/settings.json +107 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +18 -2
- data/CLAUDE.md +82 -0
- data/ecoportal-api-v2.gemspec +1 -1
- data/lib/ecoportal/api/common/concerns/benchmarkable.rb +1 -0
- data/lib/ecoportal/api/common/content/double_model/attributable/nesting/cascaded_callback.rb +1 -1
- data/lib/ecoportal/api/common/content/double_model/double_doc/linkable_doc.rb +1 -0
- data/lib/ecoportal/api/v2/s3/files/batch_upload.rb +3 -1
- data/lib/ecoportal/api/v2_version.rb +1 -1
- data/scripts/auto-worker-scheduler.sh +307 -0
- metadata +15 -9
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# CLAUDE.md — ecoportal-api-v2
|
|
2
|
+
|
|
3
|
+
AI agent instructions for this repository.
|
|
4
|
+
|
|
5
|
+
**Cross-cutting architecture context lives in `ecoportal-api-graphql` — see its `CLAUDE.md` and `.claude/` folder for the full dependency map, project history, and shared skills.**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Repository Role
|
|
10
|
+
|
|
11
|
+
`ecoportal-api-v2` is the **REST API v2 gem**, extending `ecoportal-api` with a rich content model layer (pages, registers, people, S3 file handling). It provides the `Ecoportal::API::V2` and `Ecoportal::API::Common::Content` namespaces.
|
|
12
|
+
|
|
13
|
+
**Position in chain:**
|
|
14
|
+
```
|
|
15
|
+
ecoportal-api
|
|
16
|
+
↓
|
|
17
|
+
ecoportal-api-v2 ← THIS REPO
|
|
18
|
+
↓
|
|
19
|
+
ecoportal-api-graphql
|
|
20
|
+
↓
|
|
21
|
+
eco-helpers
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Remote:** https://gitlab.ecoportal.co.nz/oscar/ecoportal-api-v2.git
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Key Folder Layout
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
lib/ecoportal/api/
|
|
32
|
+
common/
|
|
33
|
+
concerns/ Benchmarkable, Threadable mixins
|
|
34
|
+
content/ Core model infrastructure (see below)
|
|
35
|
+
v2/
|
|
36
|
+
page/ Page model — sections, stages, components, forces, permissions
|
|
37
|
+
page/component/ All field types (text, number, date, selection, files, geo, etc.)
|
|
38
|
+
pages/ Pages collection, stage results, tasks
|
|
39
|
+
registers/ Register models — search results, stage/page results
|
|
40
|
+
people/ V2 people endpoint
|
|
41
|
+
s3/ S3 file upload/download infrastructure
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `Common::Content` — the model engine
|
|
45
|
+
|
|
46
|
+
The most complex part of this gem. Key classes:
|
|
47
|
+
|
|
48
|
+
- `DoubleModel` — two-layer model (a "live" doc and a "patch" doc) with attribute DSL, diffing, nesting, and change tracking. Almost all V2 models inherit from this.
|
|
49
|
+
- `CollectionModel` — ordered collection with upsert/delete/mutation support.
|
|
50
|
+
- `ClassHelpers` — `class_resolver` macro for injectable class references (pattern shared with `ecoportal-api-graphql`).
|
|
51
|
+
- `DocHelpers`, `ModelHelpers`, `IncluderHelpers` — utility mixins for model construction.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Namespace
|
|
56
|
+
|
|
57
|
+
- `Ecoportal::API::V2` — v2 REST resources
|
|
58
|
+
- `Ecoportal::API::Common::Content` — shared model infrastructure (consumed downstream)
|
|
59
|
+
- `Ecoportal::API::Common::Concerns` — threading and benchmarking mixins
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Key Concerns
|
|
64
|
+
|
|
65
|
+
- `DoubleModel` is the foundational abstraction — understand it before modifying any V2 model.
|
|
66
|
+
- `Common::Content::ClassHelpers` mirrors the `ClassHelpers` in `ecoportal-api-graphql` — changes to one may need mirroring in the other.
|
|
67
|
+
- S3 upload is async with polling (`s3/files/poll.rb`) — timing-sensitive code.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Backwards Compatibility
|
|
72
|
+
|
|
73
|
+
`eco-helpers` and `ecoportal-api-graphql` both depend on this gem. Changes to `Common::Content::*` or `V2::Page::*` interfaces must be checked against usage in both downstream gems before committing.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Running Tests
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
bundle install
|
|
81
|
+
bundle exec rspec
|
|
82
|
+
```
|
data/ecoportal-api-v2.gemspec
CHANGED
|
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
|
|
|
33
33
|
spec.add_development_dependency 'rubocop-rake', '~> 0'
|
|
34
34
|
spec.add_development_dependency 'yard', '~> 0.9'
|
|
35
35
|
|
|
36
|
-
spec.add_dependency 'ecoportal-api', '~> 0.10', '>= 0.10.
|
|
36
|
+
spec.add_dependency 'ecoportal-api', '~> 0.10', '>= 0.10.16'
|
|
37
37
|
spec.add_dependency 'mime-types', '~> 3.5', '>= 3.5.2'
|
|
38
38
|
end
|
|
39
39
|
|
data/lib/ecoportal/api/common/content/double_model/attributable/nesting/cascaded_callback.rb
CHANGED
|
@@ -178,7 +178,7 @@ module Ecoportal::API::Common::Content::DoubleModel::Attributable::Nesting
|
|
|
178
178
|
nested_attrs: _cascaded_attributes.keys,
|
|
179
179
|
attributes: {}
|
|
180
180
|
}.merge(trace || {}).tap do |out_trace|
|
|
181
|
-
_cascaded_attributes.each.with_object(out_trace) do |
|
|
181
|
+
_cascaded_attributes.each.with_object(out_trace) do |(attribute, obj_k), out|
|
|
182
182
|
next unless (obj = send(attribute))
|
|
183
183
|
|
|
184
184
|
obj_k_path = key_path.dup.push(obj_k)
|
|
@@ -3,7 +3,9 @@ module Ecoportal
|
|
|
3
3
|
class V2
|
|
4
4
|
class S3
|
|
5
5
|
class Files
|
|
6
|
-
# Class service to upload multiple files in one go
|
|
6
|
+
# Class service to upload multiple files in one go.
|
|
7
|
+
# Full cycle: from local files to the org file manager.
|
|
8
|
+
# @note it uploads to S3, and converts it into a file container as well.
|
|
7
9
|
class BatchUpload
|
|
8
10
|
include Ecoportal::API::Common::Concerns::Benchmarkable
|
|
9
11
|
include Ecoportal::API::Common::Concerns::Threadable
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# auto-worker-scheduler.sh
|
|
3
|
+
# Scheduler for auto-todo-worker. Called by the Claude Code Stop hook.
|
|
4
|
+
# Reads local config, checks token budget and conflict guard, then
|
|
5
|
+
# launches the worker as a background process if all gates pass.
|
|
6
|
+
#
|
|
7
|
+
# Log format: TIMESTAMP | ACTION | REASON
|
|
8
|
+
# Log file: .ai-assistance/local/auto-worker-changes.log
|
|
9
|
+
#
|
|
10
|
+
# Usage: bash scripts/auto-worker-scheduler.sh
|
|
11
|
+
# (called automatically by Stop hook — no arguments)
|
|
12
|
+
#
|
|
13
|
+
# Requires: jq (for JSON parsing). Falls back to grep/sed on missing jq.
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# Paths (all relative — script must be run from repo root)
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
TOKEN_BUDGET_JSON=".ai-assistance/token-budget.json"
|
|
21
|
+
LAST_RUN_JSON=".ai-assistance/local/auto-worker-last-run.json"
|
|
22
|
+
SESSION_LOCK=".ai-assistance/local/session.lock"
|
|
23
|
+
CHANGES_LOG=".ai-assistance/local/auto-worker-changes.log"
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Helpers
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
# ai_log_usage <component> <action> <detail> [session_id]
|
|
30
|
+
# Inline copy — scheduler is self-contained; common.sh is not sourced here.
|
|
31
|
+
# Never fails fatally; all errors suppressed.
|
|
32
|
+
ai_log_usage() {
|
|
33
|
+
local component="${1:-unknown}"
|
|
34
|
+
local action="${2:-run}"
|
|
35
|
+
local detail="${3:-}"
|
|
36
|
+
local session_id="${4:-}"
|
|
37
|
+
local log_dir=".ai-assistance/local/kpi"
|
|
38
|
+
local week
|
|
39
|
+
week=$(date +%Y-W%V 2>/dev/null || echo "unknown")
|
|
40
|
+
local log_file="${log_dir}/usage-${week}.jsonl"
|
|
41
|
+
local ts
|
|
42
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "unknown")
|
|
43
|
+
mkdir -p "$log_dir" 2>/dev/null || true
|
|
44
|
+
printf '{"component":"%s","action":"%s","detail":"%s","ts":"%s","session_id":"%s"}\n' \
|
|
45
|
+
"$component" "$action" "$detail" "$ts" "$session_id" >> "$log_file" 2>/dev/null || true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
log_action() {
|
|
49
|
+
local action="$1"
|
|
50
|
+
local reason="$2"
|
|
51
|
+
local ts
|
|
52
|
+
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%dT%H:%M:%SZ")
|
|
53
|
+
mkdir -p "$(dirname "$CHANGES_LOG")"
|
|
54
|
+
printf "%s | %s | %s\n" "$ts" "$action" "$reason" >> "$CHANGES_LOG"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Read a JSON field using jq if available, otherwise grep+sed fallback.
|
|
58
|
+
# Usage: json_field <file> <dotted.key> e.g. json_field config.json auto_worker.enabled
|
|
59
|
+
json_field() {
|
|
60
|
+
local file="$1"
|
|
61
|
+
local key="$2"
|
|
62
|
+
if command -v jq >/dev/null 2>&1; then
|
|
63
|
+
jq -r ".${key} // empty" "$file" 2>/dev/null
|
|
64
|
+
else
|
|
65
|
+
# Minimal fallback: grep for the last segment key, extract bare value.
|
|
66
|
+
local leaf
|
|
67
|
+
leaf=$(printf '%s' "$key" | sed 's/.*\.//')
|
|
68
|
+
grep -o "\"${leaf}\"[[:space:]]*:[[:space:]]*[^,}]*" "$file" 2>/dev/null \
|
|
69
|
+
| tail -1 \
|
|
70
|
+
| sed 's/.*:[[:space:]]*//' \
|
|
71
|
+
| tr -d '"' \
|
|
72
|
+
| tr -d ' '
|
|
73
|
+
fi
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
# Gate 1: auto_worker.enabled
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
if [ ! -f "$TOKEN_BUDGET_JSON" ]; then
|
|
81
|
+
log_action "SKIP" "token-budget.json not found — auto_worker not configured"
|
|
82
|
+
ai_log_usage "script/auto-worker-scheduler" "skip" "auto_worker not enabled -- token-budget.json not found"
|
|
83
|
+
exit 0
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
enabled=$(json_field "$TOKEN_BUDGET_JSON" "auto_worker.enabled")
|
|
87
|
+
if [ "$enabled" != "true" ]; then
|
|
88
|
+
log_action "SKIP" "auto_worker.enabled is false or absent in token-budget.json"
|
|
89
|
+
ai_log_usage "script/auto-worker-scheduler" "skip" "auto_worker not enabled"
|
|
90
|
+
exit 0
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# Gate 2: Conflict guard — check for active Claude session via session.lock
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
if [ -f "$SESSION_LOCK" ]; then
|
|
98
|
+
lock_content=""
|
|
99
|
+
if [ -r "$SESSION_LOCK" ]; then
|
|
100
|
+
lock_content=$(cat "$SESSION_LOCK" 2>/dev/null || true)
|
|
101
|
+
fi
|
|
102
|
+
log_action "SKIP" "Active Claude session detected (session.lock present: ${lock_content})"
|
|
103
|
+
ai_log_usage "script/auto-worker-scheduler" "skip" "session.lock present"
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# Gate 3: Token budget threshold check
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
# Find the current ISO week KPI file. Format: weekly-YYYY-WNN.json
|
|
111
|
+
# We compute ISO week ourselves for portability.
|
|
112
|
+
|
|
113
|
+
iso_week() {
|
|
114
|
+
# Returns YYYY-WNN string for today (ISO 8601 week numbering)
|
|
115
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
116
|
+
python3 -c "
|
|
117
|
+
import datetime
|
|
118
|
+
today = datetime.date.today()
|
|
119
|
+
iso = today.isocalendar()
|
|
120
|
+
print('%04d-W%02d' % (iso[0], iso[1]))
|
|
121
|
+
"
|
|
122
|
+
elif command -v python >/dev/null 2>&1; then
|
|
123
|
+
python -c "
|
|
124
|
+
import datetime
|
|
125
|
+
today = datetime.date.today()
|
|
126
|
+
iso = today.isocalendar()
|
|
127
|
+
print('%04d-W%02d' % (iso[0], iso[1]))
|
|
128
|
+
"
|
|
129
|
+
else
|
|
130
|
+
# Fallback: use date command Week-of-year (not strictly ISO but close enough)
|
|
131
|
+
date +"%Y-W%V" 2>/dev/null || date +"%Y-W%U"
|
|
132
|
+
fi
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
KPI_DIR=".ai-assistance/local/kpi"
|
|
136
|
+
WEEK_KEY=$(iso_week)
|
|
137
|
+
KPI_FILE="${KPI_DIR}/weekly-${WEEK_KEY}.json"
|
|
138
|
+
|
|
139
|
+
# Read quota and usage from KPI file (if present)
|
|
140
|
+
weekly_tokens_used=0
|
|
141
|
+
weekly_quota=0
|
|
142
|
+
window_start_ts="" # ISO timestamp of first session start in current 5-hour window
|
|
143
|
+
|
|
144
|
+
if [ -f "$KPI_FILE" ] && command -v jq >/dev/null 2>&1; then
|
|
145
|
+
# Sum tokens_used across all sessions
|
|
146
|
+
weekly_tokens_used=$(jq '[.sessions[]?.tokens_used // 0] | add // 0' "$KPI_FILE" 2>/dev/null || echo "0")
|
|
147
|
+
weekly_quota=$(jq -r '.quota // 0' "$KPI_FILE" 2>/dev/null || echo "0")
|
|
148
|
+
|
|
149
|
+
# Find most recent session start to compute reset window
|
|
150
|
+
window_start_ts=$(jq -r '
|
|
151
|
+
[.sessions[]?.session_start // empty] | sort | last // empty
|
|
152
|
+
' "$KPI_FILE" 2>/dev/null || true)
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# Read quota from token-budget.json as fallback
|
|
156
|
+
if [ "$weekly_quota" = "0" ] || [ -z "$weekly_quota" ]; then
|
|
157
|
+
quota_from_config=$(json_field "$TOKEN_BUDGET_JSON" "weekly_quota.total_tokens")
|
|
158
|
+
if [ "$quota_from_config" != "null" ] && [ -n "$quota_from_config" ]; then
|
|
159
|
+
weekly_quota="$quota_from_config"
|
|
160
|
+
fi
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# If no quota is set (null), skip the threshold gate entirely
|
|
164
|
+
if [ "$weekly_quota" = "0" ] || [ "$weekly_quota" = "null" ] || [ -z "$weekly_quota" ]; then
|
|
165
|
+
log_action "OK" "No weekly quota set — skipping token threshold gate"
|
|
166
|
+
else
|
|
167
|
+
# Calculate usage percentage
|
|
168
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
169
|
+
usage_pct=$(python3 -c "
|
|
170
|
+
used = float('${weekly_tokens_used}' or '0')
|
|
171
|
+
quota = float('${weekly_quota}')
|
|
172
|
+
print('%.1f' % (used / quota * 100) if quota > 0 else '0')
|
|
173
|
+
")
|
|
174
|
+
else
|
|
175
|
+
usage_pct=0
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
# Determine time remaining in current 5-hour reset window
|
|
179
|
+
# 5-hour window = 18000 seconds
|
|
180
|
+
window_seconds_elapsed=0
|
|
181
|
+
if [ -n "$window_start_ts" ] && command -v python3 >/dev/null 2>&1; then
|
|
182
|
+
window_seconds_elapsed=$(python3 -c "
|
|
183
|
+
import datetime, sys
|
|
184
|
+
try:
|
|
185
|
+
ts = '${window_start_ts}'.replace('Z', '+00:00')
|
|
186
|
+
start = datetime.datetime.fromisoformat(ts)
|
|
187
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
188
|
+
elapsed = (now - start).total_seconds()
|
|
189
|
+
print(int(max(0, elapsed)))
|
|
190
|
+
except Exception:
|
|
191
|
+
print(0)
|
|
192
|
+
")
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
WINDOW_DURATION=18000 # 5 hours in seconds
|
|
196
|
+
window_remaining=$(( WINDOW_DURATION - window_seconds_elapsed ))
|
|
197
|
+
if [ "$window_remaining" -lt 0 ]; then
|
|
198
|
+
window_remaining=0
|
|
199
|
+
fi
|
|
200
|
+
window_remaining_min=$(( window_remaining / 60 ))
|
|
201
|
+
|
|
202
|
+
# Determine threshold based on time remaining in window (ADR-007 table)
|
|
203
|
+
# >120 min -> 65%; 60-120 -> 75%; 45-60 -> 80%; 30-45 -> 90%; 15-30 -> 95%; <15 -> skip
|
|
204
|
+
if [ "$window_remaining_min" -lt 15 ]; then
|
|
205
|
+
log_action "SKIP" "Less than 15 min remaining in token reset window — will not launch"
|
|
206
|
+
ai_log_usage "script/auto-worker-scheduler" "skip" "token budget -- less than 15 min remaining in reset window"
|
|
207
|
+
exit 0
|
|
208
|
+
elif [ "$window_remaining_min" -lt 30 ]; then
|
|
209
|
+
threshold=95
|
|
210
|
+
elif [ "$window_remaining_min" -lt 45 ]; then
|
|
211
|
+
threshold=90
|
|
212
|
+
elif [ "$window_remaining_min" -lt 60 ]; then
|
|
213
|
+
threshold=80
|
|
214
|
+
elif [ "$window_remaining_min" -lt 120 ]; then
|
|
215
|
+
threshold=75
|
|
216
|
+
else
|
|
217
|
+
threshold=65
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
# Compare usage_pct against threshold (integer comparison via python3)
|
|
221
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
222
|
+
over_threshold=$(python3 -c "
|
|
223
|
+
pct = float('${usage_pct}' or '0')
|
|
224
|
+
thr = float('${threshold}')
|
|
225
|
+
print('true' if pct >= thr else 'false')
|
|
226
|
+
")
|
|
227
|
+
else
|
|
228
|
+
over_threshold="false"
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
if [ "$over_threshold" = "true" ]; then
|
|
232
|
+
log_action "SKIP" "Token usage ${usage_pct}% >= threshold ${threshold}% (${window_remaining_min} min remaining in window)"
|
|
233
|
+
ai_log_usage "script/auto-worker-scheduler" "skip" "token budget at ${usage_pct}% (threshold ${threshold}%)"
|
|
234
|
+
exit 0
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
log_action "OK" "Token gate passed: ${usage_pct}% used, threshold ${threshold}% (${window_remaining_min} min remaining)"
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
# ---------------------------------------------------------------------------
|
|
241
|
+
# Gate 4: Determine cadence (informational — actual cron cadence managed by
|
|
242
|
+
# CronCreate; this script just logs the recommendation)
|
|
243
|
+
# ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
recommended_cadence="2hr"
|
|
246
|
+
if [ -f "$LAST_RUN_JSON" ] && command -v jq >/dev/null 2>&1; then
|
|
247
|
+
completed_count=$(jq '.completed | length' "$LAST_RUN_JSON" 2>/dev/null || echo "0")
|
|
248
|
+
if [ "$completed_count" -gt "0" ]; then
|
|
249
|
+
recommended_cadence="30min"
|
|
250
|
+
fi
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# ---------------------------------------------------------------------------
|
|
254
|
+
# Gate 5: dangerously_skip_permissions opt-in
|
|
255
|
+
# ---------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
skip_perms=$(json_field "$TOKEN_BUDGET_JSON" "auto_worker.dangerously_skip_permissions")
|
|
258
|
+
AUTO_WORKER_PROMPT="Run the auto-worker for this repo.
|
|
259
|
+
|
|
260
|
+
Instructions:
|
|
261
|
+
1. Read TODO.md and collect all items tagged \`[auto]\` or \`[auto:last]\`.
|
|
262
|
+
2. If no \`[auto]\` items exist, exit cleanly with a brief log entry.
|
|
263
|
+
3. Plan execution order as a dependency graph — do NOT follow TODO list order.
|
|
264
|
+
- Items without dependencies on each other may run in parallel (branch out when the work is self-contained enough to isolate).
|
|
265
|
+
- \`[auto:last]\` items run only after all \`[auto]\` items complete or are skipped.
|
|
266
|
+
4. For each item:
|
|
267
|
+
- Execute the task.
|
|
268
|
+
- Mark it \`[x]\` in TODO.md on completion.
|
|
269
|
+
- Add any discovered follow-up tasks as new TODO items; tag \`[auto]\` only if they meet the eligibility criteria in standards/tooling/auto-worker-eligibility.md.
|
|
270
|
+
5. Do NOT touch items that are not tagged \`[auto]\` or \`[auto:last]\`.
|
|
271
|
+
6. At the end, write a one-paragraph summary of what was done, skipped, and added to TODO."
|
|
272
|
+
|
|
273
|
+
CLAUDE_FLAGS="--print \"${AUTO_WORKER_PROMPT}\""
|
|
274
|
+
if [ "$skip_perms" = "true" ]; then
|
|
275
|
+
CLAUDE_FLAGS="--dangerously-skip-permissions --print \"${AUTO_WORKER_PROMPT}\""
|
|
276
|
+
fi
|
|
277
|
+
|
|
278
|
+
# ---------------------------------------------------------------------------
|
|
279
|
+
# Launch: run auto-todo-worker as a background process
|
|
280
|
+
# ---------------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
log_action "LAUNCH" "Launching auto-todo-worker (cadence recommendation: ${recommended_cadence})"
|
|
283
|
+
ai_log_usage "script/auto-worker-scheduler" "launch" "launching auto-todo-worker"
|
|
284
|
+
|
|
285
|
+
if [[ "${OSTYPE:-}" == "msys" ]] || [[ "${OSTYPE:-}" == "cygwin" ]] || \
|
|
286
|
+
[[ "${OS:-}" == "Windows_NT" ]]; then
|
|
287
|
+
# Windows via Git Bash / WSL
|
|
288
|
+
# Use PowerShell Start-Process to launch detached from the current terminal
|
|
289
|
+
powershell -Command "Start-Process -FilePath 'claude' \
|
|
290
|
+
-ArgumentList '${CLAUDE_FLAGS}' \
|
|
291
|
+
-WorkingDirectory '${PWD}' \
|
|
292
|
+
-NoNewWindow" &
|
|
293
|
+
launch_pid=$!
|
|
294
|
+
else
|
|
295
|
+
# Unix / macOS / WSL2
|
|
296
|
+
if [ "$skip_perms" = "true" ]; then
|
|
297
|
+
nohup claude --dangerously-skip-permissions --print "run auto worker" \
|
|
298
|
+
> ".ai-assistance/local/auto-worker-stdout.log" 2>&1 &
|
|
299
|
+
else
|
|
300
|
+
nohup claude --print "run auto worker" \
|
|
301
|
+
> ".ai-assistance/local/auto-worker-stdout.log" 2>&1 &
|
|
302
|
+
fi
|
|
303
|
+
launch_pid=$!
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
log_action "LAUNCHED" "Background PID ${launch_pid:-unknown} — flags: ${CLAUDE_FLAGS}"
|
|
307
|
+
exit 0
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ecoportal-api-v2
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.3.
|
|
4
|
+
version: 3.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oscar Segura
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: pry
|
|
@@ -135,7 +134,7 @@ dependencies:
|
|
|
135
134
|
version: '0.10'
|
|
136
135
|
- - ">="
|
|
137
136
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: 0.10.
|
|
137
|
+
version: 0.10.16
|
|
139
138
|
type: :runtime
|
|
140
139
|
prerelease: false
|
|
141
140
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -145,7 +144,7 @@ dependencies:
|
|
|
145
144
|
version: '0.10'
|
|
146
145
|
- - ">="
|
|
147
146
|
- !ruby/object:Gem::Version
|
|
148
|
-
version: 0.10.
|
|
147
|
+
version: 0.10.16
|
|
149
148
|
- !ruby/object:Gem::Dependency
|
|
150
149
|
name: mime-types
|
|
151
150
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -166,13 +165,20 @@ dependencies:
|
|
|
166
165
|
- - ">="
|
|
167
166
|
- !ruby/object:Gem::Version
|
|
168
167
|
version: 3.5.2
|
|
169
|
-
description:
|
|
170
168
|
email:
|
|
171
169
|
- oscar@ecoportal.co.nz
|
|
172
170
|
executables: []
|
|
173
171
|
extensions: []
|
|
174
172
|
extra_rdoc_files: []
|
|
175
173
|
files:
|
|
174
|
+
- ".ai-assistance/code/double_model_patterns.md"
|
|
175
|
+
- ".ai-assistance/conventions/code-working-tree-protocol.md"
|
|
176
|
+
- ".ai-assistance/scripts/token-logger.js"
|
|
177
|
+
- ".ai-assistance/scripts/token-report.ts"
|
|
178
|
+
- ".ai-assistance/scripts/token-session-start.js"
|
|
179
|
+
- ".ai-assistance/skills/eP_AI_Manager/SKILL.md"
|
|
180
|
+
- ".ai-assistance/token-budget.json"
|
|
181
|
+
- ".claude/settings.json"
|
|
176
182
|
- ".gitignore"
|
|
177
183
|
- ".markdownlint.json"
|
|
178
184
|
- ".rspec"
|
|
@@ -180,6 +186,7 @@ files:
|
|
|
180
186
|
- ".ruby-version"
|
|
181
187
|
- ".yardopts"
|
|
182
188
|
- CHANGELOG.md
|
|
189
|
+
- CLAUDE.md
|
|
183
190
|
- Gemfile
|
|
184
191
|
- LICENSE
|
|
185
192
|
- README.md
|
|
@@ -329,12 +336,12 @@ files:
|
|
|
329
336
|
- lib/ecoportal/api/v2/s3/files/poll_status.rb
|
|
330
337
|
- lib/ecoportal/api/v2/s3/upload.rb
|
|
331
338
|
- lib/ecoportal/api/v2_version.rb
|
|
339
|
+
- scripts/auto-worker-scheduler.sh
|
|
332
340
|
homepage: https://www.ecoportal.com
|
|
333
341
|
licenses:
|
|
334
342
|
- MIT
|
|
335
343
|
metadata:
|
|
336
344
|
rubygems_mfa_required: 'true'
|
|
337
|
-
post_install_message:
|
|
338
345
|
rdoc_options: []
|
|
339
346
|
require_paths:
|
|
340
347
|
- lib
|
|
@@ -349,8 +356,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
349
356
|
- !ruby/object:Gem::Version
|
|
350
357
|
version: '0'
|
|
351
358
|
requirements: []
|
|
352
|
-
rubygems_version:
|
|
353
|
-
signing_key:
|
|
359
|
+
rubygems_version: 4.0.8
|
|
354
360
|
specification_version: 4
|
|
355
361
|
summary: A collection of helpers for interacting with the ecoPortal MS's V2 API
|
|
356
362
|
test_files: []
|