tspl-cli 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -28,7 +28,7 @@ npm i -g tspl-cli → tspl setup → tspl init → tspl task new "…" → tspl
28
28
 
29
29
  ## Requirements
30
30
 
31
- - **Node 22 or newer** (`node --version`)
31
+ - **Node 20 or newer** (`node --version`)
32
32
  - **git**
33
33
  - **A TSPL account.** Ask your organization's owner or admin to add you. You get a temporary password and choose your own at the first login.
34
34
  - **An AI tool** you are signed in to with your own subscription: Claude Code, Codex CLI, Gemini CLI, Antigravity, Cursor or VS Code Copilot
@@ -129,7 +129,7 @@ tspl init
129
129
  `tspl init` does the following:
130
130
 
131
131
  1. **Links the folder to a TSPL project.** It recognizes your repositories by their git history, so folder names and git hosts don't matter. If the project doesn't exist yet, it offers to create it (owner, admin or PM only). If it finds new repositories, it offers to add them.
132
- 2. **Finds your checks.** It reads your package scripts, without running them, and asks you to confirm which ones are the real **test**, **lint**, **typecheck** and **build** commands, and which are required.
132
+ 2. **Finds your checks.** It reads your package scripts, without running them, and asks you to confirm which ones are the real **test**, **lint**, **typecheck** and **build** commands, and which are required. A test check is optional: without one, each done-when item is checked by a person (`tspl task verify`), or you finish the task with `tspl task done`.
133
133
  3. **Asks about pull requests:** the GitHub repository, the target branch and the CI policy.
134
134
  4. **Offers browser tests.** For web apps without browser tests, it offers to set up Playwright. Skip this with `--no-e2e`.
135
135
  5. **Saves the shared project files** in `.tspl/`.
@@ -326,11 +326,14 @@ tspl build push t-xxxx # push the branch of every repo on the plan
326
326
  ```bash
327
327
  tspl build stop t-xxxx # stop and lift the edit lock (branches are kept)
328
328
  tspl plan amend t-xxxx # cancel the stopped run so a new plan version can be made
329
+ tspl task done t-xxxx --note "tested by hand on staging" # mark it done yourself
329
330
  tspl task delete t-xxxx --reason "duplicate"
330
331
  ```
331
332
 
332
333
  Use `stop` and `amend` only when the plan really has to change, not for every failure. A new plan version needs new approvals.
333
334
 
335
+ `tspl task done` finishes an approved task by hand, even if checks, tests, review or the pull request did not run. Any contributor can use it; a note is required. The task history keeps who marked it done, the note, and what was still open; the console shows the skipped steps as skipped. Stop a running build first (`tspl build stop`).
336
+
334
337
  ### Build by hand instead of `task run`
335
338
 
336
339
  ```bash
@@ -435,6 +438,7 @@ The steps are `plan`, `build`, `verify` and `bulk`. The engines are `claude`, `c
435
438
  | `tspl task choose` | choose an approach before planning |
436
439
  | `tspl task run` / `watch` / `status` | build a task, follow it, see where it is |
437
440
  | `tspl task verify` | record a check you did by hand |
441
+ | `tspl task done` | mark an approved task done by hand, with a note |
438
442
  | `tspl plan list` / `show` / `approve` / `reject` / `amend` | review and decide on plans |
439
443
  | `tspl build start` / `status` / `check` / `push` / `stop` | manual build commands |
440
444
  | `tspl member ...` | people and roles |
@@ -469,7 +473,7 @@ tspl task run --help
469
473
  | `You are logged in to …, not …` | run `tspl login --api https://aitool-api.techivies.com` |
470
474
  | Login expired | run `tspl login` |
471
475
  | Forgot your password | run `tspl forgot-password --email you@company.com` |
