Commit Graph

55 Commits

Author SHA1 Message Date
Claude 3b57734dbd fix(cli-circle-login): budget-neutral rate-limit hint with retry_after_s (review F9)
A 429 from /auth/circle/start may come from the per-email, per-IP or
community budget, so the message no longer blames the email and the hint
uses the server's retry_after_s when present. Merges the split config
import in commands/auth.ts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 21:38:35 +02:00
Claude 5bec1f7f45 fix(cli-circle-login): keep polling through transient 5xx and 429 (p5)
A Circle poll that meets an upstream 5xx or a 429 now stays pending until
the server's expires_in deadline instead of aborting the login. Found by
the Phase 6 E2E under wrangler's dev proxy; the same class of hiccup can
come from the edge in production.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 21:38:35 +02:00
Claude 0d50088a7c feat(cli-circle-login): regenerate API types from the Circle login candidate spec (p5)
Types generated from the OpenAPI spec emitted by the Toolkit E2E harness
at 35d93a328dbdad2c13482132a32626597f6dfbe4 (feat/cli-circle-login).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 21:38:35 +02:00
Claude 9d7ea881ce feat(cli-circle-login): CLI method, polling and credentials (p5)
Add `10x auth --method email|circle`, the TTY chooser, the Circle
start/poll client with slow_down back-off and SIGINT cancellation,
optional AuthData.method preserved through refresh, new error codes and
exit-code mapping, tests for the Circle path, and README/guide rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 21:38:35 +02:00
przeprogramowani b0c789af70 feat: enable v4 course selection and protected first-week sync (#38)
* docs(10xdevs4-cli-access): record membership gates and bootstrap context (p1)

Add canonical access plan, accepted decisions and supporting context.
Record phase 1 verification, Toolkit revision and remaining evidence gaps.

* docs(10xdevs4-cli-access): record course access gates and scoped review (p2)

Update canonical Progress, change status, evidence and implementation review.
Record Toolkit revisions, inherited typecheck limitation and phase 3 prerequisites.

* docs(10xdevs4-cli-access): persist reviewed revisions and phase 3 boundary

Record final reviewed Toolkit and CLI context revisions.
Persist Progress attribution and unresolved W04/W05/W08 prerequisites.
Keep phases 3–6, phase 7 and Manual criteria open.

* docs(10xdevs4-cli-access): record squash-safe source prerequisite

Record PR #30, verified gates, permanent-pin lessons and the remaining merge dependency.
Keep phase 3 and all manual rollout criteria pending.

* docs(10xdevs4-cli-access): record source prerequisite review

Record independent review of PR #30 and verified CI evidence.
Keep the master pin and delivery phase completion pending.

* docs(10xdevs4-cli-access): distinguish candidate checks from final master pins

Record passing pre-merge v4 checks and defer workflow suspension.
Keep the final v3 maintenance pin dependent on the resulting master SHA.

* feat: prepare v4 course delivery and protected project sync

Capture the reviewed implementation and manual rehearsal for draft PR review. Master source prerequisites, full clean verification and coordinated Windows CI remain open; production rollout is separate.

* fix: keep paid CI evidence private and converge cumulative sync

* docs: record merged source prerequisite and passing clean gate

* fix: preserve generated API type line endings on Windows

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-13 09:24:16 +02:00
psmyrdek cc80394e16 docs(bench-kit): update post-init guidance 2026-08-20 10:21:18 +02:00
psmyrdek 8a6a32aca1 feat(tool-profiles): replace Windsurf with Devin Desktop 2026-08-20 10:21:18 +02:00
Przemek Smyrdek e1b4b236dc refactor(bench-kit): CLI woła bootstrap kitu — kontrakt v1, logika instancji w template'cie (#37)
Kit zna siebie, CLI zna maszynę użytkownika: init/update klonują
template i wołają .bench-kit/bootstrap/index.mjs z klonu (żądanie JSON
na stdin, odpowiedź w ostatniej linii stdout). Update wykonuje bootstrap
z NOWEJ wersji kitu, więc migracje układu jadą z tagiem. Wchłania
TEMPLATE_ONLY_PATHS (gałąź fix/bench-kit-template-only-paths zbędna).
Minimalna wersja template'u: v0.10.0 (brak bootstrapu → template_incomplete).
Testy zawężone do kontraktu: fake runBootstrap, asercje na żądanie,
render, koperty --json i kody wyjścia.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 10:35:45 +02:00
Przemek Smyrdek d9a822556b feat(bench-kit): init clones the detected base repo into .repos/<name> (#36)
Local working copy for the authoring skills (bench-task, bench-refresh,
bench-wiring): cloned from the surrounding product repo (instant,
offline, full history) with origin pointed at the registered remote.
Gitignored via ensureIgnored so older template tags never commit it;
clone failure degrades to a hint.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 23:25:20 +02:00
Przemek Smyrdek 5d6466ed36 feat(bench-kit): remove TENX_CLI_EXPERIMENTAL gating — init and update generally available (#35)
The bench-kit command is feature-complete (zone-aware update, agent-tool
profiles, safe init defaults shipped in v1.17.0), so the experimental
opt-in has served its purpose. The command now registers unconditionally,
shows up in --help, and is documented in the README; the gating module
and its error envelope (experimental_locked) are removed.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 23:24:25 +02:00
Przemek Smyrdek 155bde9da7 feat(bench-kit): init safe defaults — pin demo task to detected repo, install runner deps, prefer https over SSH (#34)
- init pins placeholder tasks (repo: demo-app + all-zeros commit) to the
  detected base repo and its HEAD; company tasks never touched
- init and update run npm ci in .bench-kit/runner (update's wholesale
  swap deletes node_modules); failure degrades to a hint
- detected SSH remote is probed over https (git ls-remote, no terminal
  prompt) and rewritten when the repo answers publicly

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 19:41:42 +02:00
Przemek Smyrdek 17a504b039 feat(bench-kit): sync shared root files (AGENTS.md) on update (#33)
Template v0.6.0 ships AGENTS.md (agent instructions: skill order and
purpose). init already materializes it with the template root; update
now syncs it with the same proposal semantics as skills — added or
overwritten in the working tree, reviewable via git diff, never
deleting company edits silently.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 22:59:42 +02:00
Przemek Smyrdek 8202734856 feat(bench-kit): zone-aware update + agent-tool profile in init (#31) (#32)
- init resolves the agent tool (--tool flag > manifest on repair >
  interactive pick pre-filled by marker detection > claude-code) and
  materializes template skills under the profile's skill root
  (.claude/skills, .agents/skills, ...); the chosen tool is recorded in
  instance.json so update knows where to propose skill diffs
- update works zone by zone: .bench-kit/ replaced wholesale (staged swap,
  manifest survives version-bumped), workflows and skills synced into the
  working tree as an uncommitted proposal reviewed via git diff, company
  content never touched; clean-worktree gate keeps the proposal reviewable
- template skill source auto-detects the planned .agents/skills migration

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 12:43:40 +02:00
Przemek Smyrdek 47fad2249f feat(bench-kit): install template workflows into .github/workflows (#30)
GitHub only runs workflows from .github/workflows/, so init copies
.bench-kit/workflows/* there; repair mode keeps existing files (the
company may have customized triggers or secrets).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 12:40:49 +02:00
Przemek Smyrdek 0f5c2b5858 fix: lazy-load yaml so CLI startup stays within budget (#28)
The top-level 'yaml' import in bench-kit.ts is pulled in by index.ts,
so every CLI start paid for loading the YAML parser and the binary
smoke test's 50ms startup budget blew on CI (~58ms). Import yaml
dynamically inside registerBaseRepo — the only code path that needs
it.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:02:27 +02:00
Przemek Smyrdek 7fbe39eb5e feat: auto-register the surrounding product repo as first base repo (#27)
Running 'bench-kit init' from inside a product repo is the common flow,
so init now detects the git repo containing the invocation cwd (git
rev-parse --show-toplevel + origin remote + HEAD) and replaces the
template's demo-app placeholder in bench.config.yaml with that repo,
editing the YAML document in place so company-zone comments survive.
The detection also lands in instance.json (incl. HEAD as a candidate
pin for the first task). No detection, no origin, or detecting the
instance itself → the placeholder stays. Adds the 'yaml' dependency.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 20:55:12 +02:00
Przemek Smyrdek 3184882f38 feat: add bench-kit command (init + update stub) (#26)
* feat: add bench-kit command (init + update stub)

'10x bench-kit init [dir]' creates a benchmark instance from the
przeprogramowani/10x-bench-kit template: preflight (git required,
container engine advisory), shallow clone at a tag, materialization
without git history, fresh git init + initial commit, and an instance
manifest in .bench-kit/instance.json. Re-running init on an existing
instance repairs missing template files without touching company
content. 'bench-kit update' is a not_implemented stub.

CAC has no nested command groups, so the command follows the auth
precedent: one 'bench-kit <action>' command dispatching internally.
Side effects are injectable (BenchKitDeps) so tests exercise the full
flow against fixture trees without network or real git.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: gate experimental commands behind TENX_CLI_EXPERIMENTAL

New src/lib/experimental.ts exposes experimentalEnabled() and
requireExperimental(), which exits with a stable 'experimental_locked'
envelope (exit 4 FORBIDDEN) unless TENX_CLI_EXPERIMENTAL=1 (or 'true')
is set. bench-kit stays registered so help remains discoverable and the
command never dies silently, but its action is locked before any side
effects. This lets the branch merge to master ahead of the feature
being student-ready.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: hide experimental commands entirely without the opt-in

All-or-nothing per review: registerBenchKitCommand returns early when
TENX_CLI_EXPERIMENTAL is unset, so bench-kit is absent from --help and
behaves like any unknown command instead of appearing half-locked. The
requireExperimental gate stays in the action as defense in depth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 20:36:43 +02:00
psmyrdek 8006da8efb feat(bench): add '10x bench' live leaderboard from 10xbench.ai
New public command (no auth) that fetches the preprocessed top-10
leaderboard from https://10xbench.ai/api/leaderboard.json and renders
color-coded score bars on stderr, or the standard JSON envelope when
piped. The bench client is deliberately separate from api-client.ts:
its own strict BENCH_BASE_URL allowlist, and non-JSON 200 responses
(the static site's catch-all) surface as bench_unavailable. Colors are
hand-rolled ANSI gated on stderr TTY + NO_COLOR; remote strings are
sanitized before printing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 11:08:43 +02:00
Marcin Czarkowski b000e7bbdf feat(lesson-ref): accept lesson 0 for the m0l0 General Toolkit (#4)
Allow lesson numbers >= 0 so general, non-lesson-bound artifacts can be
fetched via m0l0 (e.g. `10x get m0l0`). Update the parser doc comment
and tests; lesson 0 is now valid, negatives still rejected by the regex.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 10:00:53 +02:00
“mkczarkowski” d0d3795b73 fix(bulk-sync-update): atomic manifest write + SIGINT abort (impl-review F2/F3)
F2: writeManifest now writes to a sibling .tmp then renameSync into
place (matching saveAuth), so an interrupt mid-sweep can't truncate the
manifest into an unreadable state that drops all per-lesson tracking.

F3: the sync sweep's AbortController was inert (signal threaded but
abort never called). Wire process.once("SIGINT") to controller.abort()
so Ctrl-C cancels the in-flight fetch and stops the sweep at the next
lesson boundary; handler removed in finally.
2026-06-20 12:35:11 +02:00
“mkczarkowski” ec8a2a644d feat(bulk-sync-update): 10x sync command (p3)
- commands/sync.ts: new `10x sync` — enumerate, cheap-skip via catalog digest,
  fetch+apply, actionable report (per-resource remediation), worst-outcome exit
- index.ts: register sync command
- manifest.ts: LessonFilesEntry.catalogContentHash (digest-vs-digest skip), tolerant read
- writer.ts: applyBundle persists catalogContentHash (carried forward on plain get)
- tests/sync-command.test.ts: --all, default/--module, cheap-skip, changed, dry-run,
  conflict+remediation, --force, partial-failure exit 1, locked exclusion, digest round-trip
2026-06-20 11:37:51 +02:00
“mkczarkowski” ca4d0bf247 refactor(bulk-sync-update): planBundle() writer refactor + catalog contentHash type (p2)
- writer.ts: export pure planBundle() + WritePlan types; applyBundle now consumes
  it (shared computeFileAction/planRules) so classification and application can't drift
- api-content.ts: optional contentHash on LessonSummary (always-fetch fallback when absent)
- generated/api-types.ts: regenerated from local API — catalog lesson type carries contentHash
- tests/writer-plan.test.ts: planBundle/applyBundle parity (created/unchanged/clean/conflict)
2026-06-20 11:22:32 +02:00
“mkczarkowski” 2993481628 fix(disable-course-rules): address impl-review F1/F2
F1: updateToolConfig now throws when the merged config has no `tool`
instead of silently persisting an invalid object a later read rejects
wholesale (dropping lang/acknowledgedOrphans).

F2: add command-level course-rules tests — strip-after-apply with
surrounding-content preservation, the human [removed] rules render
line, and the positive --course-rules CAC parse guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:30:43 +02:00
“mkczarkowski” 6fb6ed6890 feat(disable-course-rules): command wiring, resolution & output (p3)
- src/commands/get.ts: register --no-course-rules, add tri-state
  resolveCourseRulesFlag (argv peek), merge-safe persistence via
  updateToolConfig (folds --lang persist, skips under --dry-run),
  explicit --type rules override, output (counts.rules 0 + [removed] line)
- tests/get-command.test.ts: resolver unit + opt-out/override/persist/dry-run
  integration; runGet mirrors process.argv
- README.md, CLAUDE.md: document the flag, persistence & override precedence

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:30:43 +02:00
“mkczarkowski” 29ee156819 feat(disable-course-rules): writer opt-out & strip (p2)
- src/lib/writer.ts: add applyCourseRules?: boolean to ApplyOptions
  (default true); when false, skip writing the course block and strip an
  existing one via removeRulesBlockWithMarkers (rules.action "removed",
  honors dryRun)
- tests/writer.test.ts: opt-out strip / no-op / dry-run / default-idempotent

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:30:43 +02:00
“mkczarkowski” d8fab8c559 feat(disable-course-rules): config schema & merge-safe persistence (p1)
- src/lib/config.ts: add courseRules?: boolean to ToolConfig, drop
  non-boolean courseRules in readToolConfig, add merge-safe
  updateToolConfig(patch) (fixes latent lang/acknowledgedOrphans clobber)
- tests/config.test.ts: courseRules shape validation + updateToolConfig merge
- context/changes/disable-course-rules: change folder (change.md, plan.md, plan-brief.md)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:30:43 +02:00
“mkczarkowski” c37f96f5c3 feat(cumulative-manifest): cumulative writer logic + scoped removal (p2)
Rewrite computeRemovals() for lesson-scoped removal with protected set.
Update manifest building to merge lesson entries, rebuild union files,
and seed from v2/v3-without-lessons manifests. Update existing cleanup
and removal tests for cumulative semantics. Add 8 new behavior tests
covering multi-lesson accumulation, protected-set, upgrade seeding.
Update CLAUDE.md with cumulative manifest documentation.

Files: src/lib/writer.ts, tests/writer.test.ts,
       tests/writer-profiles.test.ts, CLAUDE.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 20:54:41 +02:00
“mkczarkowski” 7c80b292f9 feat(cumulative-manifest): manifest schema extension (p1)
Add LessonFilesEntry type, lessons? field on CliManifest, isManifest()
validation for lessons, and buildUnionFiles() helper. Includes frame
brief, plan, and plan-brief for the change.

Files: src/lib/manifest.ts, tests/manifest.test.ts,
       context/changes/cumulative-manifest/{change,frame,plan,plan-brief}.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 20:49:19 +02:00
“mkczarkowski” 802ea46a63 feat(user-edit-protection): interactive prompts + output rendering (p3)
- Create conflict-prompt.ts with TTY/non-TTY conflict resolver factory
- Wire createConflictResolver and showUpgradeNotice into get command
- Add v2 manifest upgrade detection before applyBundle
- Render [removed] lines for skill/prompt/config removals
- Render conflict-specific action labels (conflict: overwritten/saved .user/skipped)
- Add removals to JSON output envelope with counts.removals
- Update get-command test for new counts shape

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 18:23:59 +02:00
“mkczarkowski” 536ccf6728 feat(user-edit-protection): writer three-way detection + removal tracking (p2)
- Make applyBundle() async with ConflictResolver callback injection
- Three-way conflict detection via stored content hashes in manifest
- Conflict resolutions: overwrite, save-as-.user.<ext>, skip
- Track removals in WriteResult.removals for downstream rendering
- Store per-file contentHashes and promptHashes in v3 manifest
- Update all tests for async applyBundle (writer + writer-profiles)
- Await applyBundle in get.ts command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 18:21:22 +02:00
“mkczarkowski” ed64a987e1 feat(user-edit-protection): manifest v3 schema + contentHash utility (p1)
- Bump MANIFEST_VERSION to 3 with optional contentHashes/promptHashes fields
- Add contentHash() SHA-256 utility in manifest.ts
- isManifest() accepts both v2 and v3 for backward compat
- Update test assertions for v3 manifest version

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 18:16:40 +02:00
psmyrdek 21f974e29f feat(get): add Gemini CLI tool profile
Writes skills to .gemini/skills/<name>/SKILL.md (native Gemini path),
prompts to .gemini/prompts/<name>.md, configs to .gemini/config-templates/,
and rules to GEMINI.md. Auto-detection picks up GEMINI.md (strong),
.gemini/.10x-cli-manifest.json (strong), or a bare .gemini/ (medium).

Also fixes drift in the --tool help string and README tables where
windsurf was missing; regenerates api-types after API picked up both
gemini and windsurf in SUPPORTED_TOOLS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:55:24 +02:00
psmyrdek 2b71e20fac feat(doctor): warn when newer CLI version is on npm
Doctor's version check now queries the npm registry (best-effort, 2s
timeout) and warns with an upgrade hint when the local version is
behind. Lookup failures stay silent so offline runs aren't penalized.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:13:04 +02:00
“mkczarkowski” 3d6aa64040 feat(get): persist --tool choice as user default
When --tool is passed to resolveToolProfile(), save the choice to
config.json so future invocations default to it. Shows a stderr
confirmation on TTY when the default actually changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-17 13:16:34 +02:00
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