feed-the-machine 1.1.0 → 1.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 (92) hide show
  1. package/bin/generate-manifest.mjs +253 -0
  2. package/bin/install.mjs +372 -26
  3. package/docs/INBOX.md +233 -0
  4. package/ftm/SKILL.md +34 -0
  5. package/ftm-audit/SKILL.md +69 -0
  6. package/ftm-brainstorm/SKILL.md +51 -0
  7. package/ftm-browse/SKILL.md +39 -0
  8. package/ftm-capture/SKILL.md +370 -0
  9. package/ftm-capture.yml +4 -0
  10. package/ftm-codex-gate/SKILL.md +59 -0
  11. package/ftm-config/SKILL.md +35 -0
  12. package/ftm-council/SKILL.md +56 -0
  13. package/ftm-dashboard/SKILL.md +34 -0
  14. package/ftm-debug/SKILL.md +84 -0
  15. package/ftm-diagram/SKILL.md +44 -0
  16. package/ftm-executor/SKILL.md +97 -0
  17. package/ftm-git/SKILL.md +60 -0
  18. package/ftm-inbox/backend/__init__.py +0 -0
  19. package/ftm-inbox/backend/adapters/__init__.py +0 -0
  20. package/ftm-inbox/backend/adapters/_retry.py +64 -0
  21. package/ftm-inbox/backend/adapters/base.py +230 -0
  22. package/ftm-inbox/backend/adapters/freshservice.py +104 -0
  23. package/ftm-inbox/backend/adapters/gmail.py +125 -0
  24. package/ftm-inbox/backend/adapters/jira.py +136 -0
  25. package/ftm-inbox/backend/adapters/registry.py +192 -0
  26. package/ftm-inbox/backend/adapters/slack.py +110 -0
  27. package/ftm-inbox/backend/db/__init__.py +0 -0
  28. package/ftm-inbox/backend/db/connection.py +54 -0
  29. package/ftm-inbox/backend/db/schema.py +78 -0
  30. package/ftm-inbox/backend/executor/__init__.py +7 -0
  31. package/ftm-inbox/backend/executor/engine.py +149 -0
  32. package/ftm-inbox/backend/executor/step_runner.py +98 -0
  33. package/ftm-inbox/backend/main.py +103 -0
  34. package/ftm-inbox/backend/models/__init__.py +1 -0
  35. package/ftm-inbox/backend/models/unified_task.py +36 -0
  36. package/ftm-inbox/backend/planner/__init__.py +6 -0
  37. package/ftm-inbox/backend/planner/generator.py +127 -0
  38. package/ftm-inbox/backend/planner/schema.py +34 -0
  39. package/ftm-inbox/backend/requirements.txt +5 -0
  40. package/ftm-inbox/backend/routes/__init__.py +0 -0
  41. package/ftm-inbox/backend/routes/execute.py +186 -0
  42. package/ftm-inbox/backend/routes/health.py +52 -0
  43. package/ftm-inbox/backend/routes/inbox.py +68 -0
  44. package/ftm-inbox/backend/routes/plan.py +271 -0
  45. package/ftm-inbox/bin/launchagent.mjs +91 -0
  46. package/ftm-inbox/bin/setup.mjs +188 -0
  47. package/ftm-inbox/bin/start.sh +10 -0
  48. package/ftm-inbox/bin/status.sh +17 -0
  49. package/ftm-inbox/bin/stop.sh +8 -0
  50. package/ftm-inbox/config.example.yml +55 -0
  51. package/ftm-inbox/package-lock.json +2898 -0
  52. package/ftm-inbox/package.json +26 -0
  53. package/ftm-inbox/postcss.config.js +6 -0
  54. package/ftm-inbox/src/app.css +199 -0
  55. package/ftm-inbox/src/app.html +18 -0
  56. package/ftm-inbox/src/lib/api.ts +166 -0
  57. package/ftm-inbox/src/lib/components/ExecutionLog.svelte +81 -0
  58. package/ftm-inbox/src/lib/components/InboxFeed.svelte +143 -0
  59. package/ftm-inbox/src/lib/components/PlanStep.svelte +271 -0
  60. package/ftm-inbox/src/lib/components/PlanView.svelte +206 -0
  61. package/ftm-inbox/src/lib/components/StreamPanel.svelte +99 -0
  62. package/ftm-inbox/src/lib/components/TaskCard.svelte +190 -0
  63. package/ftm-inbox/src/lib/components/ui/EmptyState.svelte +63 -0
  64. package/ftm-inbox/src/lib/components/ui/KawaiiCard.svelte +86 -0
  65. package/ftm-inbox/src/lib/components/ui/PillButton.svelte +106 -0
  66. package/ftm-inbox/src/lib/components/ui/StatusBadge.svelte +67 -0
  67. package/ftm-inbox/src/lib/components/ui/StreamDrawer.svelte +149 -0
  68. package/ftm-inbox/src/lib/components/ui/ThemeToggle.svelte +80 -0
  69. package/ftm-inbox/src/lib/theme.ts +47 -0
  70. package/ftm-inbox/src/routes/+layout.svelte +76 -0
  71. package/ftm-inbox/src/routes/+page.svelte +401 -0
  72. package/ftm-inbox/static/favicon.png +0 -0
  73. package/ftm-inbox/svelte.config.js +12 -0
  74. package/ftm-inbox/tailwind.config.ts +63 -0
  75. package/ftm-inbox/tsconfig.json +13 -0
  76. package/ftm-inbox/vite.config.ts +6 -0
  77. package/ftm-intent/SKILL.md +44 -0
  78. package/ftm-manifest.json +3794 -0
  79. package/ftm-map/SKILL.md +50 -0
  80. package/ftm-mind/SKILL.md +173 -66
  81. package/ftm-pause/SKILL.md +43 -0
  82. package/ftm-researcher/SKILL.md +55 -0
  83. package/ftm-resume/SKILL.md +47 -0
  84. package/ftm-retro/SKILL.md +54 -0
  85. package/ftm-routine/SKILL.md +36 -0
  86. package/ftm-state/blackboard/capabilities.json +5 -0
  87. package/ftm-state/blackboard/capabilities.schema.json +27 -0
  88. package/ftm-upgrade/SKILL.md +41 -0
  89. package/hooks/ftm-blackboard-enforcer.sh +28 -27
  90. package/hooks/ftm-plan-gate.sh +21 -25
  91. package/install.sh +238 -111
  92. package/package.json +6 -2
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "FTM Capabilities Snapshot",
4
+ "type": "object",
5
+ "required": ["discovered_at", "expires_at", "capabilities"],
6
+ "properties": {
7
+ "discovered_at": { "type": "string", "format": "date-time" },
8
+ "expires_at": { "type": "string", "format": "date-time" },
9
+ "capabilities": {
10
+ "type": "array",
11
+ "items": {
12
+ "type": "object",
13
+ "required": ["name", "type", "verified", "confidence"],
14
+ "properties": {
15
+ "name": { "type": "string" },
16
+ "type": { "type": "string", "enum": ["mcp", "cli", "api", "env", "browser"] },
17
+ "verified": { "type": "boolean" },
18
+ "last_verified_at": { "type": "string", "format": "date-time" },
19
+ "operations_verified": { "type": "array", "items": { "type": "string" } },
20
+ "path": { "type": "string" },
21
+ "version": { "type": "string" },
22
+ "confidence": { "type": "string", "enum": ["verified", "known", "inferred", "unavailable"] }
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
@@ -151,3 +151,44 @@ Map `CHECK_FAILED <reason>` codes to user-facing messages:
151
151
  **Cache location**: `~/.cache/ftm-brain/version-check`
152
152
  **Version file**: `~/.claude/skills/ftm-version.txt`
153
153
  **Repo**: `kkudumu/ftm-brain`
154
+
155
+ ## Requirements
156
+
157
+ - tool: `gh` | required | GitHub CLI for querying releases from kkudumu/ftm-brain
158
+ - reference: `~/.claude/skills/ftm-upgrade/scripts/check-version.sh` | required | version check and cache script
159
+ - reference: `~/.claude/skills/ftm-upgrade/scripts/upgrade.sh` | required | download and install latest release script
160
+ - reference: `~/.claude/skills/ftm-version.txt` | optional | locally installed version number
161
+
162
+ ## Risk
163
+
164
+ - level: high_write
165
+ - scope: downloads and overwrites skill files in ~/.claude/skills/ on upgrade; changes affect all ftm skill behavior going forward; irreversible without restoring previous version from backup or git
166
+ - rollback: restore from ~/.claude/skills/ backup if one was made before upgrade; or reinstall specific version by downloading an older release tarball
167
+
168
+ ## Approval Gates
169
+
170
+ - trigger: UPGRADE_AVAILABLE detected | action: show current and latest version with changelog URL, wait for explicit "yes" confirmation before running upgrade.sh
171
+ - trigger: version check during preamble (passive notice pattern) | action: show one-line notice only, do NOT ask for confirmation or interrupt workflow
172
+ - complexity_routing: micro → auto | small → auto | medium → auto | large → auto | xl → auto
173
+
174
+ ## Fallbacks
175
+
176
+ - condition: gh not installed | action: report "GitHub CLI not installed" with brew install gh instructions
177
+ - condition: no internet connection | action: report "Cannot reach GitHub. Check internet connection."
178
+ - condition: kkudumu/ftm-brain repo not found | action: report repo not found, suggest verifying access
179
+ - condition: no releases found | action: report "No releases found yet. Check back later."
180
+ - condition: upgrade.sh exits non-zero | action: report failure output, suggest running script manually
181
+
182
+ ## Capabilities
183
+
184
+ - cli: `gh` | required | GitHub CLI for release queries and download
185
+ - cli: `bash` | required | for running check-version.sh and upgrade.sh
186
+
187
+ ## Event Payloads
188
+
189
+ ### task_completed
190
+ - skill: string — "ftm-upgrade"
191
+ - action: string — "check" | "upgrade" | "already_up_to_date"
192
+ - current_version: string | null — version before upgrade
193
+ - new_version: string | null — version after upgrade (null if no upgrade)
194
+ - status: string — "success" | "failed" | "up_to_date" | "check_failed"
@@ -1,10 +1,13 @@
1
1
  #!/usr/bin/env bash
2
2
  # ftm-blackboard-enforcer.sh
3
- # Stop hook that checks if meaningful work was done but no blackboard
4
- # experience was recorded. If so, blocks the stop and tells Claude
5
- # to write the experience first.
3
+ # Stop hook that nudges Claude to record an experience if meaningful work
4
+ # was done but no blackboard entry was written.
6
5
  #
7
- # "Meaningful work" = 3+ tool uses detected by the edit counter,
6
+ # Uses additionalContext (not "decision: block") so Claude can still act on
7
+ # the reminder. A blocking stop creates a deadlock — Claude can't write files
8
+ # after the user ends the conversation.
9
+ #
10
+ # "Meaningful work" = 3+ edits tracked by the edit counter,
8
11
  # or ftm skills were invoked (checked via context.json).
9
12
  #
10
13
  # Hook: Stop
@@ -13,29 +16,23 @@ set -euo pipefail
13
16
 
14
17
  INPUT=$(cat)
15
18
 
16
- # Prevent infinite loop — if this hook already fired, let Claude stop
17
- STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
18
- if [[ "$STOP_HOOK_ACTIVE" == "true" ]]; then
19
- exit 0
20
- fi
21
-
22
19
  STATE_DIR="$HOME/.claude/ftm-state"
23
20
  BB_DIR="$STATE_DIR/blackboard"
24
21
  EDIT_COUNTER="$STATE_DIR/.edit-count"
25
22
  CONTEXT_FILE="$BB_DIR/context.json"
26
23
  EXPERIENCES_DIR="$BB_DIR/experiences"
27
- EXPERIENCE_INDEX="$EXPERIENCES_DIR/index.json"
28
-
29
- CURRENT_SESSION="${CLAUDE_SESSION_ID:-unknown}"
30
24
 
31
25
  # Check 1: Were there meaningful edits this session?
26
+ # Edit counter contains just a number now (no session ID).
27
+ # If the counter file is recent (< 4 hours) and >= 3, count as meaningful.
32
28
  HAD_EDITS=false
33
29
  if [[ -f "$EDIT_COUNTER" ]]; then
34
- STORED=$(cat "$EDIT_COUNTER" 2>/dev/null || echo "0:unknown")
35
- STORED_SESSION=$(echo "$STORED" | cut -d: -f2)
36
- STORED_COUNT=$(echo "$STORED" | cut -d: -f1)
37
- if [[ "$STORED_SESSION" == "$CURRENT_SESSION" && "$STORED_COUNT" -ge 3 ]]; then
38
- HAD_EDITS=true
30
+ COUNTER_AGE=$(( $(date +%s) - $(stat -c %Y "$EDIT_COUNTER" 2>/dev/null || stat -f %m "$EDIT_COUNTER" 2>/dev/null || echo "0") ))
31
+ if [[ "$COUNTER_AGE" -lt 14400 ]]; then
32
+ STORED_COUNT=$(cat "$EDIT_COUNTER" 2>/dev/null || echo "0")
33
+ if [[ "$STORED_COUNT" -ge 3 ]]; then
34
+ HAD_EDITS=true
35
+ fi
39
36
  fi
40
37
  fi
41
38
 
@@ -48,8 +45,10 @@ if [[ -f "$CONTEXT_FILE" ]]; then
48
45
  fi
49
46
  fi
50
47
 
51
- # If no meaningful work detected, allow stop
48
+ # If no meaningful work detected, allow stop quietly
52
49
  if [[ "$HAD_EDITS" == "false" && "$HAD_SKILLS" == "false" ]]; then
50
+ # Clean up session markers
51
+ rm -f "$EDIT_COUNTER" "$STATE_DIR/.plan-presented" 2>/dev/null
53
52
  exit 0
54
53
  fi
55
54
 
@@ -58,19 +57,17 @@ TODAY=$(date +%Y-%m-%d)
58
57
  HAS_EXPERIENCE=false
59
58
 
60
59
  if [[ -d "$EXPERIENCES_DIR" ]]; then
61
- # Check for experience files created today
62
60
  TODAY_EXPERIENCE=$(find "$EXPERIENCES_DIR" -name "${TODAY}*" -type f 2>/dev/null | head -1)
63
61
  if [[ -n "$TODAY_EXPERIENCE" ]]; then
64
62
  HAS_EXPERIENCE=true
65
63
  fi
66
64
  fi
67
65
 
68
- # Also check if context.json was updated this session (recent_decisions not empty)
66
+ # Also check if context.json was updated today (recent_decisions not empty)
69
67
  if [[ -f "$CONTEXT_FILE" ]]; then
70
68
  DECISIONS_COUNT=$(jq -r '.recent_decisions | length' "$CONTEXT_FILE" 2>/dev/null || echo "0")
71
69
  LAST_UPDATED=$(jq -r '.session_metadata.last_updated // ""' "$CONTEXT_FILE" 2>/dev/null || echo "")
72
70
  if [[ "$DECISIONS_COUNT" -gt 0 && -n "$LAST_UPDATED" ]]; then
73
- # Check if last_updated is from today
74
71
  if [[ "$LAST_UPDATED" == *"$TODAY"* ]]; then
75
72
  HAS_EXPERIENCE=true
76
73
  fi
@@ -78,17 +75,21 @@ if [[ -f "$CONTEXT_FILE" ]]; then
78
75
  fi
79
76
 
80
77
  if [[ "$HAS_EXPERIENCE" == "true" ]]; then
81
- # Blackboard was written, allow stop
82
- # Clean up session markers
78
+ # Blackboard was written, clean up and allow stop
83
79
  rm -f "$EDIT_COUNTER" "$STATE_DIR/.plan-presented" 2>/dev/null
84
80
  exit 0
85
81
  fi
86
82
 
87
- # Work was done but no blackboard write — block the stop
83
+ # Work was done but no blackboard write — nudge (don't block)
88
84
  cat <<'JSON'
89
85
  {
90
- "decision": "block",
91
- "reason": "[ftm-blackboard-enforcer] You did meaningful work this session (3+ edits or ftm skills used) but did not record an experience to the blackboard. Before stopping, you MUST: (1) Update ~/.claude/ftm-state/blackboard/context.json with current_task status and recent_decisions. (2) Write an experience file to ~/.claude/ftm-state/blackboard/experiences/ with task_type, tags, outcome, lessons, files_touched, stakeholders, and decisions_made. (3) Update ~/.claude/ftm-state/blackboard/experiences/index.json with the new entry. This is how ftm learns — skipping it means the next session starts from zero."
86
+ "hookSpecificOutput": {
87
+ "hookEventName": "Stop",
88
+ "additionalContext": "[ftm-blackboard-enforcer] You did meaningful work this session but did not record an experience to the blackboard. Before finishing, please: (1) Update ~/.claude/ftm-state/blackboard/context.json with current_task status and recent_decisions. (2) Write an experience file to ~/.claude/ftm-state/blackboard/experiences/ with task_type, tags, outcome, and lessons. (3) Update ~/.claude/ftm-state/blackboard/experiences/index.json with the new entry. This is how ftm learns — skipping it means the next session starts from zero."
89
+ }
92
90
  }
93
91
  JSON
92
+
93
+ # Clean up session markers regardless — don't let stale state carry over
94
+ rm -f "$EDIT_COUNTER" "$STATE_DIR/.plan-presented" 2>/dev/null
94
95
  exit 0
@@ -2,14 +2,13 @@
2
2
  # ftm-plan-gate.sh
3
3
  # PreToolUse hook for Edit/Write tools.
4
4
  #
5
- # Checks if a plan has been presented and approved for this session before
6
- # allowing code edits. If no plan marker exists and the session involves
7
- # a medium+ task (detected by ftm-state), injects additionalContext
5
+ # Checks if a plan has been presented this session before allowing code edits.
6
+ # If no plan marker exists and the edit count is climbing, injects warnings
8
7
  # telling Claude to stop and present a plan first.
9
8
  #
10
- # The marker file is created by Claude when it presents a plan — we check
11
- # for it here. If the marker doesn't exist but edits are happening, it
12
- # means Claude skipped the planning step.
9
+ # The marker file (~/.claude/ftm-state/.plan-presented) is created by Claude
10
+ # when it presents a plan. Any non-empty content counts as "plan presented".
11
+ # The file is cleaned up by the blackboard enforcer at session end.
13
12
  #
14
13
  # Hook: PreToolUse (matcher: Edit|Write)
15
14
 
@@ -25,9 +24,7 @@ fi
25
24
 
26
25
  STATE_DIR="$HOME/.claude/ftm-state"
27
26
  PLAN_MARKER="$STATE_DIR/.plan-presented"
28
- SESSION_MARKER="$STATE_DIR/.session-id"
29
27
  EDIT_COUNTER="$STATE_DIR/.edit-count"
30
- SKILL_FILES_DIR="$HOME/.claude/skills"
31
28
 
32
29
  # Get the file being edited
33
30
  FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
@@ -47,37 +44,36 @@ if [[ "$FILE_PATH" == *".claude/skills/"* ]] || \
47
44
  exit 0
48
45
  fi
49
46
 
50
- # If plan marker exists and matches current session, allow
51
- CURRENT_SESSION="${CLAUDE_SESSION_ID:-unknown}"
52
- if [[ -f "$PLAN_MARKER" ]]; then
53
- MARKER_SESSION=$(cat "$PLAN_MARKER" 2>/dev/null || echo "")
54
- if [[ "$MARKER_SESSION" == "$CURRENT_SESSION" ]]; then
55
- exit 0 # Plan was presented this session, allow edits
47
+ # If plan marker exists (any content), allow edits
48
+ if [[ -f "$PLAN_MARKER" ]] && [[ -s "$PLAN_MARKER" ]]; then
49
+ exit 0
50
+ fi
51
+
52
+ # Reset edit counter if it's stale (older than 4 hours = likely a new session)
53
+ if [[ -f "$EDIT_COUNTER" ]]; then
54
+ COUNTER_AGE=$(( $(date +%s) - $(stat -c %Y "$EDIT_COUNTER" 2>/dev/null || echo "0") ))
55
+ if [[ "$COUNTER_AGE" -gt 14400 ]]; then
56
+ rm -f "$EDIT_COUNTER"
56
57
  fi
57
58
  fi
58
59
 
59
- # Count edits this session (without a plan marker)
60
+ # Count edits without a plan marker
60
61
  EDIT_COUNT=0
61
62
  if [[ -f "$EDIT_COUNTER" ]]; then
62
- STORED=$(cat "$EDIT_COUNTER" 2>/dev/null || echo "0:unknown")
63
- STORED_SESSION=$(echo "$STORED" | cut -d: -f2)
64
- if [[ "$STORED_SESSION" == "$CURRENT_SESSION" ]]; then
65
- EDIT_COUNT=$(echo "$STORED" | cut -d: -f1)
66
- fi
63
+ EDIT_COUNT=$(cat "$EDIT_COUNTER" 2>/dev/null || echo "0")
67
64
  fi
68
65
 
69
66
  EDIT_COUNT=$((EDIT_COUNT + 1))
70
- echo "${EDIT_COUNT}:${CURRENT_SESSION}" > "$EDIT_COUNTER"
67
+ echo "$EDIT_COUNT" > "$EDIT_COUNTER"
71
68
 
72
- # First 2 edits get a warning injected as context (don't block — could be micro tasks)
69
+ # First 2 edits get a soft reminder (don't block — could be micro tasks)
73
70
  # After 3+ edits without a plan marker, escalate the warning
74
71
  if [[ $EDIT_COUNT -le 2 ]]; then
75
- # Soft reminder — inject context but allow
76
72
  cat <<'JSON'
77
73
  {
78
74
  "hookSpecificOutput": {
79
75
  "hookEventName": "PreToolUse",
80
- "additionalContext": "[ftm-plan-gate] You are editing files without having presented a plan this session. If this task is medium+ (touches 3+ files, involves external systems, or has stakeholder coordination), you MUST present a numbered plan and get user approval BEFORE editing code. If this is a micro/small task, you can proceed — but create the plan marker by writing the current session ID to ~/.claude/ftm-state/.plan-presented after confirming the task is genuinely small. To create the marker: Write tool → ~/.claude/ftm-state/.plan-presented with content being the session ID."
76
+ "additionalContext": "[ftm-plan-gate] You are editing files without having presented a plan this session. If this task is medium+ (touches 3+ files, involves external systems, or has stakeholder coordination), you MUST present a numbered plan and get user approval BEFORE editing code. If this is a micro/small task, you can proceed — but create the plan marker: write any content to ~/.claude/ftm-state/.plan-presented to acknowledge you've considered it."
81
77
  }
82
78
  }
83
79
  JSON
@@ -89,7 +85,7 @@ cat <<'JSON'
89
85
  {
90
86
  "hookSpecificOutput": {
91
87
  "hookEventName": "PreToolUse",
92
- "additionalContext": "[ftm-plan-gate WARNING] You have made 3+ file edits this session without presenting a plan. This is exactly the 'grinding without a plan' pattern that ftm-mind is supposed to prevent. STOP editing and do one of: (1) Present a numbered plan to the user and wait for approval, then write the session ID to ~/.claude/ftm-state/.plan-presented. (2) If the user explicitly said 'just do it' or this is genuinely a micro task, write the plan marker to acknowledge you've considered it. Do NOT continue editing without addressing this."
88
+ "additionalContext": "[ftm-plan-gate WARNING] You have made 3+ file edits this session without presenting a plan. This is exactly the 'grinding without a plan' pattern that ftm-mind is supposed to prevent. STOP editing and do one of: (1) Present a numbered plan to the user and wait for approval, then write any content to ~/.claude/ftm-state/.plan-presented. (2) If the user explicitly said 'just do it' or this is genuinely a micro task, write the plan marker to acknowledge you've considered it. Do NOT continue editing without addressing this."
93
89
  }
94
90
  }
95
91
  JSON