472
- | `tspl needs Node 22 or newer` | install Node 22+ from https://nodejs.org, then reinstall tspl |
476
+ | `tspl needs Node 20 or newer` | install Node 20+ from https://nodejs.org, then reinstall tspl |
473
477
  | `/tspl` doesn't show in your AI tool | run `tspl connect <tool>` and restart the tool |
474
478
  | `plan approve` refuses to run | run it in a normal terminal, not inside an AI tool |
475
479
  | Build won't start: uncommitted changes | commit or stash your work, then run again |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tspl-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "TSPL command-line tool: learns your code, plans changes with you, builds, tests and opens pull requests",
5
5
  "keywords": [
6
6
  "tspl",
@@ -27,13 +27,13 @@
27
27
  "README.md"
28
28
  ],
29
29
  "engines": {
30
- "node": ">=22"
30
+ "node": ">=20"
31
31
  },
32
32
  "dependencies": {
33
33
  "@babel/parser": "^7.29.9",
34
- "@inquirer/prompts": "^8.7.2",
34
+ "@inquirer/prompts": "^7.10.1",
35
35
  "@modelcontextprotocol/sdk": "^1.31.0",
36
- "commander": "^15.0.0",
36
+ "commander": "^14.0.3",
37
37
  "zod": "^4.6.5"
38
38
  },
39
39
  "scripts": {},
@@ -365,6 +365,21 @@ const verify = async (id, options) => {
365
365
  console.log(`Recorded: done-when ${criterion} checked by you on ${workflow.heads.map(head => `${head.name}@${String(head.headSha).slice(0, 7)}`).join(', ')}. Continue: tspl task run ${id}`)
366
366
  }
367
367
 
