Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Conductor skills are not re-vendored in this repository.

Use `.cursor/skills/conductor/SKILL.md` plus `conductor/index.md`. If a machine-level Conductor plugin is already installed, follow that protocol against this repo’s `conductor/` artifacts.
16 changes: 16 additions & 0 deletions .cursor/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"version": 1,
"hooks": {
"sessionStart": [
{
"command": "node .cursor/hooks/session-context.mjs"
}
],
"stop": [
{
"command": "node .cursor/hooks/stop-verify-reminder.mjs",
"loop_limit": 1
}
]
}
}
7 changes: 7 additions & 0 deletions .cursor/hooks/session-context.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node
process.stdout.write(
`${JSON.stringify({
additional_context:
"Read AGENTS.md and conductor/index.md before writing. This is a solo-maintainer repo: 0 required reviews, no CODEOWNERS. Fail-closed on live Substack writes. After TypeScript changes run npm run verify:agent. Do not rewrite conductor/github-programme.json unless that is the task.",
})}\n`,
);
7 changes: 7 additions & 0 deletions .cursor/hooks/stop-verify-reminder.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node
process.stdout.write(
`${JSON.stringify({
user_message:
"If TypeScript changed, run npm run verify:agent (typecheck + test + inspect examples/basic.md). Do not commit unless asked.",
})}\n`,
);
13 changes: 13 additions & 0 deletions .cursor/rules/secrets-and-substack.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
description: Secrets handling and fail-closed Substack writes
globs: "**/{src/publish,src/browser,src/auth,src/substack-api,src/config}/**/*.{ts,js}"
alwaysApply: false
---

# Secrets and Substack writes

- Never commit `.env`, cookies, `substack.sid`, Browserbase keys, or `.substack-cli/`.
- Redact credentials in CLI, MCP, logs, and fixtures. Prefer `redact()` helpers.
- Default tests use mocks/fixtures. Do not call live Substack in `npm test` or PR CI.
- Publish and schedule require `--yes`. Dry-run first. Missing confirmation must throw.
- E2E against a real publication is `workflow_dispatch` only and needs owner secrets.
14 changes: 14 additions & 0 deletions .cursor/rules/solo-maintainer.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
description: Solo-maintainer merge and review policy for this repository
alwaysApply: true
---

# Solo maintainer

- One maintainer may merge. Do not add CODEOWNERS, required reviewers, or team assignment.
- Automated status checks are the merge gate. Required approving review count is 0.
- Fail-closed on live Substack writes, npm publish, and registry submissions. Require `--yes` / explicit authorization and a receipt.
- Prefer this repo’s `AGENTS.md` and `conductor/`. Ignore parent `careops-finance` AGENTS.md data-boundary rules.
- Do not rewrite `conductor/github-programme.json` or Project #38 nesting unless that track is the task.
- Leave unrelated dirty work alone. Do not commit secrets or `.substack-cli/`.
- PowerShell: no `&&`; use `;`. Quote git `@{u}`.
14 changes: 14 additions & 0 deletions .cursor/rules/typescript-esm.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
description: TypeScript ESM conventions for the Substack CLI
globs: "**/*.{ts,mts,js,mjs}"
alwaysApply: false
---

# TypeScript ESM

- `type: module`. Use `.js` specifiers in relative imports.
- Keep `strict` TypeScript. Do not add `any` to silence parser or API types.
- Command handlers in `src/cli.ts` stay thin; put logic in `src/parser`, `src/config`, `src/publish`, `src/substack-api`, or `src/browser`.
- Tests are Vitest `*.test.ts` next to the module, or under `src/test/assurance/` for taxonomy suites.
- After TypeScript changes run `npm run verify:agent` (typecheck, test, inspect `examples/basic.md`).
- Do not add Camoufox/optional browser installs to default paths.
20 changes: 20 additions & 0 deletions .cursor/skills/conductor/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: conductor
description: Use Conductor context in this repository. Start at conductor/index.md for tracks, specs, and plans. Use when the user mentions Conductor, tracks, conductor-implement, or programme work.
---

# Conductor (repo pointer)

Do not re-vendor the Conductor plugin here. The maintainer already has Conductor skills installed.

## Before changing product code

