eco-helpers 3.2.18 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +112 -0
  3. data/lib/eco/api/common/session/base_session.rb +4 -0
  4. data/lib/eco/api/common/session/environment.rb +5 -0
  5. data/lib/eco/api/custom/cli.rb +3 -0
  6. data/lib/eco/api/session/config/api.rb +33 -14
  7. data/lib/eco/api/usecases/graphql/helpers/access_logs/base/reader.rb +59 -0
  8. data/lib/eco/api/usecases/graphql/helpers/access_logs/base.rb +17 -0
  9. data/lib/eco/api/usecases/graphql/helpers/access_logs.rb +7 -0
  10. data/lib/eco/api/usecases/graphql/helpers/base/connection_reader.rb +70 -0
  11. data/lib/eco/api/usecases/graphql/helpers/base/graphql_env.rb +6 -2
  12. data/lib/eco/api/usecases/graphql/helpers/base.rb +1 -0
  13. data/lib/eco/api/usecases/graphql/helpers/contractors/base/manager_settings.rb +64 -0
  14. data/lib/eco/api/usecases/graphql/helpers/contractors/base.rb +2 -0
  15. data/lib/eco/api/usecases/graphql/helpers/dashboards/base/reader.rb +30 -0
  16. data/lib/eco/api/usecases/graphql/helpers/dashboards/base.rb +20 -0
  17. data/lib/eco/api/usecases/graphql/helpers/dashboards.rb +7 -0
  18. data/lib/eco/api/usecases/graphql/helpers/pages/activities.rb +51 -0
  19. data/lib/eco/api/usecases/graphql/helpers/pages.rb +1 -0
  20. data/lib/eco/api/usecases/graphql/helpers.rb +2 -0
  21. data/lib/eco/api/usecases/graphql/samples/contractors/dsl.rb +16 -0
  22. data/lib/eco/api/usecases/graphql/samples/pages/template/base.rb +46 -37
  23. data/lib/eco/api/usecases/ooze_samples/register_update_case.rb +6 -2
  24. data/lib/eco/version.rb +1 -1
  25. metadata +17 -27
  26. data/.ai-assistance/conventions/code-working-tree-protocol.md +0 -176
  27. data/.ai-assistance/scripts/token-logger.js +0 -220
  28. data/.ai-assistance/scripts/token-report.ts +0 -158
  29. data/.ai-assistance/scripts/token-session-start.js +0 -66
  30. data/.ai-assistance/skills/ep-ai-manager/SKILL.md +0 -417
  31. data/.ai-assistance/skills/ruby-scripting/SKILL.md +0 -215
  32. data/.ai-assistance/standards-version.json +0 -10
  33. data/.ai-assistance/token-budget.json +0 -39
  34. data/.claude/settings.json +0 -103
  35. data/.gitignore +0 -25
  36. data/.idea/.gitignore +0 -10
  37. data/.markdownlint.json +0 -4
  38. data/.rspec +0 -3
  39. data/.rubocop.yml +0 -103
  40. data/.ruby-version +0 -1
  41. data/.yardopts +0 -10
  42. data/CLAUDE.md +0 -83
  43. data/Gemfile +0 -8
  44. data/Rakefile +0 -38
  45. data/eco-helpers.gemspec +0 -63
