@aitne-sh/aitne 0.1.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 (249) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +464 -0
  3. package/agent-assets/agent-profiles/_safety.md +26 -0
  4. package/agent-assets/agent-profiles/conversational.md +33 -0
  5. package/agent-assets/agent-profiles/docs-qa.md +24 -0
  6. package/agent-assets/agent-profiles/observer.md +28 -0
  7. package/agent-assets/agent-profiles/profile-importer.md +63 -0
  8. package/agent-assets/agent-profiles/proxy.md +28 -0
  9. package/agent-assets/agent-profiles/routine.md +16 -0
  10. package/agent-assets/agent-profiles/task.md +18 -0
  11. package/agent-assets/docs/concepts/agent-day.md +88 -0
  12. package/agent-assets/docs/concepts/auth-health.md +75 -0
  13. package/agent-assets/docs/concepts/backends-and-tiers.md +126 -0
  14. package/agent-assets/docs/concepts/costs-and-quotas.md +103 -0
  15. package/agent-assets/docs/concepts/delegated-mode.md +223 -0
  16. package/agent-assets/docs/concepts/memory-model.md +118 -0
  17. package/agent-assets/docs/concepts/observations.md +80 -0
  18. package/agent-assets/docs/concepts/process-keys.md +89 -0
  19. package/agent-assets/docs/concepts/routines.md +108 -0
  20. package/agent-assets/docs/concepts/safety-and-execution.md +109 -0
  21. package/agent-assets/docs/concepts/safety-model.md +279 -0
  22. package/agent-assets/docs/concepts/skills.md +100 -0
  23. package/agent-assets/docs/features/integrations/calendar.md +92 -0
  24. package/agent-assets/docs/features/integrations/git.md +95 -0
  25. package/agent-assets/docs/features/integrations/github.md +170 -0
  26. package/agent-assets/docs/features/integrations/mail.md +106 -0
  27. package/agent-assets/docs/features/integrations/notion.md +69 -0
  28. package/agent-assets/docs/features/integrations/obsidian.md +71 -0
  29. package/agent-assets/docs/features/lifestyle/git.md +178 -0
  30. package/agent-assets/docs/features/lifestyle/reading.md +93 -0
  31. package/agent-assets/docs/features/lifestyle/receipts.md +71 -0
  32. package/agent-assets/docs/features/lifestyle/travel-bookings.md +44 -0
  33. package/agent-assets/docs/features/lifestyle/travel-time.md +52 -0
  34. package/agent-assets/docs/features/memory-files/agent-journal.md +105 -0
  35. package/agent-assets/docs/features/memory-files/projects.md +56 -0
  36. package/agent-assets/docs/features/memory-files/roadmap.md +61 -0
  37. package/agent-assets/docs/features/memory-files/schedule.md +112 -0
  38. package/agent-assets/docs/features/memory-files/today.md +73 -0
  39. package/agent-assets/docs/features/memory-files/user-profile.md +81 -0
  40. package/agent-assets/docs/features/messaging/dashboard-chat.md +93 -0
  41. package/agent-assets/docs/features/messaging/discord.md +50 -0
  42. package/agent-assets/docs/features/messaging/overview.md +111 -0
  43. package/agent-assets/docs/features/messaging/pairing-and-magic-phrase.md +69 -0
  44. package/agent-assets/docs/features/messaging/slack.md +51 -0
  45. package/agent-assets/docs/features/messaging/telegram.md +63 -0
  46. package/agent-assets/docs/features/messaging/whatsapp.md +48 -0
  47. package/agent-assets/docs/features/operations/activity-and-conversations.md +105 -0
  48. package/agent-assets/docs/features/operations/approvals.md +58 -0
  49. package/agent-assets/docs/features/operations/backend-routing.md +62 -0
  50. package/agent-assets/docs/features/operations/cost-tracking.md +59 -0
  51. package/agent-assets/docs/features/operations/notifications.md +69 -0
  52. package/agent-assets/docs/features/operations/quiet-hours.md +106 -0
  53. package/agent-assets/docs/features/operations/schedule-approaching.md +60 -0
  54. package/agent-assets/docs/features/routines/custom-routines.md +101 -0
  55. package/agent-assets/docs/features/routines/evening-review.md +81 -0
  56. package/agent-assets/docs/features/routines/hourly-check.md +85 -0
  57. package/agent-assets/docs/features/routines/monthly-review.md +65 -0
  58. package/agent-assets/docs/features/routines/morning-routine.md +123 -0
  59. package/agent-assets/docs/features/routines/weekly-review.md +70 -0
  60. package/agent-assets/docs/getting-started/01-what-is-this.md +192 -0
  61. package/agent-assets/docs/getting-started/02-first-steps.md +80 -0
  62. package/agent-assets/docs/getting-started/03-what-can-this-do.md +110 -0
  63. package/agent-assets/docs/getting-started/04-first-day.md +287 -0
  64. package/agent-assets/docs/glossary.md +116 -0
  65. package/agent-assets/docs/guides/add-a-custom-routine.md +71 -0
  66. package/agent-assets/docs/guides/backup-and-restore.md +54 -0
  67. package/agent-assets/docs/guides/change-which-model-handles-x.md +47 -0
  68. package/agent-assets/docs/guides/connect-a-new-mail-account.md +59 -0
  69. package/agent-assets/docs/guides/import-knowledge-file.md +275 -0
  70. package/agent-assets/docs/guides/install-and-run.md +72 -0
  71. package/agent-assets/docs/guides/migrate-machines.md +52 -0
  72. package/agent-assets/docs/guides/pause-the-agent.md +65 -0
  73. package/agent-assets/docs/guides/reinstall-cleanly.md +52 -0
  74. package/agent-assets/docs/guides/setup-wizard.md +107 -0
  75. package/agent-assets/docs/guides/switch-default-backend.md +60 -0
  76. package/agent-assets/docs/reference/api.md +51 -0
  77. package/agent-assets/docs/reference/cli-commands.md +121 -0
  78. package/agent-assets/docs/reference/config.md +74 -0
  79. package/agent-assets/docs/reference/disallowed-tools.md +76 -0
  80. package/agent-assets/docs/reference/keyboard-shortcuts.md +39 -0
  81. package/agent-assets/docs/reference/process-keys.md +59 -0
  82. package/agent-assets/docs/reference/skills.md +50 -0
  83. package/agent-assets/docs/troubleshooting/auth-failed.md +57 -0
  84. package/agent-assets/docs/troubleshooting/dashboard-shows-degraded.md +55 -0
  85. package/agent-assets/docs/troubleshooting/fallback-keeps-firing.md +54 -0
  86. package/agent-assets/docs/troubleshooting/messaging-not-pairing.md +53 -0
  87. package/agent-assets/docs/troubleshooting/morning-routine-didnt-run.md +75 -0
  88. package/agent-assets/docs/troubleshooting/observation-not-detected.md +57 -0
  89. package/agent-assets/docs/troubleshooting/quota-exhausted.md +57 -0
  90. package/agent-assets/optimizer-skills/drift-analysis/SKILL.md +75 -0
  91. package/agent-assets/optimizer-skills/knowledge-map/SKILL.md +71 -0
  92. package/agent-assets/optimizer-skills/skill-curation/SKILL.md +108 -0
  93. package/agent-assets/project-doc-templates/git-repo.md +21 -0
  94. package/agent-assets/project-doc-templates/project.md +38 -0
  95. package/agent-assets/skills/attach/SKILL.md +104 -0
  96. package/agent-assets/skills/context/SKILL.md +257 -0
  97. package/agent-assets/skills/context/curation.json +37 -0
  98. package/agent-assets/skills/context/seeds/file-responsibilities.seed.json +13 -0
  99. package/agent-assets/skills/context/seeds/frontmatter-requirements.seed.json +40 -0
  100. package/agent-assets/skills/docs-search/SKILL.md +176 -0
  101. package/agent-assets/skills/external-services/SKILL.delegated.claude.md +369 -0
  102. package/agent-assets/skills/external-services/SKILL.delegated.codex.md +349 -0
  103. package/agent-assets/skills/external-services/SKILL.delegated.gemini.md +347 -0
  104. package/agent-assets/skills/external-services/SKILL.md +371 -0
  105. package/agent-assets/skills/mail/SKILL.delegated.claude.md +284 -0
  106. package/agent-assets/skills/mail/SKILL.delegated.codex.md +261 -0
  107. package/agent-assets/skills/mail/SKILL.delegated.gemini.md +255 -0
  108. package/agent-assets/skills/mail/SKILL.md +313 -0
  109. package/agent-assets/skills/mail/references/errors.md +17 -0
  110. package/agent-assets/skills/mail/references/providers.md +40 -0
  111. package/agent-assets/skills/mail/references/query-grammar.md +24 -0
  112. package/agent-assets/skills/management-policy/SKILL.md +307 -0
  113. package/agent-assets/skills/management-policy/curation.json +13 -0
  114. package/agent-assets/skills/management-policy/seeds/policy-file-shape.seed.json +16 -0
  115. package/agent-assets/skills/management-task-modify/SKILL.md +202 -0
  116. package/agent-assets/skills/management-task-register/SKILL.md +330 -0
  117. package/agent-assets/skills/management-task-stop/SKILL.md +166 -0
  118. package/agent-assets/skills/notify/SKILL.md +196 -0
  119. package/agent-assets/skills/notion/SKILL.delegated.claude.md +254 -0
  120. package/agent-assets/skills/notion/SKILL.delegated.codex.md +195 -0
  121. package/agent-assets/skills/notion/SKILL.delegated.gemini.md +194 -0
  122. package/agent-assets/skills/notion/SKILL.md +86 -0
  123. package/agent-assets/skills/observations/SKILL.md +234 -0
  124. package/agent-assets/skills/observations/curation.json +13 -0
  125. package/agent-assets/skills/observations/seeds/source-namespacing.seed.json +20 -0
  126. package/agent-assets/skills/project-doc/SKILL.md +86 -0
  127. package/agent-assets/skills/project-doc/curation.json +21 -0
  128. package/agent-assets/skills/project-doc/seeds/project-shape.seed.json +25 -0
  129. package/agent-assets/skills/project-doc/seeds/slug-grammar.seed.json +20 -0
  130. package/agent-assets/skills/reading/SKILL.md +198 -0
  131. package/agent-assets/skills/reading/references/reading-taste.md +197 -0
  132. package/agent-assets/skills/receipts/SKILL.md +134 -0
  133. package/agent-assets/skills/roadmap/SKILL.md +276 -0
  134. package/agent-assets/skills/roadmap/curation.json +13 -0
  135. package/agent-assets/skills/roadmap/references/horizon-tags.md +40 -0
  136. package/agent-assets/skills/roadmap/references/preparation-timeline.md +47 -0
  137. package/agent-assets/skills/roadmap/seeds/entry-types.seed.json +16 -0
  138. package/agent-assets/skills/schedule/SKILL.md +228 -0
  139. package/agent-assets/skills/scheduled-managed-task/SKILL.md +392 -0
  140. package/agent-assets/skills/today/SKILL.md +198 -0
  141. package/agent-assets/skills/today/curation.json +21 -0
  142. package/agent-assets/skills/today/seeds/agent-notes-flavors.seed.json +17 -0
  143. package/agent-assets/skills/today/seeds/section-shape.seed.json +17 -0
  144. package/agent-assets/skills/travel/SKILL.md +132 -0
  145. package/agent-assets/skills/travel-time/SKILL.md +149 -0
  146. package/agent-assets/skills/user-interview/SKILL.md +323 -0
  147. package/agent-assets/skills/user-interview/references/sweep-and-fallback.md +94 -0
  148. package/agent-assets/skills/user-profile/SKILL.md +210 -0
  149. package/agent-assets/skills/user-profile/curation.json +29 -0
  150. package/agent-assets/skills/user-profile/seeds/learned-context-format.seed.json +14 -0
  151. package/agent-assets/skills/user-profile/seeds/routing-table.seed.json +53 -0
  152. package/agent-assets/skills/user-profile/seeds/topic-files.seed.json +27 -0
  153. package/agent-assets/task-flows/dashboard.docs_qa.md +43 -0
  154. package/agent-assets/task-flows/default.md +11 -0
  155. package/agent-assets/task-flows/git.branch.created.md +25 -0
  156. package/agent-assets/task-flows/git.lifecycle.poll.md +52 -0
  157. package/agent-assets/task-flows/git.local_ahead.stale.md +34 -0
  158. package/agent-assets/task-flows/git.merge_to_default.md +30 -0
  159. package/agent-assets/task-flows/git.project.refresh_architecture.md +100 -0
  160. package/agent-assets/task-flows/git.project.retemplate.md +73 -0
  161. package/agent-assets/task-flows/git.push.detected.md +32 -0
  162. package/agent-assets/task-flows/git.push.force_pushed.md +36 -0
  163. package/agent-assets/task-flows/git.tag.created.md +24 -0
  164. package/agent-assets/task-flows/github.assigned.md +43 -0
  165. package/agent-assets/task-flows/github.pull_request.review_requested.md +57 -0
  166. package/agent-assets/task-flows/github.security_alert.md +45 -0
  167. package/agent-assets/task-flows/github.workflow_run.failed.md +57 -0
  168. package/agent-assets/task-flows/knowledge.import.md +161 -0
  169. package/agent-assets/task-flows/message.received.dm.md +142 -0
  170. package/agent-assets/task-flows/message.received.dm_first.md +117 -0
  171. package/agent-assets/task-flows/message.received.md +14 -0
  172. package/agent-assets/task-flows/routine.custom.md +38 -0
  173. package/agent-assets/task-flows/routine.evening_review.md +323 -0
  174. package/agent-assets/task-flows/routine.hourly_check.delegated.claude.md +405 -0
  175. package/agent-assets/task-flows/routine.hourly_check.delegated.codex.md +400 -0
  176. package/agent-assets/task-flows/routine.hourly_check.delegated.gemini.md +404 -0
  177. package/agent-assets/task-flows/routine.hourly_check.md +184 -0
  178. package/agent-assets/task-flows/routine.hourly_check.triage.md +93 -0
  179. package/agent-assets/task-flows/routine.monthly_review.md +250 -0
  180. package/agent-assets/task-flows/routine.morning_routine.md +300 -0
  181. package/agent-assets/task-flows/routine.morning_routine_initial.md +184 -0
  182. package/agent-assets/task-flows/routine.roadmap_refresh.md +275 -0
  183. package/agent-assets/task-flows/routine.today_refresh.md +172 -0
  184. package/agent-assets/task-flows/routine.user_profile_sweep.md +242 -0
  185. package/agent-assets/task-flows/routine.weekly_review.md +247 -0
  186. package/agent-assets/task-flows/schedule.approaching.md +124 -0
  187. package/agent-assets/task-flows/scheduled.dm.md +391 -0
  188. package/agent-assets/task-flows/scheduled.task.md +141 -0
  189. package/agent-assets/task-flows/setup.initial.md +277 -0
  190. package/agent-assets/task-flows/setup.update.md +53 -0
  191. package/agent-assets/templates/README.md +85 -0
  192. package/agent-assets/templates/_index.md +39 -0
  193. package/agent-assets/templates/_manifest.json +103 -0
  194. package/agent-assets/templates/agent/journal.md +10 -0
  195. package/agent-assets/templates/agent/profile-questions.md +74 -0
  196. package/agent-assets/templates/context-index.md +42 -0
  197. package/agent-assets/templates/dossiers/_index.md +22 -0
  198. package/agent-assets/templates/dossiers/evening.md +23 -0
  199. package/agent-assets/templates/dossiers/hourly.md +23 -0
  200. package/agent-assets/templates/dossiers/monthly.md +23 -0
  201. package/agent-assets/templates/dossiers/morning.md +23 -0
  202. package/agent-assets/templates/dossiers/roadmap.md +23 -0
  203. package/agent-assets/templates/dossiers/weekly.md +23 -0
  204. package/agent-assets/templates/projects/_active.base +14 -0
  205. package/agent-assets/templates/projects/_index.md +29 -0
  206. package/agent-assets/templates/roadmap.md +15 -0
  207. package/agent-assets/templates/routines/_index.md +20 -0
  208. package/agent-assets/templates/routines/evening.md +22 -0
  209. package/agent-assets/templates/routines/hourly.md +30 -0
  210. package/agent-assets/templates/routines/monthly.md +25 -0
  211. package/agent-assets/templates/routines/morning.md +26 -0
  212. package/agent-assets/templates/routines/weekly.md +23 -0
  213. package/agent-assets/templates/rules/_index.md +19 -0
  214. package/agent-assets/templates/rules/journal-export.md +41 -0
  215. package/agent-assets/templates/rules/journal-format.md +61 -0
  216. package/agent-assets/templates/rules/management.md +48 -0
  217. package/agent-assets/templates/rules/mcp.md +40 -0
  218. package/agent-assets/templates/rules/policies/_index.md +22 -0
  219. package/agent-assets/templates/rules/redaction.md +30 -0
  220. package/agent-assets/templates/today.md +13 -0
  221. package/agent-assets/templates/user/_index.md +16 -0
  222. package/agent-assets/templates/user/expertise.md +7 -0
  223. package/agent-assets/templates/user/goals.md +7 -0
  224. package/agent-assets/templates/user/people.md +7 -0
  225. package/agent-assets/templates/user/personal.md +7 -0
  226. package/agent-assets/templates/user/profile.md +28 -0
  227. package/agent-assets/templates/user/work.md +7 -0
  228. package/bin/aitne.mjs +1096 -0
  229. package/package.json +78 -0
  230. package/personal-agent.mjs +39 -0
  231. package/scripts/browser.mjs +99 -0
  232. package/scripts/check-redaction-coverage.mjs +109 -0
  233. package/scripts/commands/audit.mjs +309 -0
  234. package/scripts/commands/doctor.mjs +437 -0
  235. package/scripts/commands/open.mjs +40 -0
  236. package/scripts/commands/setup.mjs +21 -0
  237. package/scripts/commands/uninstall.mjs +114 -0
  238. package/scripts/commands/update.mjs +96 -0
  239. package/scripts/commands/version.mjs +62 -0
  240. package/scripts/commands.md +0 -0
  241. package/scripts/lib/sqlite-loader.mjs +49 -0
  242. package/scripts/message-discipline-digest.mjs +535 -0
  243. package/scripts/poc/google-connector-inheritance/REPORT.md +197 -0
  244. package/scripts/poc/google-connector-inheritance/claude-sdk-probe.mjs +79 -0
  245. package/scripts/remint-roadmap-ids.mjs +257 -0
  246. package/scripts/rm-paths.mjs +22 -0
  247. package/scripts/run-node.mjs +223 -0
  248. package/scripts/smoke-obsidian-api.mjs +166 -0
  249. package/scripts/start.mjs +160 -0
