21 Commits

Author SHA1 Message Date
psmyrdek 4eb70c514c fix(list): hide lesson details for locked modules
The CLI was rendering real lesson titles and "TBD" summaries from
unreleased modules. Now renderModuleDetail returns lessons: [] in JSON
mode and a single "available after unlock" line in human mode when a
module is locked, regardless of what the API returns.

This is a client-side mask; the API still emits the data on the wire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:08:54 +02:00
psmyrdek 8c90cac07f fix(api-client): render API errors with friendly messages
The API ships {error: "<code>"} envelopes (e.g. "course_not_found"); we
were displaying that raw, or worse, "[object Object]" when callers tried
to print the payload. Map the 16 known codes (catalogued from the
delivery API routes) to human strings, prefer the optional `message`
field on auth routes, and fall back to a mentor-facing message for
unknown codes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 15:23:11 +02:00
psmyrdek f8859d1e28 feat(lesson-ref): allow module 0 for prework
Drop MIN_MODULE from 1 to 0 so parseLessonRef and parseModuleRef accept
m0/m0l1 (the 10xdevs3 prework module). Lesson numbers remain positive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 10:37:21 +02:00
“mkczarkowski” 6c89834a3e feat(windows-compatibility): Windsurf profile, tool-aware doctor, CLI guide skill (p4)
Add Windsurf tool profile with detection (.windsurfrules, .windsurf/).
Make doctor check the configured tool's directory instead of hardcoded
.claude/. Add 10x-cli-guide skill for OS- and tool-aware daily usage
guidance. Add Cursor and Windsurf MCP config templates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-09 15:10:11 +02:00
“mkczarkowski” fc20d322fb feat(cli): forward ?tool= in fetchLesson to API
Pass the resolved tool profile id through fetchLesson's options bag
so the API can substitute universalContent for non-Claude learners.
Mirrors the existing fetchArtifact precedent in the same file.

- src/lib/api-content.ts: add optional tool? to fetchLesson options;
  emit ?tool= when present
- src/commands/get.ts: pass profile.toolId at both fetchLesson call
  sites (install flow + print/filter flow)
- tests/helpers/api-content-mock.ts: widen fetchLessonImpl signature
- tests/get-command.test.ts: add three propagation tests

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 15:10:11 +02:00
“mkczarkowski” 3ce857259d feat(skill-directory-bundle): CLI print-mode multi-file notice (p5)
In --print human mode, a skill's SKILL.md still goes to stdout, but every
non-SKILL.md file under the skill directory is now surfaced on stderr as a
"Note: skill X has N additional files not shown in --print: ..." line so
students don't assume the print output is the entire skill. JSON mode is
unchanged (the full files[] array is already in the response).

Touched:
- src/commands/get.ts: emitMultiFileSkillNotice() called from both single-
  artifact (--name) and multi-skill (no --name) print paths.
- tests/print-mode.test.ts: 4 new cases covering single-file no-notice,
  multi-file notice content, JSON-mode silence, and per-skill iteration
  in the no-name flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:24:14 +02:00
“mkczarkowski” ab9ee915ce feat(skill-directory-bundle): CLI schema types, writer, manifest (p4)
Update CLI to consume the new directory-shaped skill bundle:

- api-content.ts: SkillFile, SkillBundle, ArtifactResponse discriminated union
- tool-profile.ts: add skillDir(name) per profile
- manifest.ts: v2 schema (skills as Record<name, {files}>); v1 manifests rejected as null
- writer.ts: iterate skill.files[], chmod +x for executables, per-file diff/removal,
  empty parent-dir cleanup, assertSafeSkillFilePath path-traversal guard
- tool-switch.ts: migrate/delete walks every file under each skill directory
- commands/get.ts: per-file rendering for multi-file skills; print mode
  accommodates the new shape (Phase 5 polishes the stderr notice)

