Skip to content

docs(user-stories): add master CV onboarding stories (GAP-25) - #99

Open
warnes wants to merge 335 commits into
develfrom
feat/master-cv-onboarding-user-stories
Open

warnes wants to merge 335 commits into
develfrom
feat/master-cv-onboarding-user-stories

Conversation

@warnes

@warnes warnes commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds user stories and a gap entry for the documented but unspecified workflow of creating Master_CV_Data.json from scratch using external source materials. This addresses the onboarding gap previously noted in specstory history reviews: "There is no story for creating master CV from scratch for a first-time user."

New file

tasks/user-story-master-cv-onboarding.md

Twelve stories (US-O1–US-O12) across three persona layers:

Applicant / first-time user:

  • US-O1 — Guided detection and path selection when no master CV exists
  • US-O2 — Import from LinkedIn data export (ZIP with CSV files)
  • US-O3 — Import from existing resume or CV document (PDF/DOCX via LLM extraction)
  • US-O4 — Import publications from BibTeX file, Google Scholar URL, or pasted citation text
  • US-O5 — Import projects and skills from a public GitHub profile
  • US-O6 — Merge multiple sources into a unified master CV with conflict resolution
  • US-O7 — Manual entry fallback via a guided multi-step form wizard
  • US-O8 — Review and confirm all imported data before any write to disk

UI/UX expert:

  • US-O9 — Evaluate onboarding source selection and import flow clarity
  • US-O10 — Evaluate import error and gap handling (partial success, confidence signals, LLM latency)

Master CV curator:

  • US-O11 — Post-import completeness check (section-by-section readiness summary)
  • US-O12 — Conflict resolution when merging overlapping data from multiple sources

Updated files

File Change
tasks/user-story-first-time-user.md Add US-F4 — precondition detection and guided onboarding entry point
tasks/gaps.md Add GAP-25 — Master CV Onboarding (HIGH severity, OPEN)
tasks/workflow-review-expanded-personas.md Register new story file in the story-file index

Relationship to existing stories / gaps

  • Complements US-A10 (update path) and GAP-01 (NL update + document ingestion) — these are distinct. US-A10 assumes master data exists; this PR covers the path to get there.
  • Extends US-F1–F3 (first-time user orientation) with the master CV creation precondition.
  • Extends US-M4 (publication curation) with an initial import path.
  • The GitHub and Google Scholar paths are new — not mentioned in any prior story or spec.

Testing / review checklist

  • Story IDs (US-O1–US-O12, US-F4, GAP-25) do not collide with existing IDs
  • All stories follow the established format (persona, related gap, steps, acceptance criteria, failure modes)
  • GAP-25 is consistent with the existing gap severity scale and description pattern
  • workflow-review-expanded-personas.md story file list is complete

warnes added 30 commits March 22, 2026 23:45
- route frontend tab, stage, reconnect, and persisted tab data through stateManager
- tolerate stale optional sessions and return handled missing-session claim responses
- prevent invalid analysis persistence, allow review pane retries, and expand regression coverage
Centralize frontend state access and harden stale-session review flows
- align preview generation with canonical spell-audit state and restore ATS data from backend session state
- rebuild stale frontend bundles automatically at startup and surface bundle status metadata in logs and the startup banner
- add session cleanup tooling plus focused Python and JS regressions for bundle freshness, staged generation, and cleanup flows
fix: tighten staged generation and startup hygiene
- clean style/import warnings in bibtex, concurrent session, and CV orchestrator tests
- preserve direct run_tests.py execution for test_cv_orchestrator
- keep keyword-compatible fake conversation manager methods
- retain BibTeX parser regression coverage and wrapped fixture content
Tighten test diagnostics and preserve direct test execution
- add a web-scoped ESM package boundary for Node-based tests and helpers
- remove the app.js CommonJS relay so tests import canonical modules directly
- align the integration runner and state-manager test setup with ESM loading
…points

Refactor web test entrypoints to ESM modules
- add minimal pointers to the standalone duckflow toolkit
- keep repo-local annotation rules while avoiding duplicated in-repo tooling
- reference the standalone duckflow GitHub repository as the source of truth
- replace machine-local script path examples with installable CLI examples
docs: restore duckflow toolkit guidance
warnes added 29 commits April 7, 2026 21:44
…r OpenAI providers

Adds json_mode: bool = False to the LLMClient.chat() abstract method and all
concrete implementations, then enables native response_format enforcement on
providers that support it.

Provider behaviour:
- OpenAIClient (and subclasses GroqClient, GitHubModelsClient, CopilotClient):
  pass response_format={"type": "json_object"} to the API when json_mode=True,
  constraining output at the API level rather than relying on prompt text alone