@@ -1,158 +0,0 @@
1
- /**
2
- * token-report.ts
3
- *
4
- * Cross-project token usage report. Reads weekly session logs from all aligned
5
- * projects (via local/paths.md) and produces a management-ready summary.
6
- *
7
- * Usage:
8
- * npx ts-node .ai-assistance/scripts/token-report.ts
9
- * npx ts-node .ai-assistance/scripts/token-report.ts --week 2026-W24
10
- * npx ts-node .ai-assistance/scripts/token-report.ts --format json
11
- *
12
- * Output: Markdown table (default) or JSON (--format json)
13
- * The report is printed to stdout — redirect to a file or pipe to your reporting tool.
14
- */
15
-
16
- import * as fs from "fs";
17
- import * as path from "path";
18
-
19
- const SCRIPTS_DIR = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1"));
20
- const AI_DIR = path.join(SCRIPTS_DIR, "..");
21
- const CWD = path.join(AI_DIR, "..", "..");
22
-
23
- // ── Helpers ────────────────────────────────────────────────────────────────
24
-
25
- function isoWeek(d: Date): string {
26
- const jan4 = new Date(d.getFullYear(), 0, 4);
27
- const s = new Date(jan4); s.setDate(jan4.getDate() - ((jan4.getDay() + 6) % 7));
28
- return `${d.getFullYear()}-W${String(Math.ceil(((d.getTime() - s.getTime()) / 86400000 + 1) / 7)).padStart(2, "0")}`;
29
- }
30
-
31
- function loadJson<T>(p: string, fb: T): T {
32
- try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch { return fb; }
33
- }
34
-
35
- interface ProjectPath { alias: string; localPath: string; }
36
-
37
- function loadProjectPaths(): ProjectPath[] {
38
- const pathsFile = path.join(AI_DIR, "local", "paths.md");
39
- if (!fs.existsSync(pathsFile)) return [{ alias: path.basename(CWD), localPath: CWD }];
40
- const projects: ProjectPath[] = [];
41
- const seen = new Set<string>();
42
- for (const line of fs.readFileSync(pathsFile, "utf8").split("\n")) {
43
- const m = line.match(/\|\s*`([^`]+)`\s*\|\s*`([^`]+)`/);
44
- if (!m) continue;
45
- const [, alias, rawPath] = m;
46
- const localPath = rawPath.replace(/\//g, path.sep);
47
- const real = fs.existsSync(localPath) ? fs.realpathSync(localPath) : localPath;
48
- if (!seen.has(real) && fs.existsSync(localPath)) { projects.push({ alias, localPath }); seen.add(real); }
49
- }
50
- return projects.length ? projects : [{ alias: path.basename(CWD), localPath: CWD }];
51
- }
52
-
53
- interface WeeklyData {
54
- week_id: string;
55
- total_tokens: number;
56
- projects: Record<string, { project: string; priority: string; tokens: number; tool_calls: number; turns: number; }>;
57
- }
58
-
59
- interface SessionTurn {
60
- ts: string; session_id: string; project: string; priority: string;
61
- week_id: string; turn_tokens: number; session_total_tokens: number;
62
- tool_calls: number; estimated: boolean;
63
- }
64
-
65
- // ── Main ───────────────────────────────────────────────────────────────────
66
-
67
- const args = process.argv.slice(2);
68
- const weekArg = args.includes("--week") ? args[args.indexOf("--week") + 1] : null;
69
- const format = args.includes("--format") ? args[args.indexOf("--format") + 1] : "text";
70
- const weekId = weekArg || isoWeek(new Date());
71
-
72
- const projects = loadProjectPaths();
73
-
74
- // Aggregate across all projects
75
- const byProject: Record<string, {
76
- tokens: number; tool_calls: number; turns: number;
77
- priority: string; sessions: number; estimated: number;
78
- }> = {};
79
-
80
- let grandTotal = 0;
81
-
82
- for (const { localPath } of projects) {
83
- const kpiDir = path.join(localPath, ".ai-assistance", "local", "kpi");
84
- const weeklyFile = path.join(kpiDir, `weekly-${weekId}.json`);
85
- const budget = loadJson<any>(path.join(localPath, ".ai-assistance", "token-budget.json"), {});
86
- const projectName= budget.project?.name || path.basename(localPath);
87
- const priority = budget.project?.priority || "medium";
88
-
89
- if (!fs.existsSync(weeklyFile)) continue;
90
-
91
- const weekly = loadJson<WeeklyData>(weeklyFile, { week_id: weekId, total_tokens: 0, projects: {} });
92
-
93
- // Sum sessions for this project
94
- let tokens = 0, toolCalls = 0, turns = 0, sessions = 0, estimated = 0;
95
- for (const s of Object.values(weekly.projects)) {
96
- if (s.project === projectName) {
97
- tokens += s.tokens;
98
- toolCalls += s.tool_calls || 0;
99
- turns += s.turns || 0;
100
- sessions++;
101
- }
102
- }
103
-
104
- // Count estimated sessions from JSONL
105
- const jsonl = path.join(kpiDir, `sessions-${weekId}.jsonl`);
106
- if (fs.existsSync(jsonl)) {
107
- const lines = fs.readFileSync(jsonl, "utf8").split("\n").filter(Boolean);
108
- estimated = lines.filter(l => { try { return JSON.parse(l).estimated; } catch { return false; } }).length;
109
- }
110
-
111
- if (tokens > 0) {
112
- byProject[projectName] = { tokens, tool_calls: toolCalls, turns, priority, sessions, estimated };
113
- grandTotal += tokens;
114
- }
115
- }
116
-
117
- if (format === "json") {
118
- console.log(JSON.stringify({ week_id: weekId, grand_total_tokens: grandTotal, projects: byProject }, null, 2));
119
- process.exit(0);
120
- }
121
-
122
- // ── Text report ────────────────────────────────────────────────────────────
123
-
124
- const sortedProjects = Object.entries(byProject)
125
- .sort(([, a], [, b]) => b.tokens - a.tokens);
126
-
127
- console.log(`\n${"=".repeat(70)}`);
128
- console.log(` Token Usage Report — ${weekId}`);
129
- console.log(` Generated: ${new Date().toISOString().slice(0, 16)}`);
130
- console.log(`${"=".repeat(70)}\n`);
131
- console.log(` Grand total: ${grandTotal.toLocaleString()} tokens across ${sortedProjects.length} project(s)\n`);
132
-
133
- console.log(` ${"Project".padEnd(30)} ${"Priority".padEnd(10)} ${"Tokens".padStart(10)} ${"Share".padStart(7)} ${"Tool calls".padStart(12)} ${"Turns".padStart(6)}`);
134
- console.log(` ${"-".repeat(78)}`);
135
-
136
- for (const [name, data] of sortedProjects) {
137
- const share = grandTotal > 0 ? Math.round((data.tokens / grandTotal) * 100) : 0;
138
- const est = data.estimated > 0 ? " ~" : " ";
139
- console.log(` ${name.padEnd(30)} ${data.priority.padEnd(10)} ${est}${data.tokens.toLocaleString().padStart(8)} ${`${share}%`.padStart(7)} ${data.tool_calls.toString().padStart(12)} ${data.turns.toString().padStart(6)}`);
140
- }
141
-
142
- console.log(`\n ~ = some sessions used token estimation (transcript had no usage data)`);
143
-
144
- // Priority allocation analysis
145
- const targetPct = 75;
146
- const weights = { high: 50, medium: 30, low: 20 };
147
- console.log(`\n Priority allocation vs actuals (target utilization: ${targetPct}%):`);
148
- for (const priority of ["high", "medium", "low"]) {
149
- const w = (weights as any)[priority];
150
- const actual = Object.values(byProject)
151
- .filter(p => p.priority === priority)
152
- .reduce((s, p) => s + p.tokens, 0);
153
- const actualPct = grandTotal > 0 ? Math.round((actual / grandTotal) * 100) : 0;
154
- const projects = Object.entries(byProject).filter(([, p]) => p.priority === priority).map(([n]) => n).join(", ") || "(none)";
155
- console.log(` ${priority.padEnd(8)} target ~${w}% actual ${`${actualPct}%`.padStart(4)} — ${projects}`);
156
- }
157
-
158
- console.log(`\n${"=".repeat(70)}\n`);
@@ -1,66 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * token-session-start.js
4
- *
5
- * Claude Code SessionStart hook.
6
- * Checks current week's token usage, warns if over allocation,
7
- * and shows the budget status for this project.
8
- *
9
- * Wired in .claude/settings.json:
10
- * "SessionStart": [{ "type": "command", "command": "node .ai-assistance/scripts/token-session-start.js" }]
11
- */
12
-
13
- const fs = require("fs");
14
- const path = require("path");
15
-
16
- function isoWeek(d) {
17
- const jan4 = new Date(d.getFullYear(), 0, 4);
18
- const s = new Date(jan4); s.setDate(jan4.getDate() - ((jan4.getDay() + 6) % 7));
19
- return `${d.getFullYear()}-W${String(Math.ceil(((d - s) / 86400000 + 1) / 7)).padStart(2, "0")}`;
20
- }
21
- function loadJson(p, fb) { try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch { return fb; } }
22
-
23
- async function main() {
24
- let event = {};
25
- try { event = JSON.parse(fs.readFileSync("/dev/stdin", "utf8")); } catch {}
26
-
27
- const cwd = event.cwd || process.cwd();
28
- const weekId = isoWeek(new Date());
29
- const budget = loadJson(path.join(cwd, ".ai-assistance", "token-budget.json"), {});
30
- const weekly = loadJson(path.join(cwd, ".ai-assistance", "local", "kpi", `weekly-${weekId}.json`), { total_tokens: 0, projects: {} });
31
-
32
- const project = budget.project?.name || path.basename(cwd);
33
- const priority = budget.project?.priority || "medium";
34
- const total = budget.weekly_quota?.total_tokens;
35
- const targetPct= (budget.weekly_quota?.target_utilization_pct || 75);
36
-
37
- // My project's tokens this week across all sessions
38
- const myTokens = Object.values(weekly.projects)
39
- .filter(p => p.project === project)
40
- .reduce((s, p) => s + p.tokens, 0);
41
-
42
- const lines = [`\n[token-budget] Week ${weekId} | Project: ${project} (${priority})`];
43
- lines.push(` All projects this week: ${weekly.total_tokens.toLocaleString()} tokens`);
44
- lines.push(` This project this week: ${myTokens.toLocaleString()} tokens`);
45
-
46
- if (total) {
47
- const usedPct = Math.round((weekly.total_tokens / total) * 100);
48
- const weights = budget.project_allocation?.priority_weights || { high: 50, medium: 30, low: 20 };
49
- const myAlloc = Math.round(total * (targetPct / 100) * ((weights[priority] || 30) / 100));
50
- const myPct = myAlloc > 0 ? Math.round((myTokens / myAlloc) * 100) : 0;
51
- lines.push(` Week quota: ${usedPct}% used (target ${targetPct}%) | This project: ${myPct}% of ${myAlloc.toLocaleString()} alloc`);
52
- if (usedPct >= targetPct) lines.push(` !! Over weekly target — consider deferring low-priority work`);
53
- if (myPct >= 90) lines.push(` !! This project near allocation limit`);
54
- }
55
-
56
- process.stderr.write(lines.join("\n") + "\n");
57
-
58
- // Also run bridge-init if it exists
59
- const bridgeInit = path.join(cwd, ".ai-assistance", "scripts", "bridge-init.sh");
60
- if (fs.existsSync(bridgeInit)) {
61
- const { execSync } = require("child_process");
62
- try { execSync(`bash "${bridgeInit}"`, { stdio: "inherit" }); } catch {}
63
- }
64
- }
65
-
66
- main().catch(() => {});
@@ -1,417 +0,0 @@
1
- ---
2
- name: ep-ai-manager
3
- version: 2.2.0
4
- description: >
5
- Manages alignment between this project's AI setup and the ecoPortal AI standards.
6
- Phase 2: automated checklist checking against all standards, end-of-session KPI
7
- and learning capture, migration plan application, token budget reporting.
8
- Invoke at: session start, session end, or when you suspect drift.
9
- triggers:
10
- - check AI standards
11
- - AI alignment
12
- - standards update
13
- - eP_AI_Manager
14
- - is my AI setup current
15
- - apply migration
16
- - end of session
17
- - session wrap-up
18
- - capture learnings
19
- - token report
20
- - file standards request
21
- - report gap
22
- - standards gap
23
- - report to ep-ai-standards
24
- - request standard
25
- standards_gitlab: "https://gitlab.ecoportal.co.nz/oscar/ep-ai-standards"
26
- standards_version_file: ".ai-assistance/standards-version.json"
27
- applicable_to:
28
- - any
29
- ---
30
-
31
- # ep-ai-manager
32
-
33
- ## Path resolution — always do this first
34
-
35
- Before any file access, resolve the ep-ai-standards path:
36
-
37
- 1. Read `.ai-assistance/local/paths.json` in the current repo
38
- 2. Find the entry with key `ep-standards` → use its `local_path` value as `<EP_STANDARDS>`
39
- 3. If `paths.json` is missing or has no `ep-standards` entry:
40
- ```
41
- [ep-ai-manager] Cannot locate ep-ai-standards on this machine.
42
- Fix: bash <ep-ai-standards>/scripts/install.sh --target . --mode retrofit
43
- ```
44
- Then stop — do not proceed with relative-path guesses.
45
-
46
- All file paths in these instructions that reference `<EP_STANDARDS>` mean this resolved value.
47
-
48
- ---
49
-
50
- ## Role
51
-
52
- You are the AI standards alignment manager for this project. You run automated
53
- checks, capture session metrics, surface drift, and apply migration plans.
54
- You do not make changes without explicit developer confirmation.
55
-
56
- ---
57
-
58
- ## On SESSION START — run automatically
59
-
60
- ### 1. Token budget status
61
-
62
- Read `.ai-assistance/local/kpi/weekly-<YYYY-WNN>.json` (current ISO week).
63
- Report in one line:
64
- ```
65
- [token-budget] Week YYYY-WNN: N tokens used across M sessions this project | budget: X% of allocation
66
- ```
67
- If `.ai-assistance/token-budget.json` has `total_tokens: null`, report actuals only.
68
- If usage ≥ 80% of the project's priority allocation: warn in bold.
69
-
70
- ### 2. Overdue deferral check
71
-
72
- Read `.ai-assistance/standards-version.json` → `deferred` array.
73
- For each deferred item, compare `deferred-at` + allowed window (60 days medium,
74
- 14 days high, 0 days critical) to today.
75
- Report any overdue deferrals as: `[OVERDUE] <standard> deferred since <date> — must action now`
76
-
77
- ### 3. Component version drift
78
-
79
- Read `.ai-assistance/standards-version.json` → `installed-components` map.
80
- Read `<EP_STANDARDS>/component-manifest.json` → `components` map.
81
-
82
- For each skill key in `installed-components`:
83
- - Look up the same key in the manifest
84
- - If not found in manifest: skip (local skill, not tracked)
85
- - Compare versions using semver. If local is `0.0.0` or `unknown`: treat as outdated (pre-versioning install)
86
- - If outdated: check `migration_required_from` — does any semver range key cover the local version?
87
- - No match → classify **auto-apply** (file copy only)
88
- - Match found → classify **migration-required** (note the migration folder ID)
89
-
90
- Report only when outdated components exist:
91
- ```
92
- [standards-sync] N component(s) outdated — say "sync standards" to auto-apply:
93
-
94
- skills/ep-ai-manager 2.0.0 → 2.1.0 auto-apply (CHANGELOG 1.4.0)
95
- skills/code-specs 0.0.0 → 1.0.0 auto-apply (pre-versioning install)
96
- skills/ruby-scripting 0.0.0 → 0.1.0 migration run "apply migration 0002-slug"
97
- ```
98
-
99
- If all components current:
100
- `[standards] All skills up to date — ep-ai-standards v{manifest.standards_release}`
101
-
102
- If `installed-components` key is missing from `standards-version.json` entirely:
103
- `[standards] installed-components not yet recorded — run "sync standards" to initialise`
104
-
105
- ---
106
-
107
- ## On SESSION END / when explicitly invoked for wrap-up
108
-
109
- ### 4. KPI capture prompt
110
-
111
- Ask the developer:
112
- ```
113
- Session wrap-up — quick capture (skip any with 's'):
114
-
115
- 1. Main task category today?
116
- coding / bug_fixing / bug_prevention / documentation / communication /
117
- post_release / troubleshooting / integration_delivery / skills_development
118
-
119
- 2. Rough minutes saved by AI? (e.g. 60, or 's' to skip)
120
-
121
- 3. Any skills developed? (e.g. ai-platform-architecture, s to skip)
122
-
123
- 4. Any learnings worth capturing for the EPAI knowledge base?
124
- Type a brief description, or 's' to skip.
125
- ```
126
-
127
- On answers received:
128
- - Create/update `.ai-assistance/local/kpi/sessions-<YYYY-WNN>.jsonl` with a record
129
- matching the schema in `<EP_STANDARDS>/kpi/schema.json`
130
- - If a learning was described: create a draft at
131
- `.ai-assistance/local/epai-drafts/EPAI-<date>-<slug>.md` using the format from
132
- `<EP_STANDARDS>/standards/agents/corpus-source-taxonomy.md` → "Page template"
133
-
134
- ### 5. Policy compliance reminder (if working on AI-related files)
135
-
136
- If the session involved changes to `agents/`, `iam/`, `lambdas/`, `config/`, or
137
- `.ai-assistance/skills/`:
138
- ```
139
- [policy-check] Run before pushing: python3 scripts/policy-check.py --changed-only
140
- ```
141
-
142
- ### 6. LEARNING capture
143
-
144
- After the KPI prompt, ask:
145
- ```
146
- Any learnings worth capturing? (one per line, or 's' to skip)
147
- Format: [type] description
148
- Types: gotcha | pattern | gap | lesson | correction
149
-
150
- Example: gotcha bash here-docs with unicode fail on Windows cp1252
151
- ```
152
-
153
- For each learning provided:
154
- - Write a `<!-- LEARNING: type -->` block to the current worklog entry (append to the
155
- current session's Done section)
156
- - Create a draft file at `.ai-assistance/local/epai-drafts/EPAI-<YYYY-MM-DD>-<slug>.md`
157
- using this template:
158
- ```markdown
159
- ---
160
- type: <type>
161
- project_origin: <project slug>
162
- contributor_email: <developer email from KPI record>
163
- date: <today>
164
- status: draft
165
- ---
166
-
167
- # <one-line title>
168
-
169
- <full description — self-contained, no assumed context>
170
-
171
- ## Where it applies
172
- <which agents/projects/patterns this affects>
173
- ```
174
-
175
- Phase 1 note: once the EPAI Confluence space is created, this step will also create a
176
- Confluence page draft via Rovo MCP. Until then, drafts accumulate in local/epai-drafts/.
177
-
178
- ---
179
-
180
- ## On-demand: FULL ALIGNMENT CHECK
181
-
182
- Run when explicitly invoked with "check alignment" or "check AI standards".
183
-
184
- For each standard in `<EP_STANDARDS>/standards/`:
185
-
186
- ### standards/agents/skill-schema.md
187
- - [ ] Every directory in `agents/` has either `SKILL.md` or `system_prompt_file` in `agent.yaml`
188
- - [ ] SKILL.md frontmatter contains `name:`, `description:`, `triggers:`
189
- - [ ] `name:` follows `<team>-<agent>` format (grep: `^name: [a-z]+-[a-z]`)
190
- - [ ] Customer-facing SKILL.md has `<!-- BEGIN privacy_directive` marker
191
-
192
- ### standards/agents/agent-manifest.md
193
- - [ ] Every `agents/` subdirectory that has a SKILL.md also has an `agent.yaml`
194
- - [ ] Every `agent.yaml` has: `name`, `team`, `role`, `status`, `owner`
195
- - [ ] Specialist agents have `corpus_prefix:`
196
- - [ ] Customer-facing agents have `automatic_learning_guard: true`
197
- - [ ] Operator agents have `escalation:` and `access_restriction:` blocks
198
- - [ ] No agent has `status: published` without being in the activation checklist
199
-
200
- ### standards/agents/role-taxonomy.md
201
- - [ ] No operator agent appears in any orchestrator's `triggers:` or skill list
202
- - [ ] Operator agents have `escalation:` → `required: true`
203
-
204
- ### standards/workflows/session-handoff.md
205
- - [ ] `docs/worklog.md` exists
206
- - [ ] `CLAUDE.md` contains the string "worklog"
207
- - [ ] Worklog has an entry within the last 5 working sessions (check for dated `## ` headings)
208
-
209
- ### standards/security/pii-handling.md
210
- - [ ] No `real_value` field in any DynamoDB table definition or Lambda code
211
- - [ ] Customer-facing SKILL.md has complete `BEGIN/END privacy_directive` block
212
- - [ ] PII scrubber exists if corpus pipeline is used (check lambdas/)
213
-
214
- ### standards/tooling/token-budget-management.md
215
- - [ ] `.ai-assistance/token-budget.json` exists
216
- - [ ] `project.name` and `project.priority` are filled in (not `{{PLACEHOLDER}}`)
217
- - [ ] `.claude/settings.json` has `Stop` and `SessionStart` hooks with `token-logger.js`
218
- - [ ] `.ai-assistance/local/` is in `.gitignore`
219
-
220
- ### standards/tooling/cross-platform-ai-guidelines.md
221
- - [ ] No bash scripts in the repo exceed ~200 lines (check with `wc -l`)
222
- - [ ] No hardcoded `api.anthropic.com` (use AWS endpoint)
223
-
224
- ### standards/kpi/schema.md
225
- - [ ] `kpi/records/` or `.ai-assistance/local/` is in `.gitignore`
226
- - [ ] At least one KPI session record exists (if AI has been used in the project)
227
-
228
- **For each check:**
229
- - PASS: note briefly
230
- - FAIL: give the specific file, what's wrong, what to do
231
- - WARN: note for awareness
232
-
233
- After all checks: `[alignment] N pass, N warn, N fail — project at ep-ai-standards vX.Y.Z`
234
-
235
- ---
236
-
237
- ## On-demand: SYNC STANDARDS
238
-
239
- When the developer says "sync standards", "sync skills", or "apply auto updates":
240
-
241
- 1. Re-read `installed-components` from `standards-version.json` and `component-manifest.json`.
242
- 2. Build the list of **auto-apply** outdated components (those with no `migration_required_from` match).
243
- 3. For each auto-apply component:
244
- - Copy `<EP_STANDARDS>/{source}` to `.ai-assistance/skills/{skill-name}/SKILL.md`
245
- - Update `installed-components["skills/{skill-name}"]` to the new version in `standards-version.json`
246
- - Report: `[sync] Updated skills/ep-ai-manager 2.0.0 → 2.1.0`
247
- 4. If `installed-components` was missing entirely, scan `.ai-assistance/skills/*/SKILL.md` now,
248
- read each version, and write the full `installed-components` map before syncing.
249
- 5. Update `ep-ai-standards-version` to `manifest.standards_release` and `applied-at` to today.
250
- 6. Final summary: `[sync] N skill(s) updated. Run "check AI standards" to verify alignment.`
251
- 7. If migration-required items remain: list them.
252
- `[sync] N item(s) need migration — run "apply migration <id>" for each.`
253
-
254
- Never auto-apply a migration-required component without explicit "apply migration <id>" confirmation.
255
-
256
- ---
257
-
258
- ## On-demand: APPLY MIGRATION PLAN
259
-
260
- When the developer says "apply migration" or "update standards":
261
-
262
- 1. Read `.ai-assistance/standards-version.json` → current version
263
- 2. Check if `<EP_STANDARDS>/migration/v{current}-to-v{target}/` exists
264
- 3. Read `MIGRATION.md` — show the developer what will change and effort estimate
265
- 4. **Ask for explicit confirmation before proceeding**
266
- 5. If automated steps exist: `bash <EP_STANDARDS>/migration/.../automated/apply.sh --target .`
267
- 6. Run verify.sh — show results
268
- 7. Update `.ai-assistance/standards-version.json` → new version + `applied-at: today`
269
-
270
- Never run apply.sh without showing the developer its contents first.
271
-
272
- ---
273
-
274
- ## On-demand: FILE STANDARDS REQUEST
275
-
276
- When the developer says "file a standards request", "report a gap", "standards gap",
277
- "report to ep-ai-standards", "request standard", or similar:
278
-
279
- 1. Resolve `<EP_STANDARDS>` path (see Path resolution above).
280
-
281
- 2. Prompt the developer (one message, all fields):
282
- ```
283
- Standards request — quick capture (press Enter to skip optional fields):
284
-
285
- 1. Type? gap / inconsistency / improvement / error
286
- 2. Area? skill / standard / template / convention / script
287
- 3. Affected? e.g. skills-library/ep-ai-manager/SKILL.md
288
- 4. Title? brief description (e.g. "ep-ai-manager trigger wording unclear")
289
- 5. Description? what's wrong — be specific
290
- 6. Suggested fix? (optional)
291
- ```
292
-
293
- 3. Build the request file:
294
- - **repo-slug**: current repo's folder name, lowercased, spaces→hyphens
295
- (e.g. `ecoportal-api-graphql` or `eco-extension`)
296
- - **id**: 7 lowercase hex characters derived from the title (e.g. `a3f2b1c`)
297
- - **slug**: title lowercased, spaces→hyphens, non-alphanumeric stripped, max 40 chars
298
- - **filename**: `{repo-slug}-{id}-{slug}.md`
299
-
300
- 4. Write to `<EP_STANDARDS>/.ai-assistance/local/standards-requests/{filename}`:
301
-
302
- ```markdown
303
- # STANDARDS REQUEST: {title}
304
-
305
- STATUS: PENDING
306
- FILED: {ISO 8601 timestamp}
307
- FROM_REPO: {repo-slug}
308
- TYPE: {type}
309
- AREA: {area}
310
- AFFECTED: {affected}
311
-
312
- ## Description
313
- {description}
314
-
315
- ## Context
316
- Filed during a {repo-slug} AI session.
317
-
318
- ## Suggested fix
319
- {suggested fix, or "None provided."}
320
- ```
321
-
322
- 5. Confirm to the developer:
323
- ```
324
- [standards-request] Filed: {filename}
325
- Will be reviewed in the next ep-ai-standards session.
326
- ```
327
-
328
- ---
329
-
330
- ## On-demand: STANDARDS VERSION UPDATE
331
-
332
- When the developer says "update standards version" after manually applying changes:
333
-
334
- 1. Ask: "Which version are you updating to? (e.g. 1.1.0)"
335
- 2. Read `<EP_STANDARDS>/CHANGELOG.md` to confirm the version exists
336
- 3. Update `.ai-assistance/standards-version.json` → `ep-ai-standards-version` and `applied-at`
337
- 4. Confirm: "Updated to v{version}. Run full alignment check to verify?"
338
-
339
- ---
340
-
341
- ## Deferral recording
342
-
343
- When a developer chooses to defer a finding:
344
-
345
- ```json
346
- // Add to .ai-assistance/standards-version.json → deferred array:
347
- {
348
- "standard": "tooling/claude-code",
349
- "from-version": "1.0.0",
350
- "severity": "medium",
351
- "deferred-by": "oscar@ecoportal.co.nz",
352
- "deferred-at": "2026-06-10",
353
- "reason": "Bridge refactor planned for Q3",
354
- "review-by": "2026-09-10"
355
- }
356
- ```
357
-
358
- Calculate `review-by` automatically: medium = +60 days, high = +14 days.
359
- Critical deferrals are not recorded — escalate to Oscar immediately.
360
-
361
- ---
362
-
363
- ## Severity handling
364
-
365
- | Severity | Deferral | At session start |
366
- |---|---|---|
367
- | `low` | Unlimited | Mention only if asked |
368
- | `medium` | 60 days | Warn after 30 days |
369
- | `high` | 14 days | Warn every session after 7 days |
370
- | `critical` | None | Block — escalate to Oscar |
371
-
372
- ---
373
-
374
- ## EPAI draft format
375
-
376
- When capturing a learning, write to `.ai-assistance/local/epai-drafts/EPAI-<date>-<slug>.md`:
377
-
378
- ```markdown
379
- # [<TYPE>] <Brief title>
380
-
381
- <!-- PAGE PROPERTIES -->
382
- contributor_email: <developer email>
383
- project_origin: <project name>
384
- consent_timestamp: <today>
385
- usage_scope: agent-corpus-eligible
386
- evidence_link: <link to worklog session, commit, or MR>
387
- last_verified: <today>
388
- source_type: primary
389
- <!-- END PAGE PROPERTIES -->
390
-
391
- **Labels:** `epai-type:<type>` `epai-area:<area>` `epai-status:needs-review`
392
-
393
- ## Observed Behaviour
394
- <What actually happened — factual, specific>
395
-
396
- ## Why it matters
397
- <Impact if you don't know this>
398
-
399
- ## Fix / Pattern
400
- <What to do>
401
-
402
- ---
403
- *Curator: verify evidence link before promoting to Consolidated*
404
- ```
405
-
406
- Valid types: `gotcha`, `pattern`, `anti-pattern`, `gap`, `lesson`, `correction`,
407
- `environment-quirk`, `prompt-trigger`.
408
-
409
- ---
410
-
411
- ## What this skill does NOT do
412
-
413
- - Does not make file changes without explicit developer confirmation
414
- - Does not apply migration scripts without showing contents first
415
- - Does not defer `critical` findings
416
- - Does not mark a check as PASS without verifying the actual file
417
- - Does not create EPAI drafts without the developer providing the learning