@aethrekh/pi-cs 0.2.0-alpha.2 → 0.2.0-beta.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.
- package/CHANGELOG.md +4 -18
- package/README.md +62 -27
- package/extensions/folder-detector.js +10 -7
- package/extensions/gatekeeper.js +106 -0
- package/extensions/integrity-guard.js +2 -12
- package/extensions/lib/workspace-state.js +20 -0
- package/extensions/lib/workspace.js +32 -0
- package/extensions/progress-tracker.js +4 -125
- package/extensions/semester-detector.js +4 -22
- package/index.d.ts +0 -2
- package/index.js +9 -11
- package/index.js.map +1 -1
- package/package.json +1 -2
- package/pi-cs.meta.json +2 -2
- package/pi-package.yaml +35 -5
- package/extensions/workspace.js +0 -108
package/CHANGELOG.md
CHANGED
|
@@ -7,35 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
## [0.2.0-
|
|
11
|
-
|
|
12
|
-
### ### Fixed
|
|
13
|
-
|
|
14
|
-
* gate persona and skills to active Pisces workspaces ([3949f96](https://github.com/AshishBagdane/pi-cs/commit/3949f96f391306811c71e3290cb36eae3af14f42))
|
|
15
|
-
|
|
16
|
-
## [0.2.0-alpha.1](https://github.com/AshishBagdane/pi-cs/compare/v0.2.0-alpha.0...v0.2.0-alpha.1) (2026-06-05)
|
|
17
|
-
|
|
18
|
-
### ### Fixed
|
|
19
|
-
|
|
20
|
-
* resolve package.json path for both pack and dev layouts in /pisces --version ([4cb59b5](https://github.com/AshishBagdane/pi-cs/commit/4cb59b5c80f80434c9931beddb6845c7b34d033e))
|
|
21
|
-
|
|
22
|
-
## [0.2.0-alpha.0](https://github.com/AshishBagdane/pi-cs/compare/v0.1.10...v0.2.0-alpha.0) (2026-06-05)
|
|
10
|
+
## [0.2.0-beta.0](https://github.com/AshishBagdane/pi-cs/compare/v0.1.10...v0.2.0-beta.0) (2026-06-06)
|
|
23
11
|
|
|
24
12
|
### ### Added
|
|
25
13
|
|
|
26
|
-
*
|
|
14
|
+
* add workspace discovery and shared state modules ([4882f69](https://github.com/AshishBagdane/pi-cs/commit/4882f69f1d22ffe41d4861121b2a6a4c0fe5381e))
|
|
15
|
+
* workspace-scoped activation via .pisces marker file ([9deec2c](https://github.com/AshishBagdane/pi-cs/commit/9deec2c7ae9f03f9548c98934fe7c092af69d036))
|
|
27
16
|
|
|
28
17
|
### ### Fixed
|
|
29
18
|
|
|
30
|
-
*
|
|
31
|
-
* register workspace.ts as a Pi extension so workspace.js is included in pack ([7f2546c](https://github.com/AshishBagdane/pi-cs/commit/7f2546ce50debcd5bda4a6da6ece9c482a13d861))
|
|
19
|
+
* **validate:** derive skill dirs from pi-package.yaml when pi.skills absent ([c3ebfa8](https://github.com/AshishBagdane/pi-cs/commit/c3ebfa8ca578f7dd3ca9f7ee54675a216ea31d2b))
|
|
32
20
|
|
|
33
21
|
### ### Changed
|
|
34
22
|
|
|
35
|
-
* add workspace-scoping design document ([098504a](https://github.com/AshishBagdane/pi-cs/commit/098504a8262986def084c34a89d585a08e40531b))
|
|
36
23
|
* align README and STUDENT_GUIDE with v0.1.10 install behaviour ([e8f6ae5](https://github.com/AshishBagdane/pi-cs/commit/e8f6ae5a9ed4ebd54409985e00d18b3356438255))
|
|
37
24
|
* fix false local install scope claims and expand troubleshooting ([0e000f7](https://github.com/AshishBagdane/pi-cs/commit/0e000f7ac947fc641785ab46818649710d857265))
|
|
38
|
-
* update README and STUDENT_GUIDE for workspace-scoped activation ([81e8872](https://github.com/AshishBagdane/pi-cs/commit/81e8872c024edb8c41cbc0d7b1fa85b10ddb16c9))
|
|
39
25
|
|
|
40
26
|
## [0.1.10](https://github.com/AshishBagdane/pi-cs/compare/v0.1.10-alpha.1...v0.1.10) (2026-06-05)
|
|
41
27
|
|
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ It covers every dimension of a CS degree:
|
|
|
15
15
|
|
|
16
16
|
| Need | Command | What it does |
|
|
17
17
|
|---|---|---|
|
|
18
|
+
| Manage workspace activation | `/pisces` | Activate, deactivate, or check workspace status |
|
|
18
19
|
| Set up semester context | `/semester` | Init, update, or inspect your `SEMESTER.md` context |
|
|
19
20
|
| Stuck on an assignment | `/homework` | Hints, pseudocode, guided walkthrough — no cheating |
|
|
20
21
|
| Starting a new project | `/project` | Requirements → architecture → scaffolded codebase |
|
|
@@ -29,23 +30,60 @@ It covers every dimension of a CS degree:
|
|
|
29
30
|
## Quick Start
|
|
30
31
|
|
|
31
32
|
```bash
|
|
32
|
-
# Install Pisces globally (
|
|
33
|
+
# 1. Install Pisces globally (recommended for most students)
|
|
33
34
|
pi install npm:@aethrekh/pi-cs
|
|
34
35
|
|
|
35
|
-
# Mark your university
|
|
36
|
+
# 2. Mark your university workspace (one-time setup)
|
|
36
37
|
cd ~/university
|
|
38
|
+
pi
|
|
37
39
|
/pisces --activate
|
|
38
40
|
|
|
39
|
-
# Initialize your semester context
|
|
41
|
+
# 3. Initialize your semester context
|
|
40
42
|
/semester --init
|
|
41
43
|
|
|
42
|
-
# Start working
|
|
44
|
+
# 4. Start working
|
|
43
45
|
/homework
|
|
44
46
|
/explain binary search trees
|
|
45
47
|
/leetcode 42
|
|
46
48
|
```
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
After step 2, Pisces activates in `~/university` and all its subdirectories. Outside that workspace, Pi runs as stock — no skills, no persona.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Workspace Activation
|
|
55
|
+
|
|
56
|
+
Pisces uses a `.pisces` marker file to decide where it is active. Outside a marked workspace Pi is completely silent — no skills load, no persona injects, no extensions run. Inside a workspace the full Pisces experience turns on.
|
|
57
|
+
|
|
58
|
+
### Marking a workspace
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cd ~/university # navigate to your workspace root
|
|
62
|
+
/pisces --activate # creates .pisces here and reloads Pi
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### How workspace discovery works
|
|
66
|
+
|
|
67
|
+
Pisces walks up the directory tree from where Pi was started, up to 15 levels, stopping at your home directory. This means you can start Pi from any subdirectory and Pisces will still find the marker:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
~/university/ ← .pisces lives here
|
|
71
|
+
├── fall2025/
|
|
72
|
+
│ └── CS301/
|
|
73
|
+
│ └── lab1/ ← start Pi here; Pisces walks up and activates
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Workspace commands
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
/pisces --status # show whether the workspace is active and where .pisces was found
|
|
80
|
+
/pisces --activate # mark the current directory as a Pisces workspace
|
|
81
|
+
/pisces --deactivate # remove .pisces from the nearest workspace above cwd
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Switching workspace context
|
|
85
|
+
|
|
86
|
+
Pi's working directory is fixed at session start — there is no mid-session directory change mechanism in the Pi extension API. To switch workspace context, start a new Pi session (`/new` or restart Pi) from the target directory. `/pisces --status` always reflects the startup directory, not wherever you have `cd`'d during the session.
|
|
49
87
|
|
|
50
88
|
---
|
|
51
89
|
|
|
@@ -199,31 +237,26 @@ See [`config/schema.json`](config/schema.json) for the full configuration refere
|
|
|
199
237
|
**Requirements:** Pi >= 0.78.0, Node.js >= 18
|
|
200
238
|
|
|
201
239
|
```bash
|
|
202
|
-
#
|
|
240
|
+
# Recommended — installs Pisces globally
|
|
203
241
|
pi install npm:@aethrekh/pi-cs
|
|
204
|
-
|
|
205
|
-
# Step 2 — Activate in your university folder
|
|
206
|
-
cd ~/university
|
|
207
|
-
/pisces --activate
|
|
208
|
-
|
|
209
|
-
# Step 3 — Set up semester context
|
|
210
|
-
/semester --init
|
|
211
242
|
```
|
|
212
243
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
- One global install covers every workspace you mark
|
|
216
|
-
- Work and personal sessions are unaffected until you mark them
|
|
217
|
-
- Multiple independent workspaces are supported (just run `/pisces --activate` in each root)
|
|
218
|
-
- Deactivate with `/pisces --deactivate` or `rm .pisces`
|
|
244
|
+
After a global install the gatekeeper extension loads in every Pi session, but **Pisces is silent by default** — no skills, no persona, no extensions run until you activate a workspace with `/pisces --activate`. This means Pisces is available everywhere you want it without affecting non-university Pi sessions.
|
|
219
245
|
|
|
220
246
|
```bash
|
|
221
|
-
# Local install —
|
|
247
|
+
# Local install — loads the extension only in the exact install directory
|
|
222
248
|
cd ~/university
|
|
223
249
|
pi install npm:@aethrekh/pi-cs -l
|
|
224
250
|
```
|
|
225
251
|
|
|
226
|
-
|
|
252
|
+
With a local install, the extension itself only loads when Pi is started from `~/university` exactly — not from any subdirectory. Combined with workspace scoping, this offers the tightest possible containment, but requires you to always start Pi from that one root. Most students should use the global install.
|
|
253
|
+
|
|
254
|
+
| | Global | Local (`-l`) |
|
|
255
|
+
|---|---|---|
|
|
256
|
+
| Extension loads | In every Pi session | Only from exact install directory |
|
|
257
|
+
| Skills / persona active | Only inside a `.pisces` workspace | Only inside a `.pisces` workspace |
|
|
258
|
+
| Works from course subdirectories | Yes | No — exact directory only |
|
|
259
|
+
| Recommended | **Yes** | Edge case only |
|
|
227
260
|
|
|
228
261
|
---
|
|
229
262
|
|
|
@@ -244,11 +277,14 @@ pi-cs/
|
|
|
244
277
|
│ │ ├── exam/SKILL.md # /exam skill
|
|
245
278
|
│ │ └── research/SKILL.md # /research skill
|
|
246
279
|
│ ├── extensions/
|
|
247
|
-
│ │ ├──
|
|
248
|
-
│ │ ├──
|
|
249
|
-
│ │ ├──
|
|
250
|
-
│ │
|
|
251
|
-
│ │
|
|
280
|
+
│ │ ├── gatekeeper.ts # Workspace gating; /pisces command
|
|
281
|
+
│ │ ├── lib/
|
|
282
|
+
│ │ │ ├── workspace.ts # .pisces discovery (walks up 15 levels)
|
|
283
|
+
│ │ │ └── workspace-state.ts # Shared activation state across extensions
|
|
284
|
+
│ │ ├── semester-detector.ts # Auto-loads SEMESTER.md (workspace-gated)
|
|
285
|
+
│ │ ├── folder-detector.ts # Detects CS folder structures (workspace-gated)
|
|
286
|
+
│ │ ├── integrity-guard.ts # Academic integrity monitoring (workspace-gated)
|
|
287
|
+
│ │ └── progress-tracker.ts # Weekly stats & burnout nudges (workspace-gated)
|
|
252
288
|
│ └── templates/
|
|
253
289
|
│ └── semester-init.md # SEMESTER.md raw template
|
|
254
290
|
├── config/
|
|
@@ -267,7 +303,6 @@ pi-cs/
|
|
|
267
303
|
- [x] 4 background extensions
|
|
268
304
|
- [x] Semester context system
|
|
269
305
|
- [x] Academic integrity guard
|
|
270
|
-
- [x] Workspace-scoped activation (`.pisces` marker)
|
|
271
306
|
- [ ] arXiv live search integration
|
|
272
307
|
- [ ] Model routing presets
|
|
273
308
|
- [ ] Obsidian vault sync
|
|
@@ -48,7 +48,7 @@ exports.default = default_1;
|
|
|
48
48
|
exports.run = run;
|
|
49
49
|
const fs = __importStar(require("fs"));
|
|
50
50
|
const path = __importStar(require("path"));
|
|
51
|
-
const
|
|
51
|
+
const workspace_state_1 = require("./lib/workspace-state");
|
|
52
52
|
// ─── Marker Files ──────────────────────────────────────────────────────────
|
|
53
53
|
const PYTHON_MARKERS = ["requirements.txt", "pyproject.toml", "setup.py", "Pipfile"];
|
|
54
54
|
const TS_MARKERS = ["package.json", "tsconfig.json"];
|
|
@@ -189,14 +189,17 @@ function summarizeFolderContext(result) {
|
|
|
189
189
|
}
|
|
190
190
|
// ─── Pi Extension Factory ──────────────────────────────────────────────────
|
|
191
191
|
function default_1(pi) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (!(0,
|
|
192
|
+
let contextSummary = "";
|
|
193
|
+
pi.on("session_start", async () => {
|
|
194
|
+
if (!(0, workspace_state_1.getWorkspaceState)().isActive)
|
|
195
195
|
return;
|
|
196
|
-
const
|
|
197
|
-
|
|
196
|
+
const result = detectFolderContext();
|
|
197
|
+
contextSummary = summarizeFolderContext(result);
|
|
198
|
+
});
|
|
199
|
+
pi.on("before_agent_start", async (event) => {
|
|
200
|
+
if (!(0, workspace_state_1.getWorkspaceState)().isActive || !contextSummary)
|
|
198
201
|
return;
|
|
199
|
-
return { systemPrompt: `${event.systemPrompt}\n\n${
|
|
202
|
+
return { systemPrompt: `${event.systemPrompt}\n\n${contextSummary}` };
|
|
200
203
|
});
|
|
201
204
|
}
|
|
202
205
|
// ─── Pi Extension Entry Point ──────────────────────────────────────────────
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* gatekeeper.ts
|
|
4
|
+
*
|
|
5
|
+
* Extension: Workspace Gatekeeper
|
|
6
|
+
* Triggers: resources_discover
|
|
7
|
+
*
|
|
8
|
+
* Always loads. Has no academic content.
|
|
9
|
+
* Sole job: detect workspace state and gate all Pisces resources.
|
|
10
|
+
*
|
|
11
|
+
* Registers /pisces --activate | --deactivate | --status.
|
|
12
|
+
*
|
|
13
|
+
* Note: Pi's cwd is fixed at session startup — the extension context does not
|
|
14
|
+
* reflect mid-session directory changes. To switch workspace context, start Pi
|
|
15
|
+
* in the target directory or run /pisces --activate from within a new session.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.default = default_1;
|
|
19
|
+
const fs_1 = require("fs");
|
|
20
|
+
const path_1 = require("path");
|
|
21
|
+
const workspace_1 = require("./lib/workspace");
|
|
22
|
+
const workspace_state_1 = require("./lib/workspace-state");
|
|
23
|
+
function default_1(pi) {
|
|
24
|
+
// ─── Resource Gating ─────────────────────────────────────────────────────
|
|
25
|
+
// resources_discover fires before session_start and before skills are
|
|
26
|
+
// injected into the system prompt. Returning skillPaths: [] here is the
|
|
27
|
+
// only reliable way to suppress skills outside a workspace.
|
|
28
|
+
// Doubles as the authoritative initial sync of shared workspace state.
|
|
29
|
+
pi.on("resources_discover", async (event) => {
|
|
30
|
+
const { isActive } = (0, workspace_state_1.syncWorkspaceState)(event.cwd);
|
|
31
|
+
if (isActive) {
|
|
32
|
+
return {
|
|
33
|
+
skillPaths: [(0, path_1.resolve)(__dirname, "../skills")],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { skillPaths: [] };
|
|
37
|
+
});
|
|
38
|
+
// ─── /pisces Command ─────────────────────────────────────────────────────
|
|
39
|
+
pi.registerCommand("pisces", {
|
|
40
|
+
description: "Manage Pisces workspace activation",
|
|
41
|
+
handler: async (args, ctx) => {
|
|
42
|
+
const flag = args?.trim();
|
|
43
|
+
// ── --activate ──────────────────────────────────────────────────────
|
|
44
|
+
if (flag === "--activate") {
|
|
45
|
+
const cwd = process.cwd();
|
|
46
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, ".pisces"), "");
|
|
47
|
+
ctx.ui.notify(`Pisces workspace activated at ${cwd}`, "info");
|
|
48
|
+
await ctx.reload();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// ── --deactivate ─────────────────────────────────────────────────────
|
|
52
|
+
if (flag === "--deactivate") {
|
|
53
|
+
const { isActive, root } = (0, workspace_1.findWorkspace)(process.cwd());
|
|
54
|
+
if (!isActive || !root) {
|
|
55
|
+
ctx.ui.notify("No active workspace found above this directory.", "warning");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const confirmed = await ctx.ui.confirm("Deactivate workspace?", `Remove .pisces from ${root}?`);
|
|
59
|
+
if (!confirmed)
|
|
60
|
+
return;
|
|
61
|
+
(0, fs_1.unlinkSync)((0, path_1.join)(root, ".pisces"));
|
|
62
|
+
ctx.ui.notify([
|
|
63
|
+
`Workspace deactivated: ${root}`,
|
|
64
|
+
"",
|
|
65
|
+
"Skills are removed. The AI may still respond as Pisces for",
|
|
66
|
+
"the rest of this session due to conversation history.",
|
|
67
|
+
"Start a new session (/new) for a clean reset.",
|
|
68
|
+
].join("\n"), "info");
|
|
69
|
+
await ctx.reload();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// ── --status (default) ───────────────────────────────────────────────
|
|
73
|
+
if (flag === "--status" || !flag) {
|
|
74
|
+
const { isActive, root } = (0, workspace_1.findWorkspace)(process.cwd());
|
|
75
|
+
if (isActive && root) {
|
|
76
|
+
const cwd = process.cwd();
|
|
77
|
+
const depth = cwd
|
|
78
|
+
.replace(root, "")
|
|
79
|
+
.split("/")
|
|
80
|
+
.filter(Boolean).length;
|
|
81
|
+
ctx.ui.notify([
|
|
82
|
+
"Pisces Status",
|
|
83
|
+
"─────────────────────────────",
|
|
84
|
+
`Active: Yes`,
|
|
85
|
+
`Workspace root: ${root}`,
|
|
86
|
+
`.pisces found: ${depth} level${depth !== 1 ? "s" : ""} above cwd`,
|
|
87
|
+
].join("\n"), "info");
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
ctx.ui.notify([
|
|
91
|
+
"Pisces Status",
|
|
92
|
+
"─────────────────────────────",
|
|
93
|
+
`Active: No`,
|
|
94
|
+
`Reason: No .pisces found within 15 levels or above home directory`,
|
|
95
|
+
"",
|
|
96
|
+
`Run /pisces --activate to mark this directory as a workspace.`,
|
|
97
|
+
].join("\n"), "info");
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// ── unknown flag ─────────────────────────────────────────────────────
|
|
102
|
+
ctx.ui.notify("Usage: /pisces --activate | --deactivate | --status", "warning");
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=gatekeeper.js.map
|
|
@@ -12,7 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.checkIntegrityRisk = checkIntegrityRisk;
|
|
13
13
|
exports.default = default_1;
|
|
14
14
|
exports.run = run;
|
|
15
|
-
const
|
|
15
|
+
const workspace_state_1 = require("./lib/workspace-state");
|
|
16
16
|
// ─── Risk Signals ──────────────────────────────────────────────────────────
|
|
17
17
|
// High-risk: user is likely asking for submission-ready work
|
|
18
18
|
const HIGH_RISK_PATTERNS = [
|
|
@@ -102,21 +102,11 @@ Just let me know!`;
|
|
|
102
102
|
}
|
|
103
103
|
// ─── Pi Extension Factory ──────────────────────────────────────────────────
|
|
104
104
|
function default_1(pi) {
|
|
105
|
-
let isActive = false;
|
|
106
105
|
// Guard against sendUserMessage re-triggering this handler — warning text
|
|
107
106
|
// contains words like "graded" that would otherwise match medium-risk patterns.
|
|
108
107
|
let handlingWarning = false;
|
|
109
|
-
pi.on("session_start", async () => {
|
|
110
|
-
isActive = (0, workspace_1.findPiscesMarker)() !== null;
|
|
111
|
-
});
|
|
112
|
-
// Refresh isActive before each agent turn so mid-session workspace changes
|
|
113
|
-
// are picked up. The input handler may still use a one-turn-stale value for
|
|
114
|
-
// messages that arrive before the first before_agent_start of a new turn.
|
|
115
|
-
pi.on("before_agent_start", async () => {
|
|
116
|
-
isActive = (0, workspace_1.findPiscesMarker)() !== null;
|
|
117
|
-
});
|
|
118
108
|
pi.on("input", async (event) => {
|
|
119
|
-
if (!isActive)
|
|
109
|
+
if (!(0, workspace_state_1.getWorkspaceState)().isActive)
|
|
120
110
|
return { action: "continue" };
|
|
121
111
|
if (handlingWarning)
|
|
122
112
|
return { action: "continue" };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getWorkspaceState = getWorkspaceState;
|
|
4
|
+
exports.syncWorkspaceState = syncWorkspaceState;
|
|
5
|
+
const workspace_1 = require("./workspace");
|
|
6
|
+
// Shared state owned by the gatekeeper, read by all extensions.
|
|
7
|
+
// Lazy-initialises on first read so tests that mock findWorkspace work
|
|
8
|
+
// without needing an explicit setup call.
|
|
9
|
+
let _state = null;
|
|
10
|
+
function getWorkspaceState() {
|
|
11
|
+
if (_state === null) {
|
|
12
|
+
_state = (0, workspace_1.findWorkspace)(process.cwd());
|
|
13
|
+
}
|
|
14
|
+
return _state;
|
|
15
|
+
}
|
|
16
|
+
function syncWorkspaceState(cwd) {
|
|
17
|
+
_state = (0, workspace_1.findWorkspace)(cwd);
|
|
18
|
+
return _state;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=workspace-state.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findWorkspace = findWorkspace;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const os_1 = require("os");
|
|
7
|
+
/**
|
|
8
|
+
* Walks up from startCwd looking for a .pisces marker file.
|
|
9
|
+
* Stops at the home directory or filesystem root (whichever comes first).
|
|
10
|
+
* Caps at 15 levels to bound the cost — each check is a single synchronous stat.
|
|
11
|
+
*/
|
|
12
|
+
function findWorkspace(startCwd) {
|
|
13
|
+
const home = (0, path_1.resolve)((0, os_1.homedir)());
|
|
14
|
+
let current = (0, path_1.resolve)(startCwd);
|
|
15
|
+
let depth = 0;
|
|
16
|
+
while (depth < 15) {
|
|
17
|
+
if (current === home || current.length < home.length)
|
|
18
|
+
break;
|
|
19
|
+
if (current === "/")
|
|
20
|
+
break;
|
|
21
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(current, ".pisces"))) {
|
|
22
|
+
return { isActive: true, root: current };
|
|
23
|
+
}
|
|
24
|
+
const parent = (0, path_1.dirname)(current);
|
|
25
|
+
if (parent === current)
|
|
26
|
+
break;
|
|
27
|
+
current = parent;
|
|
28
|
+
depth++;
|
|
29
|
+
}
|
|
30
|
+
return { isActive: false, root: null };
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -49,8 +49,7 @@ exports.run = run;
|
|
|
49
49
|
const fs = __importStar(require("fs"));
|
|
50
50
|
const path = __importStar(require("path"));
|
|
51
51
|
const os = __importStar(require("os"));
|
|
52
|
-
const
|
|
53
|
-
const workspace_1 = require("./workspace");
|
|
52
|
+
const workspace_state_1 = require("./lib/workspace-state");
|
|
54
53
|
// ─── Storage ───────────────────────────────────────────────────────────────
|
|
55
54
|
const TRACKER_DIR = path.join(os.homedir(), ".pi-cs");
|
|
56
55
|
const SESSIONS_FILE = path.join(TRACKER_DIR, "sessions.json");
|
|
@@ -162,14 +161,8 @@ function buildWeeklySummary(stats) {
|
|
|
162
161
|
function default_1(pi) {
|
|
163
162
|
const sessionStart = Date.now();
|
|
164
163
|
const skillsUsed = [];
|
|
165
|
-
let isActive = false;
|
|
166
|
-
let workspaceRoot = null;
|
|
167
|
-
pi.on("session_start", async () => {
|
|
168
|
-
workspaceRoot = (0, workspace_1.findPiscesMarker)();
|
|
169
|
-
isActive = workspaceRoot !== null;
|
|
170
|
-
});
|
|
171
164
|
pi.on("input", async (event) => {
|
|
172
|
-
if (!isActive)
|
|
165
|
+
if (!(0, workspace_state_1.getWorkspaceState)().isActive)
|
|
173
166
|
return { action: "continue" };
|
|
174
167
|
const match = event.text.match(/^\/skill:(\w+)/);
|
|
175
168
|
if (match && !skillsUsed.includes(match[1])) {
|
|
@@ -178,10 +171,7 @@ function default_1(pi) {
|
|
|
178
171
|
return { action: "continue" };
|
|
179
172
|
});
|
|
180
173
|
pi.on("before_agent_start", async (_event, ctx) => {
|
|
181
|
-
|
|
182
|
-
workspaceRoot = (0, workspace_1.findPiscesMarker)();
|
|
183
|
-
isActive = workspaceRoot !== null;
|
|
184
|
-
if (!isActive)
|
|
174
|
+
if (!(0, workspace_state_1.getWorkspaceState)().isActive)
|
|
185
175
|
return;
|
|
186
176
|
const elapsed = Math.round((Date.now() - sessionStart) / 60000);
|
|
187
177
|
if (elapsed > 0 && elapsed % 180 === 0) {
|
|
@@ -189,7 +179,7 @@ function default_1(pi) {
|
|
|
189
179
|
}
|
|
190
180
|
});
|
|
191
181
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
192
|
-
if (!isActive)
|
|
182
|
+
if (!(0, workspace_state_1.getWorkspaceState)().isActive)
|
|
193
183
|
return;
|
|
194
184
|
const durationMinutes = Math.round((Date.now() - sessionStart) / 60000);
|
|
195
185
|
const { nudge, weeklySummary } = run({ sessionDurationMinutes: durationMinutes, skillsUsed });
|
|
@@ -198,117 +188,6 @@ function default_1(pi) {
|
|
|
198
188
|
if (weeklySummary)
|
|
199
189
|
ctx.ui.notify(weeklySummary, "info");
|
|
200
190
|
});
|
|
201
|
-
pi.registerCommand("pisces", {
|
|
202
|
-
description: "Workspace lifecycle and status for Pisces — use --activate, --deactivate, --status, --version",
|
|
203
|
-
handler: async (args, ctx) => {
|
|
204
|
-
const flag = (typeof args === "string" ? args : args[0] ?? "").trim();
|
|
205
|
-
// ── /pisces --activate ───────────────────────────────────────────────
|
|
206
|
-
if (flag === "--activate") {
|
|
207
|
-
const cwd = process.cwd();
|
|
208
|
-
const existing = (0, workspace_1.findPiscesMarker)(cwd);
|
|
209
|
-
if (existing) {
|
|
210
|
-
ctx.ui.notify(`🐠 Workspace already active at: ${existing}\n\nRun /pisces --status for details.`, "info");
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
try {
|
|
214
|
-
(0, workspace_1.createPiscesMarker)(cwd);
|
|
215
|
-
workspaceRoot = cwd;
|
|
216
|
-
isActive = true;
|
|
217
|
-
ctx.ui.notify([
|
|
218
|
-
`✅ Workspace activated at: ${cwd}`,
|
|
219
|
-
` ${workspace_1.PISCES_MARKER} created.`,
|
|
220
|
-
``,
|
|
221
|
-
`All Pi sessions in this directory tree will now activate Pisces.`,
|
|
222
|
-
`Reloading to register Pisces skills (/homework, /explain, etc.)...`,
|
|
223
|
-
].join("\n"), "info");
|
|
224
|
-
await ctx.reload();
|
|
225
|
-
}
|
|
226
|
-
catch (err) {
|
|
227
|
-
ctx.ui.notify(`❌ Failed to create ${workspace_1.PISCES_MARKER}: ${String(err)}`, "error");
|
|
228
|
-
}
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
// ── /pisces --deactivate ─────────────────────────────────────────────
|
|
232
|
-
if (flag === "--deactivate") {
|
|
233
|
-
if (!workspaceRoot) {
|
|
234
|
-
ctx.ui.notify("🐠 No active Pisces workspace in this session — nothing to deactivate.", "info");
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
try {
|
|
238
|
-
(0, workspace_1.removePiscesMarker)(workspaceRoot);
|
|
239
|
-
const removed = workspaceRoot;
|
|
240
|
-
workspaceRoot = null;
|
|
241
|
-
isActive = false;
|
|
242
|
-
ctx.ui.notify(`✅ Workspace deactivated.\n Removed ${workspace_1.PISCES_MARKER} from: ${removed}\n\nReloading to unregister Pisces skills...`, "info");
|
|
243
|
-
await ctx.reload();
|
|
244
|
-
}
|
|
245
|
-
catch (err) {
|
|
246
|
-
ctx.ui.notify(`❌ Failed to remove ${workspace_1.PISCES_MARKER}: ${String(err)}`, "error");
|
|
247
|
-
}
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
// ── /pisces --version ────────────────────────────────────────────────
|
|
251
|
-
if (flag === "--version") {
|
|
252
|
-
// Read version from package.json at runtime to stay in sync with releases
|
|
253
|
-
let version = "unknown";
|
|
254
|
-
// Pack layout: <install>/extensions/progress-tracker.js → ../package.json
|
|
255
|
-
// Dev layout: src/extensions/progress-tracker.ts → ../../package.json
|
|
256
|
-
const pkgCandidates = [
|
|
257
|
-
path.resolve(__dirname, "../package.json"),
|
|
258
|
-
path.resolve(__dirname, "../../package.json"),
|
|
259
|
-
];
|
|
260
|
-
for (const p of pkgCandidates) {
|
|
261
|
-
try {
|
|
262
|
-
const pkg = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
263
|
-
if (pkg.version) {
|
|
264
|
-
version = pkg.version;
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
catch { /* try next */ }
|
|
269
|
-
}
|
|
270
|
-
ctx.ui.notify(`🐠 pi-cs (Pisces) v${version}`, "info");
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
// ── /pisces --status (default) ───────────────────────────────────────
|
|
274
|
-
const elapsed = Math.round((Date.now() - sessionStart) / 60000);
|
|
275
|
-
if (!isActive) {
|
|
276
|
-
ctx.ui.notify([
|
|
277
|
-
"Pisces Status",
|
|
278
|
-
"─────────────────────────────────────",
|
|
279
|
-
"Active: ❌ No",
|
|
280
|
-
`Reason: No ${workspace_1.PISCES_MARKER} found within search bounds (15 levels, ~)`,
|
|
281
|
-
"",
|
|
282
|
-
"Run /pisces --activate to mark this directory as a workspace root.",
|
|
283
|
-
].join("\n"), "info");
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
const detection = (0, semester_detector_1.detectSemesterContext)();
|
|
287
|
-
const semCtx = detection.context;
|
|
288
|
-
const lines = [
|
|
289
|
-
"Pisces Status",
|
|
290
|
-
"─────────────────────────────────────",
|
|
291
|
-
"Active: ✅ Yes",
|
|
292
|
-
`Workspace root: ${workspaceRoot}`,
|
|
293
|
-
`Session: ${elapsed} minute${elapsed !== 1 ? "s" : ""}`,
|
|
294
|
-
"",
|
|
295
|
-
semCtx
|
|
296
|
-
? `Semester: ${semCtx.semester} ${semCtx.year}${semCtx.week ? ` — Week ${semCtx.week}` : ""}`
|
|
297
|
-
: "Semester: ❌ No SEMESTER.md loaded — run /semester --init",
|
|
298
|
-
semCtx?.courses?.length
|
|
299
|
-
? `Courses: ${semCtx.courses.map((c) => c.code).join(", ")}`
|
|
300
|
-
: "",
|
|
301
|
-
semCtx?.active_project ? `Project: ${semCtx.active_project}` : "",
|
|
302
|
-
"",
|
|
303
|
-
skillsUsed.length
|
|
304
|
-
? `Skills used: ${skillsUsed.map((s) => `/skill:${s}`).join(", ")}`
|
|
305
|
-
: "No skills used yet — try /skill:explain, /skill:homework, or /skill:leetcode",
|
|
306
|
-
]
|
|
307
|
-
.filter((l) => l !== undefined)
|
|
308
|
-
.join("\n");
|
|
309
|
-
ctx.ui.notify(lines, "info");
|
|
310
|
-
},
|
|
311
|
-
});
|
|
312
191
|
}
|
|
313
192
|
// ─── Pi Extension Entry Point ──────────────────────────────────────────────
|
|
314
193
|
function run(context) {
|
|
@@ -49,7 +49,7 @@ exports.run = run;
|
|
|
49
49
|
const fs = __importStar(require("fs"));
|
|
50
50
|
const path = __importStar(require("path"));
|
|
51
51
|
const os = __importStar(require("os"));
|
|
52
|
-
const
|
|
52
|
+
const workspace_state_1 = require("./lib/workspace-state");
|
|
53
53
|
// ─── Search Paths ──────────────────────────────────────────────────────────
|
|
54
54
|
function getSearchPaths() {
|
|
55
55
|
return [
|
|
@@ -224,21 +224,12 @@ function removeLegacyGlobalPersona(persona) {
|
|
|
224
224
|
function default_1(pi) {
|
|
225
225
|
const persona = loadPersona();
|
|
226
226
|
let semesterCtx = null;
|
|
227
|
-
// Dynamically register skills only when a Pisces workspace is active.
|
|
228
|
-
// Skills are absent outside a workspace — they never appear in the global command list.
|
|
229
|
-
pi.on("resources_discover", async () => {
|
|
230
|
-
if (!(0, workspace_1.findPiscesMarker)())
|
|
231
|
-
return;
|
|
232
|
-
// Relative path works for both pack (extensions/ → skills/) and dev (src/extensions/ → src/skills/) layouts
|
|
233
|
-
const skillsDir = path.resolve(__dirname, "../skills");
|
|
234
|
-
return fs.existsSync(skillsDir) ? { skillPaths: [skillsDir] } : undefined;
|
|
235
|
-
});
|
|
236
227
|
pi.on("session_start", async (_event, ctx) => {
|
|
237
|
-
if (!(0, workspace_1.findPiscesMarker)())
|
|
238
|
-
return;
|
|
239
228
|
if (removeLegacyGlobalPersona(persona)) {
|
|
240
229
|
ctx.ui.notify("🐠 Pisces: cleaned up a stale ~/.pi/agent/APPEND_SYSTEM.md from an older install. Persona is now session-scoped.", "info");
|
|
241
230
|
}
|
|
231
|
+
if (!(0, workspace_state_1.getWorkspaceState)().isActive)
|
|
232
|
+
return;
|
|
242
233
|
const result = detectSemesterContext();
|
|
243
234
|
if (result.found && result.context) {
|
|
244
235
|
semesterCtx = result.context;
|
|
@@ -250,17 +241,8 @@ function default_1(pi) {
|
|
|
250
241
|
}
|
|
251
242
|
});
|
|
252
243
|
pi.on("before_agent_start", async (event) => {
|
|
253
|
-
|
|
254
|
-
// is reflected without needing a session restart.
|
|
255
|
-
if (!(0, workspace_1.findPiscesMarker)())
|
|
244
|
+
if (!(0, workspace_state_1.getWorkspaceState)().isActive)
|
|
256
245
|
return;
|
|
257
|
-
// Lazy-load semester context if it was not available at session_start
|
|
258
|
-
// (e.g. workspace was activated mid-session via /pisces --activate).
|
|
259
|
-
if (!semesterCtx) {
|
|
260
|
-
const result = detectSemesterContext();
|
|
261
|
-
if (result.found && result.context)
|
|
262
|
-
semesterCtx = result.context;
|
|
263
|
-
}
|
|
264
246
|
const parts = [];
|
|
265
247
|
if (persona)
|
|
266
248
|
parts.push(persona);
|
package/index.d.ts
CHANGED
|
@@ -98,13 +98,11 @@ export declare function onLoad(): {
|
|
|
98
98
|
/**
|
|
99
99
|
* onStartup — called at the start of every Pi session.
|
|
100
100
|
* Runs semester + folder detectors, builds greeting and context injection.
|
|
101
|
-
* Returns empty result silently when no .pisces workspace marker is found.
|
|
102
101
|
*/
|
|
103
102
|
export declare function onStartup(state: SessionState): StartupResult;
|
|
104
103
|
/**
|
|
105
104
|
* onDirectoryChange — called when the user `cd`s to a new directory.
|
|
106
105
|
* Re-runs context detectors so Pi stays accurate.
|
|
107
|
-
* Returns empty result silently when outside a .pisces workspace.
|
|
108
106
|
*/
|
|
109
107
|
export declare function onDirectoryChange(state: SessionState): {
|
|
110
108
|
contextInjection: string;
|
package/index.js
CHANGED
|
@@ -70,7 +70,6 @@ const semester_detector_1 = require("./extensions/semester-detector");
|
|
|
70
70
|
const folder_detector_1 = require("./extensions/folder-detector");
|
|
71
71
|
const integrity_guard_1 = require("./extensions/integrity-guard");
|
|
72
72
|
const progress_tracker_1 = require("./extensions/progress-tracker");
|
|
73
|
-
const workspace_1 = require("./extensions/workspace");
|
|
74
73
|
// ─── Package Metadata ──────────────────────────────────────────────────────
|
|
75
74
|
exports.PACKAGE_NAME = "pi-cs";
|
|
76
75
|
exports.PACKAGE_VERSION = "0.1.0";
|
|
@@ -192,27 +191,28 @@ function onLoad() {
|
|
|
192
191
|
/**
|
|
193
192
|
* onStartup — called at the start of every Pi session.
|
|
194
193
|
* Runs semester + folder detectors, builds greeting and context injection.
|
|
195
|
-
* Returns empty result silently when no .pisces workspace marker is found.
|
|
196
194
|
*/
|
|
197
195
|
function onStartup(state) {
|
|
198
196
|
const config = loadConfig();
|
|
199
|
-
if (!(0, workspace_1.findPiscesMarker)()) {
|
|
200
|
-
return { contextInjection: "", greeting: "", config };
|
|
201
|
-
}
|
|
202
197
|
// Run semester detector
|
|
203
198
|
const semesterResult = (0, semester_detector_1.run)();
|
|
204
199
|
if (semesterResult) {
|
|
200
|
+
// Extract context if found (greeting contains it serialised in comment blocks)
|
|
205
201
|
const detection = (0, semester_detector_1.detectSemesterContext)();
|
|
206
202
|
state.semesterContext = detection.context ?? null;
|
|
207
203
|
}
|
|
208
204
|
// Run folder detector
|
|
209
205
|
const folderResult = (0, folder_detector_1.run)();
|
|
210
206
|
state.folderContext = (0, folder_detector_1.detectFolderContext)();
|
|
207
|
+
// Build the combined context injection block
|
|
211
208
|
const contextParts = [];
|
|
212
|
-
if (semesterResult.inject)
|
|
209
|
+
if (semesterResult.inject) {
|
|
213
210
|
contextParts.push(semesterResult.inject);
|
|
214
|
-
|
|
211
|
+
}
|
|
212
|
+
if (folderResult.inject) {
|
|
215
213
|
contextParts.push(folderResult.inject);
|
|
214
|
+
}
|
|
215
|
+
// Inject config-derived hints
|
|
216
216
|
contextParts.push(buildConfigInjection(config));
|
|
217
217
|
return {
|
|
218
218
|
contextInjection: contextParts.filter(Boolean).join("\n\n"),
|
|
@@ -223,22 +223,20 @@ function onStartup(state) {
|
|
|
223
223
|
/**
|
|
224
224
|
* onDirectoryChange — called when the user `cd`s to a new directory.
|
|
225
225
|
* Re-runs context detectors so Pi stays accurate.
|
|
226
|
-
* Returns empty result silently when outside a .pisces workspace.
|
|
227
226
|
*/
|
|
228
227
|
function onDirectoryChange(state) {
|
|
229
|
-
if (!(0, workspace_1.findPiscesMarker)()) {
|
|
230
|
-
return { contextInjection: "", message: null };
|
|
231
|
-
}
|
|
232
228
|
const semesterResult = (0, semester_detector_1.run)();
|
|
233
229
|
const folderResult = (0, folder_detector_1.run)();
|
|
234
230
|
const newFolderContext = (0, folder_detector_1.detectFolderContext)();
|
|
235
231
|
const previousProject = state.folderContext?.project?.name;
|
|
236
232
|
const newProject = newFolderContext?.project?.name;
|
|
233
|
+
// Update session state
|
|
237
234
|
state.folderContext = newFolderContext;
|
|
238
235
|
if ((0, semester_detector_1.detectSemesterContext)().context) {
|
|
239
236
|
state.semesterContext = (0, semester_detector_1.detectSemesterContext)().context ?? null;
|
|
240
237
|
}
|
|
241
238
|
const contextParts = [semesterResult.inject, folderResult.inject].filter(Boolean);
|
|
239
|
+
// Notify if we've moved into a different project
|
|
242
240
|
const message = newProject && newProject !== previousProject
|
|
243
241
|
? `📂 Switched to project: **${newProject}** (${newFolderContext.project?.type ?? "unknown"})`
|
|
244
242
|
: null;
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aethrekh/pi-cs",
|
|
3
|
-
"version": "0.2.0-
|
|
3
|
+
"version": "0.2.0-beta.0",
|
|
4
4
|
"description": "Pisces — The CS Student Edition for Pi Coding Agent. Your personal AI teaching assistant from homework to thesis.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"academic",
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
},
|
|
33
33
|
"pi": {
|
|
34
34
|
"extensions": [
|
|
35
|
-
"./extensions/workspace.js",
|
|
36
35
|
"./extensions/semester-detector.js",
|
|
37
36
|
"./extensions/folder-detector.js",
|
|
38
37
|
"./extensions/integrity-guard.js",
|
package/pi-cs.meta.json
CHANGED
package/pi-package.yaml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: pi-cs
|
|
2
|
-
version: 0.2.0-
|
|
2
|
+
version: 0.2.0-beta.0
|
|
3
3
|
display_name: "pi-cs (Pisces)"
|
|
4
4
|
description: "The CS Student Edition for Pi — Your personal AI teaching assistant for Computer Science, from homework to thesis."
|
|
5
5
|
icon: public/pisces-logo-512.png
|
|
@@ -7,13 +7,43 @@ author: ""
|
|
|
7
7
|
license: MIT
|
|
8
8
|
pi_version: ">=1.0.0"
|
|
9
9
|
|
|
10
|
+
# Core system prompt that defines the academic persona
|
|
11
|
+
system: SYSTEM.md
|
|
12
|
+
|
|
13
|
+
# Skills (slash commands) registered by this package
|
|
14
|
+
skills:
|
|
15
|
+
- name: homework
|
|
16
|
+
path: skills/homework/SKILL.md
|
|
17
|
+
description: "Break down problems, provide guided hints, and walk through step-by-step implementation"
|
|
18
|
+
- name: project
|
|
19
|
+
path: skills/project/SKILL.md
|
|
20
|
+
description: "Full project kickoff: requirements → architecture → tech stack → scaffolding"
|
|
21
|
+
- name: review
|
|
22
|
+
path: skills/review/SKILL.md
|
|
23
|
+
description: "Strict TA-level code review covering style, efficiency, edge cases, and security"
|
|
24
|
+
- name: explain
|
|
25
|
+
path: skills/explain/SKILL.md
|
|
26
|
+
description: "Concept explanations with analogies, Mermaid diagrams, and connections to prior knowledge"
|
|
27
|
+
- name: leetcode
|
|
28
|
+
path: skills/leetcode/SKILL.md
|
|
29
|
+
description: "Problem parsing, optimized solutions with time/space complexity analysis"
|
|
30
|
+
- name: exam
|
|
31
|
+
path: skills/exam/SKILL.md
|
|
32
|
+
description: "Practice questions, mind maps, and revision plans for upcoming exams"
|
|
33
|
+
- name: research
|
|
34
|
+
path: skills/research/SKILL.md
|
|
35
|
+
description: "Paper summarization, arXiv search, and literature review assistance"
|
|
36
|
+
- name: semester
|
|
37
|
+
path: skills/semester/SKILL.md
|
|
38
|
+
description: "Manage semester context: scaffold a new SEMESTER.md (--init), update fields (--update), or inspect what Pisces detects (--status)"
|
|
39
|
+
|
|
10
40
|
# Extensions that run automatically in the background
|
|
11
41
|
extensions:
|
|
12
|
-
- name:
|
|
13
|
-
path: extensions/
|
|
14
|
-
description: "
|
|
42
|
+
- name: gatekeeper
|
|
43
|
+
path: extensions/gatekeeper.js
|
|
44
|
+
description: "Gates all Pisces resources behind .pisces workspace marker; manages /pisces command"
|
|
15
45
|
triggers:
|
|
16
|
-
-
|
|
46
|
+
- resources_discover
|
|
17
47
|
- name: semester-detector
|
|
18
48
|
path: extensions/semester-detector.js
|
|
19
49
|
description: "Auto-detects semester context from SEMESTER.md and folder structures"
|
package/extensions/workspace.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* workspace.ts
|
|
4
|
-
*
|
|
5
|
-
* Walk-up logic for .pisces workspace marker detection.
|
|
6
|
-
* Shared by all four extensions — each calls findPiscesMarker()
|
|
7
|
-
* independently at session_start and before_agent_start.
|
|
8
|
-
*
|
|
9
|
-
* Registered as a Pi extension so that the pack includes workspace.js
|
|
10
|
-
* alongside the other extension files. The default export is a no-op;
|
|
11
|
-
* the module's value is its named exports (findPiscesMarker et al.).
|
|
12
|
-
*/
|
|
13
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
-
if (k2 === undefined) k2 = k;
|
|
15
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
-
}
|
|
19
|
-
Object.defineProperty(o, k2, desc);
|
|
20
|
-
}) : (function(o, m, k, k2) {
|
|
21
|
-
if (k2 === undefined) k2 = k;
|
|
22
|
-
o[k2] = m[k];
|
|
23
|
-
}));
|
|
24
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
-
}) : function(o, v) {
|
|
27
|
-
o["default"] = v;
|
|
28
|
-
});
|
|
29
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
-
var ownKeys = function(o) {
|
|
31
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
-
var ar = [];
|
|
33
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
-
return ar;
|
|
35
|
-
};
|
|
36
|
-
return ownKeys(o);
|
|
37
|
-
};
|
|
38
|
-
return function (mod) {
|
|
39
|
-
if (mod && mod.__esModule) return mod;
|
|
40
|
-
var result = {};
|
|
41
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
-
__setModuleDefault(result, mod);
|
|
43
|
-
return result;
|
|
44
|
-
};
|
|
45
|
-
})();
|
|
46
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
-
exports.PISCES_MARKER = void 0;
|
|
48
|
-
exports.default = default_1;
|
|
49
|
-
exports.findPiscesMarker = findPiscesMarker;
|
|
50
|
-
exports.createPiscesMarker = createPiscesMarker;
|
|
51
|
-
exports.removePiscesMarker = removePiscesMarker;
|
|
52
|
-
const fs = __importStar(require("fs"));
|
|
53
|
-
const path = __importStar(require("path"));
|
|
54
|
-
const os = __importStar(require("os"));
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
56
|
-
function default_1(_pi) {
|
|
57
|
-
// Shared utility module — not a standalone extension.
|
|
58
|
-
// Pi loads this file; the named exports are imported by the other extensions.
|
|
59
|
-
}
|
|
60
|
-
exports.PISCES_MARKER = ".pisces";
|
|
61
|
-
const MAX_WALK_LEVELS = 15;
|
|
62
|
-
/**
|
|
63
|
-
* Walks up from cwd looking for a .pisces file. Returns the absolute
|
|
64
|
-
* path of the directory containing .pisces (the workspace root), or null
|
|
65
|
-
* if not found within the search bounds.
|
|
66
|
-
*
|
|
67
|
-
* Bounds (whichever is reached first):
|
|
68
|
-
* - 15 directory levels above cwd
|
|
69
|
-
* - Home directory (~) — .pisces at ~ or above is not a valid workspace root
|
|
70
|
-
* - Filesystem root (/)
|
|
71
|
-
*/
|
|
72
|
-
function findPiscesMarker(cwd = process.cwd()) {
|
|
73
|
-
const home = path.resolve(os.homedir());
|
|
74
|
-
let current = path.resolve(cwd);
|
|
75
|
-
for (let level = 0; level < MAX_WALK_LEVELS; level++) {
|
|
76
|
-
// Stop at home or above — not a valid workspace root
|
|
77
|
-
if (current === home || !current.startsWith(home + path.sep))
|
|
78
|
-
break;
|
|
79
|
-
try {
|
|
80
|
-
if (fs.existsSync(path.join(current, exports.PISCES_MARKER))) {
|
|
81
|
-
return current;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
// skip unreadable directories
|
|
86
|
-
}
|
|
87
|
-
const parent = path.dirname(current);
|
|
88
|
-
if (parent === current)
|
|
89
|
-
break; // reached filesystem root
|
|
90
|
-
current = parent;
|
|
91
|
-
}
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Creates a .pisces marker in the given directory (default: cwd),
|
|
96
|
-
* marking it as a Pisces workspace root.
|
|
97
|
-
*/
|
|
98
|
-
function createPiscesMarker(dir = process.cwd()) {
|
|
99
|
-
fs.writeFileSync(path.join(path.resolve(dir), exports.PISCES_MARKER), "");
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Removes the .pisces marker from the given workspace root.
|
|
103
|
-
* Throws if the file does not exist.
|
|
104
|
-
*/
|
|
105
|
-
function removePiscesMarker(workspaceRoot) {
|
|
106
|
-
fs.unlinkSync(path.join(path.resolve(workspaceRoot), exports.PISCES_MARKER));
|
|
107
|
-
}
|
|
108
|
-
//# sourceMappingURL=workspace.js.map
|