@@ -0,0 +1,535 @@
1
+ #!/usr/bin/env node
2
+ // MESSAGING-DISCIPLINE-PLAN.md §5.1 — message-discipline post-ship monitor.
3
+ //
4
+ // Reads notification_log + agent_actions for a given day, classifies every
5
+ // user-facing message against opener_forbidden / readback_suspected /
6
+ // clean, and posts a low-priority digest DM via /api/notify so the owner
7
+ // can audit whether the new universal-discipline rules are actually
8
+ // holding in production.
9
+ //
10
+ // Mechanism rationale (§5.1): the prompt-text exit criteria in Phases
11
+ // 0-2 confirm the universal discipline section is *written* correctly.
12
+ // They do not detect agent behavior regressions — that signal previously
13
+ // only arrived via the owner manually flagging a message. This digest
14
+ // closes the loop by surfacing every user-facing emission for the day
15
+ // with regex classification, so a regression is caught the next morning,
16
+ // not weeks later.
17
+ //
18
+ // USAGE
19
+ // -----
20
+ // # Compose + send today's digest (defaults to today)
21
+ // node scripts/message-discipline-digest.mjs --send
22
+ //
23
+ // # Compose + print without sending
24
+ // node scripts/message-discipline-digest.mjs --date 2026-04-26
25
+ //
26
+ // # Seed 7 nightly agent_schedule rows at 22:00 local for the next 7 days
27
+ // node scripts/message-discipline-digest.mjs --seed
28
+ //
29
+ // # Override defaults
30
+ // node scripts/message-discipline-digest.mjs --send --date 2026-04-25 \
31
+ // --base-url http://localhost:8321 --db-path ~/.personal-agent/data/personal_agent.db
32
+ //
33
+ // FLAGS
34
+ // --send POST the digest via /api/notify (priority `low`).
35
+ // Without this flag the digest is printed to stdout.
36
+ // --date YYYY-MM-DD The day to digest. Defaults to today (local).
37
+ // --base-url URL Daemon base URL. Default http://localhost:8321.
38
+ // --db-path PATH SQLite path. Default $PA_DATA_DIR/data/personal_agent.db
39
+ // or ~/.personal-agent/data/personal_agent.db.
40
+ // --seed Insert 7 nightly agent_schedule rows starting
41
+ // tomorrow at --time (default 22:00 local).
42
+ // --days N (with --seed) how many evenings to seed. Default 7.
43
+ // --time HH:MM (with --seed) local time of day. Default 22:00.
44
+ // --start-date YYYY-MM-DD (with --seed) first evening. Default tomorrow.
45
+ //
46
+ // CLASSIFICATION REGEXES (seed list — extend as the owner flags misses):
47
+ // opener_forbidden — leading ceremony phrases the universal section
48
+ // (notify/SKILL.md § Universal user-facing message
49
+ // discipline § No ceremony) bans. Applies to EVERY
50
+ // user-facing surface — the no-ceremony rule has no
51
+ // conversational carve-out.
52
+ // readback_suspected — `Schedule:` / `Tasks:` / `Deadlines:` /
53
+ // `Notes:` lines as leading labels — likely
54
+ // readback of the user's own data, banned by
55
+ // § No table-of-contents readback. Applied ONLY
56
+ // to PROACTIVE surfaces (POST /api/notify,
57
+ // scheduled.dm, scheduled_dm). DM replies under
58
+ // message.received.* get a conversational carve-
59
+ // out per agent-profiles/conversational.md — when
60
+ // the user explicitly asks for their own data,
61
+ // returning it plainly is legal. Running the
62
+ // readback regex over those rows would produce
63
+ // systematic false positives that erode owner
64
+ // attention to the digest.
65
+ //
66
+ // IMPLEMENTATION NOTES
67
+ // - Reads SQLite via the system `sqlite3` CLI (`-json`) so the script
68
+ // has zero npm runtime deps. macOS ships sqlite3 ≥ 3.43, which
69
+ // supports `-json`. On Linux without sqlite3 CLI installed, the
70
+ // script will exit with a clear error.
71
+ // - The plan permits no daemon code change, so the script reads the
72
+ // DB directly (read-only intent — `--seed` is the one write path,
73
+ // and it INSERTs only `agent_schedule` rows that the daemon
74
+ // scheduler already understands).
75
+ // - Posts via global `fetch` (Node ≥ 18). Project pins Node ≥ 22.
76
+
77
+ import { execFileSync } from "node:child_process";
78
+ import { existsSync } from "node:fs";
79
+ import os from "node:os";
80
+ import path from "node:path";
81
+ import process from "node:process";
82
+
83
+ const FORBIDDEN_OPENERS_RE =
84
+ /^(?:Good\s+(?:morning|evening|afternoon)|Evening\s+check-in|Morning\s+briefing|Heads-up|FYI|Quick\s+update|Summary|Done|Sent|OK|Here's|Here\sis)\b/i;
85
+ const READBACK_RE =
86
+ /^(?:Schedule|Tasks|Deadlines|Notes)\s*:/im;
87
+
88
+ function parseArgs(argv) {
89
+ const args = { send: false, seed: false };
90
+ for (let i = 0; i < argv.length; i++) {
91
+ const a = argv[i];
92
+ if (a === "--send") args.send = true;
93
+ else if (a === "--seed") args.seed = true;
94
+ else if (a === "--date") args.date = argv[++i];
95
+ else if (a === "--base-url") args.baseUrl = argv[++i];
96
+ else if (a === "--db-path") args.dbPath = argv[++i];
97
+ else if (a === "--start-date") args.startDate = argv[++i];
98
+ else if (a === "--days") args.days = Number(argv[++i]);
99
+ else if (a === "--time") args.time = argv[++i];
100
+ else if (a === "--help" || a === "-h") {
101
+ printHelp();
102
+ process.exit(0);
103
+ } else {
104
+ console.error(`Unknown flag: ${a}. Use --help for usage.`);
105
+ process.exit(2);
106
+ }
107
+ }
108
+ return args;
109
+ }
110
+
111
+ function printHelp() {
112
+ // Help text is the file header above; print the relevant portion.
113
+ console.log(
114
+ "Usage:\n" +
115
+ " node scripts/message-discipline-digest.mjs [--send] [--date YYYY-MM-DD]\n" +
116
+ " node scripts/message-discipline-digest.mjs --seed [--start-date YYYY-MM-DD]\n" +
117
+ " [--days N] [--time HH:MM]\n" +
118
+ "Run with --help for the full header in the source file.",
119
+ );
120
+ }
121
+
122
+ function resolveDbPath(override) {
123
+ if (override) return override;
124
+ const dataDir = process.env.PA_DATA_DIR || path.join(os.homedir(), ".personal-agent");
125
+ return path.join(dataDir, "data", "personal_agent.db");
126
+ }
127
+
128
+ function todayLocal() {
129
+ const d = new Date();
130
+ const yyyy = d.getFullYear();
131
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
132
+ const dd = String(d.getDate()).padStart(2, "0");
133
+ return `${yyyy}-${mm}-${dd}`;
134
+ }
135
+
136
+ function addDaysLocal(yyyymmdd, n) {
137
+ const [y, m, d] = yyyymmdd.split("-").map(Number);
138
+ const dt = new Date(y, m - 1, d);
139
+ dt.setDate(dt.getDate() + n);
140
+ const yyyy = dt.getFullYear();
141
+ const mm = String(dt.getMonth() + 1).padStart(2, "0");
142
+ const dd = String(dt.getDate()).padStart(2, "0");
143
+ return `${yyyy}-${mm}-${dd}`;
144
+ }
145
+
146
+ function localIsoAt(dateStr, hhmm) {
147
+ // Returns an ISO-8601 string with the local timezone offset for
148
+ // `<dateStr>T<hhmm>:00` interpreted in the user's local zone. The daemon
149
+ // accepts any parseable date string (POST /api/schedule does
150
+ // `new Date(time)`); attaching the offset eliminates ambiguity.
151
+ const [y, m, d] = dateStr.split("-").map(Number);
152
+ const [hh, mm] = hhmm.split(":").map(Number);
153
+ const dt = new Date(y, m - 1, d, hh, mm, 0);
154
+ const tzMin = -dt.getTimezoneOffset();
155
+ const sign = tzMin >= 0 ? "+" : "-";
156
+ const absMin = Math.abs(tzMin);
157
+ const tzh = String(Math.floor(absMin / 60)).padStart(2, "0");
158
+ const tzm = String(absMin % 60).padStart(2, "0");
159
+ const pad = (n) => String(n).padStart(2, "0");
160
+ return (
161
+ `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())}` +
162
+ `T${pad(dt.getHours())}:${pad(dt.getMinutes())}:00${sign}${tzh}:${tzm}`
163
+ );
164
+ }
165
+
166
+ function querySqlite(dbPath, sql) {
167
+ if (!existsSync(dbPath)) {
168
+ throw new Error(`SQLite DB not found at ${dbPath}`);
169
+ }
170
+ let out;
171
+ try {
172
+ out = execFileSync("sqlite3", ["-json", dbPath, sql], {
173
+ encoding: "utf-8",
174
+ maxBuffer: 32 * 1024 * 1024,
175
+ });
176
+ } catch (err) {
177
+ throw new Error(
178
+ `sqlite3 invocation failed (${err.message}). The script needs the system sqlite3 CLI (macOS ships it).`,
179
+ );
180
+ }
181
+ const trimmed = out.trim();
182
+ if (!trimmed) return [];
183
+ return JSON.parse(trimmed);
184
+ }
185
+
186
+ /**
187
+ * Classify a user-facing message against the universal-discipline regexes.
188
+ *
189
+ * The `surface` argument controls which rules apply. Only "proactive"
190
+ * surfaces (the agent decided to message the user without being asked)
191
+ * are subject to the readback regex; "reactive" surfaces (DM replies)
192
+ * are exempt because conversational.md explicitly carves out
193
+ * user-requested data readback as legal — the universal section's
194
+ * "no readback" rule's load-bearing rationale ("the user already has
195
+ * it") doesn't hold when the user just asked for it. The opener regex
196
+ * always applies; the no-ceremony rule has no conversational carve-out.
197
+ *
198
+ * @param {string} text
199
+ * @param {"proactive"|"reactive"} surface
200
+ */
201
+ function classify(text, surface = "proactive") {
202
+ if (!text) return "clean";
203
+ const stripped = text.trim();
204
+ // Strip a single leading markdown bullet / heading marker — a digest
205
+ // bullet like "- Good morning" still violates the no-ceremony rule.
206
+ const noBullet = stripped.replace(/^(?:[-*]\s+|#+\s+)/, "");
207
+ if (FORBIDDEN_OPENERS_RE.test(noBullet)) return "opener_forbidden";
208
+ if (surface === "proactive" && READBACK_RE.test(noBullet)) {
209
+ return "readback_suspected";
210
+ }
211
+ return "clean";
212
+ }
213
+
214
+ function truncate(s, n) {
215
+ if (!s) return "";
216
+ return s.length <= n ? s : `${s.slice(0, n - 1)}…`;
217
+ }
218
+
219
+ function loadDayData(dbPath, date) {
220
+ // notification_log carries every user-facing emission. The
221
+ // notification_type column distinguishes the source surface:
222
+ //
223
+ // notification_type='agent' — POST /api/notify (every call,
224
+ // set in packages/daemon/src/index.ts
225
+ // sendNotification + agent.ts:357
226
+ // /notify route)
227
+ // notification_type='scheduled.dm' — LLM-composed Morning briefing
228
+ // (and any future scheduled.dm
229
+ // sub-flow) — final assistant
230
+ // text written by
231
+ // notification-manager.send,
232
+ // which logs notification_type =
233
+ // event.type literally
234
+ // notification_type='scheduled_dm' — pre-composed direct DM
235
+ // (task_type='dm') from
236
+ // scheduler.ts:handleDirectDm,
237
+ // no LLM involved
238
+ // notification_type='message.received.*' — DM reply to an inbound
239
+ // user DM (final-text,
240
+ // notification-manager auto-
241
+ // send via dispatcher)
242
+ // notification_type='schedule.approaching' / 'calendar.*' / etc. —
243
+ // other event-typed auto-sends, currently unused for user-facing
244
+ // text (the schedule.approaching prompt routes through /api/notify,
245
+ // so its rows land under 'agent', not under the event type).
246
+ //
247
+ // The buckets below mirror the §5.1 surfaces — /api/notify, scheduled
248
+ // DMs (LLM and direct), DM replies — so the regex classifier runs over
249
+ // every user-visible row produced that day.
250
+ const notifications = querySqlite(
251
+ dbPath,
252
+ `SELECT id, notification_type, priority, content_summary, status, platform, created_at
253
+ FROM notification_log
254
+ WHERE date(created_at, 'localtime') = ${sqlString(date)}
255
+ ORDER BY created_at ASC`,
256
+ );
257
+ // schedule.approaching event firings: count agent_actions rows with
258
+ // action_type='schedule.approaching'. We deliberately do NOT split into
259
+ // notified/skipped here — there is no clean join from agent_actions to
260
+ // notification_log (notification_log has no event_id / correlationId
261
+ // column, and /api/notify rewrites notification_type to 'agent', so a
262
+ // filter on 'schedule.approaching' would always be 0). Reporting a fake
263
+ // split produced misleading "100% skipped" digests; an honest total
264
+ // plus the global notify-row classifier covers regression detection.
265
+ const approachingActions = querySqlite(
266
+ dbPath,
267
+ `SELECT id, started_at, completed_at, result
268
+ FROM agent_actions
269
+ WHERE action_type = 'schedule.approaching'
270
+ AND date(started_at, 'localtime') = ${sqlString(date)}
271
+ ORDER BY started_at ASC`,
272
+ );
273
+ return { notifications, approachingActions };
274
+ }
275
+
276
+ function sqlString(s) {
277
+ // Inline literal string — `s` is always a controlled value (date in
278
+ // YYYY-MM-DD form from local arithmetic). Defense-in-depth: reject
279
+ // anything that doesn't match the date pattern.
280
+ if (!/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(s)) {
281
+ throw new Error(`refusing to inline non-date value into SQL: ${s}`);
282
+ }
283
+ return `'${s}'`;
284
+ }
285
+
286
+ function buildDigest(date, { notifications, approachingActions }) {
287
+ // Bucket rows by surface so the regex classifier runs over every
288
+ // user-visible emission. See loadDayData header comment for the
289
+ // notification_type taxonomy.
290
+ const notifyApi = notifications.filter(
291
+ (n) => n.notification_type === "agent",
292
+ );
293
+ // Both spellings cover the scheduled-DM surface: 'scheduled.dm' is
294
+ // notification-manager logging event.type literally for the LLM-composed
295
+ // path (Morning briefing); 'scheduled_dm' is scheduler.handleDirectDm
296
+ // for pre-composed direct DMs.
297
+ const scheduledDm = notifications.filter(
298
+ (n) =>
299
+ n.notification_type === "scheduled.dm" ||
300
+ n.notification_type === "scheduled_dm",
301
+ );
302
+ const dmReplies = notifications.filter((n) =>
303
+ typeof n.notification_type === "string" &&
304
+ n.notification_type.startsWith("message.received"),
305
+ );
306
+ const otherNotifications = notifications.filter(
307
+ (n) =>
308
+ n.notification_type !== "agent" &&
309
+ n.notification_type !== "scheduled.dm" &&
310
+ n.notification_type !== "scheduled_dm" &&
311
+ !(
312
+ typeof n.notification_type === "string" &&
313
+ n.notification_type.startsWith("message.received")
314
+ ),
315
+ );
316
+
317
+ const totalApproaching = approachingActions.length;
318
+
319
+ const lines = [];
320
+ lines.push(`message-discipline digest — ${date}`);
321
+ lines.push("");
322
+
323
+ lines.push(`POST /api/notify calls: ${notifyApi.length}`);
324
+ for (const n of notifyApi) {
325
+ lines.push(formatNotifyLine(n));
326
+ }
327
+ if (notifyApi.length === 0) lines.push("- (none)");
328
+ lines.push("");
329
+
330
+ lines.push(`scheduled.dm final-text DMs: ${scheduledDm.length}`);
331
+ for (const n of scheduledDm) {
332
+ lines.push(formatNotifyLine(n));
333
+ }
334
+ if (scheduledDm.length === 0) lines.push("- (none)");
335
+ lines.push("");
336
+
337
+ lines.push(`message.received.* DM replies: ${dmReplies.length}`);
338
+ for (const n of dmReplies) {
339
+ lines.push(formatNotifyLine(n));
340
+ }
341
+ if (dmReplies.length === 0) lines.push("- (none)");
342
+ lines.push("");
343
+
344
+ if (otherNotifications.length > 0) {
345
+ lines.push(`other notification_log rows: ${otherNotifications.length}`);
346
+ for (const n of otherNotifications) {
347
+ lines.push(formatNotifyLine(n));
348
+ }
349
+ lines.push("");
350
+ }
351
+
352
+ // Honest reporting: agent_actions has no clean join to notification_log
353
+ // (no shared event_id / correlationId column), and /api/notify writes
354
+ // notification_type='agent', so we cannot tell which approaching events
355
+ // produced a notification without timestamp-window heuristics. Surface
356
+ // the total here; the regex classification above already covers any
357
+ // notify rows the firing session emitted.
358
+ lines.push(`schedule.approaching events: total=${totalApproaching}`);
359
+ lines.push(
360
+ " (notified vs skipped split not derivable — notification_log has no event_id; trigger attribution a/d also pending instrumentation)",
361
+ );
362
+ lines.push("");
363
+
364
+ // Roll-up of flags across every user-visible surface. This is the line
365
+ // the owner glances at first; if it's all-zero for 7 days the digest is
366
+ // retired (§5.1 Action rules). Each row is classified at its own
367
+ // surface — readback_suspected is suppressed for DM replies because
368
+ // conversational.md carves out user-requested data readback as legal.
369
+ const allMessages = [
370
+ ...notifyApi,
371
+ ...scheduledDm,
372
+ ...dmReplies,
373
+ ...otherNotifications,
374
+ ];
375
+ const flagCounts = { opener_forbidden: 0, readback_suspected: 0, clean: 0 };
376
+ for (const m of allMessages) {
377
+ flagCounts[classify(m.content_summary, surfaceFor(m))]++;
378
+ }
379
+ lines.push(
380
+ `flags: opener_forbidden=${flagCounts.opener_forbidden} readback_suspected=${flagCounts.readback_suspected} clean=${flagCounts.clean}`,
381
+ );
382
+
383
+ return lines.join("\n");
384
+ }
385
+
386
+ /**
387
+ * Decide whether a notification_log row counts as a "proactive" surface
388
+ * for classifier purposes. DM replies (message.received.*) are reactive;
389
+ * everything else (agent / scheduled.dm / scheduled_dm / etc.) is the
390
+ * agent reaching out unprompted.
391
+ */
392
+ function surfaceFor(n) {
393
+ if (
394
+ typeof n.notification_type === "string" &&
395
+ n.notification_type.startsWith("message.received")
396
+ ) {
397
+ return "reactive";
398
+ }
399
+ return "proactive";
400
+ }
401
+
402
+ function formatNotifyLine(n) {
403
+ const cls = classify(n.content_summary, surfaceFor(n));
404
+ const tag = cls === "clean" ? "✓" : `⚠ ${cls}`;
405
+ const summary = truncate((n.content_summary ?? "").replace(/\s+/g, " "), 120);
406
+ const type = n.notification_type ?? "(none)";
407
+ const priority = n.priority ?? "(none)";
408
+ return `- [${priority}] ${type} ${tag} :: ${summary}`;
409
+ }
410
+
411
+ async function postDigest(baseUrl, message) {
412
+ const url = `${baseUrl.replace(/\/+$/, "")}/api/notify`;
413
+ const res = await fetch(url, {
414
+ method: "POST",
415
+ headers: { "Content-Type": "application/json" },
416
+ body: JSON.stringify({ message, priority: "low" }),
417
+ });
418
+ const text = await res.text();
419
+ if (!res.ok) {
420
+ throw new Error(`POST ${url} → ${res.status}: ${text}`);
421
+ }
422
+ return text;
423
+ }
424
+
425
+ async function runDigest(args) {
426
+ const date = args.date ?? todayLocal();
427
+ if (!/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(date)) {
428
+ console.error(`Invalid --date: ${date}. Expected YYYY-MM-DD.`);
429
+ process.exit(2);
430
+ }
431
+ const dbPath = resolveDbPath(args.dbPath);
432
+ const baseUrl = args.baseUrl ?? "http://localhost:8321";
433
+
434
+ const data = loadDayData(dbPath, date);
435
+ const digest = buildDigest(date, data);
436
+
437
+ if (args.send) {
438
+ const reply = await postDigest(baseUrl, digest);
439
+ console.log(`Digest sent. Daemon response: ${reply}`);
440
+ } else {
441
+ console.log(digest);
442
+ console.log(
443
+ `\n(Use --send to POST this via ${baseUrl}/api/notify at priority \`low\`.)`,
444
+ );
445
+ }
446
+ }
447
+
448
+ async function runSeed(args) {
449
+ const days = args.days ?? 7;
450
+ if (!Number.isInteger(days) || days < 1 || days > 30) {
451
+ console.error(`Invalid --days: ${days}. Expected 1..30.`);
452
+ process.exit(2);
453
+ }
454
+ const time = args.time ?? "22:00";
455
+ if (!/^[0-2][0-9]:[0-5][0-9]$/.test(time)) {
456
+ console.error(`Invalid --time: ${time}. Expected HH:MM.`);
457
+ process.exit(2);
458
+ }
459
+ const start = args.startDate ?? addDaysLocal(todayLocal(), 1);
460
+ if (!/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(start)) {
461
+ console.error(`Invalid --start-date: ${start}. Expected YYYY-MM-DD.`);
462
+ process.exit(2);
463
+ }
464
+
465
+ const baseUrl = (args.baseUrl ?? "http://localhost:8321").replace(/\/+$/, "");
466
+ const url = `${baseUrl}/api/schedule`;
467
+
468
+ // Description is plumbed straight into {event_data[task]} of the
469
+ // generated scheduled.task event. Encode the date and the explicit
470
+ // invocation so the firing LLM session has zero judgment to exercise:
471
+ // it sees a deterministic Bash command + a "do not duplicate
472
+ // delivery" instruction and exits.
473
+ const seeded = [];
474
+ for (let i = 0; i < days; i++) {
475
+ const date = addDaysLocal(start, i);
476
+ const iso = localIsoAt(date, time);
477
+ const description =
478
+ `Run message-discipline digest for ${date}: invoke ` +
479
+ `\`node scripts/message-discipline-digest.mjs --date ${date} --send\` ` +
480
+ `via Bash from the repo root. The script itself POSTs the digest via ` +
481
+ `/api/notify at priority \`low\`; you must NOT compose or send a ` +
482
+ `duplicate notification. Log one line to today.md ## Agent Log on ` +
483
+ `completion (e.g. \`HH:MM [digest] message-discipline digest sent\`).`;
484
+ const body = {
485
+ time: iso,
486
+ taskType: "agent_task",
487
+ description,
488
+ };
489
+ const res = await fetch(url, {
490
+ method: "POST",
491
+ headers: { "Content-Type": "application/json" },
492
+ body: JSON.stringify(body),
493
+ });
494
+ const replyText = await res.text();
495
+ if (!res.ok) {
496
+ console.error(`Failed to seed ${date} @ ${time}: ${res.status} ${replyText}`);
497
+ process.exit(1);
498
+ }
499
+ seeded.push({ date, iso, response: replyText });
500
+ }
501
+
502
+ console.log(
503
+ `Seeded ${seeded.length} message-discipline digest agent_schedule rows:`,
504
+ );
505
+ for (const s of seeded) {
506
+ console.log(` ${s.date} ${s.iso} ${s.response}`);
507
+ }
508
+ console.log(
509
+ "\nThe firing LLM session will need to invoke `node` from Bash. The default\n" +
510
+ "Claude allowed-tools list does NOT include Bash(node *), so the user will\n" +
511
+ "be prompted to approve the command pattern on first fire. Approve once,\n" +
512
+ "or pre-add `Bash(node *)` to the dashboard's allowed-tools override.",
513
+ );
514
+ console.log(
515
+ "\nAlternative without LLM round-trips: schedule the script directly via\n" +
516
+ "launchd / cron with the same daily cadence. Example launchd plist on macOS:\n" +
517
+ " ProgramArguments = (\"/usr/local/bin/node\", \"<repo>/scripts/message-discipline-digest.mjs\", \"--send\")\n" +
518
+ " StartCalendarInterval = { Hour = 22; Minute = 0; }\n" +
519
+ "Run for 7 days, then `launchctl unload` to retire.",
520
+ );
521
+ }
522
+
523
+ async function main() {
524
+ const args = parseArgs(process.argv.slice(2));
525
+ if (args.seed) {
526
+ await runSeed(args);
527
+ } else {
528
+ await runDigest(args);
529
+ }
530
+ }
531
+
532
+ main().catch((err) => {
533
+ console.error(err.stack ?? err.message ?? String(err));
534
+ process.exit(1);
535
+ });