@slope-dev/slope 1.51.0 → 1.51.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slope-dev/slope",
3
- "version": "1.51.0",
3
+ "version": "1.51.1",
4
4
  "description": "Quantified sprint metrics for AI-assisted development. Scorecards, handicap tracking, and real-time agent guidance for Claude Code, Cursor, Windsurf, Cline, and OpenCode.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "pi": {
27
27
  "extensions": [
28
- "./packages/pi-extension/src/index.ts"
28
+ "./packages/pi-extension/dist/index.js"
29
29
  ]
30
30
  },
31
31
  "bin": {
@@ -34,13 +34,14 @@
34
34
  },
35
35
  "files": [
36
36
  "dist",
37
+ "packages/pi-extension/dist",
37
38
  "src/core/workflows",
38
39
  "templates",
39
40
  "README.md"
40
41
  ],
41
42
  "scripts": {
42
43
  "prepare": "tsc",
43
- "build": "tsc",
44
+ "build": "tsc && cd packages/pi-extension && tsc",
44
45
  "test": "vitest run",
45
46
  "test:pg": "SLOPE_TEST_PG_URL=postgresql://postgres:slope@localhost:5432/slope_test vitest run tests/store-pg/",
46
47
  "typecheck": "tsc --noEmit",
@@ -85,6 +86,8 @@
85
86
  "zod": "^3.24.0"
86
87
  },