1. Read `conductor/index.md` and follow its links.
2. Resolve the active track from `conductor/tracks.md` or `conductor/tracks/`.
3. Read that track’s `spec.md` / `plan.md` / `metadata.json` only after the path is verified.
4. Follow `conductor/workflow.md`. Mark tasks `[~]` while in progress and `[x]` only with evidence.
5. Leave `conductor/github-programme.json` and Project #38 nesting to the programme agent unless this task owns them.

## Codex / Cursor

If a full Conductor skill is available on the machine (for example under `~/.codex/skills/conductor/`), follow that protocol. This file only binds agents to this repo’s `conductor/` artifacts.
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ body:
options: [Local read-only, Local mutation, Browser or authentication, Live publication write, Security or privacy, Release or registry]
validations:
required: true
- type: input
id: conductor
attributes:
label: Conductor track or issue
description: Track ID (for example T10-01) or existing issue number. Leave blank for unplanned requests.
- type: textarea
id: alternatives
attributes:
Expand Down
12 changes: 10 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ Describe the focused change and why it is needed.

## Validation

List the exact commands and hosted checks used as evidence.
List the exact commands and hosted checks used as evidence. Human approval is not required.

- [ ] The one-command quality gate or a documented scoped equivalent passed.
```powershell
npm run verify:agent
```

Scoped alternative (say why the full gate was deferred):

- [ ] `npm run verify:agent` or a documented scoped equivalent passed.
- [ ] Tests cover the changed behavior and relevant regression risks.
- [ ] Generated artifacts, contracts, and documentation are synchronized.
- [ ] All actionable workflow, review, and issue comments have been addressed.