- CopilotOAuthClient: same — adds response_format to the raw JSON payload
- AnthropicClient, GeminiClient, CopilotSdkClient, LocalLLMClient, StubLLMClient:
  accept json_mode param but ignore it; enforcement remains prompt-only for
  providers that do not support a native JSON mode parameter
- call_llm() convenience wrapper gains the same param and passes it through

Internal call sites updated to pass json_mode=True:
- analyze_job_description() — returns structured job-analysis dict
- recommend_customizations() — returns structured recommendations dict
- rank_publications_for_job() — returns structured publication ranking list
- _propose_rewrites_via_chat() — returns structured rewrite proposals list

Call sites intentionally NOT updated (non-JSON responses):
- generate_professional_summary() — plain text
- rewrite_achievement() — plain text
- semantic_match() — numeric score
- convert_to_bibtex() — raw BibTeX text

Part of: feat/structured-json-output (Phase 2 of 4)
Add runtime structured output validation for the three heavy LLM calls:

- New `scripts/utils/llm_response_models.py`:
  - `JobAnalysisResponse` (10 required fields + optional `reasoning`)
  - `CustomizationResult` with nested `ExperienceRecommendation`,
    `SkillRecommendation`, `AchievementRecommendation`, `SuggestedAchievement`
  - `PublicationRankingItem` (cite_key, relevance_score, confidence, etc.)

- New `LLMClient._validate_with_repair()` helper:
  - Validates a parsed dict against a Pydantic v2 BaseModel
  - On `ValidationError`, extracts missing/invalid field paths and issues a
    targeted one-shot repair prompt to the LLM (with json_mode=True)
  - Re-validates the repaired response; re-raises if still invalid

- Wired validation into three callers in `llm_client.py`:
  - `analyze_job_description` → validates with `JobAnalysisResponse`
  - `recommend_customizations` → validates with `CustomizationResult`
  - `rank_publications_for_job` → validates each array item with
    `PublicationRankingItem`; invalid items after repair are skipped with a
    warning rather than crashing the full ranking call

All 278 tests pass (pytest tests/test_llm_client.py
tests/test_layout_instructions.py tests/test_cv_orchestrator.py
tests/test_conversation_manager.py).
…ions

Wire json_mode=True into the structured clarifying-questions chat call so
API providers (OpenAI, GitHub Models, CopilotOAuth) enforce JSON output at the
protocol level rather than relying only on prompt instructions.

- `conversation_manager.py`: add `json_mode=True` to `self.llm.chat()` call
  that generates post-analysis clarifying questions (the call that feeds
  `_parse_json_questions_response`)

- Remove dead `_extract_structured_questions()` method — it parsed numbered
  free-form text but was never called; the flow has used `_parse_json_questions_response`
  (JSON-first) since the structured-questions prompt was introduced

- `tests/test_conversation_manager.py`: remove two tests that directly exercised
  the deleted `_extract_structured_questions` helper; retain the remaining
  test asserting phase transitions (61 tests pass)

All non-browser tests pass: 1248/1250 (2 pre-existing Playwright timeouts).
Four findings from systematic code review; three resolved with code changes,
one accepted as intentional design.

Finding 1 — CopilotSdkClient ignores json_mode (FIXED):
  scripts/utils/llm_client.py: When json_mode=True, prepend a hard system
  instruction 'Respond with valid JSON only. No prose, no markdown fences.'
  before forwarding messages to any-llm. The copilotsdk provider converts
  messages to a flat prompt and has no response_format API param; the system
  message provides equivalent enforcement.

Finding 2 — JobAnalysisResponse / CustomizationResult all-optional fields
  (DESIGN ACCEPTED):
  scripts/utils/llm_response_models.py: Added docstring comments explaining
  that all fields intentionally have defaults — type safety without breaking
  partial responses. Empty fields degrade gracefully in the downstream workflow.

Finding 3 — No tests for _validate_with_repair() repair path (FIXED):
  tests/test_llm_client.py: Added TestValidateWithRepair class with 3 tests
  covering all code paths — immediate success, repair-on-failure, and
  ValidationError re-raise on persistent failure.

Finding 4 — confidence field not validated against enum (ACCEPTED):
  scripts/utils/llm_response_models.py: Added inline comments
  '# expected: high | medium | low' to all four confidence fields.
  Kept as plain str for flexibility since confidence is display-only.