Tests cover: multi-file materialization with +x preservation, idempotent
re-apply, file-level removal within retained skills, v1 manifest rejection,
and path-traversal rejection ('../evil.sh', absolute, backslash, empty).
All 340 tests green; typecheck, lint, build, build:binary all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:24:14 +02:00
“mkczarkowski” 1414b2e6c2 feat(cli): tool-switch migration flow with sentinel cleanup
Wave 2.1 PR2 closes the UX gap where students who switch tools would
silently accumulate orphan artifacts from their old profile. When
resolveToolProfile settles on a new tool and a prior tool's manifest
exists under a different manifestDir, the CLI now prompts once per
orphan: migrate (recommended), remove 10x artifacts, or keep both
(don't ask again).

- findOrphanedManifests in writer.ts returns structured OrphanInfo
  (profile, manifestPath, parsed manifest). Corrupt manifests are
  skipped so migration never moves files without a file list.
- ToolConfig gains acknowledgedOrphans: string[] with shape validation
  on read; readToolConfig drops the field when it isn't a string array.
- tool-switch.ts implements migrateArtifacts and deleteArtifacts:
    - isSafeName gate on every manifest entry (skills, prompts, configs)
      before any filesystem operation.
    - moveIfSafe refuses symlinked sources, compares existing destinations
      byte-for-byte (readFileSync + Buffer.equals) so two distinct binary
      payloads that decode to U+FFFD never collapse as equal.
    - EXDEV cross-device fallback writes to <to>.tmp then renames into
      place; post-copy rmSync failure is reported via summary.skipped
      while still counting the file as moved. Non-EXDEV rename errors
      propagate.
    - deleteArtifacts scopes removal to the 10x-written surface — a
      Copilot orphan no longer wipes .github/workflows; only manifest-
      listed files plus the now-empty skills/prompts/config-templates
      subdirs are removed. Partial-failure contract documented.
- removeRulesBlockWithMarkers in sentinel-migration.ts strips the 10x
  block from the old rules file, collapsing the splice to at most one
  blank line and handling CRLF endings. Symmetric to
  applyRulesBlockWithMarkers; apply + remove round-trips cleanly.
- tool-prompt.ts wires handleToolSwitch into resolveToolProfile for TTY
  flows; the "keep" branch spreads the existing config so unknown
  fields (future CLI versions, hand-edits) survive. The first-run
  prompt's saveToolConfig also spreads existing config.
- commands/get.ts keeps the legacy verbose orphan warning for non-TTY
  paths (CI, Docker); TTY flows get the interactive prompt instead.
- config.ts: writeJsonAtomic shared tmp+rename helper used by both
  saveAuth and saveToolConfig — saveToolConfig is now atomic too, so a
  crash mid-write can no longer corrupt acknowledgedOrphans state.
- fs-utils.ts: readFileOrNull hoisted out of tool-switch for reuse.

Plans:
- thoughts/shared/plans/2026-04-17-wave2.1-cli-polish.md (Phases 3-4)
- thoughts/shared/plans/2026-04-19-wave2.1-cli-polish-review-fixes.md
- thoughts/shared/plans/2026-04-20-wave2.1-cli-polish-review-fixes-review-fixes.md

bun test: 328 pass, 0 fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 10:16:25 +02:00
“mkczarkowski” 857a825da6 feat(cli): auto-detect tool profile and add coherence invariants
Wave 2.1 PR1 lands two low-risk CLI polish items ahead of the prework
release so first-run students benefit from a smarter default and the
profile layer gains a drift-safety net.

- Auto-detection: scan the project root for tool-native markers
  (.cursor/rules, .claude/, CLAUDE.md, .github/copilot-instructions.md,
  AGENTS.md, .agents/, .ai/) and rank signals by confidence. The
  interactive first-run prompt pre-fills initialValue with the detected
  tool and prints a "Detected: <tool> (<reason>)" hint. User still
  confirms — no silent selection. Non-TTY paths are unchanged.
- Coherence invariants: SENTINEL_BEGIN/END hoisted to shared constants;
  every profile now references them. New describe block asserts 7
  invariants (paths rooted under manifestDir, safe leaf names, non-empty
  rulesFile, identical sentinels, unique manifestDir, unique toolId,
  non-empty displayName) so refactors fail fast on CI.

Plan: thoughts/shared/plans/2026-04-17-wave2.1-cli-polish.md (Phases 1-2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 10:15:40 +02:00
“mkczarkowski” a771f941fc feat: v1 canonical-string signing protocol and BUNDLE_PUBLIC_KEYSET override
Implement v1 signing verification (v1:<keyId>:<hash> canonical string) and
add loadEffectiveKeyset() with localhost-gated env var override for e2e testing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 10:53:01 +02:00
“mkczarkowski” 29d81466db feat: add --print, --type, --name flags with filtered writes and atomic artifact fetch
Add three new flags to the `get` command:
- `--print` outputs artifact content to stdout instead of writing files
- `--type` filters by artifact type (skills, prompts, rules, configs)
- `--name` filters by artifact name (requires --type)

`--type`/`--name` work both with `--print` (stdout) and without (filtered
disk writes). The writer's new `partial` mode skips cleanup and manifest
updates so filtered writes never delete previously written artifacts.

Also adds `fetchArtifact()` for the /api/artifacts endpoint with full
Ed25519 signature verification, matching the existing `fetchLesson()` pattern.

Updates README with full command reference, multi-tool docs, and usage
examples. Adds repository/homepage/bugs to package.json so npm links
back to the GitHub repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 21:15:52 +02:00
“mkczarkowski” 1e238132f6 feat: add multi-tool profile support with --tool flag
Parameterize the artifact writer to support 5 AI coding tools
(Claude Code, Cursor, GitHub Copilot, Codex CLI, Generic) via
ToolProfile config objects. Add interactive first-run tool selector,
persistent config, orphan detection for tool switching, and
parameterized sentinel markers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 19:36:08 +02:00
“mkczarkowski” 40e5b37b4f feat(ci): add release workflow with npm publish, cross-platform binaries, and GitHub Releases
- Auto-version from conventional commits (auto-version.mjs) with git-diff
  gate: only releases when src/ or package.json actually changed
- 5-platform binary builds (linux x64/arm64, macOS x64/arm64, windows)
- npm publish with auth token wiring
- GitHub Release with auto-generated notes and binary attachments
- Smoke tests for package tarball and auto-version script
- Hardened: top-level permissions: {}, env bindings (no expression injection),
  persist-credentials only where needed for git push

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:09:00 +02:00
“mkczarkowski” fc64cea7d9 security: harden supply chain, add request timeout, and document threat model
- Pin GitHub Actions by full SHA to prevent tag-swapping attacks
- Add .npmrc with ignore-scripts and 7-day minimum-release-age quarantine
- Add 30s default request timeout for API calls without caller signal
- Remove unused `open` dependency to reduce attack surface
- Strip OpenAPI source URL from generated types header
- Add SECURITY.md documenting threat model (T1–T8), review history, and
  design decisions
- Add persist-credentials: false to checkout action

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:50:03 +02:00
“mkczarkowski” 4d7375ab5b feat: add Ed25519 bundle signature verification for lesson responses
Implement client-side Ed25519 signature verification (Phase 5B) to ensure
lesson bundles haven't been tampered with between the API and CLI.
REQUIRE_SIGNATURES is set to true (fail-closed). The ApiResult type now
exposes responseHeaders so fetchLesson can extract X-Bundle-Signature
and X-Bundle-Key-Id headers for verification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:16:01 +02:00
“mkczarkowski” 5a69de7ee9 fix: implement security hardening for CLI output sanitization, config file permissions, and sentinel marker validation. 2026-04-12 08:02:47 +02:00
“mkczarkowski” 530592da67 feat: implement manifest tracking and sentinel migration for idempotent lesson application 2026-04-11 20:05:19 +02:00
“mkczarkowski” e289323d80 feat: phase 4 — get, list, doctor commands with consistent message UX
Core student commands plus shared supporting modules:

- lesson-ref parser: m<N>l<N>, module range 1..5 per 10xdevs3 shape
- api-content: typed wrappers for catalog/modules/lesson/health
- writer.ts: phase 4 planning stub; phase 5 swaps in real writes
- format.ts: shared formatReleaseAt (long-form English dates, UTC
  day-start snapping so "in N days" never drifts by one at midnight)

10x list accepts both '1' and 'm1' to match '10x get m1l1'. Drill-in
hints always include a concrete runnable example, picked from an
unlocked module when available so the suggested command works today.

10x doctor gains a hint field on every check result. Human report
uses Title-case labels (Auth/API/Config/Version/Claude); JSON keeps
stable lowercase names so machine consumers don't break.

Message style guide at the top of src/lib/output.ts codifies:
- never echo API machine codes ("module_locked", "not_found", ...)
  as human copy — build the sentence from payload fields
- error = what happened; hint = runnable '10x ...' command
- timestamps always through formatReleaseAt() on stderr; JSON
  consumers still get the raw ISO
- module hints show both accepted input forms so students discover
  the alternative

55 new tests, 121 total, all green. typecheck + oxlint clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 17:44:31 +02:00
“mkczarkowski” a68c743578 feat: serialize token refresh with file lock and pin CLI contracts via tests
Wrap the auth-guard refresh path in a proper-lockfile lock that re-reads
auth.json inside the critical section, so cooperating CLI processes (and
parallel in-process callers) racing on a near-expiry token can no longer
double-refresh — the late caller observes the rotated token and short-
circuits. Lock policy: 5 retries with 100–1000ms exponential backoff and
a 10s stale threshold, with auth_lock_timeout surfaced as a clean error
envelope on contention.

Add three test suites locking in invariants previously enforced only by
convention:
  - auth-guard-concurrency: in-process race, cross-process race via
    child_process.fork, stale-lock recovery, contention timeout
  - exit-codes: per-command exit-code matrix for auth login/--status
    /--logout, including the F1 fix (expired token in JSON mode → exit 3)
  - json-envelope: stdout envelope contract + leakage guard catching
    stray verbose markers, ANSI escapes, clack glyphs, multi-line output,
    and accidental email echo

Share auth-flow and @clack/prompts module mocks via tests/helpers/* so
mock.module registrations don't leak across test files in the same
bun test process; the mocks fall through to the real implementations
when no test state is configured, leaving auth-flow.test.ts untouched.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 15:04:20 +02:00
“mkczarkowski” 2b682262b8 feat: implement authentication flow, session polling, and token refresh guard with secure directory permissions 2026-04-11 12:53:03 +02:00
“mkczarkowski” b42b119050 feat:bootstrap the tool 2026-04-11 11:18:40 +02:00