Required reviewers: **0**. Do not add CODEOWNERS or a review count.
4 changes: 4 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
],
"labels": ["dependencies", "renovate"],
"dependencyDashboard": true,
"dependencyDashboardTitle": "Renovate Dependency Dashboard",
"dependencyDashboardLabels": ["dependencies", "renovate"],
"rangeStrategy": "pin",
"ignorePaths": ["vendor/**"],
"rebaseWhen": "behind-base-branch",
"prCreation": "immediate",
"prConcurrentLimit": 5,
"automerge": true,
"automergeType": "pr",
"platformAutomerge": true,
"ignoreTests": false,
"packageRules": [
{
"description": "Follow selected mainline prerelease channels",
Expand Down
37 changes: 37 additions & 0 deletions .github/security-insights.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
header:
schema-version: "1.0.0"
last-updated: "2026-08-11"
last-reviewed: "2026-08-11"
url: https://github.com/edithatogo/substack-cli-ts
comment: |
Solo-maintainer TypeScript CLI. Automated checks replace required human review.
Live Substack writes stay fail-closed.

project:
name: substack-cli-ts
administrators:
- name: edithatogo
affiliation: maintainer
primary: true
repositories:
- name: edithatogo/substack-cli-ts
url: https://github.com/edithatogo/substack-cli-ts
comment: Canonical source

vulnerability-reporting:
reports-accepted: true
bug-bounty-available: false
security-policy: https://github.com/edithatogo/substack-cli-ts/security/policy
comment: Use GitHub private vulnerability reporting. Do not file public issues.

security-artifacts:
- type: security-policy
url: https://github.com/edithatogo/substack-cli-ts/blob/master/SECURITY.md
- type: license
url: https://github.com/edithatogo/substack-cli-ts/blob/master/LICENSE
- type: scorecard
url: https://github.com/edithatogo/substack-cli-ts/security/code-scanning

dependencies:
third-party-packages: true
dependencies-comments: Renovate is the dependency-update bot. Dependabot PRs are disabled.
2 changes: 2 additions & 0 deletions .github/workflows/auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
# Waits for required status checks only. Codecov is informational and
# must not be required, or SKIPPED uploads block Renovate automerge.
gh pr merge "$PR_URL" --auto --squash
26 changes: 25 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ jobs:
name: Quality
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write

steps:
- name: Checkout
Expand Down Expand Up @@ -84,8 +87,11 @@ jobs:
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
use_oidc: true
files: ./coverage/lcov.info
# Protected-branch uploads require a token; do not fail PR Quality on missing token.
flags: quality
name: quality
# Token or OIDC; do not fail PR Quality on a missing/skipped upload.
fail_ci_if_error: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}

- name: Production Audit
Expand All @@ -94,6 +100,24 @@ jobs:
- name: Secret Pattern Scan
run: npm run scan:secrets

fuzz:
name: Fuzz
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 26.5.1
cache: npm
- run: npm ci
- name: Bounded parser, config, and schema fuzz
env:
FUZZ_RUNS: "80"
run: npm run test:fuzz

smoke:
name: Smoke
runs-on: ubuntu-latest
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/hardening.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,25 @@ jobs:
path: reports/sbom
if-no-files-found: error

fuzz-extended:
name: Extended Fuzz
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 26.5.1
cache: npm
- run: npm ci
- name: Longer parser and schema fuzz
env:
FUZZ_RUNS: "400"
run: npm run test:fuzz

frontier-drift:
name: Frontier Drift Monitor
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
Expand Down
96 changes: 62 additions & 34 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,80 @@
# Repository Guidelines

## Project Structure & Module Organization
This is `edithatogo/substack-cli-ts`, a solo-maintainer TypeScript CLI and MCP server for publishing local Markdown to a user-owned Substack publication.

This repository contains a TypeScript CLI scaffold for publishing local Markdown files to a user-owned Substack publication.
**Context conflict:** `OneDrive - Flinders/AGENTS.md` belongs to a different repository (`careops-finance`). Do not apply that file’s data-boundary, review, or finance-track rules here. Prefer this file, `conductor/index.md`, and verifiable evidence in this repo.

- `conductor.json` declares the setup and run entrypoints used by Conductor.
- `bin/conductor-setup` prepares a workspace by linking shared secrets and installing Ruby and Node dependencies.
- `script/server` configures runtime ports and Redis isolation, then starts the app through `bin/dev`.
- `src/cli.ts` defines the command surface.
- `src/parser/` contains front matter, Markdown, and Tiptap/ProseMirror conversion.
- `src/browser/` and `src/publish/` hold browser-session and publishing workflow code.
- `examples/` contains sample Markdown inputs.
## Solo-maintainer contract

## Build, Test, and Development Commands
- One person may create, review, merge, and release.
- Do **not** add CODEOWNERS, required approving reviews, team assignment, or mandatory human gates.
- Automated checks replace reviewers. Required status checks stay on; review count stays `0`.
- Fail-closed on unconfirmed Substack writes. Never claim a live draft, publish, schedule, npm publish, or registry submission without a hosted receipt.
- Do not commit `.env`, tokens, `.substack-cli/`, cookies, storage-state, traces, screenshots, or private publication content.

- `npm install --omit=optional`: installs the core CLI dependencies without optional Camoufox packages.
- `npm run build`: compiles TypeScript to `dist/`.
- `npm run typecheck`: runs TypeScript checks without emitting files.
- `npm test`: builds and runs the parser tests.
- `node dist/cli.js inspect examples/basic.md`: prints generated HTML and ProseMirror JSON.
- `node dist/cli.js config show`: shows effective non-secret configuration.
- `node dist/cli.js config set-runtime local`: uses a local persistent Chrome profile under `.substack-cli/chrome-profile`.
- `node dist/cli.js auth status`: shows Browserbase and session readiness.
- `node dist/cli.js schema capture examples/basic.md --out fixtures/prosemirror/basic.json`: writes a parser fixture.
- `bin/conductor-setup`: links shared secrets when present, then runs dependency installation.
## Read before writing

Run scripts from the repository root. On Windows, use a Bash-compatible shell for these scripts.
1. This file.
2. `conductor/index.md` and the active track under `conductor/tracks/` (or `conductor/tracks.md`).
3. `git status`, remotes, and the existing diff. Do not clobber unrelated dirty work.
4. `docs/quality-frontier.md` when changing tests, CI, Codecov, Renovate, or Scorecard.

## Coding Style & Naming Conventions
## Project structure

Use strict TypeScript, ESM imports, and explicit module boundaries. Keep command handlers thin and delegate parsing, config, browser, and publishing logic to separate modules. Shell scripts should use clear environment variable names, quote variable expansions, and prefer small, explicit setup steps.
- `src/cli.ts` — command surface. Keep handlers thin.
- `src/parser/` — front matter, Markdown, Tiptap/ProseMirror.
- `src/browser/` and `src/publish/` — browser session and publish workflow.
- `src/substack-api/` — HTTP transport. Mock or fixture in tests; no live network in default CI.
- `src/config/` — local non-secret config. Secrets come from env.
- `src/mcp/` — MCP server. Shared behavior lives in `src/` once.
- `src/test/assurance/` — taxonomy suites (property, DST, contract, metamorphic, agentic, fuzz).
- `examples/` and `fixtures/prosemirror/` — sample Markdown and frozen parser contracts.
- `conductor/` — product context, tracks, contracts. `conductor/github-programme.json` is owned by the GitHub programme track; do not rewrite it unless that track asks.

## Testing Guidelines
## Commands

Tests are written with Vitest. Name tests `*.test.ts` and keep them close to the module under test.
Run from the repository root. In PowerShell do not chain with `&&`; use `;`. npm scripts may still use `&&` (npm translates them on Windows).

Before changing parser or runtime behavior, verify:
| Intent | Command |
| --- | --- |
| Install (no Camoufox) | `npm install --omit=optional` or `npm ci` |
| Agent PR gate | `npm run verify:agent` |
| Typecheck | `npm run typecheck` |
| Unit + default Vitest | `npm test` / `npm run test:unit` |
| Coverage | `npm run test:coverage` |
| Inspect example | `node dist/cli.js inspect examples/basic.md` |
| Taxonomy / assurance | `npm run test:assurance` |
| Fuzz (bounded) | `npm run test:fuzz` |
| Mutation | `npm run test:mutation` |
| Full local quality | `npm run quality` |
| Lint / format | `npm run ci` |

- `npm run typecheck`
- `npm test`
- `node dist/cli.js inspect examples/basic.md`
Do not start optional Camoufox or live Substack logins unless the user explicitly asks.

## Commit & Pull Request Guidelines
## Coding style

This directory does not currently include Git history, so no local commit convention can be inferred. Use short, imperative commit subjects such as `Add Conductor server hook` or `Document workspace setup`.
Strict TypeScript, ESM only, explicit module boundaries. Parser, config, browser, and publish stay separate. Command handlers delegate. Redact secrets on every output path.

Pull requests should include a concise summary, commands run to validate the change, any required environment variables, and notes about changes to browser automation, generated payloads, or secret handling.
## Testing

## Security & Configuration Tips
Tests are Vitest `*.test.ts` next to modules, plus `src/test/assurance/` for the taxonomy. Each modality has an npm script and CI wiring. See `test/testing-taxonomy.json` and `docs/quality-frontier.md`.

Do not commit `.env`, `config/master.key`, `.substack-cli/`, browser storage-state files, credentials, traces, screenshots, or generated dependency directories. `SUBSTACK_EMAIL` and `SUBSTACK_PASSWORD` are supported for `auth login --auto-login`, but must remain in ignored local config only. Use `.env.example` for documented variables only.
Before changing parser or runtime behavior:

1. `npm run typecheck`
2. `npm test`
3. `node dist/cli.js inspect examples/basic.md`

Or `npm run verify:agent`.

## Conductor

Use Conductor tracks for planned work. Start at `conductor/index.md`. Implementation marks the active task `[~]`, follows `conductor/workflow.md`, and marks `[x]` only with evidence. Do not invent a parallel planning system. Do not fight in-flight GitHub Project #38 / issue-nesting work.

## Commits and pull requests

Short imperative subjects. PRs need a summary, exact validation commands, contract/track IDs when applicable, and a secrets/live-write checkbox. Human approval is not required.

## Security

`SUBSTACK_EMAIL`, `SUBSTACK_PASSWORD`, `SUBSTACK_COOKIE`, and Browserbase keys stay in ignored local config. Use `.env.example` for documented names only. Live publish/schedule requires `--yes` after `--dry-run`. Default CI must not hit Substack.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
[![npm](https://img.shields.io/npm/v/%40edithatogo%2Fsubstack-cli)](https://www.npmjs.com/package/@edithatogo/substack-cli)
[![npm downloads](https://img.shields.io/npm/dm/%40edithatogo%2Fsubstack-cli)](https://www.npmjs.com/package/@edithatogo/substack-cli)
[![codecov](https://codecov.io/gh/edithatogo/substack-cli-ts/branch/master/graph/badge.svg)](https://codecov.io/gh/edithatogo/substack-cli-ts)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/edithatogo/substack-cli-ts/badge)](https://securityscorecards.dev/viewer/?uri=github.com/edithatogo/substack-cli-ts)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
[![Node](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org)
[![TypeScript](https://img.shields.io/badge/typescript-5.9-blue)](https://www.typescriptlang.org)
[![Renovate](https://img.shields.io/badge/renovate-enabled-brightgreen?logo=renovate)](https://github.com/edithatogo/substack-cli-ts/network/updates)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](CONTRIBUTING.md)
Expand Down
Loading
Loading