CodeQL: 15 pre-existing findings in routes/ (path-injection, SSRF,
stack-trace-exposure) — none in the modified files.
All tests: 279 passed.
Security fixes (CodeQL):
- auth_routes: remove stack trace exposure from HTTP 500 responses in
  set-model endpoint; add logger.warning for auth poll thread failures
- job_routes: add DNS resolution check to block DNS-rebinding SSRF
  attacks on bare hostnames
- session_routes: replace user-supplied path concatenation with a
  server-side enumeration helper (_resolve_session_path) to prevent
  path-injection vulnerabilities across load, delete, rename, restore,
  and trash endpoints

Test fixes (Playwright):
- test_web_ui_workflow: increase page-ready timeout (5 s to 15 s) to
  avoid intermittent startup failures on slow CI runners
- mock_responses: add missing structured_output field to mock analysis
  fixture so phase-advance assertions pass

Logging improvements:
- auth_routes: log Copilot auth poll failures at WARNING level before
  storing error string for UI (background thread was completely silent
  on server logs)
- job_routes: existing _requests.RequestException and bare Exception
  handlers already use logger.exception; Timeout/ConnectionError are
  user-expected conditions returning 400/500 with instructions
- session_routes: silent except-pass clauses in listing loops are
  intentional (skip corrupt session.json); outer handlers already use
  logger.exception
…utput

feat(conversation): structured JSON output with Pydantic validation and self-repair
Backend:
- auth_routes: add _persist_provider_model_to_config() -- atomic YAML
  write (tmp+backup) so model/provider changes in the UI survive restart
- llm_client: forward llm_request_timeout config value to OpenAI,
  Anthropic, and Gemini API calls (guards against hanging requests)
- run_codeql.sh: add --custom-only mode to run .github/codeql/ queries
  against the VS Code extension's cached DB (~1-5 min vs ~15 min)

Frontend:
- job-analysis.js: skip extractStructuredQuestionsFromAssistantText
  when structured questions already present from API response
- job-input.js: add FORBID_ATTR:['style'] to DOMPurify to strip
  inline CSS from Word/JSON-LD job descriptions
- message-queue.js: use display='block' (not '') in show-more toggle
- session-manager.js: trigger background testCurrentModel() health
  check after session restore and loadSessionFile
- utils.js: guard cleanJsonResponse against non-string input

Tests:
- test_llm_client: patch utils.config.get_config in Anthropic and
  Gemini chat tests to isolate them from real config.yaml timeout value

CodeQL queries: add 13 custom queries under .github/codeql/ covering
  unlogged exceptions, swallowed exceptions, exception detail in
  responses, SSRF path traversal, hardcoded secrets, LLM calls without
  timeout, master-data writes, Flask route inventory, and more
Replace silent `except: pass` blocks with structured log messages, add a
slow-state badge to the LLM busy overlay, update the default provider/model,
tighten CodeQL queries, and remove a dead workspace entry.

Logging improvements
- auth_routes: log non-critical provider/model persistence failure at DEBUG
- generation_routes: log materialization failure and git-commit exceptions at WARNING
- master_data_routes: log failed backup restoration at WARNING
- session_routes: log unreadable session files at DEBUG; scan errors at WARNING
- status_routes: log skipped unreadable sessions during clarification search at DEBUG
- conversation_manager: log skipped session files and layout digest failures
- copilot_auth: log unreadable token cache at DEBUG
- session_registry: log timestamp parse failures at DEBUG

UI — LLM busy overlay
- Add "Taking longer than usual" pill badge that appears in slow-state overlay
- Badge is hidden by default; shown only when the `.slow` class is applied

Config
- Switch default_provider to copilot-sdk and default_model to gpt-5-mini

CodeQL
- llm-call-without-timeout: exclude provider chat() impls that manage timeout
  internally via config, eliminating misleading false positives
- master-data-write-outside-window: remove cv_data from master-var predicate
  (always a local rendering copy, never the master dict)
- path-traversal: add isBarrier for _resolve_session_path, _resolve_backup_path,
  and safe_join helpers; narrow sink to os.path.join only

Chore
- Remove dead clem-diagrams entry from cv-builder.code-workspace
Add 15 new tests covering:
- _getStepTooltip: all tooltip states (upcoming, active+viewing, browsing-away,
  completed+viewing/not-viewing, stale-critical, stale)
- _updateViewingIndicator: viewing class applied, other pills cleared,
  browsing-away on active pill, no browsing-away when viewing active pill,
  tab alias mappings (questions→analysis, exp-review→customizations),
  unknown tab clears all rings
…firmed

- Remove previewAvailable guard from the fresh-render path so
  generation_state.preview_html is always populated before confirm-layout,
  covering the legacy generate_cv action that writes files to disk without
  updating backend generation state