87
88
  "devDependencies": {
89
+ "@mariozechner/pi-coding-agent": "0.68.1",
90
+ "@sinclair/typebox": "0.34.49",
88
91
  "@anthropic-ai/sdk": "^0.78.0",
89
92
  "@types/better-sqlite3": "^7.6.0",
90
93
  "@types/node": "^25.3.0",
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SLOPE Extension for pi coding agent
3
+ *
4
+ * Registers SLOPE tools and enforces guards via Pi's event system.
5
+ * Install: pi install . (project-local) or pi install npm:@slope-dev/slope
6
+ */
7
+ import type { ExtensionAPI } from '@mariozechner/pi-coding-agent';
8
+ export default function slopeExtension(pi: ExtensionAPI, _cwdOverride?: string): void;
@@ -0,0 +1,235 @@
1
+ /**
2
+ * SLOPE Extension for pi coding agent
3
+ *
4
+ * Registers SLOPE tools and enforces guards via Pi's event system.
5
+ * Install: pi install . (project-local) or pi install npm:@slope-dev/slope
6
+ */
7
+ import { execSync } from 'node:child_process';
8
+ import { existsSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { Type } from '@sinclair/typebox';
11
+ // ── Helpers ─────────────────────────────────────────
12
+ function slopeCmd(args, cwd) {
13
+ try {
14
+ return execSync(`slope ${args}`, { cwd, encoding: 'utf8', timeout: 30000 }).trim();
15
+ }
16
+ catch (err) {
17
+ const msg = err instanceof Error ? err.stderr ?? err.message : String(err);
18
+ return `Error: ${msg}`;
19
+ }
20
+ }
21
+ function hasSlopeProject(cwd) {
22
+ return existsSync(join(cwd, '.slope', 'config.json'));
23
+ }
24
+ // ── Extension Entry Point ───────────────────────────
25
+ export default function slopeExtension(pi, _cwdOverride) {
26
+ const cwd = _cwdOverride ?? process.cwd();
27
+ if (!hasSlopeProject(cwd)) {
28
+ // Not a SLOPE project — register only the init tool
29
+ pi.registerTool({
30
+ name: 'slope_init',
31
+ label: 'Slope Init',
32
+ description: 'Initialize SLOPE in this project for sprint tracking and guard enforcement',
33
+ parameters: Type.Object({}),
34
+ async execute(_id, _params, _signal, _update, ctx) {
35
+ const result = slopeCmd('init', ctx.cwd);
36
+ return { content: [{ type: 'text', text: result }], details: {} };
37
+ },
38
+ });
39
+ return;
40
+ }
41
+ // ── SLOPE Tools ───────────────────────────────────
42
+ pi.registerTool({
43
+ name: 'slope_briefing',
44
+ label: 'Slope Briefing',
45
+ description: 'Get sprint briefing — handicap, hazards, claims, roadmap context. Use compact for ~200 token summary.',
46
+ parameters: Type.Object({
47
+ compact: Type.Optional(Type.Boolean({ description: 'Compact mode (~200 tokens instead of full briefing)' })),
48
+ }),
49
+ async execute(_id, params, _signal, _update, ctx) {
50
+ const result = slopeCmd(`briefing${params.compact ? ' --compact' : ''}`, ctx.cwd);
51
+ return { content: [{ type: 'text', text: result }], details: {} };
52
+ },
53
+ });
54
+ pi.registerTool({
55
+ name: 'slope_card',
56
+ label: 'Slope Card',
57
+ description: 'Display handicap card — rolling performance stats across sprints',
58
+ parameters: Type.Object({}),
59
+ async execute(_id, _params, _signal, _update, ctx) {
60
+ const result = slopeCmd('card', ctx.cwd);
61
+ return { content: [{ type: 'text', text: result }], details: {} };
62
+ },
63
+ });
64
+ pi.registerTool({
65
+ name: 'slope_guard_check',
66
+ label: 'Slope Guard Check',
67
+ description: 'Run standalone guard validation: typecheck, tests, uncommitted changes, unpushed commits. Call before committing.',
68
+ parameters: Type.Object({
69
+ json: Type.Optional(Type.Boolean({ description: 'Machine-readable JSON output' })),
70
+ }),
71
+ async execute(_id, params, _signal, _update, ctx) {
72
+ const result = slopeCmd(`guard check${params.json ? ' --json' : ''}`, ctx.cwd);
73
+ return { content: [{ type: 'text', text: result }], details: {} };
74
+ },
75
+ });
76
+ pi.registerTool({
77
+ name: 'slope_sprint_context',
78
+ label: 'Slope Sprint Context',
79
+ description: 'Get remaining workflow steps for the current sprint — include in subagent prompts for workflow awareness.',
80
+ parameters: Type.Object({
81
+ sprint_id: Type.String({ description: 'Sprint ID (e.g., S80)' }),
82
+ }),
83
+ async execute(_id, params, _signal, _update, ctx) {
84
+ const result = slopeCmd(`sprint context ${params.sprint_id}`, ctx.cwd);
85
+ return { content: [{ type: 'text', text: result }], details: {} };
86
+ },
87
+ });
88
+ pi.registerTool({
89
+ name: 'slope_sprint_validate',
90
+ label: 'Slope Sprint Validate',
91
+ description: 'Post-hoc validation: check workflow complete, scorecard exists, plan exists, tests pass.',
92
+ parameters: Type.Object({
93
+ sprint_id: Type.String({ description: 'Sprint ID (e.g., S80)' }),
94
+ }),
95
+ async execute(_id, params, _signal, _update, ctx) {
96
+ const result = slopeCmd(`sprint validate ${params.sprint_id}`, ctx.cwd);
97
+ return { content: [{ type: 'text', text: result }], details: {} };
98
+ },
99
+ });
100
+ pi.registerTool({
101
+ name: 'slope_review_run',
102
+ label: 'Slope Review Run',
103
+ description: 'Generate isolated review prompts from a PR diff for subagent-based code/architect reviews.',
104
+ parameters: Type.Object({
105
+ pr: Type.Optional(Type.Number({ description: 'PR number (default: current branch)' })),
106
+ type: Type.Optional(Type.Union([Type.Literal('architect'), Type.Literal('code'), Type.Literal('both')], { description: 'Review type' })),
107
+ }),
108
+ async execute(_id, params, _signal, _update, ctx) {
109
+ const args = [
110
+ params.pr ? `--pr=${params.pr}` : '',
111
+ params.type ? `--type=${params.type}` : '',
112
+ ].filter(Boolean).join(' ');
113
+ const result = slopeCmd(`review run ${args}`, ctx.cwd);
114
+ return { content: [{ type: 'text', text: result }], details: {} };
115
+ },
116
+ });
117
+ pi.registerTool({
118
+ name: 'slope_guard_metrics',
119
+ label: 'Slope Guard Metrics',
120
+ description: 'Display guard execution metrics — per-guard totals, allow/deny rates, most active/blocking.',
121
+ parameters: Type.Object({}),
122
+ async execute(_id, _params, _signal, _update, ctx) {
123
+ const result = slopeCmd('guard metrics', ctx.cwd);
124
+ return { content: [{ type: 'text', text: result }], details: {} };
125
+ },
126
+ });
127
+ pi.registerTool({
128
+ name: 'slope_convergence',
129
+ label: 'Slope Convergence',
130
+ description: 'Detect convergence patterns: improvement rate, plateau, reversion. Requires 10+ scorecards.',
131
+ parameters: Type.Object({
132
+ json: Type.Optional(Type.Boolean({ description: 'JSON output' })),
133
+ }),
134
+ async execute(_id, params, _signal, _update, ctx) {
135
+ const result = slopeCmd(`loop convergence${params.json ? ' --json' : ''}`, ctx.cwd);
136
+ return { content: [{ type: 'text', text: result }], details: {} };
137
+ },
138
+ });
139
+ // ── Guard Enforcement via Events ──────────────────
140
+ // Guard: hazard warning on write/edit; commit discipline nudge on bash
141
+ pi.on('tool_call', async (event, ctx) => {
142
+ const { toolName, input } = event;
143
+ const inp = input;
144
+ // Hazard warning on file writes/edits
145
+ if ((toolName === 'write' || toolName === 'edit') && typeof inp.path === 'string') {
146
+ try {
147
+ const payload = JSON.stringify({
148
+ session_id: 'pi-session',
149
+ cwd: ctx.cwd,
150
+ hook_event_name: 'PreToolUse',
151
+ tool_name: toolName === 'write' ? 'Write' : 'Edit',
152
+ tool_input: { file_path: inp.path },
153
+ });
154
+ const result = execSync(`echo ${JSON.stringify(payload)} | slope guard hazard`, {
155
+ cwd: ctx.cwd,
156
+ encoding: 'utf8',
157
+ timeout: 5000,
158
+ }).trim();
159
+ if (result.includes('additionalContext')) {
160
+ const match = result.match(/"additionalContext":"([^"]+)"/);
161
+ if (match) {
162
+ const context = match[1].replace(/\\n/g, '\n');
163
+ pi.sendMessage({ customType: 'slope-hazard', content: context, display: true }, { deliverAs: 'steer' });
164
+ }
165
+ }
166
+ }
167
+ catch { /* guard failure should never block */ }
168
+ }
169
+ // Commit discipline: warn on direct main/master commits
170
+ if (toolName === 'bash' && typeof inp.command === 'string' && /git\s+commit/.test(inp.command)) {
171
+ try {
172
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: ctx.cwd, encoding: 'utf8' }).trim();
173
+ if (branch === 'main' || branch === 'master') {
174
+ pi.sendMessage({
175
+ customType: 'slope-guard',
176
+ content: 'SLOPE: Committing directly on main/master. Create a feature branch first: git checkout -b feat/<description>',
177
+ display: true,
178
+ }, { deliverAs: 'steer' });
179
+ }
180
+ }
181
+ catch { /* not in git repo */ }
182
+ }
183
+ });
184
+ // Guard: post-push sprint nudge
185
+ pi.on('tool_result', async (event, ctx) => {
186
+ const inp = event.input;
187
+ if (event.toolName === 'bash' && typeof inp.command === 'string' && /git\s+push/.test(inp.command)) {
188
+ try {
189
+ const status = slopeCmd('sprint status', ctx.cwd);
190
+ if (status && !status.includes('Error')) {
191
+ pi.sendMessage({
192
+ customType: 'slope-post-push',
193
+ content: 'SLOPE post-push: Sprint active. Run `slope guard check` to verify, or `slope sprint context` for next steps.',
194
+ display: true,
195
+ }, { deliverAs: 'steer' });
196
+ }
197
+ }
198
+ catch { /* ignore */ }
199
+ }
200
+ });
201
+ // ── Slash Commands ────────────────────────────────
202
+ pi.registerCommand('slope', {
203
+ description: 'Run any SLOPE CLI command (default: briefing --compact)',
204
+ handler: async (args, ctx) => {
205
+ const output = slopeCmd(args || 'briefing --compact', ctx.cwd);
206
+ ctx.ui.notify(output, 'info');
207
+ },
208
+ });
209
+ pi.registerCommand('sprint', {
210
+ description: 'Quick sprint status',
211
+ handler: async (_args, ctx) => {
212
+ const output = slopeCmd('sprint status', ctx.cwd);
213
+ ctx.ui.notify(output, 'info');
214
+ },
215
+ });
216
+ // ── Session Start: Inject Briefing on First Turn ──
217
+ let briefingInjected = false;
218
+ pi.on('session_start', async (_event, ctx) => {
219
+ briefingInjected = false;
220
+ ctx.ui.notify('SLOPE loaded — use /slope, /sprint, or ask for slope_* tools', 'info');
221
+ });
222
+ pi.on('before_agent_start', async (_event, ctx) => {
223
+ if (briefingInjected)
224
+ return;
225
+ briefingInjected = true;
226
+ const briefing = slopeCmd('briefing --compact', ctx.cwd);
227
+ return {
228
+ message: {
229
+ customType: 'slope-briefing',
230
+ content: `SLOPE Session Briefing:\n${briefing}`,
231
+ display: true,
232
+ },
233
+ };
234
+ });
235
+ }