# tasknotes-nlp-core

Framework-agnostic natural-language task parsing for TaskNotes-style workflows.

`tasknotes-nlp-core` turns free text like:

```text
Pay rent tomorrow 9am #finance @home +admin every month
```

into structured task data (title, dates, tags, contexts, projects, recurrence, estimates, and custom fields).

## Install

```bash
npm install tasknotes-nlp-core
```

## What This Package Provides

- `NaturalLanguageParserCore`: main parser engine
- `TriggerConfigService`: helper for trigger/property mapping
- Built-in defaults via `DEFAULT_NLP_TRIGGERS`
- Language registry and helpers (`getAvailableLanguages`, `getLanguageConfig`, `detectSystemLanguage`)
- Shared types for status/priority/trigger/user-field config

## Quick Start

```ts
import {
  NaturalLanguageParserCore,
  DEFAULT_NLP_TRIGGERS,
  type StatusConfig,
  type PriorityConfig,
} from "tasknotes-nlp-core";

const statusConfigs: StatusConfig[] = [
  {
    id: "open",
    value: "open",
    label: "Open",
    color: "#64748b",
    isCompleted: false,
    order: 0,
    autoArchive: false,
    autoArchiveDelay: 0,
  },
  {
    id: "done",
    value: "done",
    label: "Done",
    color: "#22c55e",
    isCompleted: true,
    order: 1,
    autoArchive: true,
    autoArchiveDelay: 7,
  },
];

const priorityConfigs: PriorityConfig[] = [
  { id: "urgent", value: "urgent", label: "Urgent", color: "#ef4444", weight: 100 },
  { id: "normal", value: "normal", label: "Normal", color: "#3b82f6", weight: 50 },
];

const parser = new NaturalLanguageParserCore(
  statusConfigs,
  priorityConfigs,
  true,                  // defaultToScheduled
  "en",                 // language code
  DEFAULT_NLP_TRIGGERS   // optional trigger config
);

const parsed = parser.parseInput(
  "Pay rent tomorrow 9am #finance @home +admin every month 30m"
);

console.log(parsed);
```

## Parsed Output Shape

`parseInput()` returns:

```ts
interface ParsedTaskData {
  title: string;
  details?: string;
  dueDate?: string;        // yyyy-MM-dd
  scheduledDate?: string;  // yyyy-MM-dd
  dueTime?: string;        // HH:mm
  scheduledTime?: string;  // HH:mm
  priority?: string;
  status?: string;
  tags: string[];
  contexts: string[];
  projects: string[];
  recurrence?: string;     // rrule string
  estimate?: number;       // minutes
  isCompleted?: boolean;
  userFields?: Record<string, string | string[]>;
}
```

## Constructor Options

```ts
new NaturalLanguageParserCore(
  statusConfigs?,
  priorityConfigs?,
  defaultToScheduled?,
  languageCode?,
  nlpTriggers?,
  userFields?,
  options?
)
```

- `statusConfigs`: optional custom status list. If omitted, language fallback matching is used.
- `priorityConfigs`: optional custom priority list. If omitted, language fallback matching is used.
- `defaultToScheduled`:
  - `true`: ambiguous date text (for example `tomorrow`) goes to `scheduledDate`
  - `false`: ambiguous date text goes to `dueDate`
- `languageCode`: one of supported codes (defaults to `en`).
- `nlpTriggers`: trigger map for tags/contexts/projects/status/priority.
- `userFields`: custom field definitions with trigger support.
- `options.dateLocale`: locale used for ambiguous numeric dates, for example `en-GB` parses `11/06/2026` as `2026-06-11`.
- `options.dateOrder`: explicit numeric date order override, either `day-first` or `month-first`.

Quoted spans and backslash-prefixed literals are preserved as plain title text
instead of being parsed as NLP syntax. For example, `"Today"` and `\tomorrow`
remain literal text, and `\@ABC` remains `@ABC` instead of becoming a context.

## Trigger Configuration

Starting with 0.2.0, tag, context, project and custom-field selectors match only
at the start of input or after whitespace (including tabs). The boundary is
checked against original input positions: removing `@work` from
`Task @work+project` does not make `+project` a selector. `task@work`, `(@work)`,
email addresses, URL fragments and `C++programming` stay literal under these
selector rules. Status/priority phrase fallback and date parsing are unchanged
by this boundary contract; this is not a universal URL/code shield.

Only the first line is parsed; subsequent lines are details. Wikilinks, quoted
spans (double quotes, single quotes or backticks), and escaped tokens are
protected before extraction, including nested quoted wikilinks.

A repeated project prefix removes exactly one configured prefix:
`++personal` produces project `+personal`, and `::::personal` with prefix `::`
produces project `::personal`. Linked projects are returned before simple
projects, preserving input order within each group.

Default triggers:

- `#` -> `tags`
- `@` -> `contexts`
- `+` -> `projects`
- `*` -> `status`
- `!` -> `priority` (disabled by default)

Example custom trigger config:

```ts
const customTriggers = {
  triggers: [
    { propertyId: "tags", trigger: "#", enabled: true },
    { propertyId: "contexts", trigger: "@", enabled: true },
    { propertyId: "projects", trigger: ">", enabled: true },
    { propertyId: "status", trigger: "~", enabled: true },
    { propertyId: "priority", trigger: "!", enabled: true },
  ],
};
```

## User-Defined Fields

`userFields` supports these types:

- `text`
- `number`
- `date`
- `boolean`
- `list`

List and text-like values support quoted multi-word input (for example `~client "Acme Corp"`).

## Language Support

Built-in languages:

- `en`, `es`, `fr`, `de`, `ru`, `zh`, `ja`, `it`, `nl`, `pt`, `sv`, `uk`

Helpers:

```ts
import { getAvailableLanguages, detectSystemLanguage } from "tasknotes-nlp-core";
```

Note: `detectSystemLanguage()` uses `navigator.language` when available; in non-browser runtimes it falls back to `en`.

Italian relative days `oggi`, `domani` and `dopodomani` use the local calendar
date. Bare words follow `defaultToScheduled`; `entro` and `per` set Due, while
`programmato per` sets Scheduled. This is partial Italian date support, not a
complete Italian Chrono locale. Only unprotected title text is date-parsed;
`[[oggi]]`, `+[[domani]]`, `#domani`, `@oggi`, `\\oggi`, and details stay literal.

## Recurrence Output

Recurrence phrases are parsed into RRULE strings (for example `FREQ=WEEKLY;BYDAY=MO`).

For display, you can convert RRULE text with `rrule` or use parser preview helpers:

```ts
const previewItems = parser.getPreviewData(parsed);
const previewText = parser.getPreviewText(parsed);
```

## Integration Pattern

This package is intentionally UI/framework agnostic. A common setup is:

1. Keep app-specific config/state in your host app.
2. Instantiate `NaturalLanguageParserCore` in an adapter service.
3. Map parsed fields to your storage schema.
4. Keep UI behavior (suggesters, editor state, persistence) outside this package.

## Development

```bash
npm install
npm run build
```

## Release

```bash
npm run release:patch   # x.y.z -> x.y.(z+1)
npm run release:minor   # x.y.z -> x.(y+1).0
npm run release:major   # x.y.z -> (x+1).0.0
```

These commands run `npm version`, create a git tag, and push `main` with tags. GitHub Actions verifies and publishes the tagged version to npm.

## License

MIT