368
+ const markDone = async (id, options) => {
369
+ const { project } = await findProject(process.cwd())
370
+ const task = await request('GET', taskUrl(project._id, id))
371
+ const open = task.workflow ? `stage ${task.workflow.stage}` : `status ${task.status}`
372
+ if (!options.yes && !(await confirm({ message: `Mark ${id} done by hand (now ${open})? Checks that did not run stay recorded as skipped.`, default: false }))) {
373
+ console.log('Nothing changed.')
374
+ return
375
+ }
376
+ const { task: done, workflow } = await request('POST', `${taskUrl(project._id, id)}/workflow/done`, { note: options.note })
377
+ require('../workflow/runtime').finishLocal(id)
378
+ const problems = (workflow && workflow.manualCompletion && workflow.manualCompletion.problems) || []
379
+ console.log(`${id} is ${done.status}, marked by you: "${options.note}".`)
380
+ if (problems.length) console.log(`Still open when it was marked done (kept in the task history):\n${problems.map(item => ` - ${item}`).join('\n')}`)
381
+ }
382
+
368
383
  const remove = async (id, options) => {
369
384
  const { project } = await findProject(process.cwd())
370
385
  if (!options.yes && !(await confirm({ message: `Delete task ${id} for everyone? It disappears from the console and from every list.`, default: false }))) {
@@ -465,6 +480,12 @@ module.exports = {
465
480
  .option('--note <text>', 'what you looked at')
466
481
  .option('--file <paths...>', 'screenshots or a screen recording that show it (png, jpg, webp, webm, mp4)')
467
482
  .action(verify)
483
+ task
484
+ .command('done <task>')
485
+ .description('Mark an approved task done by hand, even if checks, tests or delivery did not run (kept in its history with your note)')
486
+ .requiredOption('--note <text>', 'why it is done, e.g. "tested by hand on staging"')
487
+ .option('-y, --yes', 'do not ask to confirm')
488
+ .action(markDone)
468
489
  task
469
490
  .command('scope <task>')
470
491
  .description('Confirm a task scope as PM, or send it back with --back --note "…"')
@@ -219,7 +219,7 @@ const readiness = (config, localPaths) => {
219
219
  const problems = []
220
220
  const warnings = []
221
221
  if (!config.checks.some(check => check.required && TEST_CATEGORIES.includes(check.category))) {
222
- problems.push('No required test check is configured. Add one to .tspl/project.json (category unit, integration or e2e); a tested PR needs it.')
222
+ warnings.push('No required test check is configured: done-when items without a runnable test need a person (tspl task verify), or finish with tspl task done.')
223
223
  }
224
224
  config.repos.forEach(repo => {
225
225
  if (!localPaths.has(repo.name)) problems.push(`Repo ${repo.name} is not cloned on this machine.`)
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const MIN_NODE_MAJOR = 22
3
+ const MIN_NODE_MAJOR = 20
4
4
 
5
5
  const nodeVersion = () => process.env.TSPL_TEST_NODE_VERSION || process.versions.node
6
6
 
@@ -11,8 +11,14 @@ const workflowProgress = (task, at = new Date()) => {
11
11
  if (!wf) return null
12
12
  const current = effectiveStage(wf), position = wf.stage === 'ready' ? STAGES.length : STAGES.indexOf(current)
13
13
  const options = task.plan?.workflow?.options || {}
14
+ const manual = wf.stage === 'ready' && wf.manualCompletion
14
15
  const steps = STAGES.map((stage, i) => {
15
16
  const saved = wf.stageResults?.[stage] || {}
17
+ if (manual) {
18
+ const done = saved.status === 'done'
19
+ const by = manual.userName || 'a person'
20
+ return { stage, label: LABELS[stage], status: done ? 'done' : 'skipped', reason: done ? saved.reason || `${LABELS[stage]} completed.` : `Skipped: marked done by ${by} (${manual.note}).`, startedAt: saved.startedAt || null, finishedAt: saved.finishedAt || null, command: null }
21
+ }
16
22
  let status = i < position ? 'done' : i === position ? 'running' : 'pending'
17
23
  let reason = status === 'done' ? `${LABELS[stage]} completed; workflow advanced.` : status === 'pending' ? `Waiting for ${LABELS[STAGES[i - 1]] || 'plan approval'} to complete.` : `${LABELS[stage]} is running.`
18
24
  if (stage === 'reviewing' && options.review === false || ['awaiting-delivery', 'creating-pr', 'awaiting-ci'].includes(stage) && options.pullRequest === false) { status = 'skipped'; reason = 'Not required by this task’s approved workflow.' }
@@ -212,9 +212,6 @@ const validateSnapshot = snapshot => {
212
212
  if (ids.has(check.id)) throw new Error(`Duplicate check id "${check.id}"`)
213
213
  ids.add(check.id)
214
214
  })
215
- if (!checks.some(check => check.required && TEST_CATEGORIES.includes(check.category))) {
216
- throw new Error('The project has no required test check; add one to .tspl/project.json before planning a tested pull request')
217
- }
218
215
  const acceptance = snapshot.acceptance === undefined ? 0 : snapshot.acceptance
219
216
  const manualCriteria = snapshot.manualCriteria === undefined ? [] : snapshot.manualCriteria
220
217
  if (!Number.isInteger(acceptance) || acceptance < 0) throw new Error('acceptance must be a count')
@@ -380,7 +380,7 @@ const runTask = async ({ cwd, taskId, signal, options = {} }, deps) => {
380
380
  if (problems.length) {
381
381
  const manual = problems.filter(text => /Acceptance criterion/.test(text))
382
382
  if (manual.length === problems.length) {
383
- return outcome('reviewing', manual, snapshot.manualCriteria.map(n => `tspl task verify ${taskKey} --check ${n}`).concat([`Then: ${again}`]))
383
+ return outcome('reviewing', manual, snapshot.manualCriteria.map(n => `tspl task verify ${taskKey} --check ${n}`).concat([`Then: ${again}`, `Or finish by hand: tspl task done ${taskKey} --note "…"`]))
384
384
  }
385
385
  return block('other', problems.join(' '), [`Fix it, then: ${again}`])
386
386
  }