- Collapse the now-redundant recovery path into the fresh-render path;
  passive restore is used only for confirmed/final_complete sessions or
  when generate-preview is unavailable (no job_analysis yet)
- Regenerate web/bundle.js
…et leakage

.specstory/history/ files can contain API keys from chat sessions.
Also untrack .specstory/statistics.json which was previously committed.
…rvive tab navigation

Agent-Logs-Url: https://github.com/Warnes-Innovations/cv-builder/sessions/ad3a0147-1320-484e-abdc-a603588e6fae

Co-authored-by: warnes <6144863+warnes@users.noreply.github.com>

# Conflicts:
#	web/bundle.js
Extend the state-persistence pattern introduced in PR #92 (questions panel)
to all remaining panels that fully re-render on tab switch.

cover-letter.js
- Add _coverLetterFormState module-level object tracking tone, hiringManager,
  companyAddress, highlight, letterText, and letterVisible
- Add _restoreCoverLetterFormState() called at end of populateCoverLetterTab()
  to restore field values and wire live-update listeners
- generateCoverLetter() now saves letterText and letterVisible on success

screening-questions.js
- Add _screeningInputText module-level variable to persist sc-input textarea
- Restore value and wire input event listener at end of populateScreeningTab()
- Add _resetScreeningInputText() helper for test isolation

rewrite-review.js
- Re-apply stored rewriteDecisions (accept/reject/edit) after renderRewritePanel()
  re-renders the panel; calls applyRewriteAction, restores edited textarea text
  via saveRewriteEdit, and refreshes the tally display

Tests
- tests/js/cover-letter.test.js: 4 new tests for _coverLetterFormState persistence
- tests/js/screening-questions.test.js: 3 new tests for _screeningInputText persistence
- tests/js/rewrite-review.test.js: 4 new tests for decision state restoration on re-render

All 1074 JS tests pass.

# Conflicts:
#	web/bundle.js
Add a new user-story file and related updates covering the initial
creation of Master_CV_Data.json from external source materials.
This closes the documented onboarding gap (previously noted in the
specstory review as "no story for creating master CV from scratch").

New file:
- tasks/user-story-master-cv-onboarding.md
  Twelve stories (US-O1–US-O12) across three persona layers:
  - Applicant / first-time user: guided path detection (US-O1),
    LinkedIn data export import (US-O2), resume/CV document import
    (US-O3), BibTeX / Google Scholar publications import (US-O4),
    GitHub profile import (US-O5), multi-source merge (US-O6),
    manual guided-form fallback (US-O7), review-and-confirm gate
    (US-O8).
  - UI/UX expert: source selection and import flow clarity (US-O9),
    error and gap handling (US-O10).
  - Master CV curator: post-import completeness check (US-O11),
    conflict resolution when merging sources (US-O12).

Updated files:
- tasks/user-story-first-time-user.md: add US-F4 covering the
  precondition check and onboarding entry point for first-time users
  who have no existing master CV file.
- tasks/gaps.md: add GAP-25 documenting the onboarding gap, its
  severity (HIGH), affected stories, and recommended resolution.
- tasks/workflow-review-expanded-personas.md: register the new story
  file in the story-file index.
…nput

Add five additional input paths to the resume/CV document import story:
- Plain text file upload (.txt)
- Markdown file upload (.md) — heading structure used as section hints
- HTML file upload (.html/.htm) — tags stripped; heading elements used as
  section-boundary hints
- Pasted text — any format accepted (plain, Markdown, or lightly formatted
  HTML copy-paste); auto-detected and tag-stripped where needed
- The paste path is presented as a first-class equal-weight option alongside
  file upload (not a secondary fallback)

Expand acceptance criteria and failure modes accordingly:
- HTML tag-stripping required before LLM prompt (prevents script/style
  tag pollution and prompt injection via malicious HTML)
- Pasted HTML auto-detected and stripped rather than passed raw to LLM
- Markdown structural markers preserv- Markdown structural markers preserv- Markdown structural markers preserv- Markdown structural markers preserv- Markdown structural markers preserv- Markdown structural markers preserv- ripti- Markdown structural markers preserv- Markdset.
- Fix integration test: read textarea value via .value not .textContent,
  resolving the "buildSummaryFocusSection did not use session summary" failure
- Upgrade CodeQL Action v3 -> v4 across both workflow files
- Upgrade actions/setup-python v4 -> v5 across both workflow files
- Add FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true workflow env to silence
  Node.js 20 deprecation warnings on cache/checkout/setup-node actions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants