@refactco/refact-os 1.5.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 +60 -0
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/bin/refact-os.js +154 -0
- package/lib/adapters.js +302 -0
- package/lib/company.js +76 -0
- package/lib/frontmatter.js +30 -0
- package/lib/migrate.js +116 -0
- package/lib/project-utils.js +179 -0
- package/lib/refact-config.js +324 -0
- package/lib/scaffold.js +329 -0
- package/lib/validate.js +145 -0
- package/package.json +46 -0
- package/templates/base/AGENTS.md +9 -0
- package/templates/base/CLAUDE.md +3 -0
- package/templates/base/README.md +54 -0
- package/templates/base/agent/AGENTS.md +60 -0
- package/templates/base/agent/CLAUDE.md +7 -0
- package/templates/base/agent/claude-hooks.json +32 -0
- package/templates/base/agent/hooks/claude-sync-transcript.py +236 -0
- package/templates/base/agent/hooks/preflight-metadata.mjs +202 -0
- package/templates/base/agent/hooks/send-transcript-to-remote-server.py +238 -0
- package/templates/base/agent/hooks/sync-chat-transcript.py +188 -0
- package/templates/base/agent/hooks.json +29 -0
- package/templates/base/agent/scripts/import-project-chat-history.py +196 -0
- package/templates/base/agent/scripts/sync-asana.mjs +408 -0
- package/templates/base/agent/skills/adopt/SKILL.md +46 -0
- package/templates/base/agent/skills/close-ticket/SKILL.md +31 -0
- package/templates/base/agent/skills/extract-learnings/SKILL.md +90 -0
- package/templates/base/agent/skills/git-it/SKILL.md +138 -0
- package/templates/base/agent/skills/import-chat-history/SKILL.md +85 -0
- package/templates/base/agent/skills/ingest-input/SKILL.md +43 -0
- package/templates/base/agent/skills/open-ticket/SKILL.md +36 -0
- package/templates/base/agent/skills/process-docs/SKILL.md +69 -0
- package/templates/base/agent/skills/project-status/SKILL.md +35 -0
- package/templates/base/agent/skills/project-status/scripts/scan-status.mjs +153 -0
- package/templates/base/agent/skills/refact/SKILL.md +139 -0
- package/templates/base/agent/skills/setup-project/SKILL.md +140 -0
- package/templates/base/agent/skills/sync-asana/SKILL.md +106 -0
- package/templates/base/agent/skills/update-canonical-record/SKILL.md +28 -0
- package/templates/base/agent/skills/update-package/SKILL.md +51 -0
- package/templates/base/docs/context/project.md +30 -0
- package/templates/base/docs/decisions.md +22 -0
- package/templates/base/docs/index.md +31 -0
- package/templates/base/docs/sources/raw/.gitkeep +0 -0
- package/templates/base/docs/task/.gitkeep +0 -0
- package/templates/base/env.example +14 -0
- package/templates/base/gitignore +34 -0
- package/templates/overlays/client/agent/skills/create-deliverable/SKILL.md +29 -0
- package/templates/overlays/client/docs/deliverables/.gitkeep +0 -0
- package/templates/overlays/code/agent/skills/add-codebase/SKILL.md +239 -0
- package/templates/overlays/code/agent/skills/code-development/SKILL.md +58 -0
- package/templates/overlays/code/agent/skills/code-development/references/gitflow.md +144 -0
- package/templates/overlays/nextjs/agent/skills/nextjs-dev/SKILL.md +93 -0
- package/templates/overlays/nextjs/agent/skills/setup-netlify-deploy/SKILL.md +143 -0
- package/templates/overlays/nextjs/agent/skills/setup-nextjs-app/SKILL.md +118 -0
- package/templates/overlays/nextjs/agent/skills/setup-vercel-deploy/SKILL.md +116 -0
- package/templates/overlays/wordpress/agent/skills/install-wp-skills/SKILL.md +130 -0
- package/templates/overlays/wordpress/agent/skills/setup-kinsta-deploy/SKILL.md +201 -0
- package/templates/overlays/wordpress/agent/skills/wp-env/SKILL.md +478 -0
- package/templates/overlays/wordpress/wp-cli.yml.example +46 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-it
|
|
3
|
+
description: Initialize the git repo, make the first commit, and create the GitHub remote.
|
|
4
|
+
pattern: procedure
|
|
5
|
+
requires_approval: true
|
|
6
|
+
when_to_use: /refact git it | set up the repo | create the remote | first commit | publish to GitHub.
|
|
7
|
+
when_not_to_use: Feature work on an existing repo (use code-development).
|
|
8
|
+
next_skills: []
|
|
9
|
+
sub_agents: []
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Git It Reference
|
|
13
|
+
|
|
14
|
+
Use this reference when the user invokes `/refact git it` (or asks to "set up the repo", "create the remote", "make the first commit", "publish to GitHub").
|
|
15
|
+
|
|
16
|
+
## Goal
|
|
17
|
+
|
|
18
|
+
Get this project from "no git history" to "pushed to a fresh GitHub repo" with one guided flow:
|
|
19
|
+
1. Make sure `gh` is installed and authenticated.
|
|
20
|
+
2. Ask the user 4 short questions.
|
|
21
|
+
3. `git init` if needed, make the first commit if there isn't one, `gh repo create`, push.
|
|
22
|
+
|
|
23
|
+
## Step 1 — Prerequisites
|
|
24
|
+
|
|
25
|
+
### 1a. Check for the GitHub CLI
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
command -v gh
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If it's missing, **detect the OS** and **show the right install command**, then **ask the user for permission** before running it. Never run install commands autonomously (especially anything with `sudo`).
|
|
32
|
+
|
|
33
|
+
| OS | Suggested install |
|
|
34
|
+
|---|---|
|
|
35
|
+
| macOS (Homebrew) | `brew install gh` |
|
|
36
|
+
| Debian / Ubuntu | follow https://cli.github.com/manual/installation (uses an apt repository, requires sudo) |
|
|
37
|
+
| Fedora / RHEL | `sudo dnf install gh` |
|
|
38
|
+
| Arch | `sudo pacman -S github-cli` |
|
|
39
|
+
| Windows (winget) | `winget install --id GitHub.cli` |
|
|
40
|
+
|
|
41
|
+
Detect OS with `uname -s` (Darwin / Linux) and, on Linux, `cat /etc/os-release` for the distro.
|
|
42
|
+
|
|
43
|
+
If the user declines or you can't auto-install, stop here and ask them to install `gh` and re-run `/refact git it`.
|
|
44
|
+
|
|
45
|
+
### 1b. Check authentication
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
gh auth status
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If it reports "not logged in" or fails: stop and tell the user to run `gh auth login` themselves — it's an interactive OAuth flow you can't drive for them. Once they confirm they've logged in, continue.
|
|
52
|
+
|
|
53
|
+
## Step 2 — Ask the user 4 questions
|
|
54
|
+
|
|
55
|
+
Ask one at a time; accept the suggested default if the user just confirms.
|
|
56
|
+
|
|
57
|
+
### Q1: Project name
|
|
58
|
+
|
|
59
|
+
- **Default suggestion:** humanize the current directory name. e.g. `flower-shop` → "Flower Shop".
|
|
60
|
+
- Use this for the eventual repo description and the README's H1 if it still has `<TODO: project name>`.
|
|
61
|
+
|
|
62
|
+
### Q2: Slug (repo name on GitHub)
|
|
63
|
+
|
|
64
|
+
- **Default suggestion:** lowercase the project name, replace any non-alphanumeric run with a single `-`, strip leading/trailing `-`.
|
|
65
|
+
- The slug must be valid for GitHub (`^[a-zA-Z0-9._-]+$`). If it isn't, suggest a corrected one and re-ask.
|
|
66
|
+
|
|
67
|
+
### Q3: Visibility
|
|
68
|
+
|
|
69
|
+
- **Default: `private`.**
|
|
70
|
+
- Other valid choice: `public`.
|
|
71
|
+
- Don't offer `internal` unless the user specifically asks for it.
|
|
72
|
+
|
|
73
|
+
### Q4: Owner
|
|
74
|
+
|
|
75
|
+
Run:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
gh api user/orgs --jq '.[].login'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- If the list is empty → owner is the authenticated user. Get it with `gh api user --jq '.login'`. No question needed; just confirm.
|
|
82
|
+
- If the list has one or more orgs → present `<user>, <org1>, <org2>, …` and ask which one. Default to the personal account.
|
|
83
|
+
|
|
84
|
+
## Step 3 — Execute
|
|
85
|
+
|
|
86
|
+
Run the steps below in order. Stop on the first error and surface it; do not retry destructively.
|
|
87
|
+
|
|
88
|
+
### 3a. Initialize git if needed
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
test -d .git || git init -b main
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If `.git` already exists, leave the current branch alone.
|
|
95
|
+
|
|
96
|
+
### 3b. Ensure a first commit exists
|
|
97
|
+
|
|
98
|
+
Check:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
git rev-parse --verify HEAD 2>/dev/null
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
If that fails (no commits yet):
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git add -A
|
|
108
|
+
git commit -m "chore: initial commit (refact-os scaffold)"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
If commits already exist, **do not** create a synthetic "initial" commit on top — just continue.
|
|
112
|
+
|
|
113
|
+
### 3c. Create the remote and push
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
gh repo create <owner>/<slug> --<visibility> --source=. --remote=origin --push --description "<humanized project name>"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- `<visibility>` is literally `--private` or `--public`.
|
|
120
|
+
- If a remote named `origin` already exists locally, do **not** overwrite it. Stop and ask the user.
|
|
121
|
+
- If `gh` reports the slug is taken on the chosen owner, surface the exact error and ask for a different slug. Do not invent a fallback name yourself.
|
|
122
|
+
|
|
123
|
+
### 3d. Report
|
|
124
|
+
|
|
125
|
+
Print:
|
|
126
|
+
|
|
127
|
+
- Remote URL: `gh repo view --json url --jq .url`
|
|
128
|
+
- First commit hash + subject: `git log -1 --format='%h %s'`
|
|
129
|
+
- Two suggested next steps: "invite collaborators with `gh repo edit --add-collaborator …`" and "set branch protection in the GitHub UI".
|
|
130
|
+
|
|
131
|
+
## Guardrails
|
|
132
|
+
|
|
133
|
+
- **Never** run `sudo` autonomously. Always ask permission first.
|
|
134
|
+
- **Never** force-push. If `git push` fails, stop and surface the error.
|
|
135
|
+
- **Never** overwrite an existing `origin` remote.
|
|
136
|
+
- **Never** commit `.env`, `*.pem`, `*.key`, or anything matched by `.gitignore`. The generated `.gitignore` covers these, but verify by inspecting `git status` before the first commit.
|
|
137
|
+
- **Never** invent the slug or organization on the user's behalf — always show the suggestion and let them confirm or override.
|
|
138
|
+
- If the working tree has nothing to commit AND no prior commits exist, stop and ask the user to add something first — `gh repo create --source=.` requires a non-empty initial commit.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: import-chat-history
|
|
3
|
+
description: Import the project's Claude Code / Cursor chat history into docs/sources/raw/agent-transcripts/.
|
|
4
|
+
pattern: procedure
|
|
5
|
+
when_to_use: /refact get chat history | import chats.
|
|
6
|
+
when_not_to_use: Saving brand-new inbound material (use ingest-input) — this is only for agent chat logs.
|
|
7
|
+
next_skills:
|
|
8
|
+
- process-docs
|
|
9
|
+
sub_agents: []
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Chat History Reference
|
|
13
|
+
|
|
14
|
+
Backfill this project's agent chats into:
|
|
15
|
+
|
|
16
|
+
- `docs/sources/raw/agent-transcripts/`
|
|
17
|
+
|
|
18
|
+
Use this reference when handling requests such as `/refact get chat history` or `/refact import chats`.
|
|
19
|
+
|
|
20
|
+
> New **Claude Code** chats are mirrored here automatically by the
|
|
21
|
+
> `claude-sync-transcript` hook (on every Stop). This script is for backfilling
|
|
22
|
+
> history that predates the hook, and for importing **Cursor** chats. It is
|
|
23
|
+
> local-only — nothing is sent to the remote server.
|
|
24
|
+
|
|
25
|
+
## Script
|
|
26
|
+
|
|
27
|
+
- `.claude/scripts/import-project-chat-history.py` (or `.cursor/scripts/…` — identical copies)
|
|
28
|
+
|
|
29
|
+
## Default behavior
|
|
30
|
+
|
|
31
|
+
With `--tool auto` (the default) the script imports from whichever sources exist
|
|
32
|
+
for this repo, auto-detecting both:
|
|
33
|
+
|
|
34
|
+
- Claude Code: `~/.claude/projects/<encoded-cwd>/*.jsonl` (full native transcripts;
|
|
35
|
+
`<encoded-cwd>` is the absolute repo path with `/` and `.` replaced by `-`)
|
|
36
|
+
- Cursor: `~/.cursor/projects/<project-key>/agent-transcripts/*.jsonl`
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
From project root:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm run chats:import
|
|
44
|
+
# or:
|
|
45
|
+
python3 .claude/scripts/import-project-chat-history.py
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Restrict to one tool:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python3 .claude/scripts/import-project-chat-history.py --tool claude
|
|
52
|
+
python3 .claude/scripts/import-project-chat-history.py --tool cursor
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Dry run:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm run chats:import:dry
|
|
59
|
+
# or:
|
|
60
|
+
python3 .claude/scripts/import-project-chat-history.py --dry-run
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Custom source (overrides auto-detection):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python3 .claude/scripts/import-project-chat-history.py --source "/absolute/path/to/transcripts"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Custom owner for generated meta files:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python3 .claude/scripts/import-project-chat-history.py --owner "Owner Name"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Optional environment variables
|
|
76
|
+
|
|
77
|
+
- `CLAUDE_PROJECT_TRANSCRIPTS_DIR`: override the Claude Code source directory.
|
|
78
|
+
- `CURSOR_PROJECT_TRANSCRIPTS_DIR`: override the Cursor source directory.
|
|
79
|
+
- `REFACT_CHAT_OWNER` / `CURSOR_CHAT_OWNER`: default owner for generated `.meta.json` files.
|
|
80
|
+
|
|
81
|
+
## What it imports
|
|
82
|
+
|
|
83
|
+
- Copies all `*.jsonl` chat transcript files from the detected source(s) to the destination.
|
|
84
|
+
- Updates files only when content changed (SHA-256 compare).
|
|
85
|
+
- Creates `<chat-id>.meta.json` when missing (includes `session_id`, `owner`, `tool`, and source info).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ingest-input
|
|
3
|
+
description: Classify and save any inbound material (email, transcript, deck, file, RFP, chat) into docs/sources/raw/ with a dated filename and a small frontmatter header, then suggest the next move.
|
|
4
|
+
pattern: procedure
|
|
5
|
+
when_to_use: The user pastes or points at new inbound material — "here's an email from the client", "save this transcript", "a new RFP came in", "add this file".
|
|
6
|
+
when_not_to_use: For material that already lives in docs/sources/raw/ (just read it), or when the user wants curated truth updated (use update-canonical-record), or to open a ticket (use open-ticket).
|
|
7
|
+
inputs:
|
|
8
|
+
- the pasted or referenced inbound material
|
|
9
|
+
outputs:
|
|
10
|
+
- a dated file under docs/sources/raw/<class>/ with a 3-line header
|
|
11
|
+
next_skills:
|
|
12
|
+
- open-ticket # if the material implies trackable work
|
|
13
|
+
- update-canonical-record # if it changes curated truth
|
|
14
|
+
sub_agents: []
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Ingest Input
|
|
18
|
+
|
|
19
|
+
The universal entry point for any material arriving from outside the codebase. Capture it as Evidence *before* acting on it — agents work from saved files, not chat memory.
|
|
20
|
+
|
|
21
|
+
## Steps
|
|
22
|
+
|
|
23
|
+
1. **Classify the input type** and pick the destination + extension:
|
|
24
|
+
- email → `docs/sources/raw/email/<yyyy-mm-dd>-<slug>.email.md`
|
|
25
|
+
- meeting/call transcript → `docs/sources/raw/call-transcripts/<yyyy-mm-dd>-<slug>.transcript.md`
|
|
26
|
+
- agent chat history → `docs/sources/raw/agent-transcripts/` (usually synced by the hook; only save by hand if pasted)
|
|
27
|
+
- document / deck / RFP / misc file → `docs/sources/raw/<yyyy-mm-dd>-<slug>.<type>.md`
|
|
28
|
+
2. **Write the file** with a 3-line header:
|
|
29
|
+
```yaml
|
|
30
|
+
---
|
|
31
|
+
source: gmail | fathom | asana | other
|
|
32
|
+
added-by: <name or "agent">
|
|
33
|
+
processed: false
|
|
34
|
+
---
|
|
35
|
+
```
|
|
36
|
+
3. **Never edit** raw evidence after saving — it is received state.
|
|
37
|
+
4. **Detect the pattern** in the content (quote request, scope change, bug report, decision) and surface the relevant `next_skills` to the user.
|
|
38
|
+
|
|
39
|
+
## Hard rules
|
|
40
|
+
|
|
41
|
+
- Raw is evidence: write once, never rewrite.
|
|
42
|
+
- One file per item. Date-prefix the filename.
|
|
43
|
+
- Bucket by source class only when volume earns it; flat files under `raw/` are fine for light projects.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: open-ticket
|
|
3
|
+
description: Create a tracked ticket under docs/task/open/ with frontmatter, linking the source evidence that prompted it.
|
|
4
|
+
pattern: procedure
|
|
5
|
+
when_to_use: A piece of work needs tracking — a client request, a bug, a scoped task — and there isn't already an open ticket for it.
|
|
6
|
+
when_not_to_use: For closing a ticket (use close-ticket), or for work small enough to finish in the same turn without tracking.
|
|
7
|
+
inputs:
|
|
8
|
+
- the source evidence file under docs/sources/raw/ (if any)
|
|
9
|
+
outputs:
|
|
10
|
+
- docs/task/open/<yyyy-mm-dd>-<slug>.md with status frontmatter
|
|
11
|
+
next_skills: []
|
|
12
|
+
sub_agents: []
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Open Ticket
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
1. Create `docs/task/open/<yyyy-mm-dd>-<slug>.md`.
|
|
20
|
+
2. Add frontmatter:
|
|
21
|
+
```yaml
|
|
22
|
+
---
|
|
23
|
+
date: <yyyy-mm-dd>
|
|
24
|
+
status: open
|
|
25
|
+
description: <one line: what needs doing and why>
|
|
26
|
+
source: <path to docs/sources/raw/... if this came from inbound material>
|
|
27
|
+
---
|
|
28
|
+
```
|
|
29
|
+
3. Write a short body: the ask, acceptance criteria, and any links.
|
|
30
|
+
4. Tell the user the ticket path.
|
|
31
|
+
|
|
32
|
+
## Notes
|
|
33
|
+
|
|
34
|
+
- One markdown file per ticket. `docs/task/open/` holds active tickets.
|
|
35
|
+
- Status transitions (`open → in-progress`) are frontmatter, not folder moves.
|
|
36
|
+
- When the ticket closes, hand off to `close-ticket`.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: process-docs
|
|
3
|
+
description: Walk unprocessed files under docs/sources/raw/, integrate them into docs/context/, and flip the processed flag.
|
|
4
|
+
pattern: procedure
|
|
5
|
+
when_to_use: /refact process docs | ingest new emails | digest new inputs.
|
|
6
|
+
when_not_to_use: Saving material that hasn't been captured yet (use ingest-input first).
|
|
7
|
+
next_skills: []
|
|
8
|
+
sub_agents: []
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Process Docs Reference
|
|
12
|
+
|
|
13
|
+
Use this reference when the user invokes `/refact process-docs` (or asks to process / ingest / digest new docs).
|
|
14
|
+
|
|
15
|
+
## What "processing" means
|
|
16
|
+
|
|
17
|
+
Every file under `docs/` (except `agent-transcripts/`, which is for raw chat history) has a 3-line YAML header with a `processed` flag. "Processing" means reading an unprocessed file, integrating its information into `docs/context/`, and flipping the flag.
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
### 1. Find unprocessed files
|
|
22
|
+
|
|
23
|
+
Don't hand-walk folders to find them — "which files are unprocessed" is a deterministic question, so let a script answer it (a model eyeballing folders is right most times and silently off once). Run the shared scanner (the same one `project-status` uses) and read its `unprocessed.files` list:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
node agent/skills/project-status/scripts/scan-status.mjs --json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Every path in `unprocessed.files` is a `.md` file with `processed: false` somewhere under `docs/` (raw chat logs in `agent-transcripts/` are already excluded). That list is your work queue for the steps below — the *judgment* about what each file means is yours; the *enumeration* is not.
|
|
30
|
+
|
|
31
|
+
### 2. For each unprocessed file, decide what to update
|
|
32
|
+
|
|
33
|
+
Read the file. Then update one or more of these, as appropriate:
|
|
34
|
+
|
|
35
|
+
- **`docs/decisions.md`** — if the file records a finalized decision. Include the source file path under `docs/` as part of the **Data** field of the entry.
|
|
36
|
+
- **`docs/context/open-decisions.md`** — if the file raises a question, ambiguity, or request that needs a human's call. Tag the responsible person from `docs/context/people.md`. If the right person isn't in roles, ask the user.
|
|
37
|
+
- **`docs/context/people.md`** — if the file mentions a new person on either team. Append a bullet with name + role.
|
|
38
|
+
- **`docs/context/learnings.md`** — if the file contains a non-obvious project/customer preference or convention worth remembering.
|
|
39
|
+
|
|
40
|
+
A single file can update multiple `docs/context/` files. Some files may update none — if the content is purely informational and not actionable, no `docs/context/` change is needed, but you still flip the header (see step 3).
|
|
41
|
+
|
|
42
|
+
### 3. Flip the header
|
|
43
|
+
|
|
44
|
+
After processing, change the file's header from `processed: false` to `processed: true`. Leave `source` and `added-by` untouched.
|
|
45
|
+
|
|
46
|
+
### 4. Report
|
|
47
|
+
|
|
48
|
+
Print a concise summary at the end:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Processed N files.
|
|
52
|
+
|
|
53
|
+
decisions.md: +X entries
|
|
54
|
+
open-decisions.md: +Y entries
|
|
55
|
+
people.md: +Z entries
|
|
56
|
+
learnings.md: +W bullets
|
|
57
|
+
|
|
58
|
+
Files processed:
|
|
59
|
+
- docs/sources/raw/email/2026-05-09-customer-feedback.md
|
|
60
|
+
- docs/sources/raw/call-transcripts/2026-05-10-weekly-sync.md
|
|
61
|
+
- ...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Guardrails
|
|
65
|
+
|
|
66
|
+
- **Never** add an entry to `decisions.md` without including the source `docs/` file path as part of the **Data** field. Traceability is the whole point of that file.
|
|
67
|
+
- **Never** invent a person for `people.md`. If a name appears in a doc but the role is unclear, surface it as an open question instead.
|
|
68
|
+
- **Never** flip `processed: true` if you skipped the file due to an error — leave the flag as-is so it gets retried next time.
|
|
69
|
+
- If a file's content seems duplicated against an existing `docs/context/` entry, prefer **updating** the existing entry over adding a new one.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-status
|
|
3
|
+
description: Report what's unprocessed, open decisions and their owners, recent learnings, and unfilled placeholders.
|
|
4
|
+
pattern: procedure
|
|
5
|
+
when_to_use: /refact status | what's pending | what's unprocessed.
|
|
6
|
+
when_not_to_use: Making a change — this is read-only reporting.
|
|
7
|
+
next_skills: []
|
|
8
|
+
sub_agents: []
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Status Reference
|
|
12
|
+
|
|
13
|
+
Use this when the user invokes `/refact status` (or asks "what's the status of the project context?").
|
|
14
|
+
|
|
15
|
+
## How it works
|
|
16
|
+
|
|
17
|
+
The scan — counting unprocessed files, open decisions, and role placeholders, and pulling recent learnings — is **deterministic work, so a script does it**, not the model. Eyeballing folders and counting by hand is exactly the kind of mechanical task a model gets *plausibly* wrong (right most times, silently off once). Run:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
node agent/skills/project-status/scripts/scan-status.mjs
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
It prints a ready-to-show snapshot: unprocessed docs (grouped by folder), open decisions, recent learnings, and unfilled role placeholders. Add `--json` if you want to post-process the facts instead of showing them.
|
|
24
|
+
|
|
25
|
+
## What you do
|
|
26
|
+
|
|
27
|
+
1. Run the script from the repo root.
|
|
28
|
+
2. Present its output as the snapshot — it's already under ~20 lines and human-readable.
|
|
29
|
+
3. **Then** add the one thing the script can't: judgment. If something stands out — a pile of unprocessed inbound, a decision that's been open a long time, roles still unfilled — say so in a line and suggest the next move (`/refact process docs`, or resolving a specific decision). That interpretation is the only part of this skill that belongs to you.
|
|
30
|
+
|
|
31
|
+
## Scope
|
|
32
|
+
|
|
33
|
+
- This reports project **context** state, not repo health. Adapter drift, missing skill frontmatter, and structure checks are `refact-os validate`'s job — don't duplicate them here.
|
|
34
|
+
- Read-only. Don't modify any files.
|
|
35
|
+
- If the script reports a file as "not present," relay that — it's normal early in a project, since those `docs/context/` files are earned, not seeded.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Deterministic status scan for the project-status skill (`/refact status`).
|
|
3
|
+
//
|
|
4
|
+
// Counts unprocessed docs, open decisions, and role placeholders, and lists the
|
|
5
|
+
// most recent learnings — the mechanical facts the model must NOT eyeball-count
|
|
6
|
+
// (see the standard's "Latent vs. Deterministic Work"). The skill body runs this
|
|
7
|
+
// and adds interpretation on top; it does not re-derive these numbers by hand.
|
|
8
|
+
//
|
|
9
|
+
// node agent/skills/project-status/scripts/scan-status.mjs # text snapshot
|
|
10
|
+
// node agent/skills/project-status/scripts/scan-status.mjs --json # machine-readable
|
|
11
|
+
//
|
|
12
|
+
// Paths resolve relative to this file, so it works whether it's run from the
|
|
13
|
+
// canonical agent/ copy or a generated .cursor/ / .claude/ mirror. Repo-structure
|
|
14
|
+
// health (adapter drift, skill frontmatter) is `refact-os validate`'s job, not
|
|
15
|
+
// this script's — this reports project *context* state only.
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
|
|
21
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const root = path.resolve(scriptDir, "../../../..");
|
|
23
|
+
const docs = path.join(root, "docs");
|
|
24
|
+
const asJson = process.argv.includes("--json");
|
|
25
|
+
|
|
26
|
+
function read(p) {
|
|
27
|
+
try {
|
|
28
|
+
return fs.readFileSync(p, "utf8");
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function walk(dir, acc = []) {
|
|
35
|
+
let entries;
|
|
36
|
+
try {
|
|
37
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
38
|
+
} catch {
|
|
39
|
+
return acc;
|
|
40
|
+
}
|
|
41
|
+
for (const e of entries) {
|
|
42
|
+
const full = path.join(dir, e.name);
|
|
43
|
+
if (e.isDirectory()) {
|
|
44
|
+
if (e.name === "agent-transcripts") continue; // raw chat logs — never "processed"
|
|
45
|
+
walk(full, acc);
|
|
46
|
+
} else if (e.name.endsWith(".md")) {
|
|
47
|
+
acc.push(full);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return acc;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 1. Unprocessed docs: files whose frontmatter carries `processed: false`.
|
|
54
|
+
// `byFolder` powers the snapshot count; `files` is the exact list process-docs
|
|
55
|
+
// consumes so it never has to hand-walk folders either.
|
|
56
|
+
const unprocessed = {};
|
|
57
|
+
const unprocessedFiles = [];
|
|
58
|
+
for (const file of walk(docs)) {
|
|
59
|
+
const head = (read(file) || "").slice(0, 800);
|
|
60
|
+
if (/^processed:\s*false\s*$/m.test(head)) {
|
|
61
|
+
const label = path.basename(path.dirname(file));
|
|
62
|
+
unprocessed[label] = (unprocessed[label] || 0) + 1;
|
|
63
|
+
unprocessedFiles.push(path.relative(root, file));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const unprocessedTotal = unprocessedFiles.length;
|
|
67
|
+
|
|
68
|
+
// 2. Open decisions: dated bullet entries in docs/context/open-decisions.md.
|
|
69
|
+
const openDecRaw = read(path.join(docs, "context", "open-decisions.md"));
|
|
70
|
+
const openDecisions = [];
|
|
71
|
+
if (openDecRaw != null) {
|
|
72
|
+
for (const line of openDecRaw.split("\n")) {
|
|
73
|
+
const m = line.match(/^-\s+(\d{4}-\d{2}-\d{2}\b.*)$/);
|
|
74
|
+
if (m) openDecisions.push(m[1].trim());
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 3. Recent learnings: newest 3 bullets under "## Entries" (newest-first by convention).
|
|
79
|
+
const learnRaw = read(path.join(docs, "context", "learnings.md"));
|
|
80
|
+
const learnings = [];
|
|
81
|
+
if (learnRaw != null) {
|
|
82
|
+
const idx = learnRaw.indexOf("## Entries");
|
|
83
|
+
const body = idx >= 0 ? learnRaw.slice(idx) : learnRaw;
|
|
84
|
+
for (const line of body.split("\n")) {
|
|
85
|
+
const m = line.match(/^-\s+(.+)$/);
|
|
86
|
+
if (m) {
|
|
87
|
+
learnings.push(m[1].trim());
|
|
88
|
+
if (learnings.length >= 3) break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 4. Role placeholders: unfilled <TODO> markers in docs/context/people.md.
|
|
94
|
+
const peopleRaw = read(path.join(docs, "context", "people.md"));
|
|
95
|
+
const rolePlaceholders = peopleRaw == null ? null : (peopleRaw.match(/<TODO>/g) || []).length;
|
|
96
|
+
|
|
97
|
+
const missing = [
|
|
98
|
+
openDecRaw == null && "docs/context/open-decisions.md",
|
|
99
|
+
learnRaw == null && "docs/context/learnings.md",
|
|
100
|
+
peopleRaw == null && "docs/context/people.md",
|
|
101
|
+
].filter(Boolean);
|
|
102
|
+
|
|
103
|
+
if (asJson) {
|
|
104
|
+
process.stdout.write(
|
|
105
|
+
`${JSON.stringify(
|
|
106
|
+
{
|
|
107
|
+
unprocessed: { total: unprocessedTotal, byFolder: unprocessed, files: unprocessedFiles },
|
|
108
|
+
openDecisions: openDecRaw == null ? null : openDecisions,
|
|
109
|
+
recentLearnings: learnRaw == null ? null : learnings,
|
|
110
|
+
rolePlaceholders,
|
|
111
|
+
missing,
|
|
112
|
+
},
|
|
113
|
+
null,
|
|
114
|
+
2,
|
|
115
|
+
)}\n`,
|
|
116
|
+
);
|
|
117
|
+
} else {
|
|
118
|
+
const lines = [];
|
|
119
|
+
if (unprocessedTotal === 0) {
|
|
120
|
+
lines.push("Unprocessed: all docs processed.");
|
|
121
|
+
} else {
|
|
122
|
+
const parts = Object.entries(unprocessed).map(([k, v]) => `${v} ${k}`);
|
|
123
|
+
lines.push(`Unprocessed: ${parts.join(", ")} (${unprocessedTotal} total).`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (openDecRaw == null) {
|
|
127
|
+
lines.push("Open decisions: (docs/context/open-decisions.md not present).");
|
|
128
|
+
} else if (openDecisions.length === 0) {
|
|
129
|
+
lines.push("Open decisions: none.");
|
|
130
|
+
} else {
|
|
131
|
+
lines.push(`Open decisions: ${openDecisions.length}.`);
|
|
132
|
+
for (const d of openDecisions) lines.push(` - ${d}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (learnRaw == null) {
|
|
136
|
+
lines.push("Recent learnings: (docs/context/learnings.md not present).");
|
|
137
|
+
} else if (learnings.length === 0) {
|
|
138
|
+
lines.push("Recent learnings: none yet.");
|
|
139
|
+
} else {
|
|
140
|
+
lines.push("Recent learnings:");
|
|
141
|
+
for (const l of learnings) lines.push(` - ${l}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (peopleRaw == null) {
|
|
145
|
+
lines.push("Roles: (docs/context/people.md not present).");
|
|
146
|
+
} else if (rolePlaceholders > 0) {
|
|
147
|
+
lines.push(`Roles: ${rolePlaceholders} <TODO> placeholder(s) in people.md.`);
|
|
148
|
+
} else {
|
|
149
|
+
lines.push("Roles: all filled.");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
153
|
+
}
|